Commit e3285466 by Andrew Lewis

Make reusable fakes for extractor+track outputs.

Improve Mp4Extractor test.

Add support for Xiph lacing in Matroska files.

Add support for EBML lacing in Matroska files.

Handle the initial sticky intent for HDMI audio plug.
parent bc14e87c
...@@ -68,7 +68,12 @@ public final class AudioCapabilitiesReceiver { ...@@ -68,7 +68,12 @@ public final class AudioCapabilitiesReceiver {
@TargetApi(21) @TargetApi(21)
public void register() { public void register() {
if (receiver != null) { if (receiver != null) {
Intent initialStickyIntent =
context.registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)); context.registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
if (initialStickyIntent != null) {
receiver.onReceive(context, initialStickyIntent);
return;
}
} }
listener.onAudioCapabilitiesChanged(DEFAULT_AUDIO_CAPABILITIES); listener.onAudioCapabilitiesChanged(DEFAULT_AUDIO_CAPABILITIES);
...@@ -86,6 +91,10 @@ public final class AudioCapabilitiesReceiver { ...@@ -86,6 +91,10 @@ public final class AudioCapabilitiesReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (isInitialStickyBroadcast()) {
return;
}
String action = intent.getAction(); String action = intent.getAction();
if (!action.equals(AudioManager.ACTION_HDMI_AUDIO_PLUG)) { if (!action.equals(AudioManager.ACTION_HDMI_AUDIO_PLUG)) {
return; return;
......
...@@ -126,7 +126,9 @@ public final class WebmExtractor implements Extractor { ...@@ -126,7 +126,9 @@ public final class WebmExtractor implements Extractor {
private static final int ID_CUE_CLUSTER_POSITION = 0xF1; private static final int ID_CUE_CLUSTER_POSITION = 0xF1;
private static final int LACING_NONE = 0; private static final int LACING_NONE = 0;
private static final int LACING_XIPH = 1;
private static final int LACING_FIXED_SIZE = 2; private static final int LACING_FIXED_SIZE = 2;
private static final int LACING_EBML = 3;
private final EbmlReader reader; private final EbmlReader reader;
private final VarintReader varintReader; private final VarintReader varintReader;
...@@ -168,7 +170,7 @@ public final class WebmExtractor implements Extractor { ...@@ -168,7 +170,7 @@ public final class WebmExtractor implements Extractor {
private long blockTimeUs; private long blockTimeUs;
private int blockLacingSampleIndex; private int blockLacingSampleIndex;
private int blockLacingSampleCount; private int blockLacingSampleCount;
private int blockLacingSampleSize; private int[] blockLacingSampleSizes;
private int blockTrackNumber; private int blockTrackNumber;
private int blockTrackNumberLength; private int blockTrackNumberLength;
private int blockFlags; private int blockFlags;
...@@ -600,8 +602,9 @@ public final class WebmExtractor implements Extractor { ...@@ -600,8 +602,9 @@ public final class WebmExtractor implements Extractor {
int lacing = (scratch.data[2] & 0x06) >> 1; int lacing = (scratch.data[2] & 0x06) >> 1;
if (lacing == LACING_NONE) { if (lacing == LACING_NONE) {
blockLacingSampleCount = 1; blockLacingSampleCount = 1;
blockLacingSampleSize = contentSize - blockTrackNumberLength - 3; blockLacingSampleSizes = ensureArrayCapacity(blockLacingSampleSizes, 1);
} else if (lacing == LACING_FIXED_SIZE) { blockLacingSampleSizes[0] = contentSize - blockTrackNumberLength - 3;
} else {
if (id != ID_SIMPLE_BLOCK) { if (id != ID_SIMPLE_BLOCK) {
throw new ParserException("Lacing only supported in SimpleBlocks."); throw new ParserException("Lacing only supported in SimpleBlocks.");
} }
...@@ -609,10 +612,69 @@ public final class WebmExtractor implements Extractor { ...@@ -609,10 +612,69 @@ public final class WebmExtractor implements Extractor {
// Read the sample count (1 byte). // Read the sample count (1 byte).
readScratch(input, 4); readScratch(input, 4);
blockLacingSampleCount = (scratch.data[3] & 0xFF) + 1; blockLacingSampleCount = (scratch.data[3] & 0xFF) + 1;
blockLacingSampleSize = blockLacingSampleSizes =
ensureArrayCapacity(blockLacingSampleSizes, blockLacingSampleCount);
if (lacing == LACING_FIXED_SIZE) {
int blockLacingSampleSize =
(contentSize - blockTrackNumberLength - 4) / blockLacingSampleCount; (contentSize - blockTrackNumberLength - 4) / blockLacingSampleCount;
Arrays.fill(blockLacingSampleSizes, 0, blockLacingSampleCount, blockLacingSampleSize);
} else if (lacing == LACING_XIPH) {
int totalSamplesSize = 0;
int headerSize = 4;
for (int sampleIndex = 0; sampleIndex < blockLacingSampleCount - 1; sampleIndex++) {
blockLacingSampleSizes[sampleIndex] = 0;
int byteValue;
do {
readScratch(input, ++headerSize);
byteValue = scratch.data[headerSize - 1] & 0xFF;
blockLacingSampleSizes[sampleIndex] += byteValue;
} while (byteValue == 0xFF);
totalSamplesSize += blockLacingSampleSizes[sampleIndex];
}
blockLacingSampleSizes[blockLacingSampleCount - 1] =
contentSize - blockTrackNumberLength - headerSize - totalSamplesSize;
} else if (lacing == LACING_EBML) {
int totalSamplesSize = 0;
int headerSize = 4;
for (int sampleIndex = 0; sampleIndex < blockLacingSampleCount - 1; sampleIndex++) {
blockLacingSampleSizes[sampleIndex] = 0;
readScratch(input, ++headerSize);
if (scratch.data[headerSize - 1] == 0) {
throw new ParserException("No valid varint length mask found");
}
long readValue = 0;
for (int i = 0; i < 8; i++) {
int lengthMask = 1 << (7 - i);
if ((scratch.data[headerSize - 1] & lengthMask) != 0) {
int readPosition = headerSize - 1;
headerSize += i;
readScratch(input, headerSize);
readValue = (scratch.data[readPosition++] & 0xFF) & ~lengthMask;
while (readPosition < headerSize) {
readValue <<= 8;
readValue |= (scratch.data[readPosition++] & 0xFF);
}
// The first read value is the first size. Later values are signed offsets.
if (sampleIndex > 0) {
readValue -= (1L << 6 + i * 7) - 1;
}
break;
}
}
if (readValue < Integer.MIN_VALUE || readValue > Integer.MAX_VALUE) {
throw new ParserException("EBML lacing sample size out of range.");
}
int intReadValue = (int) readValue;
blockLacingSampleSizes[sampleIndex] = sampleIndex == 0
? intReadValue : blockLacingSampleSizes[sampleIndex - 1] + intReadValue;
totalSamplesSize += blockLacingSampleSizes[sampleIndex];
}
blockLacingSampleSizes[blockLacingSampleCount - 1] =
contentSize - blockTrackNumberLength - headerSize - totalSamplesSize;
} else { } else {
throw new ParserException("Lacing mode not supported: " + lacing); // Lacing is always in the range 0--3.
throw new IllegalStateException("Unexpected lacing value: " + lacing);
}
} }
int timecode = (scratch.data[0] << 8) | (scratch.data[1] & 0xFF); int timecode = (scratch.data[0] << 8) | (scratch.data[1] & 0xFF);
...@@ -629,7 +691,8 @@ public final class WebmExtractor implements Extractor { ...@@ -629,7 +691,8 @@ public final class WebmExtractor implements Extractor {
if (id == ID_SIMPLE_BLOCK) { if (id == ID_SIMPLE_BLOCK) {
// For SimpleBlock, we have metadata for each sample here. // For SimpleBlock, we have metadata for each sample here.
while (blockLacingSampleIndex < blockLacingSampleCount) { while (blockLacingSampleIndex < blockLacingSampleCount) {
writeSampleData(input, trackOutput, sampleTrackFormat, blockLacingSampleSize); writeSampleData(input, trackOutput, sampleTrackFormat,
blockLacingSampleSizes[blockLacingSampleIndex]);
long sampleTimeUs = this.blockTimeUs long sampleTimeUs = this.blockTimeUs
+ (blockLacingSampleIndex * sampleTrackFormat.defaultSampleDurationNs) / 1000; + (blockLacingSampleIndex * sampleTrackFormat.defaultSampleDurationNs) / 1000;
outputSampleMetadata(trackOutput, sampleTimeUs); outputSampleMetadata(trackOutput, sampleTimeUs);
...@@ -639,7 +702,7 @@ public final class WebmExtractor implements Extractor { ...@@ -639,7 +702,7 @@ public final class WebmExtractor implements Extractor {
} else { } else {
// For Block, we send the metadata at the end of the BlockGroup element since we'll know // For Block, we send the metadata at the end of the BlockGroup element since we'll know
// if the sample is a keyframe or not only at that point. // if the sample is a keyframe or not only at that point.
writeSampleData(input, trackOutput, sampleTrackFormat, blockLacingSampleSize); writeSampleData(input, trackOutput, sampleTrackFormat, blockLacingSampleSizes[0]);
} }
return; return;
...@@ -665,6 +728,10 @@ public final class WebmExtractor implements Extractor { ...@@ -665,6 +728,10 @@ public final class WebmExtractor implements Extractor {
if (scratch.limit() >= requiredLength) { if (scratch.limit() >= requiredLength) {
return; return;
} }
if (scratch.capacity() < requiredLength) {
scratch.reset(Arrays.copyOf(scratch.data, Math.max(scratch.data.length * 2, requiredLength)),
scratch.limit());
}
input.readFully(scratch.data, scratch.limit(), requiredLength - scratch.limit()); input.readFully(scratch.data, scratch.limit(), requiredLength - scratch.limit());
scratch.setLimit(requiredLength); scratch.setLimit(requiredLength);
} }
...@@ -814,7 +881,7 @@ public final class WebmExtractor implements Extractor { ...@@ -814,7 +881,7 @@ public final class WebmExtractor implements Extractor {
return TimeUnit.NANOSECONDS.toMicros(unscaledTimecode * timecodeScale); return TimeUnit.NANOSECONDS.toMicros(unscaledTimecode * timecodeScale);
} }
private boolean isCodecSupported(String codecId) { private static boolean isCodecSupported(String codecId) {
return CODEC_ID_VP8.equals(codecId) return CODEC_ID_VP8.equals(codecId)
|| CODEC_ID_VP9.equals(codecId) || CODEC_ID_VP9.equals(codecId)
|| CODEC_ID_H264.equals(codecId) || CODEC_ID_H264.equals(codecId)
...@@ -825,6 +892,21 @@ public final class WebmExtractor implements Extractor { ...@@ -825,6 +892,21 @@ public final class WebmExtractor implements Extractor {
} }
/** /**
* Returns an array that can store (at least) {@code length} elements, which will be either a new
* array or {@code array} if it's not null and large enough.
*/
private static int[] ensureArrayCapacity(int[] array, int length) {
if (array == null) {
return new int[length];
} else if (array.length >= length) {
return array;
} else {
// Double the size to avoid allocating constantly if the required length increases gradually.
return new int[Math.max(array.length * 2, length)];
}
}
/**
* Passes events through to the outer {@link WebmExtractor}. * Passes events through to the outer {@link WebmExtractor}.
*/ */
private final class InnerEbmlReaderOutput implements EbmlReaderOutput { private final class InnerEbmlReaderOutput implements EbmlReaderOutput {
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer.util; package com.google.android.exoplayer.util;
import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
/** /**
...@@ -105,23 +104,6 @@ public final class NalUnitUtil { ...@@ -105,23 +104,6 @@ public final class NalUnitUtil {
} }
/** /**
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
* {@code size} bytes preceding the buffer's position.
*/
public static void replaceLengthPrefixesWithAvcStartCodes(ByteBuffer buffer, int size) {
int sampleOffset = buffer.position() - size;
int position = sampleOffset;
while (position < sampleOffset + size) {
buffer.position(position);
int length = readUnsignedIntToInt(buffer);
buffer.position(position);
buffer.put(NAL_START_CODE);
position += length + 4;
}
buffer.position(sampleOffset + size);
}
/**
* Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. * Constructs and returns a NAL unit with a start code followed by the data in {@code atom}.
*/ */
public static byte[] parseChildNalUnit(ParsableByteArray atom) { public static byte[] parseChildNalUnit(ParsableByteArray atom) {
...@@ -254,24 +236,6 @@ public final class NalUnitUtil { ...@@ -254,24 +236,6 @@ public final class NalUnitUtil {
return limit; return limit;
} }
/**
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
* assumed that the top bit will always be set to zero.
*
* @throws IllegalArgumentException If the top bit of the input data is set.
*/
private static int readUnsignedIntToInt(ByteBuffer data) {
int result = 0xFF & data.get();
for (int i = 1; i < 4; i++) {
result <<= 8;
result |= 0xFF & data.get();
}
if (result < 0) {
throw new IllegalArgumentException("Top bit not zero: " + result);
}
return result;
}
private NalUnitUtil() { private NalUnitUtil() {
// Prevent instantiation. // Prevent instantiation.
} }
......
...@@ -37,7 +37,7 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate; ...@@ -37,7 +37,7 @@ import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement; import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase; import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer.dash.mpd.UrlTemplate; import com.google.android.exoplayer.dash.mpd.UrlTemplate;
import com.google.android.exoplayer.testutil.Util; import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.FakeClock; import com.google.android.exoplayer.util.FakeClock;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
...@@ -86,7 +86,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase { ...@@ -86,7 +86,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
@Override @Override
public void setUp() throws Exception { public void setUp() throws Exception {
Util.setUpMockito(this); TestUtil.setUpMockito(this);
} }
public void testMaxVideoDimensions() { public void testMaxVideoDimensions() {
......
...@@ -24,7 +24,7 @@ import com.google.android.exoplayer.extractor.DefaultExtractorInput; ...@@ -24,7 +24,7 @@ import com.google.android.exoplayer.extractor.DefaultExtractorInput;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.testutil.FakeDataSource; import com.google.android.exoplayer.testutil.FakeDataSource;
import com.google.android.exoplayer.testutil.Util; import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -52,7 +52,7 @@ public class BufferingInputTest extends InstrumentationTestCase { ...@@ -52,7 +52,7 @@ public class BufferingInputTest extends InstrumentationTestCase {
@Override @Override
public void setUp() throws Exception { public void setUp() throws Exception {
Util.setUpMockito(this); TestUtil.setUpMockito(this);
FakeDataSource.Builder builder = new FakeDataSource.Builder(); FakeDataSource.Builder builder = new FakeDataSource.Builder();
builder.appendReadData(STREAM_DATA); builder.appendReadData(STREAM_DATA);
......
...@@ -17,35 +17,26 @@ package com.google.android.exoplayer.extractor.mp4; ...@@ -17,35 +17,26 @@ package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.testutil.FakeExtractorOutput;
import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.testutil.FakeTrackOutput;
import com.google.android.exoplayer.extractor.ExtractorSampleSource; import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.upstream.ByteArrayDataSource;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
/** /**
* Tests for {@link Mp4Extractor}. * Tests for {@link Mp4Extractor}.
*/ */
@TargetApi(16) @TargetApi(16)
public class Mp4ExtractorTest extends TestCase { public final class Mp4ExtractorTest extends TestCase {
/** String of hexadecimal bytes containing the video stsd payload from an AVC video. */ /** String of hexadecimal bytes containing the video stsd payload from an AVC video. */
private static final byte[] VIDEO_STSD_PAYLOAD = getByteArray( private static final byte[] VIDEO_STSD_PAYLOAD = getByteArray(
...@@ -94,159 +85,122 @@ public class Mp4ExtractorTest extends TestCase { ...@@ -94,159 +85,122 @@ public class Mp4ExtractorTest extends TestCase {
/** Video frame sizes in bytes, including a very large sample. */ /** Video frame sizes in bytes, including a very large sample. */
private static final int[] SAMPLE_SIZES = {100, 20, 20, 44, 100, 1 * 1024 * 1024}; private static final int[] SAMPLE_SIZES = {100, 20, 20, 44, 100, 1 * 1024 * 1024};
/** Indices of key-frames. */ /** Indices of key-frames. */
private static final int[] SYNCHRONIZATION_SAMPLE_INDICES = {0, 4, 5}; private static final boolean[] SAMPLE_IS_SYNC = {true, false, false, false, true, true};
/** Indices of video frame chunk offsets. */ /** Indices of video frame chunk offsets. */
private static final int[] CHUNK_OFFSETS = {1080, 2000, 3000, 4000}; private static final int[] CHUNK_OFFSETS = {1080, 2000, 3000, 4000};
/** Numbers of video frames in each chunk. */ /** Numbers of video frames in each chunk. */
private static final int[] SAMPLES_IN_CHUNK = {2, 2, 1, 1}; private static final int[] SAMPLES_IN_CHUNK = {2, 2, 1, 1};
/** The mdat box must be large enough to avoid reading chunk sample data out of bounds. */ /** The mdat box must be large enough to avoid reading chunk sample data out of bounds. */
private static final int MDAT_SIZE = 10 * 1024 * 1024; private static final int MDAT_SIZE = 10 * 1024 * 1024;
/** Fake HTTP URI that can't be opened. */
private static final Uri FAKE_URI = Uri.parse("http://");
/** Empty byte array. */ /** Empty byte array. */
private static final byte[] EMPTY = new byte[0]; private static final byte[] EMPTY = new byte[0];
public void testParsesValidMp4File() throws Exception { private Mp4Extractor extractor;
// Given an extractor with an AVC/AAC file private FakeExtractorOutput extractorOutput;
Mp4ExtractorWrapper extractor =
prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */));
// The MIME type and metadata are set correctly.
assertEquals(MimeTypes.VIDEO_H264, extractor.mediaFormats[0].mimeType);
assertEquals(MimeTypes.AUDIO_AAC, extractor.mediaFormats[1].mimeType);
assertEquals(VIDEO_WIDTH, extractor.selectedTrackMediaFormat.width); @Override
assertEquals(VIDEO_HEIGHT, extractor.selectedTrackMediaFormat.height); public void setUp() {
extractor = new Mp4Extractor();
extractorOutput = new FakeExtractorOutput();
extractor.init(extractorOutput);
} }
public void testParsesValidMp4vFile() throws Exception { @Override
// Given an extractor with an mp4v file public void tearDown() {
Mp4ExtractorWrapper extractor = extractor = null;
prepareSampleExtractor(getFakeDataSource(true /* includeStss */, true /* mp4vFormat */)); extractorOutput = null;
// The MIME type and metadata are set correctly.
assertEquals(MimeTypes.VIDEO_MP4V, extractor.selectedTrackMediaFormat.mimeType);
assertEquals(VIDEO_MP4V_WIDTH, extractor.selectedTrackMediaFormat.width);
assertEquals(VIDEO_MP4V_HEIGHT, extractor.selectedTrackMediaFormat.height);
} }
public void testSampleTimestampsMatch() throws Exception { public void testParsesValidMp4File() throws Exception {
// Given an extractor TestUtil.consumeTestData(extractor,
Mp4ExtractorWrapper extractor = getTestInputData(true /* includeStss */, false /* mp4vFormat */));
prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */));
// The seek map is correct.
// The timestamps are set correctly. assertSeekMap(extractorOutput.seekMap, true);
SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
// The video and audio formats are set correctly.
assertEquals(2, extractorOutput.trackOutputs.size());
MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format;
MediaFormat audioFormat = extractorOutput.trackOutputs.get(1).format;
assertEquals(MimeTypes.VIDEO_H264, videoFormat.mimeType);
assertEquals(VIDEO_WIDTH, videoFormat.width);
assertEquals(VIDEO_HEIGHT, videoFormat.height);
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.mimeType);
// The timestamps and sizes are set correctly.
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
videoTrackOutput.assertSampleCount(SAMPLE_TIMESTAMPS.length);
for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) { for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) {
extractor.readSample(0, sampleHolder); byte[] sampleData = getOutputSampleData(i, true);
assertEquals(getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]), sampleHolder.timeUs); int sampleFlags = SAMPLE_IS_SYNC[i] ? C.SAMPLE_FLAG_SYNC : 0;
long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]);
videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null);
} }
assertEquals(SampleSource.END_OF_STREAM, extractor.readSample(0, sampleHolder));
} }
public void testSeekToStart() throws Exception { public void testParsesValidMp4FileWithoutStss() throws Exception {
// When seeking to the start TestUtil.consumeTestData(extractor,
int timestampTimeUnits = SAMPLE_TIMESTAMPS[0]; getTestInputData(false /* includeStss */, false /* mp4vFormat */));
long sampleTimestampUs =
getTimestampUsResultingFromSeek(getVideoTimestampUs(timestampTimeUnits));
// The timestamp is at the start. // The seek map is correct.
assertEquals(getVideoTimestampUs(timestampTimeUnits), sampleTimestampUs); assertSeekMap(extractorOutput.seekMap, false);
}
public void testSeekToEnd() throws Exception { // The timestamps and sizes are set correctly, and all samples are synchronization samples.
// When seeking to the end FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
int timestampTimeUnits = SAMPLE_TIMESTAMPS[SAMPLE_TIMESTAMPS.length - 1]; videoTrackOutput.assertSampleCount(SAMPLE_TIMESTAMPS.length);
long sampleTimestampUs = for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) {
getTimestampUsResultingFromSeek(getVideoTimestampUs(timestampTimeUnits)); byte[] sampleData = getOutputSampleData(i, true);
int sampleFlags = C.SAMPLE_FLAG_SYNC;
// The timestamp is at the end. long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]);
assertEquals(getVideoTimestampUs(timestampTimeUnits), sampleTimestampUs); videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null);
}
public void testSeekToNearStart() throws Exception {
// When seeking to just after the start
int timestampTimeUnits = SAMPLE_TIMESTAMPS[0];
long sampleTimestampUs =
getTimestampUsResultingFromSeek(getVideoTimestampUs(timestampTimeUnits) + 1);
// The timestamp is at the start.
assertEquals(getVideoTimestampUs(timestampTimeUnits), sampleTimestampUs);
}
public void testSeekToBeforeLastSynchronizationSample() throws Exception {
// When seeking to just after the start
long sampleTimestampUs =
getTimestampUsResultingFromSeek(getVideoTimestampUs(SAMPLE_TIMESTAMPS[4]) - 1);
// The timestamp is at the start.
assertEquals(getVideoTimestampUs(SAMPLE_TIMESTAMPS[0]), sampleTimestampUs);
}
public void testAllSamplesAreSynchronizationSamplesWhenStssIsMissing() throws Exception {
// Given an extractor without an stss box
Mp4ExtractorWrapper extractor =
prepareSampleExtractor(getFakeDataSource(false /* includeStss */, false /* mp4vFormat */));
// All samples are synchronization samples.
SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
int sampleIndex = 0;
while (true) {
int result = extractor.readSample(0, sampleHolder);
if (result == SampleSource.SAMPLE_READ) {
assertTrue(sampleHolder.isSyncFrame());
sampleHolder.clearData();
sampleIndex++;
} else if (result == SampleSource.END_OF_STREAM) {
break;
}
} }
assertTrue(sampleIndex == SAMPLE_SIZES.length);
} }
public void testReadAllSamplesSucceeds() throws Exception { public void testParsesValidMp4vFile() throws Exception {
// Given an extractor TestUtil.consumeTestData(extractor,
Mp4ExtractorWrapper extractor = getTestInputData(true /* includeStss */, true /* mp4vFormat */));
prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */));
// The seek map is correct.
// The sample sizes are set correctly. assertSeekMap(extractorOutput.seekMap, true);
SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
int sampleIndex = 0; // The video and audio formats are set correctly.
while (true) { assertEquals(2, extractorOutput.trackOutputs.size());
int result = extractor.readSample(0, sampleHolder); MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format;
if (result == SampleSource.SAMPLE_READ) { MediaFormat audioFormat = extractorOutput.trackOutputs.get(1).format;
assertEquals(SAMPLE_SIZES[sampleIndex], sampleHolder.size); assertEquals(MimeTypes.VIDEO_MP4V, videoFormat.mimeType);
sampleHolder.clearData(); assertEquals(VIDEO_MP4V_WIDTH, videoFormat.width);
sampleIndex++; assertEquals(VIDEO_MP4V_HEIGHT, videoFormat.height);
} else if (result == SampleSource.END_OF_STREAM) { assertEquals(MimeTypes.AUDIO_AAC, audioFormat.mimeType);
break;
} // The timestamps and sizes are set correctly.
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
videoTrackOutput.assertSampleCount(SAMPLE_TIMESTAMPS.length);
for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) {
byte[] sampleData = getOutputSampleData(i, false);
int sampleFlags = SAMPLE_IS_SYNC[i] ? C.SAMPLE_FLAG_SYNC : 0;
long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]);
videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null);
} }
assertEquals(SAMPLE_SIZES.length, sampleIndex);
} }
/** Returns the sample time read after seeking to {@code timestampTimeUnits}. */ private static void assertSeekMap(SeekMap seekMap, boolean haveStss) {
private static long getTimestampUsResultingFromSeek(long timestampTimeUnits) throws Exception { assertNotNull(seekMap);
Mp4ExtractorWrapper extractor = int expectedSeekPosition = getSampleOffset(0);
prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */)); for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) {
// Seek to just before the current sample.
extractor.seekTo(timestampTimeUnits); long seekPositionUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]) - 1;
assertEquals(expectedSeekPosition, seekMap.getPosition(seekPositionUs));
SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); // If the current sample is a sync sample, the expected seek position will change.
while (true) { if (SAMPLE_IS_SYNC[i] || !haveStss) {
int result = extractor.readSample(0, sampleHolder); expectedSeekPosition = getSampleOffset(i);
if (result == SampleSource.SAMPLE_READ) {
return sampleHolder.timeUs;
} else if (result == SampleSource.END_OF_STREAM) {
return -1;
}
} }
// Seek to the current sample.
seekPositionUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]);
assertEquals(expectedSeekPosition, seekMap.getPosition(seekPositionUs));
// Seek to just after the current sample.
seekPositionUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]) + 1;
assertEquals(expectedSeekPosition, seekMap.getPosition(seekPositionUs));
} }
private static Mp4ExtractorWrapper prepareSampleExtractor(DataSource dataSource)
throws Exception {
Mp4ExtractorWrapper extractor = new Mp4ExtractorWrapper(dataSource);
extractor.prepare();
return extractor;
} }
/** Returns a video timestamp in microseconds corresponding to {@code timeUnits}. */ /** Returns a video timestamp in microseconds corresponding to {@code timeUnits}. */
...@@ -300,12 +254,20 @@ public class Mp4ExtractorTest extends TestCase { ...@@ -300,12 +254,20 @@ public class Mp4ExtractorTest extends TestCase {
} }
private static byte[] getStss() { private static byte[] getStss() {
byte[] result = new byte[4 + 4 + 4 * SYNCHRONIZATION_SAMPLE_INDICES.length]; int synchronizationSampleCount = 0;
for (int i = 0; i < SAMPLE_IS_SYNC.length; i++) {
if (SAMPLE_IS_SYNC[i]) {
synchronizationSampleCount++;
}
}
byte[] result = new byte[4 + 4 + 4 * synchronizationSampleCount];
ByteBuffer buffer = ByteBuffer.wrap(result); ByteBuffer buffer = ByteBuffer.wrap(result);
buffer.putInt(0); // Version (skipped) buffer.putInt(0); // Version (skipped)
buffer.putInt(SYNCHRONIZATION_SAMPLE_INDICES.length); buffer.putInt(synchronizationSampleCount);
for (int synchronizationSampleIndex : SYNCHRONIZATION_SAMPLE_INDICES) { for (int i = 0; i < SAMPLE_IS_SYNC.length; i++) {
buffer.putInt(synchronizationSampleIndex + 1); if (SAMPLE_IS_SYNC[i]) {
buffer.putInt(i + 1);
}
} }
return result; return result;
} }
...@@ -342,23 +304,64 @@ public class Mp4ExtractorTest extends TestCase { ...@@ -342,23 +304,64 @@ public class Mp4ExtractorTest extends TestCase {
return result; return result;
} }
private static byte[] getMdat(int mdatOffset) { private static byte[] getMdat(int mdatOffset, boolean isH264) {
ByteBuffer mdat = ByteBuffer.allocate(MDAT_SIZE); ByteBuffer mdat = ByteBuffer.allocate(MDAT_SIZE);
int sampleIndex = 0; int sampleIndex = 0;
for (int chunk = 0; chunk < CHUNK_OFFSETS.length; chunk++) { for (int chunk = 0; chunk < CHUNK_OFFSETS.length; chunk++) {
int sampleOffset = CHUNK_OFFSETS[chunk]; mdat.position(CHUNK_OFFSETS[chunk] - mdatOffset);
for (int sample = 0; sample < SAMPLES_IN_CHUNK[chunk]; sample++) { for (int sample = 0; sample < SAMPLES_IN_CHUNK[chunk]; sample++) {
int sampleSize = SAMPLE_SIZES[sampleIndex++]; mdat.put(getInputSampleData(sampleIndex++, isH264));
mdat.putInt(sampleOffset - mdatOffset, sampleSize);
sampleOffset += sampleSize;
} }
} }
return mdat.array(); return mdat.array();
} }
private static final DataSource getFakeDataSource(boolean includeStss, boolean mp4vFormat) { private static byte[] getInputSampleData(int index, boolean isH264) {
return new ByteArrayDataSource(includeStss ByteBuffer sample = ByteBuffer.allocate(SAMPLE_SIZES[index]);
? getTestMp4File(mp4vFormat) : getTestMp4FileWithoutSynchronizationData(mp4vFormat)); for (int i = 0; i < SAMPLE_SIZES[index]; i++) {
sample.put((byte) i);
}
if (isH264) {
// First four bytes should specify the remaining length of the sample. This assumes that the
// sample consists of a single length delimited NAL unit.
sample.position(0);
sample.putInt(SAMPLE_SIZES[index] - 4);
}
return sample.array();
}
private static byte[] getOutputSampleData(int index, boolean isH264) {
byte[] sampleData = getInputSampleData(index, isH264);
if (isH264) {
// The output sample should begin with a NAL start code.
sampleData[0] = 0;
sampleData[1] = 0;
sampleData[2] = 0;
sampleData[3] = 1;
}
return sampleData;
}
private static int getSampleOffset(int index) {
int sampleCount = 0;
int chunkIndex = 0;
int samplesLeftInChunk = SAMPLES_IN_CHUNK[chunkIndex];
int offsetInChunk = 0;
while (sampleCount < index) {
offsetInChunk += SAMPLE_SIZES[sampleCount++];
samplesLeftInChunk--;
if (samplesLeftInChunk == 0) {
chunkIndex++;
samplesLeftInChunk = SAMPLES_IN_CHUNK[chunkIndex];
offsetInChunk = 0;
}
}
return CHUNK_OFFSETS[chunkIndex] + offsetInChunk;
}
private static final byte[] getTestInputData(boolean includeStss, boolean mp4vFormat) {
return includeStss ? getTestMp4File(mp4vFormat)
: getTestMp4FileWithoutSynchronizationData(mp4vFormat);
} }
/** Gets a valid MP4 file with audio/video tracks and synchronization data. */ /** Gets a valid MP4 file with audio/video tracks and synchronization data. */
...@@ -396,7 +399,7 @@ public class Mp4ExtractorTest extends TestCase { ...@@ -396,7 +399,7 @@ public class Mp4ExtractorTest extends TestCase {
atom(Atom.TYPE_stsc, getStsc()), atom(Atom.TYPE_stsc, getStsc()),
atom(Atom.TYPE_stsz, getStsz()), atom(Atom.TYPE_stsz, getStsz()),
atom(Atom.TYPE_stco, getStco())))))), atom(Atom.TYPE_stco, getStco())))))),
atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 1048 : 1038))); atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 1048 : 1038, !mp4vFormat)));
} }
/** Gets a valid MP4 file with audio/video tracks and without a synchronization table. */ /** Gets a valid MP4 file with audio/video tracks and without a synchronization table. */
...@@ -432,7 +435,7 @@ public class Mp4ExtractorTest extends TestCase { ...@@ -432,7 +435,7 @@ public class Mp4ExtractorTest extends TestCase {
atom(Atom.TYPE_stsc, getStsc()), atom(Atom.TYPE_stsc, getStsc()),
atom(Atom.TYPE_stsz, getStsz()), atom(Atom.TYPE_stsz, getStsz()),
atom(Atom.TYPE_stco, getStco())))))), atom(Atom.TYPE_stco, getStco())))))),
atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 992 : 982))); atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 992 : 982, !mp4vFormat)));
} }
private static Mp4Atom atom(int type, Mp4Atom... containedMp4Atoms) { private static Mp4Atom atom(int type, Mp4Atom... containedMp4Atoms) {
...@@ -452,7 +455,9 @@ public class Mp4ExtractorTest extends TestCase { ...@@ -452,7 +455,9 @@ public class Mp4ExtractorTest extends TestCase {
return result; return result;
} }
/** MP4 atom that can be serialized as a byte array. */ /**
* MP4 atom that can be serialized as a byte array.
*/
private static final class Mp4Atom { private static final class Mp4Atom {
public static byte[] serialize(Mp4Atom... atoms) { public static byte[] serialize(Mp4Atom... atoms) {
...@@ -512,122 +517,4 @@ public class Mp4ExtractorTest extends TestCase { ...@@ -512,122 +517,4 @@ public class Mp4ExtractorTest extends TestCase {
} }
/**
* Creates a {@link Mp4Extractor} on a separate thread with a looper, so that it can use a handler
* for loading, and provides blocking operations like {@link #seekTo} and {@link #readSample}.
*/
private static final class Mp4ExtractorWrapper extends Thread {
private static final int MSG_PREPARE = 0;
private static final int MSG_SEEK_TO = 1;
private static final int MSG_READ_SAMPLE = 2;
private final DataSource dataSource;
// Written by the handler's thread and read by the main thread.
public volatile MediaFormat[] mediaFormats;
public volatile MediaFormat selectedTrackMediaFormat;
private volatile Handler handler;
private volatile int readSampleResult;
private volatile Exception exception;
private volatile CountDownLatch pendingOperationLatch;
public Mp4ExtractorWrapper(DataSource dataSource) {
super("Mp4ExtractorTest");
this.dataSource = Assertions.checkNotNull(dataSource);
pendingOperationLatch = new CountDownLatch(1);
start();
}
public void prepare() throws Exception {
// Block until the handler has been created.
pendingOperationLatch.await();
// Block until the extractor has been prepared.
pendingOperationLatch = new CountDownLatch(1);
handler.sendEmptyMessage(MSG_PREPARE);
pendingOperationLatch.await();
if (exception != null) {
throw exception;
}
}
public void seekTo(long timestampUs) {
handler.obtainMessage(MSG_SEEK_TO, timestampUs).sendToTarget();
}
public int readSample(int trackIndex, SampleHolder sampleHolder) throws Exception {
// Block until the extractor has completed readSample.
pendingOperationLatch = new CountDownLatch(1);
handler.obtainMessage(MSG_READ_SAMPLE, trackIndex, 0, sampleHolder).sendToTarget();
pendingOperationLatch.await();
if (exception != null) {
throw exception;
}
return readSampleResult;
}
@SuppressLint("HandlerLeak")
@Override
public void run() {
final ExtractorSampleSource source = new ExtractorSampleSource(FAKE_URI, dataSource,
new Mp4Extractor(), 2 * 1024 * 1024);
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message message) {
try {
switch (message.what) {
case MSG_PREPARE:
if (!source.prepare(0)) {
sendEmptyMessage(MSG_PREPARE);
} else {
// Select the video track and get its metadata.
mediaFormats = new MediaFormat[source.getTrackCount()];
MediaFormatHolder mediaFormatHolder = new MediaFormatHolder();
for (int track = 0; track < source.getTrackCount(); track++) {
source.enable(track, 0);
source.readData(track, 0, mediaFormatHolder, null, false);
MediaFormat mediaFormat = mediaFormatHolder.format;
mediaFormats[track] = mediaFormat;
if (MimeTypes.isVideo(mediaFormat.mimeType)) {
selectedTrackMediaFormat = mediaFormat;
} else {
source.disable(track);
}
}
pendingOperationLatch.countDown();
}
break;
case MSG_SEEK_TO:
long timestampUs = (Long) message.obj;
source.seekToUs(timestampUs);
break;
case MSG_READ_SAMPLE:
int trackIndex = message.arg1;
SampleHolder sampleHolder = (SampleHolder) message.obj;
sampleHolder.clearData();
readSampleResult = source.readData(trackIndex, 0, null, sampleHolder, false);
if (readSampleResult == SampleSource.NOTHING_READ) {
Message.obtain(message).sendToTarget();
return;
}
pendingOperationLatch.countDown();
break;
}
} catch (Exception e) {
exception = e;
pendingOperationLatch.countDown();
}
}
};
// Unblock waiting for the handler.
pendingOperationLatch.countDown();
Looper.loop();
}
}
} }
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.extractor.webm; package com.google.android.exoplayer.extractor.webm;
import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
...@@ -47,28 +48,6 @@ import java.util.List; ...@@ -47,28 +48,6 @@ import java.util.List;
} }
public static byte[] createByteArray(int... intArray) {
byte[] byteArray = new byte[intArray.length];
for (int i = 0; i < byteArray.length; i++) {
byteArray[i] = (byte) intArray[i];
}
return byteArray;
}
public static byte[] joinByteArrays(byte[]... byteArrays) {
int length = 0;
for (byte[] byteArray : byteArrays) {
length += byteArray.length;
}
byte[] joined = new byte[length];
length = 0;
for (byte[] byteArray : byteArrays) {
System.arraycopy(byteArray, 0, joined, length, byteArray.length);
length += byteArray.length;
}
return joined;
}
public static final byte[] TEST_ENCRYPTION_KEY_ID = { 0x00, 0x01, 0x02, 0x03 }; public static final byte[] TEST_ENCRYPTION_KEY_ID = { 0x00, 0x01, 0x02, 0x03 };
public static final byte[] TEST_INITIALIZATION_VECTOR = { public static final byte[] TEST_INITIALIZATION_VECTOR = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
...@@ -163,6 +142,14 @@ import java.util.List; ...@@ -163,6 +142,14 @@ import java.util.List;
return this; return this;
} }
public StreamBuilder addSimpleBlockMediaWithXiphLacing(int trackNumber, int clusterTimecode,
int blockTimecode, byte[] data, int... lacingFrameSizes) {
EbmlElement simpleBlockElement = createSimpleBlock(trackNumber, blockTimecode,
0x80 /* flags = keyframe */, false, true, data, lacingFrameSizes);
mediaSegments.add(createCluster(clusterTimecode, simpleBlockElement));
return this;
}
public StreamBuilder addBlockMedia(int trackNumber, int clusterTimecode, int blockTimecode, public StreamBuilder addBlockMedia(int trackNumber, int clusterTimecode, int blockTimecode,
boolean keyframe, boolean invisible, byte[] data) { boolean keyframe, boolean invisible, byte[] data) {
byte flags = (byte) (invisible ? 0x08 : 0x00); byte flags = (byte) (invisible ? 0x08 : 0x00);
...@@ -309,32 +296,66 @@ import java.util.List; ...@@ -309,32 +296,66 @@ import java.util.List;
byte[] simpleBlockBytes; byte[] simpleBlockBytes;
if (lacingFrameCount > 1) { if (lacingFrameCount > 1) {
flags |= 0x04; // Fixed-size lacing flags |= 0x04; // Fixed-size lacing
simpleBlockBytes = createByteArray( simpleBlockBytes = TestUtil.createByteArray(
0x40, trackNumberBytes[3], // Track number size=2 0x40, trackNumberBytes[3], // Track number size=2
timeBytes[2], timeBytes[3], flags, lacingFrameCount - 1); // Timecode, flags and lacing. timeBytes[2], timeBytes[3], flags, lacingFrameCount - 1); // Timecode, flags and lacing.
} else { } else {
simpleBlockBytes = createByteArray( simpleBlockBytes = TestUtil.createByteArray(
0x40, trackNumberBytes[3], // Track number size=2 0x40, trackNumberBytes[3], // Track number size=2
timeBytes[2], timeBytes[3], flags); // Timecode and flags timeBytes[2], timeBytes[3], flags); // Timecode and flags
} }
if (encrypted) { if (encrypted) {
simpleBlockBytes = joinByteArrays( simpleBlockBytes = TestUtil.joinByteArrays(
simpleBlockBytes, createByteArray(validSignalByte ? 0x01 : 0x80), simpleBlockBytes, TestUtil.createByteArray(validSignalByte ? 0x01 : 0x80),
Arrays.copyOfRange(TEST_INITIALIZATION_VECTOR, 0, 8));
}
return element(0xA3, // SimpleBlock
TestUtil.joinByteArrays(simpleBlockBytes, data));
}
private static EbmlElement createSimpleBlock(int trackNumber, int timecode, int flags,
boolean encrypted, boolean validSignalByte, byte[] data, int... xiphLacingSampleSizes) {
byte[] trackNumberBytes = getIntegerBytes(trackNumber);
byte[] timeBytes = getIntegerBytes(timecode);
byte[] simpleBlockBytes;
flags |= 0x02; // Xiph lacing
simpleBlockBytes = TestUtil.createByteArray(
0x40, trackNumberBytes[3], // Track number size=2
timeBytes[2], timeBytes[3], // Timecode
flags, xiphLacingSampleSizes.length - 1); // Flags and lacing.
int lacingBufferSize = 0;
for (int sampleIndex = 0; sampleIndex < xiphLacingSampleSizes.length - 1; sampleIndex++) {
lacingBufferSize += (xiphLacingSampleSizes[sampleIndex] + 254) / 255;
}
ByteBuffer lacingBytes = ByteBuffer.allocate(lacingBufferSize);
for (int sampleIndex = 0; sampleIndex < xiphLacingSampleSizes.length - 1; sampleIndex++) {
int sampleSize = xiphLacingSampleSizes[sampleIndex];
while (sampleSize > 255) {
sampleSize -= 255;
lacingBytes.put((byte) 0xFF);
}
lacingBytes.put((byte) sampleSize);
}
simpleBlockBytes = TestUtil.joinByteArrays(simpleBlockBytes, lacingBytes.array());
if (encrypted) {
simpleBlockBytes = TestUtil.joinByteArrays(
simpleBlockBytes, TestUtil.createByteArray(validSignalByte ? 0x01 : 0x80),
Arrays.copyOfRange(TEST_INITIALIZATION_VECTOR, 0, 8)); Arrays.copyOfRange(TEST_INITIALIZATION_VECTOR, 0, 8));
} }
return element(0xA3, // SimpleBlock return element(0xA3, // SimpleBlock
joinByteArrays(simpleBlockBytes, data)); TestUtil.joinByteArrays(simpleBlockBytes, data));
} }
private static EbmlElement createBlock(int trackNumber, int timecode, boolean keyframe, int flags, private static EbmlElement createBlock(int trackNumber, int timecode, boolean keyframe, int flags,
byte[] data) { byte[] data) {
byte[] trackNumberBytes = getIntegerBytes(trackNumber); byte[] trackNumberBytes = getIntegerBytes(trackNumber);
byte[] timeBytes = getIntegerBytes(timecode); byte[] timeBytes = getIntegerBytes(timecode);
byte[] blockBytes = createByteArray( byte[] blockBytes = TestUtil.createByteArray(
0x40, trackNumberBytes[3], // Track number size=2 0x40, trackNumberBytes[3], // Track number size=2
timeBytes[2], timeBytes[3], flags); // Timecode and flags timeBytes[2], timeBytes[3], flags); // Timecode and flags
EbmlElement block = element(0xA1, // Block EbmlElement block = element(0xA1, // Block
joinByteArrays(blockBytes, data)); TestUtil.joinByteArrays(blockBytes, data));
EbmlElement referenceBlock = keyframe ? empty() : element(0xFB, (byte) 0x00); // ReferenceBlock EbmlElement referenceBlock = keyframe ? empty() : element(0xFB, (byte) 0x00); // ReferenceBlock
return element(0xA0, // BlockGroup return element(0xA0, // BlockGroup
referenceBlock, referenceBlock,
...@@ -342,7 +363,7 @@ import java.util.List; ...@@ -342,7 +363,7 @@ import java.util.List;
} }
private static byte[] getIntegerBytes(int value) { private static byte[] getIntegerBytes(int value) {
return createByteArray( return TestUtil.createByteArray(
(value & 0xFF000000) >> 24, (value & 0xFF000000) >> 24,
(value & 0x00FF0000) >> 16, (value & 0x00FF0000) >> 16,
(value & 0x0000FF00) >> 8, (value & 0x0000FF00) >> 8,
......
...@@ -22,34 +22,23 @@ import com.google.android.exoplayer.MediaFormat; ...@@ -22,34 +22,23 @@ import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.ChunkIndex; import com.google.android.exoplayer.extractor.ChunkIndex;
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.ExtractorOutput;
import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.extractor.webm.StreamBuilder.ContentEncodingSettings; import com.google.android.exoplayer.extractor.webm.StreamBuilder.ContentEncodingSettings;
import com.google.android.exoplayer.testutil.FakeDataSource; import com.google.android.exoplayer.testutil.FakeExtractorOutput;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.testutil.FakeTrackOutput;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray;
import android.net.Uri;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID; import java.util.UUID;
/** /**
* Tests for {@link WebmExtractor}. * Tests for {@link WebmExtractor}.
*/ */
public class WebmExtractorTest extends InstrumentationTestCase { public final class WebmExtractorTest extends InstrumentationTestCase {
private static final int DEFAULT_TIMECODE_SCALE = 1000000; private static final int DEFAULT_TIMECODE_SCALE = 1000000;
private static final long TEST_DURATION_US = 9920000L; private static final long TEST_DURATION_US = 9920000L;
...@@ -64,7 +53,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -64,7 +53,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
private static final int TEST_VORBIS_BOOKS_SIZE = 4140; private static final int TEST_VORBIS_BOOKS_SIZE = 4140;
private static final byte[] TEST_OPUS_CODEC_PRIVATE = new byte[] {0, 0}; private static final byte[] TEST_OPUS_CODEC_PRIVATE = new byte[] {0, 0};
private static final int TEST_DEFAULT_DURATION_NS = 33 * 1000 * 1000; private static final int TEST_DEFAULT_DURATION_NS = 33 * 1000 * 1000;
private static final byte[] TEST_H264_CODEC_PRIVATE = StreamBuilder.createByteArray(0x01, 0x4D, private static final byte[] TEST_H264_CODEC_PRIVATE = TestUtil.createByteArray(0x01, 0x4D,
0x40, 0x1E, 0xFF, 0xE1, 0x00, 0x17, 0x67, 0x4D, 0x40, 0x1E, 0xE8, 0x80, 0x50, 0x17, 0xFC, 0x40, 0x1E, 0xFF, 0xE1, 0x00, 0x17, 0x67, 0x4D, 0x40, 0x1E, 0xE8, 0x80, 0x50, 0x17, 0xFC,
0xB8, 0x08, 0x80, 0x00, 0x01, 0xF4, 0x80, 0x00, 0x75, 0x30, 0x07, 0x8B, 0x16, 0x89, 0x01, 0xB8, 0x08, 0x80, 0x00, 0x01, 0xF4, 0x80, 0x00, 0x75, 0x30, 0x07, 0x8B, 0x16, 0x89, 0x01,
0x00, 0x04, 0x68, 0xEB, 0xEF, 0x20); 0x00, 0x04, 0x68, 0xEB, 0xEF, 0x20);
...@@ -75,16 +64,12 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -75,16 +64,12 @@ public class WebmExtractorTest extends InstrumentationTestCase {
private static final String MATROSKA_DOC_TYPE = "matroska"; private static final String MATROSKA_DOC_TYPE = "matroska";
private WebmExtractor extractor; private WebmExtractor extractor;
private TestExtractorOutput extractorOutput; private FakeExtractorOutput extractorOutput;
private TestTrackOutput audioOutput;
private TestTrackOutput videoOutput;
@Override @Override
public void setUp() { public void setUp() {
extractor = new WebmExtractor(); extractor = new WebmExtractor();
extractorOutput = new TestExtractorOutput(); extractorOutput = new FakeExtractorOutput();
audioOutput = new TestTrackOutput();
videoOutput = new TestTrackOutput();
extractor.init(extractorOutput); extractor.init(extractorOutput);
} }
...@@ -92,8 +77,6 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -92,8 +77,6 @@ public class WebmExtractorTest extends InstrumentationTestCase {
public void tearDown() { public void tearDown() {
extractor = null; extractor = null;
extractorOutput = null; extractorOutput = null;
audioOutput = null;
videoOutput = null;
} }
public void testReadInitializationSegment() throws IOException, InterruptedException { public void testReadInitializationSegment() throws IOException, InterruptedException {
...@@ -103,7 +86,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -103,7 +86,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
...@@ -117,7 +100,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -117,7 +100,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertAudioFormat(MimeTypes.AUDIO_OPUS); assertAudioFormat(MimeTypes.AUDIO_OPUS);
assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
...@@ -130,7 +113,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -130,7 +113,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVorbisTrack(TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, getVorbisCodecPrivate()) .addVorbisTrack(TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, getVorbisCodecPrivate())
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertAudioFormat(MimeTypes.AUDIO_VORBIS); assertAudioFormat(MimeTypes.AUDIO_VORBIS);
assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
...@@ -143,7 +126,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -143,7 +126,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addH264Track(TEST_WIDTH, TEST_HEIGHT, TEST_H264_CODEC_PRIVATE) .addH264Track(TEST_WIDTH, TEST_HEIGHT, TEST_H264_CODEC_PRIVATE)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertH264VideoFormat(); assertH264VideoFormat();
assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
...@@ -158,7 +141,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -158,7 +141,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertEquals(2, extractorOutput.numberOfTracks); assertEquals(2, extractorOutput.numberOfTracks);
assertVp9VideoFormat(); assertVp9VideoFormat();
...@@ -176,7 +159,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -176,7 +159,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
// Even though the input stream has 3 tracks, only 2 of them are supported and will be reported. // Even though the input stream has 3 tracks, only 2 of them are supported and will be reported.
assertEquals(2, extractorOutput.numberOfTracks); assertEquals(2, extractorOutput.numberOfTracks);
...@@ -196,7 +179,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -196,7 +179,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
// Even though the input stream has 4 supported tracks, only the first video and audio track // Even though the input stream has 4 supported tracks, only the first video and audio track
// will be reported. // will be reported.
...@@ -214,7 +197,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -214,7 +197,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US));
...@@ -231,7 +214,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -231,7 +214,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null)
.build(3); .build(3);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertIndex( assertIndex(
...@@ -247,7 +230,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -247,7 +230,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null)
.build(3); .build(3);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertIndex( assertIndex(
...@@ -263,7 +246,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -263,7 +246,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null)
.build(0); .build(0);
try { try {
consume(data); TestUtil.consumeTestData(extractor, data);
fail(); fail();
} catch (ParserException exception) { } catch (ParserException exception) {
assertEquals("Invalid/missing cue points", exception.getMessage()); assertEquals("Invalid/missing cue points", exception.getMessage());
...@@ -278,7 +261,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -278,7 +261,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.build(1); .build(1);
// No exception is thrown. // No exception is thrown.
consume(data); TestUtil.consumeTestData(extractor, data);
} }
public void testAcceptsMatroskaDocType() throws IOException, InterruptedException { public void testAcceptsMatroskaDocType() throws IOException, InterruptedException {
...@@ -289,7 +272,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -289,7 +272,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.build(1); .build(1);
// No exception is thrown. // No exception is thrown.
consume(data); TestUtil.consumeTestData(extractor, data);
} }
public void testPrepareInvalidDocType() throws IOException, InterruptedException { public void testPrepareInvalidDocType() throws IOException, InterruptedException {
...@@ -299,7 +282,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -299,7 +282,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null)
.build(1); .build(1);
try { try {
consume(data); TestUtil.consumeTestData(extractor, data);
fail(); fail();
} catch (ParserException exception) { } catch (ParserException exception) {
assertEquals("DocType webB not supported", exception.getMessage()); assertEquals("DocType webB not supported", exception.getMessage());
...@@ -314,7 +297,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -314,7 +297,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings)
.build(1); .build(1);
try { try {
consume(data); TestUtil.consumeTestData(extractor, data);
fail(); fail();
} catch (ParserException exception) { } catch (ParserException exception) {
assertEquals("ContentEncodingOrder 1 not supported", exception.getMessage()); assertEquals("ContentEncodingOrder 1 not supported", exception.getMessage());
...@@ -329,7 +312,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -329,7 +312,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings)
.build(1); .build(1);
try { try {
consume(data); TestUtil.consumeTestData(extractor, data);
fail(); fail();
} catch (ParserException exception) { } catch (ParserException exception) {
assertEquals("ContentEncodingScope 0 not supported", exception.getMessage()); assertEquals("ContentEncodingScope 0 not supported", exception.getMessage());
...@@ -344,7 +327,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -344,7 +327,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings)
.build(1); .build(1);
try { try {
consume(data); TestUtil.consumeTestData(extractor, data);
fail(); fail();
} catch (ParserException exception) { } catch (ParserException exception) {
assertEquals("ContentEncodingType 0 not supported", exception.getMessage()); assertEquals("ContentEncodingType 0 not supported", exception.getMessage());
...@@ -359,7 +342,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -359,7 +342,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings)
.build(1); .build(1);
try { try {
consume(data); TestUtil.consumeTestData(extractor, data);
fail(); fail();
} catch (ParserException exception) { } catch (ParserException exception) {
assertEquals("ContentEncAlgo 4 not supported", exception.getMessage()); assertEquals("ContentEncAlgo 4 not supported", exception.getMessage());
...@@ -374,7 +357,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -374,7 +357,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings)
.build(1); .build(1);
try { try {
consume(data); TestUtil.consumeTestData(extractor, data);
fail(); fail();
} catch (ParserException exception) { } catch (ParserException exception) {
assertEquals("AESSettingsCipherMode 0 not supported", exception.getMessage()); assertEquals("AESSettingsCipherMode 0 not supported", exception.getMessage());
...@@ -391,10 +374,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -391,10 +374,10 @@ public class WebmExtractorTest extends InstrumentationTestCase {
true /* keyframe */, false /* invisible */, media) true /* keyframe */, false /* invisible */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertSample(media, 0, true, false, null, videoOutput); assertSample(0, media, 0, true, false, null, getVideoOutput());
} }
public void testReadTwoTrackSamples() throws IOException, InterruptedException { public void testReadTwoTrackSamples() throws IOException, InterruptedException {
...@@ -411,13 +394,13 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -411,13 +394,13 @@ public class WebmExtractorTest extends InstrumentationTestCase {
true /* keyframe */, false /* invisible */, media) true /* keyframe */, false /* invisible */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertEquals(2, extractorOutput.numberOfTracks); assertEquals(2, extractorOutput.numberOfTracks);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertAudioFormat(MimeTypes.AUDIO_OPUS); assertAudioFormat(MimeTypes.AUDIO_OPUS);
assertSample(media, 0, true, false, null, videoOutput); assertSample(0, media, 0, true, false, null, getVideoOutput());
assertSample(media, 0, true, false, null, audioOutput); assertSample(0, media, 0, true, false, null, getAudioOutput());
} }
public void testReadTwoTrackSamplesWithSkippedTrack() throws IOException, InterruptedException { public void testReadTwoTrackSamplesWithSkippedTrack() throws IOException, InterruptedException {
...@@ -437,13 +420,13 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -437,13 +420,13 @@ public class WebmExtractorTest extends InstrumentationTestCase {
true /* keyframe */, false /* invisible */, media) true /* keyframe */, false /* invisible */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertEquals(2, extractorOutput.numberOfTracks); assertEquals(2, extractorOutput.numberOfTracks);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertAudioFormat(MimeTypes.AUDIO_OPUS); assertAudioFormat(MimeTypes.AUDIO_OPUS);
assertSample(media, 0, true, false, null, videoOutput); assertSample(0, media, 0, true, false, null, getVideoOutput());
assertSample(media, 0, true, false, null, audioOutput); assertSample(0, media, 0, true, false, null, getAudioOutput());
} }
public void testReadBlock() throws IOException, InterruptedException { public void testReadBlock() throws IOException, InterruptedException {
...@@ -457,10 +440,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -457,10 +440,10 @@ public class WebmExtractorTest extends InstrumentationTestCase {
true /* keyframe */, false /* invisible */, media) true /* keyframe */, false /* invisible */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertAudioFormat(MimeTypes.AUDIO_OPUS); assertAudioFormat(MimeTypes.AUDIO_OPUS);
assertSample(media, 0, true, false, null, audioOutput); assertSample(0, media, 0, true, false, null, getAudioOutput());
} }
public void testReadBlockNonKeyframe() throws IOException, InterruptedException { public void testReadBlockNonKeyframe() throws IOException, InterruptedException {
...@@ -473,10 +456,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -473,10 +456,10 @@ public class WebmExtractorTest extends InstrumentationTestCase {
false /* keyframe */, false /* invisible */, media) false /* keyframe */, false /* invisible */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertSample(media, 0, false, false, null, videoOutput); assertSample(0, media, 0, false, false, null, getVideoOutput());
} }
public void testReadEncryptedFrame() throws IOException, InterruptedException { public void testReadEncryptedFrame() throws IOException, InterruptedException {
...@@ -491,10 +474,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -491,10 +474,10 @@ public class WebmExtractorTest extends InstrumentationTestCase {
true /* validSignalByte */, media) true /* validSignalByte */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertSample(media, 0, true, false, TEST_ENCRYPTION_KEY_ID, videoOutput); assertSample(0, media, 0, true, false, TEST_ENCRYPTION_KEY_ID, getVideoOutput());
} }
public void testReadEncryptedFrameWithInvalidSignalByte() public void testReadEncryptedFrameWithInvalidSignalByte()
...@@ -511,7 +494,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -511,7 +494,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
.build(1); .build(1);
try { try {
consume(data); TestUtil.consumeTestData(extractor, data);
fail(); fail();
} catch (ParserException exception) { } catch (ParserException exception) {
assertEquals("Extension bit is set in signal byte", exception.getMessage()); assertEquals("Extension bit is set in signal byte", exception.getMessage());
...@@ -528,10 +511,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -528,10 +511,10 @@ public class WebmExtractorTest extends InstrumentationTestCase {
false /* keyframe */, true /* invisible */, media) false /* keyframe */, true /* invisible */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertSample(media, 25000, false, true, null, videoOutput); assertSample(0, media, 25000, false, true, null, getVideoOutput());
} }
public void testReadSampleCustomTimescale() throws IOException, InterruptedException { public void testReadSampleCustomTimescale() throws IOException, InterruptedException {
...@@ -544,10 +527,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -544,10 +527,10 @@ public class WebmExtractorTest extends InstrumentationTestCase {
false /* keyframe */, false /* invisible */, media) false /* keyframe */, false /* invisible */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertSample(media, 25, false, false, null, videoOutput); assertSample(0, media, 25, false, false, null, getVideoOutput());
} }
public void testReadSampleNegativeSimpleBlockTimecode() throws IOException, InterruptedException { public void testReadSampleNegativeSimpleBlockTimecode() throws IOException, InterruptedException {
...@@ -560,13 +543,13 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -560,13 +543,13 @@ public class WebmExtractorTest extends InstrumentationTestCase {
true /* keyframe */, true /* invisible */, media) true /* keyframe */, true /* invisible */, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertVp9VideoFormat(); assertVp9VideoFormat();
assertSample(media, 1000, true, true, null, videoOutput); assertSample(0, media, 1000, true, true, null, getVideoOutput());
} }
public void testReadSampleWithLacing() throws IOException, InterruptedException { public void testReadSampleWithFixedSizeLacing() throws IOException, InterruptedException {
byte[] media = createFrameData(100); byte[] media = createFrameData(100);
byte[] data = new StreamBuilder() byte[] data = new StreamBuilder()
.setHeader(WEBM_DOC_TYPE) .setHeader(WEBM_DOC_TYPE)
...@@ -577,48 +560,64 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -577,48 +560,64 @@ public class WebmExtractorTest extends InstrumentationTestCase {
0 /* blockTimecode */, 20, media) 0 /* blockTimecode */, 20, media)
.build(1); .build(1);
consume(data); TestUtil.consumeTestData(extractor, data);
assertAudioFormat(MimeTypes.AUDIO_OPUS); assertAudioFormat(MimeTypes.AUDIO_OPUS);
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
long expectedTimeUs = i * TEST_DEFAULT_DURATION_NS / 1000; long expectedTimeUs = i * TEST_DEFAULT_DURATION_NS / 1000;
assertSample(Arrays.copyOfRange(media, i * 5, i * 5 + 5), expectedTimeUs, true, false, null, assertSample(i, Arrays.copyOfRange(media, i * 5, i * 5 + 5), expectedTimeUs, true, false,
audioOutput); null, getAudioOutput());
} }
} }
private void consume(byte[] data) throws IOException, InterruptedException { public void testReadSampleWithXiphLacing() throws IOException, InterruptedException {
ExtractorInput input = createTestInput(data); byte[] media = createFrameData(300);
int readResult = Extractor.RESULT_CONTINUE; byte[] data = new StreamBuilder()
while (readResult == Extractor.RESULT_CONTINUE) { .setHeader(WEBM_DOC_TYPE)
readResult = extractor.read(input, null); .setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_US)
.addOpusTrack(TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY, TEST_SEEK_PRE_ROLL,
TEST_OPUS_CODEC_PRIVATE, TEST_DEFAULT_DURATION_NS)
.addSimpleBlockMediaWithXiphLacing(2 /* trackNumber */, 0 /* clusterTimecode */,
0 /* blockTimecode */, media, 256, 1, 243)
.build(1);
TestUtil.consumeTestData(extractor, data);
assertAudioFormat(MimeTypes.AUDIO_OPUS);
assertSample(0, Arrays.copyOfRange(media, 0, 256), 0 * TEST_DEFAULT_DURATION_NS / 1000, true,
false, null, getAudioOutput());
assertSample(1, Arrays.copyOfRange(media, 256, 257), 1 * TEST_DEFAULT_DURATION_NS / 1000, true,
false, null, getAudioOutput());
assertSample(2, Arrays.copyOfRange(media, 257, 300), 2 * TEST_DEFAULT_DURATION_NS / 1000, true,
false, null, getAudioOutput());
} }
assertEquals(Extractor.RESULT_END_OF_INPUT, readResult);
private FakeTrackOutput getVideoOutput() {
// In the sample data the video track has id 1.
return extractorOutput.trackOutputs.get(1);
} }
private static ExtractorInput createTestInput(byte[] data) throws IOException { private FakeTrackOutput getAudioOutput() {
DataSource dataSource = new FakeDataSource.Builder().appendReadData(data).build(); // In the sample data the video track has id 2.
dataSource.open(new DataSpec(Uri.parse("http://www.google.com"))); return extractorOutput.trackOutputs.get(2);
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
return input;
} }
private void assertVp9VideoFormat() { private void assertVp9VideoFormat() {
MediaFormat format = videoOutput.format; MediaFormat format = getVideoOutput().format;
assertEquals(TEST_WIDTH, format.width); assertEquals(TEST_WIDTH, format.width);
assertEquals(TEST_HEIGHT, format.height); assertEquals(TEST_HEIGHT, format.height);
assertEquals(MimeTypes.VIDEO_VP9, format.mimeType); assertEquals(MimeTypes.VIDEO_VP9, format.mimeType);
} }
private void assertH264VideoFormat() { private void assertH264VideoFormat() {
MediaFormat format = videoOutput.format; MediaFormat format = getVideoOutput().format;
assertEquals(TEST_WIDTH, format.width); assertEquals(TEST_WIDTH, format.width);
assertEquals(TEST_HEIGHT, format.height); assertEquals(TEST_HEIGHT, format.height);
assertEquals(MimeTypes.VIDEO_H264, format.mimeType); assertEquals(MimeTypes.VIDEO_H264, format.mimeType);
} }
private void assertAudioFormat(String expectedMimeType) { private void assertAudioFormat(String expectedMimeType) {
MediaFormat format = audioOutput.format; MediaFormat format = getAudioOutput().format;
assertEquals(TEST_CHANNEL_COUNT, format.channelCount); assertEquals(TEST_CHANNEL_COUNT, format.channelCount);
assertEquals(TEST_SAMPLE_RATE, format.sampleRate); assertEquals(TEST_SAMPLE_RATE, format.sampleRate);
assertEquals(expectedMimeType, format.mimeType); assertEquals(expectedMimeType, format.mimeType);
...@@ -646,10 +645,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -646,10 +645,10 @@ public class WebmExtractorTest extends InstrumentationTestCase {
} }
} }
private void assertSample(byte[] expectedMedia, long timeUs, boolean keyframe, boolean invisible, private void assertSample(int index, byte[] expectedMedia, long timeUs, boolean keyframe,
byte[] encryptionKey, TestTrackOutput output) { boolean invisible, byte[] encryptionKey, FakeTrackOutput output) {
if (encryptionKey != null) { if (encryptionKey != null) {
expectedMedia = StreamBuilder.joinByteArrays( expectedMedia = TestUtil.joinByteArrays(
new byte[] {(byte) StreamBuilder.TEST_INITIALIZATION_VECTOR.length}, new byte[] {(byte) StreamBuilder.TEST_INITIALIZATION_VECTOR.length},
StreamBuilder.TEST_INITIALIZATION_VECTOR, expectedMedia); StreamBuilder.TEST_INITIALIZATION_VECTOR, expectedMedia);
} }
...@@ -657,7 +656,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -657,7 +656,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
flags |= keyframe ? C.SAMPLE_FLAG_SYNC : 0; flags |= keyframe ? C.SAMPLE_FLAG_SYNC : 0;
flags |= invisible ? C.SAMPLE_FLAG_DECODE_ONLY : 0; flags |= invisible ? C.SAMPLE_FLAG_DECODE_ONLY : 0;
flags |= encryptionKey != null ? C.SAMPLE_FLAG_ENCRYPTED : 0; flags |= encryptionKey != null ? C.SAMPLE_FLAG_ENCRYPTED : 0;
output.assertNextSample(expectedMedia, timeUs, flags, encryptionKey); output.assertSample(index, expectedMedia, timeUs, flags, encryptionKey);
} }
private byte[] getVorbisCodecPrivate() { private byte[] getVorbisCodecPrivate() {
...@@ -694,103 +693,4 @@ public class WebmExtractorTest extends InstrumentationTestCase { ...@@ -694,103 +693,4 @@ public class WebmExtractorTest extends InstrumentationTestCase {
} }
/** Implements {@link ExtractorOutput} for test purposes. */
public class TestExtractorOutput implements ExtractorOutput {
public boolean tracksEnded;
public SeekMap seekMap;
public DrmInitData drmInitData;
public int numberOfTracks;
@Override
public TrackOutput track(int trackId) {
numberOfTracks++;
// In the test samples, track number 1 is always video and track number 2 is always audio.
return (trackId == 1) ? videoOutput : audioOutput;
}
@Override
public void endTracks() {
tracksEnded = true;
}
@Override
public void seekMap(SeekMap seekMap) {
this.seekMap = seekMap;
}
@Override
public void drmInitData(DrmInitData drmInitData) {
this.drmInitData = drmInitData;
}
}
/** Implements {@link TrackOutput} for test purposes. */
public static class TestTrackOutput implements TrackOutput {
private final Queue<byte[]> sampleData;
private final Queue<Long> sampleTimesUs;
private final Queue<Integer> sampleFlags;
private final Queue<Integer> sampleSizes;
private final Queue<byte[]> sampleEncryptionKeys;
public MediaFormat format;
private byte[] currentSampleData;
public TestTrackOutput() {
sampleData = new LinkedList<>();
sampleTimesUs = new LinkedList<>();
sampleFlags = new LinkedList<>();
sampleSizes = new LinkedList<>();
sampleEncryptionKeys = new LinkedList<>();
}
@Override
public void format(MediaFormat format) {
this.format = format;
}
@Override
public int sampleData(ExtractorInput input, int length) throws IOException,
InterruptedException {
byte[] newData = new byte[length];
input.readFully(newData, 0, length);
currentSampleData = currentSampleData == null
? newData : StreamBuilder.joinByteArrays(currentSampleData, newData);
return length;
}
@Override
public void sampleData(ParsableByteArray data, int length) {
byte[] newData = new byte[length];
data.readBytes(newData, 0, length);
currentSampleData = currentSampleData == null
? newData : StreamBuilder.joinByteArrays(currentSampleData, newData);
}
@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
sampleData.add(currentSampleData);
sampleTimesUs.add(timeUs);
sampleFlags.add(flags);
sampleSizes.add(size);
sampleEncryptionKeys.add(encryptionKey);
currentSampleData = null;
}
public void assertNextSample(byte[] data, Long timeUs, Integer flags, byte[] encryptionKey) {
assertEquals((Integer) data.length, sampleSizes.poll());
MoreAsserts.assertEquals(data, sampleData.poll());
assertEquals(timeUs, sampleTimesUs.poll());
assertEquals(flags, sampleFlags.poll());
byte[] sampleEncryptionKey = sampleEncryptionKeys.poll();
if (encryptionKey == null) {
assertEquals(null, sampleEncryptionKey);
} else {
MoreAsserts.assertEquals(encryptionKey, sampleEncryptionKey);
}
}
}
} }
/*
* Copyright (C) 2014 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.exoplayer.testutil;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.ExtractorOutput;
import com.google.android.exoplayer.extractor.SeekMap;
import android.util.SparseArray;
import junit.framework.TestCase;
/**
* A fake {@link ExtractorOutput}.
*/
public final class FakeExtractorOutput implements ExtractorOutput {
private final boolean allowDuplicateTrackIds;
public final SparseArray<FakeTrackOutput> trackOutputs;
public boolean tracksEnded;
public SeekMap seekMap;
public DrmInitData drmInitData;
public int numberOfTracks;
public FakeExtractorOutput() {
this(false);
}
public FakeExtractorOutput(boolean allowDuplicateTrackIds) {
this.allowDuplicateTrackIds = allowDuplicateTrackIds;
trackOutputs = new SparseArray<>();
}
@Override
public FakeTrackOutput track(int trackId) {
FakeTrackOutput output = trackOutputs.get(trackId);
if (output == null) {
numberOfTracks++;
output = new FakeTrackOutput();
trackOutputs.put(trackId, output);
} else {
TestCase.assertTrue("Duplicate track id: " + trackId, allowDuplicateTrackIds);
}
return output;
}
@Override
public void endTracks() {
tracksEnded = true;
}
@Override
public void seekMap(SeekMap seekMap) {
this.seekMap = seekMap;
}
@Override
public void drmInitData(DrmInitData drmInitData) {
this.drmInitData = drmInitData;
}
}
/*
* Copyright (C) 2014 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.exoplayer.testutil;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.ParsableByteArray;
import android.test.MoreAsserts;
import junit.framework.TestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
/**
* A fake {@link TrackOutput}.
*/
public final class FakeTrackOutput implements TrackOutput {
private final ArrayList<Long> sampleTimesUs;
private final ArrayList<Integer> sampleFlags;
private final ArrayList<Integer> sampleStartOffsets;
private final ArrayList<Integer> sampleEndOffsets;
private final ArrayList<byte[]> sampleEncryptionKeys;
private byte[] sampleData;
public MediaFormat format;
public FakeTrackOutput() {
sampleData = new byte[0];
sampleTimesUs = new ArrayList<>();
sampleFlags = new ArrayList<>();
sampleStartOffsets = new ArrayList<>();
sampleEndOffsets = new ArrayList<>();
sampleEncryptionKeys = new ArrayList<>();
}
@Override
public void format(MediaFormat format) {
this.format = format;
}
@Override
public int sampleData(ExtractorInput input, int length) throws IOException,
InterruptedException {
byte[] newData = new byte[length];
input.readFully(newData, 0, length);
sampleData = TestUtil.joinByteArrays(sampleData, newData);
return length;
}
@Override
public void sampleData(ParsableByteArray data, int length) {
byte[] newData = new byte[length];
data.readBytes(newData, 0, length);
sampleData = TestUtil.joinByteArrays(sampleData, newData);
}
@Override
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
sampleTimesUs.add(timeUs);
sampleFlags.add(flags);
sampleStartOffsets.add(sampleData.length - offset - size);
sampleEndOffsets.add(sampleData.length - offset);
sampleEncryptionKeys.add(encryptionKey);
}
public void assertSampleCount(int count) {
TestCase.assertEquals(count, sampleTimesUs.size());
}
public void assertSample(int index, byte[] data, long timeUs, int flags, byte[] encryptionKey) {
byte[] actualData = Arrays.copyOfRange(sampleData, sampleStartOffsets.get(index),
sampleEndOffsets.get(index));
MoreAsserts.assertEquals(data, actualData);
TestCase.assertEquals(timeUs, (long) sampleTimesUs.get(index));
TestCase.assertEquals(flags, (int) sampleFlags.get(index));
byte[] sampleEncryptionKey = sampleEncryptionKeys.get(index);
if (encryptionKey == null) {
TestCase.assertEquals(null, sampleEncryptionKey);
} else {
MoreAsserts.assertEquals(encryptionKey, sampleEncryptionKey);
}
}
}
...@@ -15,18 +15,56 @@ ...@@ -15,18 +15,56 @@
*/ */
package com.google.android.exoplayer.testutil; package com.google.android.exoplayer.testutil;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.PositionHolder;
import com.google.android.exoplayer.upstream.DataSpec;
import android.net.Uri;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.util.Arrays;
import java.util.Random; import java.util.Random;
/** /**
* Utility methods for tests. * Utility methods for tests.
*/ */
public class Util { public class TestUtil {
private TestUtil() {}
private Util() {} public static void consumeTestData(Extractor extractor, byte[] data)
throws IOException, InterruptedException {
ExtractorInput input = createTestExtractorInput(data);
PositionHolder seekPositionHolder = new PositionHolder();
int readResult = Extractor.RESULT_CONTINUE;
while (readResult != Extractor.RESULT_END_OF_INPUT) {
readResult = extractor.read(input, seekPositionHolder);
if (readResult == Extractor.RESULT_SEEK) {
input = createTestExtractorInput(data, (int) seekPositionHolder.position);
}
}
}
public static ExtractorInput createTestExtractorInput(byte[] data) throws IOException {
return createTestExtractorInput(data, 0);
}
public static ExtractorInput createTestExtractorInput(byte[] data, int offset)
throws IOException {
if (offset != 0) {
data = Arrays.copyOfRange(data, offset, data.length);
}
FakeDataSource dataSource = new FakeDataSource.Builder().appendReadData(data).build();
dataSource.open(new DataSpec(Uri.parse("http://www.google.com")));
ExtractorInput input = new DefaultExtractorInput(dataSource, offset, C.LENGTH_UNBOUNDED);
return input;
}
public static byte[] buildTestData(int length) { public static byte[] buildTestData(int length) {
return buildTestData(length, length); return buildTestData(length, length);
...@@ -39,6 +77,28 @@ public class Util { ...@@ -39,6 +77,28 @@ public class Util {
return source; return source;
} }
public static byte[] createByteArray(int... intArray) {
byte[] byteArray = new byte[intArray.length];
for (int i = 0; i < byteArray.length; i++) {
byteArray[i] = (byte) intArray[i];
}
return byteArray;
}
public static byte[] joinByteArrays(byte[]... byteArrays) {
int length = 0;
for (byte[] byteArray : byteArrays) {
length += byteArray.length;
}
byte[] joined = new byte[length];
length = 0;
for (byte[] byteArray : byteArrays) {
System.arraycopy(byteArray, 0, joined, length, byteArray.length);
length += byteArray.length;
}
return joined;
}
public static void setUpMockito(InstrumentationTestCase instrumentationTestCase) { public static void setUpMockito(InstrumentationTestCase instrumentationTestCase) {
// Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2. // Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2.
System.setProperty("dexmaker.dexcache", System.setProperty("dexmaker.dexcache",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment