mirror of
https://github.com/samsonjs/media.git
synced 2026-04-18 13:25:47 +00:00
Add test for DefaultTrackOutput
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=158745843
This commit is contained in:
parent
fa4f876668
commit
350998219a
1 changed files with 457 additions and 0 deletions
|
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* 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.extractor;
|
||||
|
||||
import android.test.MoreAsserts;
|
||||
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.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 junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Test for {@link DefaultTrackOutput}.
|
||||
*/
|
||||
public class DefaultTrackOutputTest extends TestCase {
|
||||
|
||||
private static final int ALLOCATION_SIZE = 16;
|
||||
|
||||
private static final Format TEST_FORMAT_1 = Format.createSampleFormat("1", "mimeType", 0);
|
||||
private static final Format TEST_FORMAT_2 = Format.createSampleFormat("2", "mimeType", 0);
|
||||
private static final Format TEST_FORMAT_1_COPY = Format.createSampleFormat("1", "mimeType", 0);
|
||||
private static final byte[] TEST_DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
|
||||
|
||||
/*
|
||||
* TEST_SAMPLE_SIZES and TEST_SAMPLE_OFFSETS are intended to test various boundary cases (with
|
||||
* respect to the allocation size). TEST_SAMPLE_OFFSETS values are defined as the backward offsets
|
||||
* (as expected by DefaultTrackOutput.sampleMetadata) assuming that TEST_DATA has been written to
|
||||
* the trackOutput 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[] TEST_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[] TEST_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 int[] TEST_SAMPLE_TIMESTAMPS = new int[] {
|
||||
0, 1000, 2000, 3000, 4000, 5000, 6000, 7000
|
||||
};
|
||||
private static final int[] TEST_SAMPLE_FLAGS = new int[] {
|
||||
C.BUFFER_FLAG_KEY_FRAME, 0, 0, 0, C.BUFFER_FLAG_KEY_FRAME, 0, 0, 0
|
||||
};
|
||||
private static final Format[] TEST_SAMPLE_FORMATS = new Format[] {
|
||||
TEST_FORMAT_1, TEST_FORMAT_1, TEST_FORMAT_1, TEST_FORMAT_1, TEST_FORMAT_2, TEST_FORMAT_2,
|
||||
TEST_FORMAT_2, TEST_FORMAT_2
|
||||
};
|
||||
private static final int TEST_DATA_SECOND_KEYFRAME_INDEX = 4;
|
||||
|
||||
private Allocator allocator;
|
||||
private DefaultTrackOutput trackOutput;
|
||||
private FormatHolder formatHolder;
|
||||
private DecoderInputBuffer inputBuffer;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
|
||||
trackOutput = new DefaultTrackOutput(allocator);
|
||||
formatHolder = new FormatHolder();
|
||||
inputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
allocator = null;
|
||||
trackOutput = null;
|
||||
formatHolder = null;
|
||||
inputBuffer = null;
|
||||
}
|
||||
|
||||
public void testReadWithoutWrite() {
|
||||
assertNoSamplesToRead(null);
|
||||
}
|
||||
|
||||
public void testReadFormatDeduplicated() {
|
||||
trackOutput.format(TEST_FORMAT_1);
|
||||
assertReadFormat(false, TEST_FORMAT_1);
|
||||
// If the same format is input then it should be de-duplicated (i.e. not output again).
|
||||
trackOutput.format(TEST_FORMAT_1);
|
||||
assertNoSamplesToRead(TEST_FORMAT_1);
|
||||
// The same applies for a format that's equal (but a different object).
|
||||
trackOutput.format(TEST_FORMAT_1_COPY);
|
||||
assertNoSamplesToRead(TEST_FORMAT_1);
|
||||
}
|
||||
|
||||
public void testReadSingleSamples() {
|
||||
trackOutput.sampleData(new ParsableByteArray(TEST_DATA), ALLOCATION_SIZE);
|
||||
|
||||
assertAllocationCount(1);
|
||||
// Nothing to read.
|
||||
assertNoSamplesToRead(null);
|
||||
|
||||
trackOutput.format(TEST_FORMAT_1);
|
||||
|
||||
// Read the format.
|
||||
assertReadFormat(false, TEST_FORMAT_1);
|
||||
// Nothing to read.
|
||||
assertNoSamplesToRead(TEST_FORMAT_1);
|
||||
|
||||
trackOutput.sampleMetadata(1000, C.BUFFER_FLAG_KEY_FRAME, ALLOCATION_SIZE, 0, null);
|
||||
|
||||
// If formatRequired, should read the format rather than the sample.
|
||||
assertReadFormat(true, TEST_FORMAT_1);
|
||||
// Otherwise should read the sample.
|
||||
assertSampleRead(1000, true, TEST_DATA, 0, ALLOCATION_SIZE);
|
||||
// The allocation should have been released.
|
||||
assertAllocationCount(0);
|
||||
|
||||
// Nothing to read.
|
||||
assertNoSamplesToRead(TEST_FORMAT_1);
|
||||
|
||||
// Write a second sample followed by one byte that does not belong to it.
|
||||
trackOutput.sampleData(new ParsableByteArray(TEST_DATA), ALLOCATION_SIZE);
|
||||
trackOutput.sampleMetadata(2000, 0, ALLOCATION_SIZE - 1, 1, null);
|
||||
|
||||
// If formatRequired, should read the format rather than the sample.
|
||||
assertReadFormat(true, TEST_FORMAT_1);
|
||||
// Read the sample.
|
||||
assertSampleRead(2000, false, TEST_DATA, 0, ALLOCATION_SIZE - 1);
|
||||
// The last byte written to the output 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.
|
||||
trackOutput.sampleMetadata(3000, 0, 1, 0, null);
|
||||
|
||||
// If formatRequired, should read the format rather than the sample.
|
||||
assertReadFormat(true, TEST_FORMAT_1);
|
||||
// Read the sample.
|
||||
assertSampleRead(3000, false, TEST_DATA, ALLOCATION_SIZE - 1, 1);
|
||||
// The allocation should have been released.
|
||||
assertAllocationCount(0);
|
||||
}
|
||||
|
||||
public void testReadMultiSamples() {
|
||||
writeTestData();
|
||||
assertEquals(TEST_SAMPLE_TIMESTAMPS[TEST_SAMPLE_TIMESTAMPS.length - 1],
|
||||
trackOutput.getLargestQueuedTimestampUs());
|
||||
assertAllocationCount(10);
|
||||
assertReadTestData();
|
||||
assertAllocationCount(0);
|
||||
}
|
||||
|
||||
public void testReadMultiSamplesTwice() {
|
||||
writeTestData();
|
||||
writeTestData();
|
||||
assertAllocationCount(20);
|
||||
assertReadTestData(TEST_FORMAT_2);
|
||||
assertReadTestData(TEST_FORMAT_2);
|
||||
assertAllocationCount(0);
|
||||
}
|
||||
|
||||
public void testSkipAll() {
|
||||
writeTestData();
|
||||
trackOutput.skipAll();
|
||||
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, TEST_FORMAT_2);
|
||||
// Once the format has been read, there's nothing else to read.
|
||||
assertNoSamplesToRead(TEST_FORMAT_2);
|
||||
}
|
||||
|
||||
public void testSkipAllRetainsUnassignedData() {
|
||||
trackOutput.format(TEST_FORMAT_1);
|
||||
trackOutput.sampleData(new ParsableByteArray(TEST_DATA), ALLOCATION_SIZE);
|
||||
trackOutput.skipAll();
|
||||
// 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, TEST_FORMAT_1);
|
||||
// Once the format has been read, there's nothing else to read.
|
||||
assertNoSamplesToRead(TEST_FORMAT_1);
|
||||
|
||||
trackOutput.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.
|
||||
assertSampleRead(0, true, TEST_DATA, 0, ALLOCATION_SIZE);
|
||||
assertNoSamplesToRead(TEST_FORMAT_1);
|
||||
assertAllocationCount(0);
|
||||
}
|
||||
|
||||
public void testSkipToKeyframeBeforeBuffer() {
|
||||
writeTestData();
|
||||
boolean result = trackOutput.skipToKeyframeBefore(TEST_SAMPLE_TIMESTAMPS[0] - 1, false);
|
||||
// Should fail and have no effect.
|
||||
assertFalse(result);
|
||||
assertReadTestData();
|
||||
assertNoSamplesToRead(TEST_FORMAT_2);
|
||||
}
|
||||
|
||||
public void testSkipToKeyframeStartOfBuffer() {
|
||||
writeTestData();
|
||||
boolean result = trackOutput.skipToKeyframeBefore(TEST_SAMPLE_TIMESTAMPS[0], false);
|
||||
// Should succeed but have no effect (we're already at the first frame).
|
||||
assertTrue(result);
|
||||
assertReadTestData();
|
||||
assertNoSamplesToRead(TEST_FORMAT_2);
|
||||
}
|
||||
|
||||
public void testSkipToKeyframeEndOfBuffer() {
|
||||
writeTestData();
|
||||
boolean result = trackOutput.skipToKeyframeBefore(
|
||||
TEST_SAMPLE_TIMESTAMPS[TEST_SAMPLE_TIMESTAMPS.length - 1], false);
|
||||
// Should succeed and skip to 2nd keyframe.
|
||||
assertTrue(result);
|
||||
assertReadTestData(null, TEST_DATA_SECOND_KEYFRAME_INDEX);
|
||||
assertNoSamplesToRead(TEST_FORMAT_2);
|
||||
}
|
||||
|
||||
public void testSkipToKeyframeAfterBuffer() {
|
||||
writeTestData();
|
||||
boolean result = trackOutput.skipToKeyframeBefore(
|
||||
TEST_SAMPLE_TIMESTAMPS[TEST_SAMPLE_TIMESTAMPS.length - 1] + 1, false);
|
||||
// Should fail and have no effect.
|
||||
assertFalse(result);
|
||||
assertReadTestData();
|
||||
assertNoSamplesToRead(TEST_FORMAT_2);
|
||||
}
|
||||
|
||||
public void testSkipToKeyframeAfterBufferAllowed() {
|
||||
writeTestData();
|
||||
boolean result = trackOutput.skipToKeyframeBefore(
|
||||
TEST_SAMPLE_TIMESTAMPS[TEST_SAMPLE_TIMESTAMPS.length - 1] + 1, true);
|
||||
// Should succeed and skip to 2nd keyframe.
|
||||
assertTrue(result);
|
||||
assertReadTestData(null, TEST_DATA_SECOND_KEYFRAME_INDEX);
|
||||
assertNoSamplesToRead(TEST_FORMAT_2);
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
/**
|
||||
* Writes standard test data to {@code trackOutput}.
|
||||
*/
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
private void writeTestData() {
|
||||
trackOutput.sampleData(new ParsableByteArray(TEST_DATA), TEST_DATA.length);
|
||||
Format format = null;
|
||||
for (int i = 0; i < TEST_SAMPLE_TIMESTAMPS.length; i++) {
|
||||
if (TEST_SAMPLE_FORMATS[i] != format) {
|
||||
trackOutput.format(TEST_SAMPLE_FORMATS[i]);
|
||||
format = TEST_SAMPLE_FORMATS[i];
|
||||
}
|
||||
trackOutput.sampleMetadata(TEST_SAMPLE_TIMESTAMPS[i], TEST_SAMPLE_FLAGS[i],
|
||||
TEST_SAMPLE_SIZES[i], TEST_SAMPLE_OFFSETS[i], null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts correct reading of standard test data from {@code trackOutput}.
|
||||
*/
|
||||
private void assertReadTestData() {
|
||||
assertReadTestData(null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts correct reading of standard test data from {@code trackOutput}.
|
||||
*
|
||||
* @param startFormat The format of the last sample previously read from {@code trackOutput}.
|
||||
*/
|
||||
private void assertReadTestData(Format startFormat) {
|
||||
assertReadTestData(startFormat, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts correct reading of standard test data from {@code trackOutput}.
|
||||
*
|
||||
* @param startFormat The format of the last sample previously read from {@code trackOutput}.
|
||||
* @param firstSampleIndex The index of the first sample that's expected to be read.
|
||||
*/
|
||||
private void assertReadTestData(Format startFormat, int firstSampleIndex) {
|
||||
Format format = startFormat;
|
||||
for (int i = firstSampleIndex; i < TEST_SAMPLE_TIMESTAMPS.length; i++) {
|
||||
// Use equals() on the read side despite using referential equality on the write side, since
|
||||
// trackOutput de-duplicates written formats using equals().
|
||||
if (!TEST_SAMPLE_FORMATS[i].equals(format)) {
|
||||
// If the format has changed, we should read it.
|
||||
assertReadFormat(false, TEST_SAMPLE_FORMATS[i]);
|
||||
format = TEST_SAMPLE_FORMATS[i];
|
||||
}
|
||||
// If we require the format, we should always read it.
|
||||
assertReadFormat(true, TEST_SAMPLE_FORMATS[i]);
|
||||
// Assert the sample is as expected.
|
||||
assertSampleRead(TEST_SAMPLE_TIMESTAMPS[i],
|
||||
(TEST_SAMPLE_FLAGS[i] & C.BUFFER_FLAG_KEY_FRAME) != 0,
|
||||
TEST_DATA,
|
||||
TEST_DATA.length - TEST_SAMPLE_OFFSETS[i] - TEST_SAMPLE_SIZES[i],
|
||||
TEST_SAMPLE_SIZES[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts {@link DefaultTrackOutput#readData} is behaving correctly, given there are no samples
|
||||
* to read and the last format to be written to the output is {@code endFormat}.
|
||||
*
|
||||
* @param endFormat The last format to be written to the output, 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 DefaultTrackOutput#readData} returns {@link C#RESULT_NOTHING_READ}.
|
||||
*
|
||||
* @param formatRequired The value of {@code formatRequired} passed to readData.
|
||||
*/
|
||||
private void assertReadNothing(boolean formatRequired) {
|
||||
clearFormatHolderAndInputBuffer();
|
||||
int result = trackOutput.readData(formatHolder, inputBuffer, formatRequired, false, 0);
|
||||
assertEquals(C.RESULT_NOTHING_READ, result);
|
||||
// formatHolder should not be populated.
|
||||
assertNull(formatHolder.format);
|
||||
// inputBuffer should not be populated.
|
||||
assertInputBufferContainsNoSampleData();
|
||||
assertInputBufferHasNoDefaultFlagsSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts {@link DefaultTrackOutput#readData} returns {@link C#RESULT_BUFFER_READ} and that the
|
||||
* {@link DecoderInputBuffer#isEndOfStream()} is set.
|
||||
*
|
||||
* @param formatRequired The value of {@code formatRequired} passed to readData.
|
||||
*/
|
||||
private void assertReadEndOfStream(boolean formatRequired) {
|
||||
clearFormatHolderAndInputBuffer();
|
||||
int result = trackOutput.readData(formatHolder, inputBuffer, formatRequired, true, 0);
|
||||
assertEquals(C.RESULT_BUFFER_READ, result);
|
||||
// formatHolder should not be populated.
|
||||
assertNull(formatHolder.format);
|
||||
// inputBuffer should not contain sample data, but end of stream flag should be set.
|
||||
assertInputBufferContainsNoSampleData();
|
||||
assertTrue(inputBuffer.isEndOfStream());
|
||||
assertFalse(inputBuffer.isDecodeOnly());
|
||||
assertFalse(inputBuffer.isEncrypted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts {@link DefaultTrackOutput#readData} 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 format The expected format.
|
||||
*/
|
||||
private void assertReadFormat(boolean formatRequired, Format format) {
|
||||
clearFormatHolderAndInputBuffer();
|
||||
int result = trackOutput.readData(formatHolder, inputBuffer, formatRequired, false, 0);
|
||||
assertEquals(C.RESULT_FORMAT_READ, result);
|
||||
// formatHolder should be populated.
|
||||
assertEquals(format, formatHolder.format);
|
||||
// inputBuffer should not be populated.
|
||||
assertInputBufferContainsNoSampleData();
|
||||
assertInputBufferHasNoDefaultFlagsSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts {@link DefaultTrackOutput#readData} 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 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 assertSampleRead(long timeUs, boolean isKeyframe, byte[] sampleData, int offset,
|
||||
int length) {
|
||||
clearFormatHolderAndInputBuffer();
|
||||
int result = trackOutput.readData(formatHolder, inputBuffer, false, false, 0);
|
||||
assertEquals(C.RESULT_BUFFER_READ, result);
|
||||
// formatHolder should not be populated.
|
||||
assertNull(formatHolder.format);
|
||||
// inputBuffer should be populated.
|
||||
assertEquals(timeUs, inputBuffer.timeUs);
|
||||
assertEquals(isKeyframe, inputBuffer.isKeyFrame());
|
||||
assertFalse(inputBuffer.isDecodeOnly());
|
||||
assertFalse(inputBuffer.isEncrypted());
|
||||
inputBuffer.flip();
|
||||
assertEquals(length, inputBuffer.data.limit());
|
||||
byte[] readData = new byte[length];
|
||||
inputBuffer.data.get(readData);
|
||||
MoreAsserts.assertEquals(Arrays.copyOfRange(sampleData, offset, offset + length), readData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the number of allocations currently in use by {@code trackOutput}.
|
||||
*
|
||||
* @param count The expected number of allocations.
|
||||
*/
|
||||
private void assertAllocationCount(int count) {
|
||||
assertEquals(ALLOCATION_SIZE * count, allocator.getTotalBytesAllocated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts {@code inputBuffer} does not contain any sample data.
|
||||
*/
|
||||
private void assertInputBufferContainsNoSampleData() {
|
||||
if (inputBuffer.data == null) {
|
||||
return;
|
||||
}
|
||||
inputBuffer.flip();
|
||||
assertEquals(0, inputBuffer.data.limit());
|
||||
}
|
||||
|
||||
private void assertInputBufferHasNoDefaultFlagsSet() {
|
||||
assertFalse(inputBuffer.isEndOfStream());
|
||||
assertFalse(inputBuffer.isDecodeOnly());
|
||||
assertFalse(inputBuffer.isEncrypted());
|
||||
}
|
||||
|
||||
private void clearFormatHolderAndInputBuffer() {
|
||||
formatHolder.format = null;
|
||||
inputBuffer.clear();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue