blob: 9a4524819ee76b7c6750fbbd7f4a907bd3be8b20 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source;
import static com.google.android.exoplayer2.C.BUFFER_FLAG_ENCRYPTED;
import static com.google.android.exoplayer2.C.BUFFER_FLAG_KEY_FRAME;
import static com.google.android.exoplayer2.C.RESULT_BUFFER_READ;
import static com.google.android.exoplayer2.C.RESULT_FORMAT_READ;
import static com.google.android.exoplayer2.C.RESULT_NOTHING_READ;
import static com.google.common.truth.Truth.assertThat;
import static java.lang.Long.MIN_VALUE;
import static java.util.Arrays.copyOfRange;
import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.when;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
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.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
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.MediaSourceEventDispatcher;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
/** Test for {@link SampleQueue}. */
@RunWith(AndroidJUnit4.class)
public final class SampleQueueTest {
private static final int ALLOCATION_SIZE = 16;
private static final Format FORMAT_1 = buildFormat(/* id= */ "1");
private static final Format FORMAT_2 = buildFormat(/* id= */ "2");
private static final Format FORMAT_1_COPY = buildFormat(/* id= */ "1");
private static final Format FORMAT_SPLICED = buildFormat(/* id= */ "spliced");
private static final Format FORMAT_ENCRYPTED =
new Format.Builder().setId(/* id= */ "encrypted").setDrmInitData(new DrmInitData()).build();
private static final byte[] DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
/*
* SAMPLE_SIZES and SAMPLE_OFFSETS are intended to test various boundary cases (with
* respect to the allocation size). SAMPLE_OFFSETS values are defined as the backward offsets
* (as expected by SampleQueue.sampleMetadata) assuming that DATA has been written to the
* sampleQueue in full. The allocations are filled as follows, where | indicates a boundary
* between allocations and x indicates a byte that doesn't belong to a sample:
*
* x<s1>|x<s2>x|x<s3>|<s4>x|<s5>|<s6|s6>|x<s7|s7>x|<s8>
*/
private static final int[] SAMPLE_SIZES =
new int[] {
ALLOCATION_SIZE - 1,
ALLOCATION_SIZE - 2,
ALLOCATION_SIZE - 1,
ALLOCATION_SIZE - 1,
ALLOCATION_SIZE,
ALLOCATION_SIZE * 2,
ALLOCATION_SIZE * 2 - 2,
ALLOCATION_SIZE
};
private static final int[] SAMPLE_OFFSETS =
new int[] {
ALLOCATION_SIZE * 9,
ALLOCATION_SIZE * 8 + 1,
ALLOCATION_SIZE * 7,
ALLOCATION_SIZE * 6 + 1,
ALLOCATION_SIZE * 5,
ALLOCATION_SIZE * 3,
ALLOCATION_SIZE + 1,
0
};
private static final long[] SAMPLE_TIMESTAMPS =
new long[] {0, 1000, 2000, 3000, 4000, 5000, 6000, 7000};
private static final long LAST_SAMPLE_TIMESTAMP = SAMPLE_TIMESTAMPS[SAMPLE_TIMESTAMPS.length - 1];
private static final int[] SAMPLE_FLAGS =
new int[] {C.BUFFER_FLAG_KEY_FRAME, 0, 0, 0, C.BUFFER_FLAG_KEY_FRAME, 0, 0, 0};
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 int[] ENCRYPTED_SAMPLES_FLAGS =
new int[] {
C.BUFFER_FLAG_KEY_FRAME, C.BUFFER_FLAG_ENCRYPTED, 0, C.BUFFER_FLAG_ENCRYPTED,
};
private static final long[] ENCRYPTED_SAMPLE_TIMESTAMPS = new long[] {0, 1000, 2000, 3000};
private static final Format[] ENCRYPTED_SAMPLE_FORMATS =
new Format[] {FORMAT_ENCRYPTED, FORMAT_ENCRYPTED, FORMAT_1, FORMAT_ENCRYPTED};
/** Encrypted samples require the encryption preamble. */
private static final int[] ENCRYPTED_SAMPLE_SIZES = new int[] {1, 3, 1, 3};
private static final int[] ENCRYPTED_SAMPLE_OFFSETS = new int[] {7, 4, 3, 0};
private static final byte[] ENCRYPTED_SAMPLE_DATA = new byte[] {1, 1, 1, 1, 1, 1, 1, 1};
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 DrmSessionManager mockDrmSessionManager;
private DrmSession mockDrmSession;
private MediaSourceEventDispatcher eventDispatcher;
private SampleQueue sampleQueue;
private FormatHolder formatHolder;
private DecoderInputBuffer inputBuffer;
@Before
public void setUp() {
allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
mockDrmSessionManager = Mockito.mock(DrmSessionManager.class);
mockDrmSession = Mockito.mock(DrmSession.class);
when(mockDrmSessionManager.acquireSession(
ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(mockDrmSession);
eventDispatcher = new MediaSourceEventDispatcher();
sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher);
formatHolder = new FormatHolder();
inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
}
@After
public void tearDown() {
allocator = null;
sampleQueue = null;
formatHolder = null;
inputBuffer = null;
}
@Test
public void capacityIncreases() {
int numberOfSamplesToInput = 3 * SampleQueue.SAMPLE_CAPACITY_INCREMENT + 1;
sampleQueue.format(FORMAT_1);
sampleQueue.sampleData(
new ParsableByteArray(numberOfSamplesToInput), /* length= */ numberOfSamplesToInput);
for (int i = 0; i < numberOfSamplesToInput; i++) {
sampleQueue.sampleMetadata(
/* timeUs= */ i * 1000,
/* flags= */ C.BUFFER_FLAG_KEY_FRAME,
/* size= */ 1,
/* offset= */ numberOfSamplesToInput - i - 1,
/* cryptoData= */ null);
}
assertReadFormat(/* formatRequired= */ false, FORMAT_1);
for (int i = 0; i < numberOfSamplesToInput; i++) {
assertReadSample(
/* timeUs= */ i * 1000,
/* isKeyFrame= */ true,
/* isEncrypted= */ false,
/* sampleData= */ new byte[1],
/* offset= */ 0,
/* length= */ 1);
}
assertReadNothing(/* formatRequired= */ false);
}
@Test
public void resetReleasesAllocations() {
writeTestData();
assertAllocationCount(10);
sampleQueue.reset();
assertAllocationCount(0);
}
@Test
public void readWithoutWrite() {
assertNoSamplesToRead(null);
}
@Test
public void equalFormatsDeduplicated() {
sampleQueue.format(FORMAT_1);
assertReadFormat(false, FORMAT_1);
// If the same format is written then it should not cause a format change on the read side.
sampleQueue.format(FORMAT_1);
assertNoSamplesToRead(FORMAT_1);
// The same applies for a format that's equal (but a different object).
sampleQueue.format(FORMAT_1_COPY);
assertNoSamplesToRead(FORMAT_1);
}
@Test
public void multipleFormatsDeduplicated() {
sampleQueue.format(FORMAT_1);
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
sampleQueue.sampleMetadata(0, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
// Writing multiple formats should not cause a format change on the read side, provided the last
// format to be written is equal to the format of the previous sample.
sampleQueue.format(FORMAT_2);
sampleQueue.format(FORMAT_1_COPY);
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
sampleQueue.sampleMetadata(1000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
assertReadFormat(false, FORMAT_1);
assertReadSample(0, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
// Assert the second sample is read without a format change.
assertReadSample(1000, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
// The same applies if the queue is empty when the formats are written.
sampleQueue.format(FORMAT_2);
sampleQueue.format(FORMAT_1);
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
sampleQueue.sampleMetadata(2000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
// Assert the third sample is read without a format change.
assertReadSample(2000, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
}
@Test
public void readSingleSamples() {
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
assertAllocationCount(1);
// Nothing to read.
assertNoSamplesToRead(null);
sampleQueue.format(FORMAT_1);
// Read the format.
assertReadFormat(false, FORMAT_1);
// Nothing to read.
assertNoSamplesToRead(FORMAT_1);
sampleQueue.sampleMetadata(1000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
// If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1);
// Otherwise should read the sample.
assertReadSample(1000, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
// Allocation should still be held.
assertAllocationCount(1);
sampleQueue.discardToRead();
// The allocation should have been released.
assertAllocationCount(0);
// Nothing to read.
assertNoSamplesToRead(FORMAT_1);
// Write a second sample followed by one byte that does not belong to it.
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
sampleQueue.sampleMetadata(2000, 0, ALLOCATION_SIZE - 1, 1, null);
// If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1);
// Read the sample.
assertReadSample(2000, false, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE - 1);
// Allocation should still be held.
assertAllocationCount(1);
sampleQueue.discardToRead();
// The last byte written to the sample queue may belong to a sample whose metadata has yet to be
// written, so an allocation should still be held.
assertAllocationCount(1);
// Write metadata for a third sample containing the remaining byte.
sampleQueue.sampleMetadata(3000, 0, 1, 0, null);
// If formatRequired, should read the format rather than the sample.
assertReadFormat(true, FORMAT_1);
// Read the sample.
assertReadSample(3000, false, /* isEncrypted= */ false, DATA, ALLOCATION_SIZE - 1, 1);
// Allocation should still be held.
assertAllocationCount(1);
sampleQueue.discardToRead();
// The allocation should have been released.
assertAllocationCount(0);
}
@Test
public void readMultiSamples() {
writeTestData();
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP);
assertAllocationCount(10);
assertReadTestData();
assertAllocationCount(10);
sampleQueue.discardToRead();
assertAllocationCount(0);
}
@Test
public void readMultiSamplesTwice() {
writeTestData();
writeTestData();
assertAllocationCount(20);
assertReadTestData(FORMAT_2);
assertReadTestData(FORMAT_2);
assertAllocationCount(20);
sampleQueue.discardToRead();
assertAllocationCount(0);
}
@Test
public void readMultiWithSeek() {
writeTestData();
assertReadTestData();
assertThat(sampleQueue.getFirstIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(8);
assertAllocationCount(10);
sampleQueue.seekTo(0);
assertAllocationCount(10);
// Read again.
assertThat(sampleQueue.getFirstIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertReadTestData();
}
@Test
public void emptyQueueReturnsLoadingFinished() {
sampleQueue.sampleData(new ParsableByteArray(DATA), DATA.length);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isFalse();
assertThat(sampleQueue.isReady(/* loadingFinished= */ true)).isTrue();
}
@Test
public void isReadyWithUpstreamFormatOnlyReturnsTrue() {
sampleQueue.format(FORMAT_ENCRYPTED);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
}
@Test
public void isReadyReturnsTrueForValidDrmSession() {
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isFalse();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
}
@Test
public void isReadyReturnsTrueForClearSampleAndPlayClearSamplesWithoutKeysIsTrue() {
when(mockDrmSession.playClearSamplesWithoutKeys()).thenReturn(true);
// We recreate the queue to ensure the mock DRM session manager flags are taken into account.
sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher);
writeTestDataWithEncryptedSections();
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
}
@Test
public void readEncryptedSectionsWaitsForKeys() {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertReadNothing(/* formatRequired= */ false);
assertThat(inputBuffer.waitingForKeys).isTrue();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
assertReadEncryptedSample(/* sampleIndex= */ 0);
assertThat(inputBuffer.waitingForKeys).isFalse();
}
@Test
public void readEncryptedSectionsPopulatesDrmSession() {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
writeTestDataWithEncryptedSections();
int result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
assertReadEncryptedSample(/* sampleIndex= */ 0);
assertReadEncryptedSample(/* sampleIndex= */ 1);
formatHolder.clear();
assertThat(formatHolder.drmSession).isNull();
result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
assertThat(formatHolder.drmSession).isNull();
assertReadEncryptedSample(/* sampleIndex= */ 2);
result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
}
@Test
public void allowPlaceholderSessionPopulatesDrmSession() {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
when(mockDrmSessionManager.acquirePlaceholderSession(
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
.thenReturn(mockPlaceholderDrmSession);
writeTestDataWithEncryptedSections();
int result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
assertReadEncryptedSample(/* sampleIndex= */ 0);
assertReadEncryptedSample(/* sampleIndex= */ 1);
formatHolder.clear();
assertThat(formatHolder.drmSession).isNull();
result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
assertThat(formatHolder.drmSession).isSameInstanceAs(mockPlaceholderDrmSession);
assertReadEncryptedSample(/* sampleIndex= */ 2);
result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
assertThat(formatHolder.drmSession).isSameInstanceAs(mockDrmSession);
assertReadEncryptedSample(/* sampleIndex= */ 3);
}
@Test
public void trailingCryptoInfoInitializationVectorBytesZeroed() {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
when(mockDrmSessionManager.acquirePlaceholderSession(
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
.thenReturn(mockPlaceholderDrmSession);
writeFormat(ENCRYPTED_SAMPLE_FORMATS[0]);
byte[] sampleData = new byte[] {0, 1, 2};
byte[] initializationVector = new byte[] {7, 6, 5, 4, 3, 2, 1, 0};
byte[] encryptedSampleData =
TestUtil.joinByteArrays(
new byte[] {
0x08, // subsampleEncryption = false (1 bit), ivSize = 8 (7 bits).
},
initializationVector,
sampleData);
writeSample(
encryptedSampleData, /* timestampUs= */ 0, BUFFER_FLAG_KEY_FRAME | BUFFER_FLAG_ENCRYPTED);
int result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
// Fill cryptoInfo.iv with non-zero data. When the 8 byte initialization vector is written into
// it, we expect the trailing 8 bytes to be zeroed.
inputBuffer.cryptoInfo.iv = new byte[16];
Arrays.fill(inputBuffer.cryptoInfo.iv, (byte) 1);
result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_BUFFER_READ);
// Assert cryptoInfo.iv contains the 8-byte initialization vector and that the trailing 8 bytes
// have been zeroed.
byte[] expectedInitializationVector = Arrays.copyOf(initializationVector, 16);
assertArrayEquals(expectedInitializationVector, inputBuffer.cryptoInfo.iv);
}
@Test
public void readWithErrorSessionReadsNothingAndThrows() throws IOException {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertReadNothing(/* formatRequired= */ false);
sampleQueue.maybeThrowError();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_ERROR);
when(mockDrmSession.getError()).thenReturn(new DrmSession.DrmSessionException(new Exception()));
assertReadNothing(/* formatRequired= */ false);
try {
sampleQueue.maybeThrowError();
Assert.fail();
} catch (IOException e) {
// Expected.
}
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
assertReadEncryptedSample(/* sampleIndex= */ 0);
}
@Test
public void allowPlayClearSamplesWithoutKeysReadsClearSamples() {
when(mockDrmSession.playClearSamplesWithoutKeys()).thenReturn(true);
// We recreate the queue to ensure the mock DRM session manager flags are taken into account.
sampleQueue = new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher);
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertReadEncryptedSample(/* sampleIndex= */ 0);
}
@Test
public void seekAfterDiscard() {
writeTestData();
assertReadTestData();
sampleQueue.discardToRead();
assertThat(sampleQueue.getFirstIndex()).isEqualTo(8);
assertThat(sampleQueue.getReadIndex()).isEqualTo(8);
assertAllocationCount(0);
sampleQueue.seekTo(0);
assertAllocationCount(0);
// Can't read again.
assertThat(sampleQueue.getFirstIndex()).isEqualTo(8);
assertThat(sampleQueue.getReadIndex()).isEqualTo(8);
assertReadEndOfStream(false);
}
@Test
public void advanceToEnd() {
writeTestData();
sampleQueue.advanceToEnd();
assertAllocationCount(10);
sampleQueue.discardToRead();
assertAllocationCount(0);
// Despite skipping all samples, we should still read the last format, since this is the
// expected format for a subsequent sample.
assertReadFormat(false, FORMAT_2);
// Once the format has been read, there's nothing else to read.
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void advanceToEndRetainsUnassignedData() {
sampleQueue.format(FORMAT_1);
sampleQueue.sampleData(new ParsableByteArray(DATA), ALLOCATION_SIZE);
sampleQueue.advanceToEnd();
assertAllocationCount(1);
sampleQueue.discardToRead();
// Skipping shouldn't discard data that may belong to a sample whose metadata has yet to be
// written.
assertAllocationCount(1);
// We should be able to read the format.
assertReadFormat(false, FORMAT_1);
// Once the format has been read, there's nothing else to read.
assertNoSamplesToRead(FORMAT_1);
sampleQueue.sampleMetadata(0, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
// Once the metadata has been written, check the sample can be read as expected.
assertReadSample(0, true, /* isEncrypted= */ false, DATA, 0, ALLOCATION_SIZE);
assertNoSamplesToRead(FORMAT_1);
assertAllocationCount(1);
sampleQueue.discardToRead();
assertAllocationCount(0);
}
@Test
public void advanceToBeforeBuffer() {
writeTestData();
int skipCount = sampleQueue.advanceTo(SAMPLE_TIMESTAMPS[0] - 1);
// Should have no effect (we're already at the first frame).
assertThat(skipCount).isEqualTo(0);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void advanceToStartOfBuffer() {
writeTestData();
int skipCount = sampleQueue.advanceTo(SAMPLE_TIMESTAMPS[0]);
// Should have no effect (we're already at the first frame).
assertThat(skipCount).isEqualTo(0);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void advanceToEndOfBuffer() {
writeTestData();
int skipCount = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP);
// Should advance to 2nd keyframe (the 4th frame).
assertThat(skipCount).isEqualTo(4);
assertReadTestData(null, DATA_SECOND_KEYFRAME_INDEX);
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void advanceToAfterBuffer() {
writeTestData();
int skipCount = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP + 1);
// Should advance to 2nd keyframe (the 4th frame).
assertThat(skipCount).isEqualTo(4);
assertReadTestData(null, DATA_SECOND_KEYFRAME_INDEX);
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToBeforeBuffer() {
writeTestData();
boolean success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0] - 1, false);
assertThat(success).isFalse();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToStartOfBuffer() {
writeTestData();
boolean success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0], false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToEndOfBuffer() {
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP, false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(4);
assertReadTestData(null, DATA_SECOND_KEYFRAME_INDEX);
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToAfterBuffer() {
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP + 1, false);
assertThat(success).isFalse();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToAfterBufferAllowed() {
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP + 1, true);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(4);
assertReadTestData(null, DATA_SECOND_KEYFRAME_INDEX);
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void seekToEndAndBackToStart() {
writeTestData();
boolean success = sampleQueue.seekTo(LAST_SAMPLE_TIMESTAMP, false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(4);
assertReadTestData(null, DATA_SECOND_KEYFRAME_INDEX);
assertNoSamplesToRead(FORMAT_2);
// Seek back to the start.
success = sampleQueue.seekTo(SAMPLE_TIMESTAMPS[0], false);
assertThat(success).isTrue();
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertReadTestData();
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void discardToEnd() {
writeTestData();
// Should discard everything.
sampleQueue.discardToEnd();
assertThat(sampleQueue.getFirstIndex()).isEqualTo(8);
assertThat(sampleQueue.getReadIndex()).isEqualTo(8);
assertAllocationCount(0);
// We should still be able to read the upstream format.
assertReadFormat(false, FORMAT_2);
// We should be able to write and read subsequent samples.
writeTestData();
assertReadTestData(FORMAT_2);
}
@Test
public void discardToStopAtReadPosition() {
writeTestData();
// Shouldn't discard anything.
sampleQueue.discardTo(LAST_SAMPLE_TIMESTAMP, false, true);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertAllocationCount(10);
// Read the first sample.
assertReadTestData(null, 0, 1);
// Shouldn't discard anything.
sampleQueue.discardTo(SAMPLE_TIMESTAMPS[1] - 1, false, true);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(1);
assertAllocationCount(10);
// Should discard the read sample.
sampleQueue.discardTo(SAMPLE_TIMESTAMPS[1], false, true);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(1);
assertThat(sampleQueue.getReadIndex()).isEqualTo(1);
assertAllocationCount(9);
// Shouldn't discard anything.
sampleQueue.discardTo(LAST_SAMPLE_TIMESTAMP, false, true);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(1);
assertThat(sampleQueue.getReadIndex()).isEqualTo(1);
assertAllocationCount(9);
// Should be able to read the remaining samples.
assertReadTestData(FORMAT_1, 1, 7);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(1);
assertThat(sampleQueue.getReadIndex()).isEqualTo(8);
// Should discard up to the second last sample
sampleQueue.discardTo(LAST_SAMPLE_TIMESTAMP - 1, false, true);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(6);
assertThat(sampleQueue.getReadIndex()).isEqualTo(8);
assertAllocationCount(3);
// Should discard up the last sample
sampleQueue.discardTo(LAST_SAMPLE_TIMESTAMP, false, true);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(7);
assertThat(sampleQueue.getReadIndex()).isEqualTo(8);
assertAllocationCount(1);
}
@Test
public void discardToDontStopAtReadPosition() {
writeTestData();
// Shouldn't discard anything.
sampleQueue.discardTo(SAMPLE_TIMESTAMPS[1] - 1, false, false);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(0);
assertThat(sampleQueue.getReadIndex()).isEqualTo(0);
assertAllocationCount(10);
// Should discard the first sample.
sampleQueue.discardTo(SAMPLE_TIMESTAMPS[1], false, false);
assertThat(sampleQueue.getFirstIndex()).isEqualTo(1);
assertThat(sampleQueue.getReadIndex()).isEqualTo(1);
assertAllocationCount(9);
// Should be able to read the remaining samples.
assertReadTestData(FORMAT_1, 1, 7);
}
@Test
public void discardUpstream() {
writeTestData();
sampleQueue.discardUpstreamSamples(8);
assertAllocationCount(10);
sampleQueue.discardUpstreamSamples(7);
assertAllocationCount(9);
sampleQueue.discardUpstreamSamples(6);
assertAllocationCount(7);
sampleQueue.discardUpstreamSamples(5);
assertAllocationCount(5);
sampleQueue.discardUpstreamSamples(4);
assertAllocationCount(4);
sampleQueue.discardUpstreamSamples(3);
assertAllocationCount(3);
sampleQueue.discardUpstreamSamples(2);
assertAllocationCount(2);
sampleQueue.discardUpstreamSamples(1);
assertAllocationCount(1);
sampleQueue.discardUpstreamSamples(0);
assertAllocationCount(0);
assertReadFormat(false, FORMAT_2);
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void discardUpstreamMulti() {
writeTestData();
sampleQueue.discardUpstreamSamples(4);
assertAllocationCount(4);
sampleQueue.discardUpstreamSamples(0);
assertAllocationCount(0);
assertReadFormat(false, FORMAT_2);
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void discardUpstreamBeforeRead() {
writeTestData();
sampleQueue.discardUpstreamSamples(4);
assertAllocationCount(4);
assertReadTestData(null, 0, 4);
assertReadFormat(false, FORMAT_2);
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void discardUpstreamAfterRead() {
writeTestData();
assertReadTestData(null, 0, 3);
sampleQueue.discardUpstreamSamples(8);
assertAllocationCount(10);
sampleQueue.discardToRead();
assertAllocationCount(7);
sampleQueue.discardUpstreamSamples(7);
assertAllocationCount(6);
sampleQueue.discardUpstreamSamples(6);
assertAllocationCount(4);
sampleQueue.discardUpstreamSamples(5);
assertAllocationCount(2);
sampleQueue.discardUpstreamSamples(4);
assertAllocationCount(1);
sampleQueue.discardUpstreamSamples(3);
assertAllocationCount(0);
assertReadFormat(false, FORMAT_2);
assertNoSamplesToRead(FORMAT_2);
}
@Test
public void largestQueuedTimestampWithDiscardUpstream() {
writeTestData();
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP);
sampleQueue.discardUpstreamSamples(SAMPLE_TIMESTAMPS.length - 1);
// Discarding from upstream should reduce the largest timestamp.
assertThat(sampleQueue.getLargestQueuedTimestampUs())
.isEqualTo(SAMPLE_TIMESTAMPS[SAMPLE_TIMESTAMPS.length - 2]);
sampleQueue.discardUpstreamSamples(0);
// Discarding everything from upstream without reading should unset the largest timestamp.
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(MIN_VALUE);
}
@Test
public void largestQueuedTimestampWithDiscardUpstreamDecodeOrder() {
long[] decodeOrderTimestamps = new long[] {0, 3000, 2000, 1000, 4000, 7000, 6000, 5000};
writeTestData(
DATA, SAMPLE_SIZES, SAMPLE_OFFSETS, decodeOrderTimestamps, SAMPLE_FORMATS, SAMPLE_FLAGS);
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(7000);
sampleQueue.discardUpstreamSamples(SAMPLE_TIMESTAMPS.length - 2);
// Discarding the last two samples should not change the largest timestamp, due to the decode
// ordering of the timestamps.
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(7000);
sampleQueue.discardUpstreamSamples(SAMPLE_TIMESTAMPS.length - 3);
// Once a third sample is discarded, the largest timestamp should have changed.
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(4000);
sampleQueue.discardUpstreamSamples(0);
// Discarding everything from upstream without reading should unset the largest timestamp.
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(MIN_VALUE);
}
@Test
public void largestQueuedTimestampWithRead() {
writeTestData();
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP);
assertReadTestData();
// Reading everything should not reduce the largest timestamp.
assertThat(sampleQueue.getLargestQueuedTimestampUs()).isEqualTo(LAST_SAMPLE_TIMESTAMP);
}
@Test
public void setSampleOffsetBeforeData() {
long sampleOffsetUs = 1000;
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
writeTestData();
assertReadTestData(
/* startFormat= */ null, /* firstSampleIndex= */ 0, /* sampleCount= */ 8, sampleOffsetUs);
assertReadEndOfStream(/* formatRequired= */ false);
}
@Test
public void setSampleOffsetBetweenSamples() {
writeTestData();
long sampleOffsetUs = 1000;
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
// Write a final sample now the offset is set.
long unadjustedTimestampUs = LAST_SAMPLE_TIMESTAMP + 1234;
writeSample(DATA, unadjustedTimestampUs, /* sampleFlags= */ 0);
assertReadTestData();
// We expect to read the format adjusted to account for the sample offset, followed by the final
// sample and then the end of stream.
assertReadFormat(
/* formatRequired= */ false,
FORMAT_2.buildUpon().setSubsampleOffsetUs(sampleOffsetUs).build());
assertReadSample(
unadjustedTimestampUs + sampleOffsetUs,
/* isKeyFrame= */ false,
/* isEncrypted= */ false,
DATA,
/* offset= */ 0,
DATA.length);
assertReadEndOfStream(/* formatRequired= */ false);
}
@Test
public void adjustUpstreamFormat() {
String label = "label";
sampleQueue =
new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher) {
@Override
public Format getAdjustedUpstreamFormat(Format format) {
return super.getAdjustedUpstreamFormat(copyWithLabel(format, label));
}
};
writeFormat(FORMAT_1);
assertReadFormat(/* formatRequired= */ false, copyWithLabel(FORMAT_1, label));
assertReadEndOfStream(/* formatRequired= */ false);
}
@Test
public void invalidateUpstreamFormatAdjustment() {
AtomicReference<String> label = new AtomicReference<>("label1");
sampleQueue =
new SampleQueue(allocator, mockDrmSessionManager, eventDispatcher) {
@Override
public Format getAdjustedUpstreamFormat(Format format) {
return super.getAdjustedUpstreamFormat(copyWithLabel(format, label.get()));
}
};
writeFormat(FORMAT_1);
writeSample(DATA, /* timestampUs= */ 0, BUFFER_FLAG_KEY_FRAME);
// Make a change that'll affect the SampleQueue's format adjustment, and invalidate it.
label.set("label2");
sampleQueue.invalidateUpstreamFormatAdjustment();
writeSample(DATA, /* timestampUs= */ 1, /* sampleFlags= */ 0);
assertReadFormat(/* formatRequired= */ false, copyWithLabel(FORMAT_1, "label1"));
assertReadSample(
/* timeUs= */ 0,
/* isKeyFrame= */ true,
/* isEncrypted= */ false,
DATA,
/* offset= */ 0,
DATA.length);
assertReadFormat(/* formatRequired= */ false, copyWithLabel(FORMAT_1, "label2"));
assertReadSample(
/* timeUs= */ 1,
/* isKeyFrame= */ false,
/* isEncrypted= */ false,
DATA,
/* offset= */ 0,
DATA.length);
assertReadEndOfStream(/* formatRequired= */ false);
}
@Test
public void splice() {
writeTestData();
sampleQueue.splice();
// Splice should succeed, replacing the last 4 samples with the sample being written.
long spliceSampleTimeUs = SAMPLE_TIMESTAMPS[4];
writeFormat(FORMAT_SPLICED);
writeSample(DATA, spliceSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(null, 0, 4);
assertReadFormat(false, FORMAT_SPLICED);
assertReadSample(spliceSampleTimeUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false);
}
@Test
public void spliceAfterRead() {
writeTestData();
assertReadTestData(null, 0, 4);
sampleQueue.splice();
// Splice should fail, leaving the last 4 samples unchanged.
long spliceSampleTimeUs = SAMPLE_TIMESTAMPS[3];
writeFormat(FORMAT_SPLICED);
writeSample(DATA, spliceSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(SAMPLE_FORMATS[3], 4, 4);
assertReadEndOfStream(false);
sampleQueue.seekTo(0);
assertReadTestData(null, 0, 4);
sampleQueue.splice();
// Splice should succeed, replacing the last 4 samples with the sample being written
spliceSampleTimeUs = SAMPLE_TIMESTAMPS[3] + 1;
writeFormat(FORMAT_SPLICED);
writeSample(DATA, spliceSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME);
assertReadFormat(false, FORMAT_SPLICED);
assertReadSample(spliceSampleTimeUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false);
}
@Test
public void spliceWithSampleOffset() {
long sampleOffsetUs = 30000;
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
writeTestData();
sampleQueue.splice();
// Splice should succeed, replacing the last 4 samples with the sample being written.
long spliceSampleTimeUs = SAMPLE_TIMESTAMPS[4];
writeFormat(FORMAT_SPLICED);
writeSample(DATA, spliceSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME);
assertReadTestData(null, 0, 4, sampleOffsetUs);
assertReadFormat(
false, FORMAT_SPLICED.buildUpon().setSubsampleOffsetUs(sampleOffsetUs).build());
assertReadSample(
spliceSampleTimeUs + sampleOffsetUs, true, /* isEncrypted= */ false, DATA, 0, DATA.length);
assertReadEndOfStream(false);
}
// Internal methods.
/**
* Writes standard test data to {@code sampleQueue}.
*/
private void writeTestData() {
writeTestData(
DATA, SAMPLE_SIZES, SAMPLE_OFFSETS, SAMPLE_TIMESTAMPS, SAMPLE_FORMATS, SAMPLE_FLAGS);
}
private void writeTestDataWithEncryptedSections() {
writeTestData(
ENCRYPTED_SAMPLE_DATA,
ENCRYPTED_SAMPLE_SIZES,
ENCRYPTED_SAMPLE_OFFSETS,
ENCRYPTED_SAMPLE_TIMESTAMPS,
ENCRYPTED_SAMPLE_FORMATS,
ENCRYPTED_SAMPLES_FLAGS);
}
/**
* Writes the specified test data to {@code sampleQueue}.
*/
@SuppressWarnings("ReferenceEquality")
private void writeTestData(byte[] data, int[] sampleSizes, int[] sampleOffsets,
long[] sampleTimestamps, Format[] sampleFormats, int[] sampleFlags) {
sampleQueue.sampleData(new ParsableByteArray(data), data.length);
Format format = null;
for (int i = 0; i < sampleTimestamps.length; i++) {
if (sampleFormats[i] != format) {
sampleQueue.format(sampleFormats[i]);
format = sampleFormats[i];
}
sampleQueue.sampleMetadata(
sampleTimestamps[i],
sampleFlags[i],
sampleSizes[i],
sampleOffsets[i],
(sampleFlags[i] & C.BUFFER_FLAG_ENCRYPTED) != 0 ? DUMMY_CRYPTO_DATA : null);
}
}
/** Writes a {@link Format} to the {@code sampleQueue}. */
private void writeFormat(Format format) {
sampleQueue.format(format);
}
/** Writes a single sample to {@code sampleQueue}. */
private void writeSample(byte[] data, long timestampUs, int sampleFlags) {
sampleQueue.sampleData(new ParsableByteArray(data), data.length);
sampleQueue.sampleMetadata(
timestampUs,
sampleFlags,
data.length,
/* offset= */ 0,
(sampleFlags & C.BUFFER_FLAG_ENCRYPTED) != 0 ? DUMMY_CRYPTO_DATA : null);
}
/**
* Asserts correct reading of standard test data from {@code sampleQueue}.
*/
private void assertReadTestData() {
assertReadTestData(null, 0);
}
/**
* Asserts correct reading of standard test data from {@code sampleQueue}.
*
* @param startFormat The format of the last sample previously read from {@code sampleQueue}.
*/
private void assertReadTestData(Format startFormat) {
assertReadTestData(startFormat, 0);
}
/**
* Asserts correct reading of standard test data from {@code sampleQueue}.
*
* @param startFormat The format of the last sample previously read from {@code sampleQueue}.
* @param firstSampleIndex The index of the first sample that's expected to be read.
*/
private void assertReadTestData(Format startFormat, int firstSampleIndex) {
assertReadTestData(startFormat, firstSampleIndex, SAMPLE_TIMESTAMPS.length - firstSampleIndex);
}
/**
* Asserts correct reading of standard test data from {@code sampleQueue}.
*
* @param startFormat The format of the last sample previously read from {@code sampleQueue}.
* @param firstSampleIndex The index of the first sample that's expected to be read.
* @param sampleCount The number of samples to read.
*/
private void assertReadTestData(Format startFormat, int firstSampleIndex, int sampleCount) {
assertReadTestData(startFormat, firstSampleIndex, sampleCount, 0);
}
/**
* Asserts correct reading of standard test data from {@code sampleQueue}.
*
* @param startFormat The format of the last sample previously read from {@code sampleQueue}.
* @param firstSampleIndex The index of the first sample that's expected to be read.
* @param sampleCount The number of samples to read.
* @param sampleOffsetUs The expected sample offset.
*/
private void assertReadTestData(
Format startFormat, int firstSampleIndex, int sampleCount, long sampleOffsetUs) {
Format format = adjustFormat(startFormat, sampleOffsetUs);
for (int i = firstSampleIndex; i < firstSampleIndex + sampleCount; i++) {
// Use equals() on the read side despite using referential equality on the write side, since
// sampleQueue de-duplicates written formats using equals().
Format testSampleFormat = adjustFormat(SAMPLE_FORMATS[i], sampleOffsetUs);
if (!testSampleFormat.equals(format)) {
// If the format has changed, we should read it.
assertReadFormat(false, testSampleFormat);
format = testSampleFormat;
}
// If we require the format, we should always read it.
assertReadFormat(true, testSampleFormat);
// Assert the sample is as expected.
assertReadSample(
SAMPLE_TIMESTAMPS[i] + sampleOffsetUs,
(SAMPLE_FLAGS[i] & C.BUFFER_FLAG_KEY_FRAME) != 0,
/* isEncrypted= */ false,
DATA,
DATA.length - SAMPLE_OFFSETS[i] - SAMPLE_SIZES[i],
SAMPLE_SIZES[i]);
}
}
/**
* Asserts {@link SampleQueue#read} is behaving correctly, given there are no samples to read and
* the last format to be written to the sample queue is {@code endFormat}.
*
* @param endFormat The last format to be written to the sample queue, or null of no format has
* been written.
*/
private void assertNoSamplesToRead(Format endFormat) {
// If not formatRequired or loadingFinished, should read nothing.
assertReadNothing(false);
// If formatRequired, should read the end format if set, else read nothing.
if (endFormat == null) {
assertReadNothing(true);
} else {
assertReadFormat(true, endFormat);
}
// If loadingFinished, should read end of stream.
assertReadEndOfStream(false);
assertReadEndOfStream(true);
// Having read end of stream should not affect other cases.
assertReadNothing(false);
if (endFormat == null) {
assertReadNothing(true);
} else {
assertReadFormat(true, endFormat);
}
}
/**
* Asserts {@link SampleQueue#read} returns {@link C#RESULT_NOTHING_READ}.
*
* @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,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_NOTHING_READ);
// formatHolder should not be populated.
assertThat(formatHolder.format).isNull();
// inputBuffer should not be populated.
assertInputBufferContainsNoSampleData();
assertInputBufferHasNoDefaultFlagsSet();
}
/**
* 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 {@link SampleQueue#read}.
*/
private void assertReadEndOfStream(boolean formatRequired) {
clearFormatHolderAndInputBuffer();
int result =
sampleQueue.read(
formatHolder,
inputBuffer,
formatRequired,
/* loadingFinished= */ true,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_BUFFER_READ);
// formatHolder should not be populated.
assertThat(formatHolder.format).isNull();
// inputBuffer should not contain sample data, but end of stream flag should be set.
assertInputBufferContainsNoSampleData();
assertThat(inputBuffer.isEndOfStream()).isTrue();
assertThat(inputBuffer.isDecodeOnly()).isFalse();
assertThat(inputBuffer.isEncrypted()).isFalse();
}
/**
* 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 {@link SampleQueue#read}.
* @param format The expected format.
*/
private void assertReadFormat(boolean formatRequired, Format format) {
clearFormatHolderAndInputBuffer();
int result =
sampleQueue.read(
formatHolder,
inputBuffer,
formatRequired,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_FORMAT_READ);
// formatHolder should be populated.
assertThat(formatHolder.format).isEqualTo(format);
// inputBuffer should not be populated.
assertInputBufferContainsNoSampleData();
assertInputBufferHasNoDefaultFlagsSet();
}
private void assertReadEncryptedSample(int sampleIndex) {
byte[] sampleData = new byte[ENCRYPTED_SAMPLE_SIZES[sampleIndex]];
Arrays.fill(sampleData, (byte) 1);
boolean isKeyFrame = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0;
boolean isEncrypted = (ENCRYPTED_SAMPLES_FLAGS[sampleIndex] & C.BUFFER_FLAG_ENCRYPTED) != 0;
assertReadSample(
ENCRYPTED_SAMPLE_TIMESTAMPS[sampleIndex],
isKeyFrame,
isEncrypted,
sampleData,
/* offset= */ 0,
ENCRYPTED_SAMPLE_SIZES[sampleIndex] - (isEncrypted ? 2 : 0));
}
/**
* Asserts {@link SampleQueue#read} returns {@link C#RESULT_BUFFER_READ} and that the buffer is
* filled with the specified sample data.
*
* @param timeUs The expected buffer timestamp.
* @param isKeyFrame The expected keyframe flag.
* @param isEncrypted The expected encrypted flag.
* @param sampleData An array containing the expected sample data.
* @param offset The offset in {@code sampleData} of the expected sample data.
* @param length The length of the expected sample data.
*/
private void assertReadSample(
long timeUs,
boolean isKeyFrame,
boolean isEncrypted,
byte[] sampleData,
int offset,
int length) {
clearFormatHolderAndInputBuffer();
int result =
sampleQueue.read(
formatHolder,
inputBuffer,
/* formatRequired= */ false,
/* loadingFinished= */ false,
/* decodeOnlyUntilUs= */ 0);
assertThat(result).isEqualTo(RESULT_BUFFER_READ);
// formatHolder should not be populated.
assertThat(formatHolder.format).isNull();
// inputBuffer should be populated.
assertThat(inputBuffer.timeUs).isEqualTo(timeUs);
assertThat(inputBuffer.isKeyFrame()).isEqualTo(isKeyFrame);
assertThat(inputBuffer.isDecodeOnly()).isFalse();
assertThat(inputBuffer.isEncrypted()).isEqualTo(isEncrypted);
inputBuffer.flip();
assertThat(inputBuffer.data.limit()).isEqualTo(length);
byte[] readData = new byte[length];
inputBuffer.data.get(readData);
assertThat(readData).isEqualTo(copyOfRange(sampleData, offset, offset + length));
}
/**
* Asserts the number of allocations currently in use by {@code sampleQueue}.
*
* @param count The expected number of allocations.
*/
private void assertAllocationCount(int count) {
assertThat(allocator.getTotalBytesAllocated()).isEqualTo(ALLOCATION_SIZE * count);
}
/**
* Asserts {@code inputBuffer} does not contain any sample data.
*/
private void assertInputBufferContainsNoSampleData() {
if (inputBuffer.data == null) {
return;
}
inputBuffer.flip();
assertThat(inputBuffer.data.limit()).isEqualTo(0);
}
private void assertInputBufferHasNoDefaultFlagsSet() {
assertThat(inputBuffer.isEndOfStream()).isFalse();
assertThat(inputBuffer.isDecodeOnly()).isFalse();
assertThat(inputBuffer.isEncrypted()).isFalse();
}
private void clearFormatHolderAndInputBuffer() {
formatHolder.format = null;
inputBuffer.clear();
}
private static Format adjustFormat(@Nullable Format format, long sampleOffsetUs) {
return format == null || sampleOffsetUs == 0
? format
: format.buildUpon().setSubsampleOffsetUs(sampleOffsetUs).build();
}
private static Format buildFormat(String id) {
return new Format.Builder().setId(id).setSubsampleOffsetUs(0).build();
}
private static Format copyWithLabel(Format format, String label) {
return format.buildUpon().setLabel(label).build();
}
}