diff --git a/library/src/main/java/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.java b/library/src/main/java/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.java index 18e8d2a281..90273e434b 100644 --- a/library/src/main/java/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.java +++ b/library/src/main/java/com/google/android/exoplayer/audio/AudioCapabilitiesReceiver.java @@ -68,7 +68,12 @@ public final class AudioCapabilitiesReceiver { @TargetApi(21) public void register() { if (receiver != null) { - context.registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)); + Intent initialStickyIntent = + context.registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)); + if (initialStickyIntent != null) { + receiver.onReceive(context, initialStickyIntent); + return; + } } listener.onAudioCapabilitiesChanged(DEFAULT_AUDIO_CAPABILITIES); @@ -86,6 +91,10 @@ public final class AudioCapabilitiesReceiver { @Override public void onReceive(Context context, Intent intent) { + if (isInitialStickyBroadcast()) { + return; + } + String action = intent.getAction(); if (!action.equals(AudioManager.ACTION_HDMI_AUDIO_PLUG)) { return; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java index 5fbf64c871..1976f2b8b6 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java @@ -126,7 +126,9 @@ public final class WebmExtractor implements Extractor { private static final int ID_CUE_CLUSTER_POSITION = 0xF1; 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_EBML = 3; private final EbmlReader reader; private final VarintReader varintReader; @@ -168,7 +170,7 @@ public final class WebmExtractor implements Extractor { private long blockTimeUs; private int blockLacingSampleIndex; private int blockLacingSampleCount; - private int blockLacingSampleSize; + private int[] blockLacingSampleSizes; private int blockTrackNumber; private int blockTrackNumberLength; private int blockFlags; @@ -600,8 +602,9 @@ public final class WebmExtractor implements Extractor { int lacing = (scratch.data[2] & 0x06) >> 1; if (lacing == LACING_NONE) { blockLacingSampleCount = 1; - blockLacingSampleSize = contentSize - blockTrackNumberLength - 3; - } else if (lacing == LACING_FIXED_SIZE) { + blockLacingSampleSizes = ensureArrayCapacity(blockLacingSampleSizes, 1); + blockLacingSampleSizes[0] = contentSize - blockTrackNumberLength - 3; + } else { if (id != ID_SIMPLE_BLOCK) { throw new ParserException("Lacing only supported in SimpleBlocks."); } @@ -609,10 +612,69 @@ public final class WebmExtractor implements Extractor { // Read the sample count (1 byte). readScratch(input, 4); blockLacingSampleCount = (scratch.data[3] & 0xFF) + 1; - blockLacingSampleSize = - (contentSize - blockTrackNumberLength - 4) / blockLacingSampleCount; - } else { - throw new ParserException("Lacing mode not supported: " + lacing); + blockLacingSampleSizes = + ensureArrayCapacity(blockLacingSampleSizes, blockLacingSampleCount); + if (lacing == LACING_FIXED_SIZE) { + int blockLacingSampleSize = + (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 { + // 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); @@ -629,7 +691,8 @@ public final class WebmExtractor implements Extractor { if (id == ID_SIMPLE_BLOCK) { // For SimpleBlock, we have metadata for each sample here. while (blockLacingSampleIndex < blockLacingSampleCount) { - writeSampleData(input, trackOutput, sampleTrackFormat, blockLacingSampleSize); + writeSampleData(input, trackOutput, sampleTrackFormat, + blockLacingSampleSizes[blockLacingSampleIndex]); long sampleTimeUs = this.blockTimeUs + (blockLacingSampleIndex * sampleTrackFormat.defaultSampleDurationNs) / 1000; outputSampleMetadata(trackOutput, sampleTimeUs); @@ -639,7 +702,7 @@ public final class WebmExtractor implements Extractor { } else { // 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. - writeSampleData(input, trackOutput, sampleTrackFormat, blockLacingSampleSize); + writeSampleData(input, trackOutput, sampleTrackFormat, blockLacingSampleSizes[0]); } return; @@ -665,6 +728,10 @@ public final class WebmExtractor implements Extractor { if (scratch.limit() >= requiredLength) { 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()); scratch.setLimit(requiredLength); } @@ -814,7 +881,7 @@ public final class WebmExtractor implements Extractor { return TimeUnit.NANOSECONDS.toMicros(unscaledTimecode * timecodeScale); } - private boolean isCodecSupported(String codecId) { + private static boolean isCodecSupported(String codecId) { return CODEC_ID_VP8.equals(codecId) || CODEC_ID_VP9.equals(codecId) || CODEC_ID_H264.equals(codecId) @@ -824,6 +891,21 @@ public final class WebmExtractor implements Extractor { || CODEC_ID_AC3.equals(codecId); } + /** + * 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}. */ diff --git a/library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java b/library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java index 93575ba80f..75830c9cc9 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/NalUnitUtil.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer.util; -import java.nio.ByteBuffer; import java.util.Arrays; /** @@ -104,23 +103,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}. */ @@ -254,24 +236,6 @@ public final class NalUnitUtil { 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() { // Prevent instantiation. } diff --git a/library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java b/library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java index 3a3d867a34..70d3b195db 100644 --- a/library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java +++ b/library/src/test/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java @@ -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.SingleSegmentBase; 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.util.FakeClock; import com.google.android.exoplayer.util.ManifestFetcher; @@ -86,7 +86,7 @@ public class DashChunkSourceTest extends InstrumentationTestCase { @Override public void setUp() throws Exception { - Util.setUpMockito(this); + TestUtil.setUpMockito(this); } public void testMaxVideoDimensions() { diff --git a/library/src/test/java/com/google/android/exoplayer/extractor/mp3/BufferingInputTest.java b/library/src/test/java/com/google/android/exoplayer/extractor/mp3/BufferingInputTest.java index d6668f94c6..6b140c9ab2 100644 --- a/library/src/test/java/com/google/android/exoplayer/extractor/mp3/BufferingInputTest.java +++ b/library/src/test/java/com/google/android/exoplayer/extractor/mp3/BufferingInputTest.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer.extractor.DefaultExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.TrackOutput; 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.util.ParsableByteArray; @@ -52,7 +52,7 @@ public class BufferingInputTest extends InstrumentationTestCase { @Override public void setUp() throws Exception { - Util.setUpMockito(this); + TestUtil.setUpMockito(this); FakeDataSource.Builder builder = new FakeDataSource.Builder(); builder.appendReadData(STREAM_DATA); diff --git a/library/src/test/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java b/library/src/test/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java index f0d7f2d224..d3480164e2 100644 --- a/library/src/test/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java +++ b/library/src/test/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java @@ -17,35 +17,26 @@ package com.google.android.exoplayer.extractor.mp4; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.MediaFormatHolder; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.SampleSource; -import com.google.android.exoplayer.extractor.ExtractorSampleSource; -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.extractor.SeekMap; +import com.google.android.exoplayer.testutil.FakeExtractorOutput; +import com.google.android.exoplayer.testutil.FakeTrackOutput; +import com.google.android.exoplayer.testutil.TestUtil; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.Util; -import android.annotation.SuppressLint; 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 java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; /** * Tests for {@link Mp4Extractor}. */ @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. */ private static final byte[] VIDEO_STSD_PAYLOAD = getByteArray( @@ -94,159 +85,122 @@ public class Mp4ExtractorTest extends TestCase { /** Video frame sizes in bytes, including a very large sample. */ private static final int[] SAMPLE_SIZES = {100, 20, 20, 44, 100, 1 * 1024 * 1024}; /** 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. */ private static final int[] CHUNK_OFFSETS = {1080, 2000, 3000, 4000}; /** Numbers of video frames in each chunk. */ 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. */ 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. */ private static final byte[] EMPTY = new byte[0]; + private Mp4Extractor extractor; + private FakeExtractorOutput extractorOutput; + + @Override + public void setUp() { + extractor = new Mp4Extractor(); + extractorOutput = new FakeExtractorOutput(); + extractor.init(extractorOutput); + } + + @Override + public void tearDown() { + extractor = null; + extractorOutput = null; + } + public void testParsesValidMp4File() throws Exception { - // Given an extractor with an AVC/AAC file - Mp4ExtractorWrapper extractor = - prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */)); + TestUtil.consumeTestData(extractor, + getTestInputData(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); + // The seek map is correct. + assertSeekMap(extractorOutput.seekMap, true); - assertEquals(VIDEO_WIDTH, extractor.selectedTrackMediaFormat.width); - assertEquals(VIDEO_HEIGHT, extractor.selectedTrackMediaFormat.height); + // 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++) { + byte[] sampleData = getOutputSampleData(i, true); + int sampleFlags = SAMPLE_IS_SYNC[i] ? C.SAMPLE_FLAG_SYNC : 0; + long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]); + videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null); + } + } + + public void testParsesValidMp4FileWithoutStss() throws Exception { + TestUtil.consumeTestData(extractor, + getTestInputData(false /* includeStss */, false /* mp4vFormat */)); + + // The seek map is correct. + assertSeekMap(extractorOutput.seekMap, false); + + // The timestamps and sizes are set correctly, and all samples are synchronization samples. + FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0); + videoTrackOutput.assertSampleCount(SAMPLE_TIMESTAMPS.length); + for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) { + byte[] sampleData = getOutputSampleData(i, true); + int sampleFlags = C.SAMPLE_FLAG_SYNC; + long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]); + videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null); + } } public void testParsesValidMp4vFile() throws Exception { - // Given an extractor with an mp4v file - Mp4ExtractorWrapper extractor = - prepareSampleExtractor(getFakeDataSource(true /* includeStss */, true /* mp4vFormat */)); + TestUtil.consumeTestData(extractor, + getTestInputData(true /* includeStss */, true /* mp4vFormat */)); - // 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); - } + // The seek map is correct. + assertSeekMap(extractorOutput.seekMap, true); - public void testSampleTimestampsMatch() throws Exception { - // Given an extractor - Mp4ExtractorWrapper extractor = - prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */)); + // 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_MP4V, videoFormat.mimeType); + assertEquals(VIDEO_MP4V_WIDTH, videoFormat.width); + assertEquals(VIDEO_MP4V_HEIGHT, videoFormat.height); + assertEquals(MimeTypes.AUDIO_AAC, audioFormat.mimeType); - // The timestamps are set correctly. - SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); + // 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++) { - extractor.readSample(0, sampleHolder); - assertEquals(getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]), sampleHolder.timeUs); + 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(SampleSource.END_OF_STREAM, extractor.readSample(0, sampleHolder)); } - public void testSeekToStart() throws Exception { - // When seeking to the start - int timestampTimeUnits = SAMPLE_TIMESTAMPS[0]; - long sampleTimestampUs = - getTimestampUsResultingFromSeek(getVideoTimestampUs(timestampTimeUnits)); - - // The timestamp is at the start. - assertEquals(getVideoTimestampUs(timestampTimeUnits), sampleTimestampUs); - } - - public void testSeekToEnd() throws Exception { - // When seeking to the end - int timestampTimeUnits = SAMPLE_TIMESTAMPS[SAMPLE_TIMESTAMPS.length - 1]; - long sampleTimestampUs = - getTimestampUsResultingFromSeek(getVideoTimestampUs(timestampTimeUnits)); - - // The timestamp is at the end. - assertEquals(getVideoTimestampUs(timestampTimeUnits), sampleTimestampUs); - } - - 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; + private static void assertSeekMap(SeekMap seekMap, boolean haveStss) { + assertNotNull(seekMap); + int expectedSeekPosition = getSampleOffset(0); + for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) { + // Seek to just before the current sample. + long seekPositionUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]) - 1; + assertEquals(expectedSeekPosition, seekMap.getPosition(seekPositionUs)); + // If the current sample is a sync sample, the expected seek position will change. + if (SAMPLE_IS_SYNC[i] || !haveStss) { + expectedSeekPosition = getSampleOffset(i); } + // 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)); } - assertTrue(sampleIndex == SAMPLE_SIZES.length); - } - - public void testReadAllSamplesSucceeds() throws Exception { - // Given an extractor - Mp4ExtractorWrapper extractor = - prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */)); - - // The sample sizes are set correctly. - 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) { - assertEquals(SAMPLE_SIZES[sampleIndex], sampleHolder.size); - sampleHolder.clearData(); - sampleIndex++; - } else if (result == SampleSource.END_OF_STREAM) { - break; - } - } - assertEquals(SAMPLE_SIZES.length, sampleIndex); - } - - /** Returns the sample time read after seeking to {@code timestampTimeUnits}. */ - private static long getTimestampUsResultingFromSeek(long timestampTimeUnits) throws Exception { - Mp4ExtractorWrapper extractor = - prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */)); - - extractor.seekTo(timestampTimeUnits); - - SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); - while (true) { - int result = extractor.readSample(0, sampleHolder); - if (result == SampleSource.SAMPLE_READ) { - return sampleHolder.timeUs; - } else if (result == SampleSource.END_OF_STREAM) { - return -1; - } - } - } - - 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}. */ @@ -300,12 +254,20 @@ public class Mp4ExtractorTest extends TestCase { } 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); buffer.putInt(0); // Version (skipped) - buffer.putInt(SYNCHRONIZATION_SAMPLE_INDICES.length); - for (int synchronizationSampleIndex : SYNCHRONIZATION_SAMPLE_INDICES) { - buffer.putInt(synchronizationSampleIndex + 1); + buffer.putInt(synchronizationSampleCount); + for (int i = 0; i < SAMPLE_IS_SYNC.length; i++) { + if (SAMPLE_IS_SYNC[i]) { + buffer.putInt(i + 1); + } } return result; } @@ -342,23 +304,64 @@ public class Mp4ExtractorTest extends TestCase { return result; } - private static byte[] getMdat(int mdatOffset) { + private static byte[] getMdat(int mdatOffset, boolean isH264) { ByteBuffer mdat = ByteBuffer.allocate(MDAT_SIZE); int sampleIndex = 0; 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++) { - int sampleSize = SAMPLE_SIZES[sampleIndex++]; - mdat.putInt(sampleOffset - mdatOffset, sampleSize); - sampleOffset += sampleSize; + mdat.put(getInputSampleData(sampleIndex++, isH264)); } } return mdat.array(); } - private static final DataSource getFakeDataSource(boolean includeStss, boolean mp4vFormat) { - return new ByteArrayDataSource(includeStss - ? getTestMp4File(mp4vFormat) : getTestMp4FileWithoutSynchronizationData(mp4vFormat)); + private static byte[] getInputSampleData(int index, boolean isH264) { + ByteBuffer sample = ByteBuffer.allocate(SAMPLE_SIZES[index]); + 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. */ @@ -396,7 +399,7 @@ public class Mp4ExtractorTest extends TestCase { atom(Atom.TYPE_stsc, getStsc()), atom(Atom.TYPE_stsz, getStsz()), 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. */ @@ -432,7 +435,7 @@ public class Mp4ExtractorTest extends TestCase { atom(Atom.TYPE_stsc, getStsc()), atom(Atom.TYPE_stsz, getStsz()), 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) { @@ -452,7 +455,9 @@ public class Mp4ExtractorTest extends TestCase { 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 { public static byte[] serialize(Mp4Atom... atoms) { @@ -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(); - } - - } - } diff --git a/library/src/test/java/com/google/android/exoplayer/extractor/webm/StreamBuilder.java b/library/src/test/java/com/google/android/exoplayer/extractor/webm/StreamBuilder.java index bafc765324..be4b9ed9da 100644 --- a/library/src/test/java/com/google/android/exoplayer/extractor/webm/StreamBuilder.java +++ b/library/src/test/java/com/google/android/exoplayer/extractor/webm/StreamBuilder.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer.extractor.webm; +import com.google.android.exoplayer.testutil.TestUtil; import com.google.android.exoplayer.util.Assertions; import java.nio.ByteBuffer; @@ -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_INITIALIZATION_VECTOR = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 @@ -163,6 +142,14 @@ import java.util.List; 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, boolean keyframe, boolean invisible, byte[] data) { byte flags = (byte) (invisible ? 0x08 : 0x00); @@ -309,32 +296,66 @@ import java.util.List; byte[] simpleBlockBytes; if (lacingFrameCount > 1) { flags |= 0x04; // Fixed-size lacing - simpleBlockBytes = createByteArray( + simpleBlockBytes = TestUtil.createByteArray( 0x40, trackNumberBytes[3], // Track number size=2 timeBytes[2], timeBytes[3], flags, lacingFrameCount - 1); // Timecode, flags and lacing. } else { - simpleBlockBytes = createByteArray( + simpleBlockBytes = TestUtil.createByteArray( 0x40, trackNumberBytes[3], // Track number size=2 timeBytes[2], timeBytes[3], flags); // Timecode and flags } if (encrypted) { - simpleBlockBytes = joinByteArrays( - simpleBlockBytes, createByteArray(validSignalByte ? 0x01 : 0x80), + simpleBlockBytes = TestUtil.joinByteArrays( + simpleBlockBytes, TestUtil.createByteArray(validSignalByte ? 0x01 : 0x80), Arrays.copyOfRange(TEST_INITIALIZATION_VECTOR, 0, 8)); } return element(0xA3, // SimpleBlock - joinByteArrays(simpleBlockBytes, data)); + 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)); + } + return element(0xA3, // SimpleBlock + TestUtil.joinByteArrays(simpleBlockBytes, data)); } private static EbmlElement createBlock(int trackNumber, int timecode, boolean keyframe, int flags, byte[] data) { byte[] trackNumberBytes = getIntegerBytes(trackNumber); byte[] timeBytes = getIntegerBytes(timecode); - byte[] blockBytes = createByteArray( + byte[] blockBytes = TestUtil.createByteArray( 0x40, trackNumberBytes[3], // Track number size=2 timeBytes[2], timeBytes[3], flags); // Timecode and flags EbmlElement block = element(0xA1, // Block - joinByteArrays(blockBytes, data)); + TestUtil.joinByteArrays(blockBytes, data)); EbmlElement referenceBlock = keyframe ? empty() : element(0xFB, (byte) 0x00); // ReferenceBlock return element(0xA0, // BlockGroup referenceBlock, @@ -342,7 +363,7 @@ import java.util.List; } private static byte[] getIntegerBytes(int value) { - return createByteArray( + return TestUtil.createByteArray( (value & 0xFF000000) >> 24, (value & 0x00FF0000) >> 16, (value & 0x0000FF00) >> 8, diff --git a/library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java b/library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java index ea6637ecb4..ddcd9a9ac8 100644 --- a/library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java +++ b/library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java @@ -22,34 +22,23 @@ import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.drm.DrmInitData; 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.testutil.FakeDataSource; -import com.google.android.exoplayer.upstream.DataSource; -import com.google.android.exoplayer.upstream.DataSpec; +import com.google.android.exoplayer.testutil.FakeExtractorOutput; +import com.google.android.exoplayer.testutil.FakeTrackOutput; +import com.google.android.exoplayer.testutil.TestUtil; 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.MoreAsserts; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; -import java.util.LinkedList; -import java.util.Queue; import java.util.UUID; /** * 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 long TEST_DURATION_US = 9920000L; @@ -64,7 +53,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { private static final int TEST_VORBIS_BOOKS_SIZE = 4140; 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 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, 0xB8, 0x08, 0x80, 0x00, 0x01, 0xF4, 0x80, 0x00, 0x75, 0x30, 0x07, 0x8B, 0x16, 0x89, 0x01, 0x00, 0x04, 0x68, 0xEB, 0xEF, 0x20); @@ -75,16 +64,12 @@ public class WebmExtractorTest extends InstrumentationTestCase { private static final String MATROSKA_DOC_TYPE = "matroska"; private WebmExtractor extractor; - private TestExtractorOutput extractorOutput; - private TestTrackOutput audioOutput; - private TestTrackOutput videoOutput; + private FakeExtractorOutput extractorOutput; @Override public void setUp() { extractor = new WebmExtractor(); - extractorOutput = new TestExtractorOutput(); - audioOutput = new TestTrackOutput(); - videoOutput = new TestTrackOutput(); + extractorOutput = new FakeExtractorOutput(); extractor.init(extractorOutput); } @@ -92,8 +77,6 @@ public class WebmExtractorTest extends InstrumentationTestCase { public void tearDown() { extractor = null; extractorOutput = null; - audioOutput = null; - videoOutput = null; } public void testReadInitializationSegment() throws IOException, InterruptedException { @@ -103,7 +86,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertVp9VideoFormat(); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); @@ -117,7 +100,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertAudioFormat(MimeTypes.AUDIO_OPUS); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); @@ -130,7 +113,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVorbisTrack(TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, getVorbisCodecPrivate()) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertAudioFormat(MimeTypes.AUDIO_VORBIS); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); @@ -143,7 +126,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addH264Track(TEST_WIDTH, TEST_HEIGHT, TEST_H264_CODEC_PRIVATE) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertH264VideoFormat(); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); @@ -158,7 +141,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertEquals(2, extractorOutput.numberOfTracks); assertVp9VideoFormat(); @@ -176,7 +159,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) .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. assertEquals(2, extractorOutput.numberOfTracks); @@ -196,7 +179,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); // Even though the input stream has 4 supported tracks, only the first video and audio track // will be reported. @@ -214,7 +197,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertVp9VideoFormat(); assertIndex(new IndexPoint(0, 0, TEST_DURATION_US)); @@ -231,7 +214,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .build(3); - consume(data); + TestUtil.consumeTestData(extractor, data); assertVp9VideoFormat(); assertIndex( @@ -247,7 +230,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .build(3); - consume(data); + TestUtil.consumeTestData(extractor, data); assertVp9VideoFormat(); assertIndex( @@ -263,7 +246,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .build(0); try { - consume(data); + TestUtil.consumeTestData(extractor, data); fail(); } catch (ParserException exception) { assertEquals("Invalid/missing cue points", exception.getMessage()); @@ -278,7 +261,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .build(1); // No exception is thrown. - consume(data); + TestUtil.consumeTestData(extractor, data); } public void testAcceptsMatroskaDocType() throws IOException, InterruptedException { @@ -289,7 +272,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .build(1); // No exception is thrown. - consume(data); + TestUtil.consumeTestData(extractor, data); } public void testPrepareInvalidDocType() throws IOException, InterruptedException { @@ -299,7 +282,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, null) .build(1); try { - consume(data); + TestUtil.consumeTestData(extractor, data); fail(); } catch (ParserException exception) { assertEquals("DocType webB not supported", exception.getMessage()); @@ -314,7 +297,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - consume(data); + TestUtil.consumeTestData(extractor, data); fail(); } catch (ParserException exception) { assertEquals("ContentEncodingOrder 1 not supported", exception.getMessage()); @@ -329,7 +312,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - consume(data); + TestUtil.consumeTestData(extractor, data); fail(); } catch (ParserException exception) { assertEquals("ContentEncodingScope 0 not supported", exception.getMessage()); @@ -344,7 +327,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - consume(data); + TestUtil.consumeTestData(extractor, data); fail(); } catch (ParserException exception) { assertEquals("ContentEncodingType 0 not supported", exception.getMessage()); @@ -359,7 +342,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - consume(data); + TestUtil.consumeTestData(extractor, data); fail(); } catch (ParserException exception) { assertEquals("ContentEncAlgo 4 not supported", exception.getMessage()); @@ -374,7 +357,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .addVp9Track(TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - consume(data); + TestUtil.consumeTestData(extractor, data); fail(); } catch (ParserException exception) { assertEquals("AESSettingsCipherMode 0 not supported", exception.getMessage()); @@ -391,10 +374,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertVp9VideoFormat(); - assertSample(media, 0, true, false, null, videoOutput); + assertSample(0, media, 0, true, false, null, getVideoOutput()); } public void testReadTwoTrackSamples() throws IOException, InterruptedException { @@ -411,13 +394,13 @@ public class WebmExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertEquals(2, extractorOutput.numberOfTracks); assertVp9VideoFormat(); assertAudioFormat(MimeTypes.AUDIO_OPUS); - assertSample(media, 0, true, false, null, videoOutput); - assertSample(media, 0, true, false, null, audioOutput); + assertSample(0, media, 0, true, false, null, getVideoOutput()); + assertSample(0, media, 0, true, false, null, getAudioOutput()); } public void testReadTwoTrackSamplesWithSkippedTrack() throws IOException, InterruptedException { @@ -437,13 +420,13 @@ public class WebmExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertEquals(2, extractorOutput.numberOfTracks); assertVp9VideoFormat(); assertAudioFormat(MimeTypes.AUDIO_OPUS); - assertSample(media, 0, true, false, null, videoOutput); - assertSample(media, 0, true, false, null, audioOutput); + assertSample(0, media, 0, true, false, null, getVideoOutput()); + assertSample(0, media, 0, true, false, null, getAudioOutput()); } public void testReadBlock() throws IOException, InterruptedException { @@ -457,10 +440,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); 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 { @@ -473,10 +456,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { false /* keyframe */, false /* invisible */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertVp9VideoFormat(); - assertSample(media, 0, false, false, null, videoOutput); + assertSample(0, media, 0, false, false, null, getVideoOutput()); } public void testReadEncryptedFrame() throws IOException, InterruptedException { @@ -491,10 +474,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { true /* validSignalByte */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); 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() @@ -511,7 +494,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { .build(1); try { - consume(data); + TestUtil.consumeTestData(extractor, data); fail(); } catch (ParserException exception) { assertEquals("Extension bit is set in signal byte", exception.getMessage()); @@ -528,10 +511,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { false /* keyframe */, true /* invisible */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertVp9VideoFormat(); - assertSample(media, 25000, false, true, null, videoOutput); + assertSample(0, media, 25000, false, true, null, getVideoOutput()); } public void testReadSampleCustomTimescale() throws IOException, InterruptedException { @@ -544,10 +527,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { false /* keyframe */, false /* invisible */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertVp9VideoFormat(); - assertSample(media, 25, false, false, null, videoOutput); + assertSample(0, media, 25, false, false, null, getVideoOutput()); } public void testReadSampleNegativeSimpleBlockTimecode() throws IOException, InterruptedException { @@ -560,13 +543,13 @@ public class WebmExtractorTest extends InstrumentationTestCase { true /* keyframe */, true /* invisible */, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); 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[] data = new StreamBuilder() .setHeader(WEBM_DOC_TYPE) @@ -577,48 +560,64 @@ public class WebmExtractorTest extends InstrumentationTestCase { 0 /* blockTimecode */, 20, media) .build(1); - consume(data); + TestUtil.consumeTestData(extractor, data); assertAudioFormat(MimeTypes.AUDIO_OPUS); for (int i = 0; i < 20; i++) { long expectedTimeUs = i * TEST_DEFAULT_DURATION_NS / 1000; - assertSample(Arrays.copyOfRange(media, i * 5, i * 5 + 5), expectedTimeUs, true, false, null, - audioOutput); + assertSample(i, Arrays.copyOfRange(media, i * 5, i * 5 + 5), expectedTimeUs, true, false, + null, getAudioOutput()); } } - private void consume(byte[] data) throws IOException, InterruptedException { - ExtractorInput input = createTestInput(data); - int readResult = Extractor.RESULT_CONTINUE; - while (readResult == Extractor.RESULT_CONTINUE) { - readResult = extractor.read(input, null); - } - assertEquals(Extractor.RESULT_END_OF_INPUT, readResult); + public void testReadSampleWithXiphLacing() throws IOException, InterruptedException { + byte[] media = createFrameData(300); + byte[] data = new StreamBuilder() + .setHeader(WEBM_DOC_TYPE) + .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()); } - private static ExtractorInput createTestInput(byte[] data) throws IOException { - DataSource dataSource = new FakeDataSource.Builder().appendReadData(data).build(); - dataSource.open(new DataSpec(Uri.parse("http://www.google.com"))); - ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED); - return input; + private FakeTrackOutput getVideoOutput() { + // In the sample data the video track has id 1. + return extractorOutput.trackOutputs.get(1); + } + + private FakeTrackOutput getAudioOutput() { + // In the sample data the video track has id 2. + return extractorOutput.trackOutputs.get(2); } private void assertVp9VideoFormat() { - MediaFormat format = videoOutput.format; + MediaFormat format = getVideoOutput().format; assertEquals(TEST_WIDTH, format.width); assertEquals(TEST_HEIGHT, format.height); assertEquals(MimeTypes.VIDEO_VP9, format.mimeType); } private void assertH264VideoFormat() { - MediaFormat format = videoOutput.format; + MediaFormat format = getVideoOutput().format; assertEquals(TEST_WIDTH, format.width); assertEquals(TEST_HEIGHT, format.height); assertEquals(MimeTypes.VIDEO_H264, format.mimeType); } private void assertAudioFormat(String expectedMimeType) { - MediaFormat format = audioOutput.format; + MediaFormat format = getAudioOutput().format; assertEquals(TEST_CHANNEL_COUNT, format.channelCount); assertEquals(TEST_SAMPLE_RATE, format.sampleRate); assertEquals(expectedMimeType, format.mimeType); @@ -646,10 +645,10 @@ public class WebmExtractorTest extends InstrumentationTestCase { } } - private void assertSample(byte[] expectedMedia, long timeUs, boolean keyframe, boolean invisible, - byte[] encryptionKey, TestTrackOutput output) { + private void assertSample(int index, byte[] expectedMedia, long timeUs, boolean keyframe, + boolean invisible, byte[] encryptionKey, FakeTrackOutput output) { if (encryptionKey != null) { - expectedMedia = StreamBuilder.joinByteArrays( + expectedMedia = TestUtil.joinByteArrays( new byte[] {(byte) StreamBuilder.TEST_INITIALIZATION_VECTOR.length}, StreamBuilder.TEST_INITIALIZATION_VECTOR, expectedMedia); } @@ -657,7 +656,7 @@ public class WebmExtractorTest extends InstrumentationTestCase { flags |= keyframe ? C.SAMPLE_FLAG_SYNC : 0; flags |= invisible ? C.SAMPLE_FLAG_DECODE_ONLY : 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() { @@ -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 sampleData; - private final Queue sampleTimesUs; - private final Queue sampleFlags; - private final Queue sampleSizes; - private final Queue 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); - } - } - - } - } diff --git a/library/src/test/java/com/google/android/exoplayer/testutil/FakeExtractorOutput.java b/library/src/test/java/com/google/android/exoplayer/testutil/FakeExtractorOutput.java new file mode 100644 index 0000000000..6dbab04e7d --- /dev/null +++ b/library/src/test/java/com/google/android/exoplayer/testutil/FakeExtractorOutput.java @@ -0,0 +1,77 @@ +/* + * 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 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; + } + +} diff --git a/library/src/test/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java b/library/src/test/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java new file mode 100644 index 0000000000..f673c06f00 --- /dev/null +++ b/library/src/test/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java @@ -0,0 +1,102 @@ +/* + * 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 sampleTimesUs; + private final ArrayList sampleFlags; + private final ArrayList sampleStartOffsets; + private final ArrayList sampleEndOffsets; + private final ArrayList 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); + } + } + +} diff --git a/library/src/test/java/com/google/android/exoplayer/testutil/TestUtil.java b/library/src/test/java/com/google/android/exoplayer/testutil/TestUtil.java new file mode 100644 index 0000000000..77fc5fa12a --- /dev/null +++ b/library/src/test/java/com/google/android/exoplayer/testutil/TestUtil.java @@ -0,0 +1,109 @@ +/* + * 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.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 org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Random; + +/** + * Utility methods for tests. + */ +public class TestUtil { + + private TestUtil() {} + + 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) { + return buildTestData(length, length); + } + + public static byte[] buildTestData(int length, int seed) { + Random random = new Random(seed); + byte[] source = new byte[length]; + random.nextBytes(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) { + // Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2. + System.setProperty("dexmaker.dexcache", + instrumentationTestCase.getInstrumentation().getTargetContext().getCacheDir().getPath()); + MockitoAnnotations.initMocks(instrumentationTestCase); + } + +} diff --git a/library/src/test/java/com/google/android/exoplayer/testutil/Util.java b/library/src/test/java/com/google/android/exoplayer/testutil/Util.java deleted file mode 100644 index 4d2897e7a4..0000000000 --- a/library/src/test/java/com/google/android/exoplayer/testutil/Util.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 android.test.InstrumentationTestCase; - -import org.mockito.MockitoAnnotations; - -import java.util.Random; - -/** - * Utility methods for tests. - */ -public class Util { - - private Util() {} - - public static byte[] buildTestData(int length) { - return buildTestData(length, length); - } - - public static byte[] buildTestData(int length, int seed) { - Random random = new Random(seed); - byte[] source = new byte[length]; - random.nextBytes(source); - return source; - } - - public static void setUpMockito(InstrumentationTestCase instrumentationTestCase) { - // Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2. - System.setProperty("dexmaker.dexcache", - instrumentationTestCase.getInstrumentation().getTargetContext().getCacheDir().getPath()); - MockitoAnnotations.initMocks(instrumentationTestCase); - } - -}