From 04f38885501f8f6091c35d7a091e6dd43ded95c8 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 28 May 2019 17:26:19 +0100 Subject: [PATCH] Add allowOnlyClearBuffers to SampleQueue#read Removes the need for duplicate calls to SampleQueue#read when implementing DecryptionResources acquisition in the MediaSources. PiperOrigin-RevId: 250298175 --- .../source/ProgressiveMediaPeriod.java | 7 +- .../source/SampleMetadataQueue.java | 7 ++ .../exoplayer2/source/SampleQueue.java | 14 ++- .../source/chunk/ChunkSampleStream.java | 14 ++- .../exoplayer2/source/SampleQueueTest.java | 114 ++++++++++++++++-- .../source/dash/PlayerEmsgHandler.java | 9 +- .../source/hls/HlsSampleStreamWrapper.java | 7 +- 7 files changed, 154 insertions(+), 18 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java index e8f630f202..dbf5f8aa5d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaPeriod.java @@ -447,7 +447,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; maybeNotifyDownstreamFormat(track); int result = sampleQueues[track].read( - formatHolder, buffer, formatRequired, loadingFinished, lastSeekPositionUs); + formatHolder, + buffer, + formatRequired, + /* allowOnlyClearBuffers= */ false, + loadingFinished, + lastSeekPositionUs); if (result == C.RESULT_NOTHING_READ) { maybeStartDeferredRetry(track); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java index 25cc73d4ae..b2c09bd70f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleMetadataQueue.java @@ -230,6 +230,8 @@ import com.google.android.exoplayer2.util.Util; * @param formatRequired Whether the caller requires that the format of the stream be read even if * it's not changing. A sample will never be read if set to true, however it is still possible * for the end of stream or nothing to be read. + * @param allowOnlyClearBuffers If set to true, this method will not return encrypted buffers, + * returning {@link C#RESULT_NOTHING_READ} (without advancing the read position) instead. * @param loadingFinished True if an empty queue should be considered the end of the stream. * @param downstreamFormat The current downstream {@link Format}. If the format of the next sample * is different to the current downstream format then a format will be read. @@ -242,6 +244,7 @@ import com.google.android.exoplayer2.util.Util; FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired, + boolean allowOnlyClearBuffers, boolean loadingFinished, Format downstreamFormat, SampleExtrasHolder extrasHolder) { @@ -264,6 +267,10 @@ import com.google.android.exoplayer2.util.Util; return C.RESULT_FORMAT_READ; } + if (allowOnlyClearBuffers && (flags[relativeReadIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0) { + return C.RESULT_NOTHING_READ; + } + buffer.setFlags(flags[relativeReadIndex]); buffer.timeUs = timesUs[relativeReadIndex]; if (buffer.isFlagsOnly()) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java index e8f4953436..976a5d4e48 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SampleQueue.java @@ -324,6 +324,8 @@ public class SampleQueue implements TrackOutput { * @param formatRequired Whether the caller requires that the format of the stream be read even if * it's not changing. A sample will never be read if set to true, however it is still possible * for the end of stream or nothing to be read. + * @param allowOnlyClearBuffers If set to true, this method will not return encrypted buffers, + * returning {@link C#RESULT_NOTHING_READ} (without advancing the read position) instead. * @param loadingFinished True if an empty queue should be considered the end of the stream. * @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will * be set if the buffer's timestamp is less than this value. @@ -334,10 +336,18 @@ public class SampleQueue implements TrackOutput { FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired, + boolean allowOnlyClearBuffers, boolean loadingFinished, long decodeOnlyUntilUs) { - int result = metadataQueue.read(formatHolder, buffer, formatRequired, loadingFinished, - downstreamFormat, extrasHolder); + int result = + metadataQueue.read( + formatHolder, + buffer, + formatRequired, + allowOnlyClearBuffers, + loadingFinished, + downstreamFormat, + extrasHolder); switch (result) { case C.RESULT_FORMAT_READ: downstreamFormat = formatHolder.format; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java index d7a19fa9d4..d9b28d9c92 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java @@ -409,7 +409,12 @@ public class ChunkSampleStream implements SampleStream, S } maybeNotifyPrimaryTrackFormatChanged(); return primarySampleQueue.read( - formatHolder, buffer, formatRequired, loadingFinished, decodeOnlyUntilPositionUs); + formatHolder, + buffer, + formatRequired, + /* allowOnlyClearBuffers= */ false, + loadingFinished, + decodeOnlyUntilPositionUs); } @Override @@ -801,7 +806,12 @@ public class ChunkSampleStream implements SampleStream, S } maybeNotifyDownstreamFormat(); return sampleQueue.read( - formatHolder, buffer, formatRequired, loadingFinished, decodeOnlyUntilPositionUs); + formatHolder, + buffer, + formatRequired, + /* allowOnlyClearBuffers= */ false, + loadingFinished, + decodeOnlyUntilPositionUs); } public void release() { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java index 450f0ecd3a..bfc6bb52c9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/SampleQueueTest.java @@ -29,10 +29,12 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.Arrays; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -89,6 +91,8 @@ public final class SampleQueueTest { private static final Format[] SAMPLE_FORMATS = new Format[] {FORMAT_1, FORMAT_1, FORMAT_1, FORMAT_1, FORMAT_2, FORMAT_2, FORMAT_2, FORMAT_2}; private static final int DATA_SECOND_KEYFRAME_INDEX = 4; + private static final TrackOutput.CryptoData DUMMY_CRYPTO_DATA = + new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0); private Allocator allocator; private SampleQueue sampleQueue; @@ -511,6 +515,49 @@ public final class SampleQueueTest { assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(MIN_VALUE); } + @Test + public void testAllowOnlyClearBuffers() { + int[] flags = + new int[] { + C.BUFFER_FLAG_KEY_FRAME, + C.BUFFER_FLAG_ENCRYPTED, + 0, + 0, + 0, + C.BUFFER_FLAG_KEY_FRAME | C.BUFFER_FLAG_ENCRYPTED, + 0, + 0 + }; + int[] sampleSizes = new int[flags.length]; + Arrays.fill(sampleSizes, /* val= */ 1); + + // Two encryption preamble bytes per encrypted sample in the sample queue. + byte[] sampleData = new byte[flags.length + 2 + 2]; + Arrays.fill(sampleData, /* val= */ (byte) 1); + + writeTestData( + sampleData, sampleSizes, new int[flags.length], SAMPLE_TIMESTAMPS, SAMPLE_FORMATS, flags); + assertReadFormat(/* formatRequired= */ false, FORMAT_1); + assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true); + assertResult(RESULT_NOTHING_READ, /* allowOnlyClearBuffers= */ true); + + assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ false); + + assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true); + assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true); + assertResult(RESULT_FORMAT_READ, /* allowOnlyClearBuffers= */ true); + assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true); + assertResult(RESULT_NOTHING_READ, /* allowOnlyClearBuffers= */ true); + + assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ false); + + assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true); + assertResult(RESULT_BUFFER_READ, /* allowOnlyClearBuffers= */ true); + assertResult(RESULT_NOTHING_READ, /* allowOnlyClearBuffers= */ true); + + assertResult(RESULT_NOTHING_READ, /* allowOnlyClearBuffers= */ false); + } + @Test public void testLargestQueuedTimestampWithRead() { writeTestData(); @@ -602,8 +649,12 @@ public final class SampleQueueTest { sampleQueue.format(sampleFormats[i]); format = sampleFormats[i]; } - sampleQueue.sampleMetadata(sampleTimestamps[i], sampleFlags[i], sampleSizes[i], - sampleOffsets[i], null); + sampleQueue.sampleMetadata( + sampleTimestamps[i], + sampleFlags[i], + sampleSizes[i], + sampleOffsets[i], + (sampleFlags[i] & C.BUFFER_FLAG_ENCRYPTED) != 0 ? DUMMY_CRYPTO_DATA : null); } } @@ -714,11 +765,18 @@ public final class SampleQueueTest { /** * Asserts {@link SampleQueue#read} returns {@link C#RESULT_NOTHING_READ}. * - * @param formatRequired The value of {@code formatRequired} passed to readData. + * @param formatRequired The value of {@code formatRequired} passed to {@link SampleQueue#read}. */ private void assertReadNothing(boolean formatRequired) { clearFormatHolderAndInputBuffer(); - int result = sampleQueue.read(formatHolder, inputBuffer, formatRequired, false, 0); + int result = + sampleQueue.read( + formatHolder, + inputBuffer, + formatRequired, + /* allowOnlyClearBuffers= */ false, + /* loadingFinished= */ false, + /* decodeOnlyUntilUs= */ 0); assertThat(result).isEqualTo(RESULT_NOTHING_READ); // formatHolder should not be populated. assertThat(formatHolder.format).isNull(); @@ -728,14 +786,21 @@ public final class SampleQueueTest { } /** - * Asserts {@link SampleQueue#read} returns {@link C#RESULT_BUFFER_READ} and that the - * {@link DecoderInputBuffer#isEndOfStream()} is set. + * Asserts {@link SampleQueue#read} returns {@link C#RESULT_BUFFER_READ} and that the {@link + * DecoderInputBuffer#isEndOfStream()} is set. * - * @param formatRequired The value of {@code formatRequired} passed to readData. + * @param formatRequired The value of {@code formatRequired} passed to {@link SampleQueue#read}. */ private void assertReadEndOfStream(boolean formatRequired) { clearFormatHolderAndInputBuffer(); - int result = sampleQueue.read(formatHolder, inputBuffer, formatRequired, true, 0); + int result = + sampleQueue.read( + formatHolder, + inputBuffer, + formatRequired, + /* allowOnlyClearBuffers= */ false, + /* loadingFinished= */ true, + /* decodeOnlyUntilUs= */ 0); assertThat(result).isEqualTo(RESULT_BUFFER_READ); // formatHolder should not be populated. assertThat(formatHolder.format).isNull(); @@ -750,12 +815,19 @@ public final class SampleQueueTest { * Asserts {@link SampleQueue#read} returns {@link C#RESULT_FORMAT_READ} and that the format * holder is filled with a {@link Format} that equals {@code format}. * - * @param formatRequired The value of {@code formatRequired} passed to readData. + * @param formatRequired The value of {@code formatRequired} passed to {@link SampleQueue#read}. * @param format The expected format. */ private void assertReadFormat(boolean formatRequired, Format format) { clearFormatHolderAndInputBuffer(); - int result = sampleQueue.read(formatHolder, inputBuffer, formatRequired, false, 0); + int result = + sampleQueue.read( + formatHolder, + inputBuffer, + formatRequired, + /* allowOnlyClearBuffers= */ false, + /* loadingFinished= */ false, + /* decodeOnlyUntilUs= */ 0); assertThat(result).isEqualTo(RESULT_FORMAT_READ); // formatHolder should be populated. assertThat(formatHolder.format).isEqualTo(format); @@ -777,7 +849,14 @@ public final class SampleQueueTest { private void assertReadSample( long timeUs, boolean isKeyframe, byte[] sampleData, int offset, int length) { clearFormatHolderAndInputBuffer(); - int result = sampleQueue.read(formatHolder, inputBuffer, false, false, 0); + int result = + sampleQueue.read( + formatHolder, + inputBuffer, + /* formatRequired= */ false, + /* allowOnlyClearBuffers= */ false, + /* loadingFinished= */ false, + /* decodeOnlyUntilUs= */ 0); assertThat(result).isEqualTo(RESULT_BUFFER_READ); // formatHolder should not be populated. assertThat(formatHolder.format).isNull(); @@ -793,6 +872,19 @@ public final class SampleQueueTest { assertThat(readData).isEqualTo(copyOfRange(sampleData, offset, offset + length)); } + /** Asserts {@link SampleQueue#read} returns the given result. */ + private void assertResult(int expectedResult, boolean allowOnlyClearBuffers) { + int obtainedResult = + sampleQueue.read( + formatHolder, + inputBuffer, + /* formatRequired= */ false, + allowOnlyClearBuffers, + /* loadingFinished= */ false, + /* decodeOnlyUntilUs= */ 0); + assertThat(obtainedResult).isEqualTo(expectedResult); + } + /** * Asserts the number of allocations currently in use by {@code sampleQueue}. * diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java index 34e1ecc2b6..af4bf3ad70 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/PlayerEmsgHandler.java @@ -371,7 +371,14 @@ public final class PlayerEmsgHandler implements Handler.Callback { @Nullable private MetadataInputBuffer dequeueSample() { buffer.clear(); - int result = sampleQueue.read(formatHolder, buffer, false, false, 0); + int result = + sampleQueue.read( + formatHolder, + buffer, + /* formatRequired= */ false, + /* allowOnlyClearBuffers= */ false, + /* loadingFinished= */ false, + /* decodeOnlyUntilUs= */ 0); if (result == C.RESULT_BUFFER_READ) { buffer.flip(); return buffer; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 434b6c2011..96704053cb 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -491,7 +491,12 @@ import java.util.Map; int result = sampleQueues[sampleQueueIndex].read( - formatHolder, buffer, requireFormat, loadingFinished, lastSeekPositionUs); + formatHolder, + buffer, + requireFormat, + /* allowOnlyClearBuffers= */ false, + loadingFinished, + lastSeekPositionUs); if (result == C.RESULT_FORMAT_READ) { Format format = formatHolder.format; if (sampleQueueIndex == primarySampleQueueIndex) {