mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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.
This commit is contained in:
parent
bc14e87cfb
commit
e328546607
12 changed files with 694 additions and 592 deletions
|
|
@ -68,7 +68,12 @@ public final class AudioCapabilitiesReceiver {
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
public void register() {
|
public void register() {
|
||||||
if (receiver != null) {
|
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);
|
listener.onAudioCapabilitiesChanged(DEFAULT_AUDIO_CAPABILITIES);
|
||||||
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
// 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 =
|
||||||
(contentSize - blockTrackNumberLength - 4) / blockLacingSampleCount;
|
ensureArrayCapacity(blockLacingSampleSizes, blockLacingSampleCount);
|
||||||
} else {
|
if (lacing == LACING_FIXED_SIZE) {
|
||||||
throw new ParserException("Lacing mode not supported: " + lacing);
|
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);
|
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) {
|
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 {
|
||||||
} 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 {
|
||||||
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 {
|
||||||
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)
|
||||||
|
|
@ -824,6 +891,21 @@ public final class WebmExtractor implements Extractor {
|
||||||
|| CODEC_ID_AC3.equals(codecId);
|
|| 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}.
|
* Passes events through to the outer {@link WebmExtractor}.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -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}.
|
* 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;
|
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;
|
||||||
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 {
|
||||||
|
|
||||||
@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;
|
||||||
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 {
|
||||||
|
|
||||||
@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;
|
||||||
|
|
||||||
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 {
|
||||||
/** 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];
|
||||||
|
|
||||||
|
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 {
|
public void testParsesValidMp4File() throws Exception {
|
||||||
// Given an extractor with an AVC/AAC file
|
TestUtil.consumeTestData(extractor,
|
||||||
Mp4ExtractorWrapper extractor =
|
getTestInputData(true /* includeStss */, false /* mp4vFormat */));
|
||||||
prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */));
|
|
||||||
|
|
||||||
// The MIME type and metadata are set correctly.
|
// The seek map is correct.
|
||||||
assertEquals(MimeTypes.VIDEO_H264, extractor.mediaFormats[0].mimeType);
|
assertSeekMap(extractorOutput.seekMap, true);
|
||||||
assertEquals(MimeTypes.AUDIO_AAC, extractor.mediaFormats[1].mimeType);
|
|
||||||
|
|
||||||
assertEquals(VIDEO_WIDTH, extractor.selectedTrackMediaFormat.width);
|
// The video and audio formats are set correctly.
|
||||||
assertEquals(VIDEO_HEIGHT, extractor.selectedTrackMediaFormat.height);
|
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 {
|
public void testParsesValidMp4vFile() throws Exception {
|
||||||
// Given an extractor with an mp4v file
|
TestUtil.consumeTestData(extractor,
|
||||||
Mp4ExtractorWrapper extractor =
|
getTestInputData(true /* includeStss */, true /* mp4vFormat */));
|
||||||
prepareSampleExtractor(getFakeDataSource(true /* includeStss */, true /* mp4vFormat */));
|
|
||||||
|
|
||||||
// The MIME type and metadata are set correctly.
|
// The seek map is correct.
|
||||||
assertEquals(MimeTypes.VIDEO_MP4V, extractor.selectedTrackMediaFormat.mimeType);
|
assertSeekMap(extractorOutput.seekMap, true);
|
||||||
assertEquals(VIDEO_MP4V_WIDTH, extractor.selectedTrackMediaFormat.width);
|
|
||||||
assertEquals(VIDEO_MP4V_HEIGHT, extractor.selectedTrackMediaFormat.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSampleTimestampsMatch() throws Exception {
|
// The video and audio formats are set correctly.
|
||||||
// Given an extractor
|
assertEquals(2, extractorOutput.trackOutputs.size());
|
||||||
Mp4ExtractorWrapper extractor =
|
MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format;
|
||||||
prepareSampleExtractor(getFakeDataSource(true /* includeStss */, false /* mp4vFormat */));
|
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.
|
// The timestamps and sizes are set correctly.
|
||||||
SampleHolder sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
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, false);
|
||||||
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 {
|
private static void assertSeekMap(SeekMap seekMap, boolean haveStss) {
|
||||||
// When seeking to the start
|
assertNotNull(seekMap);
|
||||||
int timestampTimeUnits = SAMPLE_TIMESTAMPS[0];
|
int expectedSeekPosition = getSampleOffset(0);
|
||||||
long sampleTimestampUs =
|
for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) {
|
||||||
getTimestampUsResultingFromSeek(getVideoTimestampUs(timestampTimeUnits));
|
// Seek to just before the current sample.
|
||||||
|
long seekPositionUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]) - 1;
|
||||||
// The timestamp is at the start.
|
assertEquals(expectedSeekPosition, seekMap.getPosition(seekPositionUs));
|
||||||
assertEquals(getVideoTimestampUs(timestampTimeUnits), sampleTimestampUs);
|
// If the current sample is a sync sample, the expected seek position will change.
|
||||||
}
|
if (SAMPLE_IS_SYNC[i] || !haveStss) {
|
||||||
|
expectedSeekPosition = getSampleOffset(i);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
// 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}. */
|
/** Returns a video timestamp in microseconds corresponding to {@code timeUnits}. */
|
||||||
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 @@
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
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;
|
||||||
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));
|
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 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,
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
.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 {
|
||||||
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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
.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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
.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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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,
|
||||||
assertEquals(Extractor.RESULT_END_OF_INPUT, readResult);
|
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 {
|
private FakeTrackOutput getVideoOutput() {
|
||||||
DataSource dataSource = new FakeDataSource.Builder().appendReadData(data).build();
|
// In the sample data the video track has id 1.
|
||||||
dataSource.open(new DataSpec(Uri.parse("http://www.google.com")));
|
return extractorOutput.trackOutputs.get(1);
|
||||||
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
|
}
|
||||||
return input;
|
|
||||||
|
private FakeTrackOutput getAudioOutput() {
|
||||||
|
// In the sample data the video track has id 2.
|
||||||
|
return extractorOutput.trackOutputs.get(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue