diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java index 330a70fea8..dff16d39f1 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java @@ -19,6 +19,7 @@ import androidx.annotation.Nullable; import com.google.common.base.Charsets; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.util.Arrays; /** * Wraps a byte array, providing a set of methods for parsing data from it. Numerical values are @@ -100,8 +101,21 @@ public final class ParsableByteArray { } /** - * Returns the number of bytes yet to be read. + * Ensures the backing array is at least {@code requiredCapacity} long. + * + *
{@link #getPosition() position}, {@link #limit() limit}, and all data in the underlying + * array (including that beyond {@link #limit()}) are preserved. + * + *
This might replace or wipe the {@link #getData() underlying array}, potentially invalidating + * any local references. */ + public void ensureCapacity(int requiredCapacity) { + if (requiredCapacity > capacity()) { + data = Arrays.copyOf(data, requiredCapacity); + } + } + + /** Returns the number of bytes yet to be read. */ public int bytesLeft() { return limit - position; } @@ -148,8 +162,8 @@ public final class ParsableByteArray { * *
Changes to this array are reflected in the results of the {@code read...()} methods. * - *
This reference must be assumed to become invalid when {@link #reset} is called (because the - * array might get reallocated). + *
This reference must be assumed to become invalid when {@link #reset} or {@link + * #ensureCapacity} are called (because the array might get reallocated). */ public byte[] getData() { return data; diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 408718a377..fd7237ad28 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -2214,9 +2214,8 @@ public final class Util { if (input.bytesLeft() <= 0) { return false; } - byte[] outputData = output.getData(); - if (outputData.length < input.bytesLeft()) { - outputData = new byte[2 * input.bytesLeft()]; + if (output.capacity() < input.bytesLeft()) { + output.ensureCapacity(2 * input.bytesLeft()); } if (inflater == null) { inflater = new Inflater(); @@ -2225,16 +2224,17 @@ public final class Util { try { int outputSize = 0; while (true) { - outputSize += inflater.inflate(outputData, outputSize, outputData.length - outputSize); + outputSize += + inflater.inflate(output.getData(), outputSize, output.capacity() - outputSize); if (inflater.finished()) { - output.reset(outputData, outputSize); + output.setLimit(outputSize); return true; } if (inflater.needsDictionary() || inflater.needsInput()) { return false; } - if (outputSize == outputData.length) { - outputData = Arrays.copyOf(outputData, outputData.length * 2); + if (outputSize == output.capacity()) { + output.ensureCapacity(output.capacity() * 2); } } } catch (DataFormatException e) { diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java index 8c9cd62cc6..444e5f7b46 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java @@ -20,6 +20,7 @@ import static java.nio.charset.Charset.forName; import static org.junit.Assert.fail; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.primitives.Bytes; import java.nio.ByteBuffer; import java.util.Arrays; import org.junit.Test; @@ -38,6 +39,36 @@ public final class ParsableByteArrayTest { return testArray; } + @Test + public void ensureCapacity_doesntReallocateNeedlesslyAndPreservesPositionAndLimit() { + ParsableByteArray array = getTestDataArray(); + byte[] dataBefore = array.getData(); + byte[] copyOfDataBefore = dataBefore.clone(); + + array.setPosition(3); + array.setLimit(4); + array.ensureCapacity(array.capacity() - 1); + + assertThat(array.getData()).isSameInstanceAs(dataBefore); + assertThat(array.getData()).isEqualTo(copyOfDataBefore); + assertThat(array.getPosition()).isEqualTo(3); + assertThat(array.limit()).isEqualTo(4); + } + + @Test + public void ensureCapacity_preservesDataPositionAndLimitWhenReallocating() { + ParsableByteArray array = getTestDataArray(); + byte[] copyOfDataBefore = array.getData().clone(); + + array.setPosition(3); + array.setLimit(4); + array.ensureCapacity(array.capacity() + 1); + + assertThat(array.getData()).isEqualTo(Bytes.concat(copyOfDataBefore, new byte[] {0})); + assertThat(array.getPosition()).isEqualTo(3); + assertThat(array.limit()).isEqualTo(4); + } + @Test public void readShort() { testReadShort((short) -1); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index d6c797b743..af011fa751 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -1341,9 +1341,7 @@ public class MatroskaExtractor implements Extractor { return; } if (scratch.capacity() < requiredLength) { - scratch.reset( - Arrays.copyOf(scratch.getData(), max(scratch.getData().length * 2, requiredLength)), - scratch.limit()); + scratch.ensureCapacity(max(scratch.capacity() * 2, requiredLength)); } input.readFully(scratch.getData(), scratch.limit(), requiredLength - scratch.limit()); scratch.setLimit(requiredLength); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java index c7718e7fa9..14a5fea607 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggPacket.java @@ -87,11 +87,7 @@ import java.util.Arrays; int size = calculatePacketSize(currentSegmentIndex); int segmentIndex = currentSegmentIndex + segmentCount; if (size > 0) { - if (packetArray.capacity() < packetArray.limit() + size) { - packetArray.reset( - Arrays.copyOf(packetArray.getData(), packetArray.limit() + size), - /* limit= */ packetArray.limit()); - } + packetArray.ensureCapacity(packetArray.limit() + size); input.readFully(packetArray.getData(), packetArray.limit(), size); packetArray.setLimit(packetArray.limit() + size); populated = pageHeader.laces[segmentIndex - 1] != 255; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java index 898be3b8b4..3b2bd4ce89 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java @@ -23,7 +23,6 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.Util; -import java.util.Arrays; /** * Reads section data packets and feeds the whole sections to a given {@link SectionPayloadReader}. @@ -107,12 +106,9 @@ public final class SectionReader implements TsPayloadReader { (((secondHeaderByte & 0x0F) << 8) | thirdHeaderByte) + SECTION_HEADER_LENGTH; if (sectionData.capacity() < totalSectionLength) { // Ensure there is enough space to keep the whole section. - byte[] bytes = sectionData.getData(); - int limit = min(MAX_SECTION_LENGTH, max(totalSectionLength, bytes.length * 2)); - if (limit > bytes.length) { - bytes = Arrays.copyOf(sectionData.getData(), limit); - } - sectionData.reset(bytes, limit); + int limit = + min(MAX_SECTION_LENGTH, max(totalSectionLength, sectionData.capacity() * 2)); + sectionData.ensureCapacity(limit); } } } else {