Remove/simplify some extractor tests.
We have our snazzy new file-based extractor tests now, and the other ones have historically proven way more hassle than they're worth (e.g. I spent a good few hours once just trying to work out how to fix the Mp4 extractor test, having established it was the test and not the code that was broken!). I think some more can go from the ogg package, but leaving in place for now because it's a bit less clear what to get rid of. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=125330072
This commit is contained in:
parent
53cc88e2dc
commit
4c434005d4
@ -24,7 +24,7 @@ import junit.framework.TestCase;
|
||||
*/
|
||||
public class ExtractorTest extends TestCase {
|
||||
|
||||
public static void testContants() {
|
||||
public static void testConstants() {
|
||||
// Sanity check that constant values match those defined by {@link C}.
|
||||
assertEquals(C.RESULT_END_OF_INPUT, Extractor.RESULT_END_OF_INPUT);
|
||||
// Sanity check that the other constant values don't overlap.
|
||||
|
@ -15,788 +15,16 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.extractor.mkv;
|
||||
|
||||
import static com.google.android.exoplayer.extractor.mkv.StreamBuilder.TEST_ENCRYPTION_KEY_ID;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.drm.DrmInitData;
|
||||
import com.google.android.exoplayer.drm.DrmInitData.SchemeData;
|
||||
import com.google.android.exoplayer.extractor.ChunkIndex;
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.extractor.mkv.StreamBuilder.ContentEncodingSettings;
|
||||
import com.google.android.exoplayer.testutil.FakeExtractorOutput;
|
||||
import com.google.android.exoplayer.testutil.FakeTrackOutput;
|
||||
import com.google.android.exoplayer.testutil.TestUtil;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Tests for {@link MatroskaExtractor}.
|
||||
*/
|
||||
public final class MatroskaExtractorTest extends InstrumentationTestCase {
|
||||
|
||||
private static final int DEFAULT_TIMECODE_SCALE = 1000000;
|
||||
private static final long TEST_DURATION_TIMECODE = 9920L;
|
||||
private static final int TEST_WIDTH = 1280;
|
||||
private static final int TEST_HEIGHT = 720;
|
||||
private static final int TEST_CHANNEL_COUNT = 1;
|
||||
private static final int TEST_SAMPLE_RATE = 48000;
|
||||
private static final int TEST_CODEC_DELAY = 6500000;
|
||||
private static final int TEST_SEEK_PRE_ROLL = 80000000;
|
||||
private static final String TEST_VORBIS_CODEC_PRIVATE = "webm/vorbis_codec_private";
|
||||
private static final int TEST_VORBIS_INFO_SIZE = 30;
|
||||
private static final int TEST_VORBIS_BOOKS_SIZE = 4140;
|
||||
private static final byte[] TEST_OPUS_CODEC_PRIVATE = new byte[] {0, 0};
|
||||
private static final int TEST_DEFAULT_DURATION_NS = 33 * 1000 * 1000;
|
||||
private static final byte[] TEST_H264_CODEC_PRIVATE = TestUtil.createByteArray(0x01, 0x4D,
|
||||
0x40, 0x1E, 0xFF, 0xE1, 0x00, 0x17, 0x67, 0x4D, 0x40, 0x1E, 0xE8, 0x80, 0x50, 0x17, 0xFC,
|
||||
0xB8, 0x08, 0x80, 0x00, 0x01, 0xF4, 0x80, 0x00, 0x75, 0x30, 0x07, 0x8B, 0x16, 0x89, 0x01,
|
||||
0x00, 0x04, 0x68, 0xEB, 0xEF, 0x20);
|
||||
private static final byte VIDEO_TRACK_NUMBER = 0x01;
|
||||
private static final byte AUDIO_TRACK_NUMBER = 0x02;
|
||||
private static final byte UNSUPPORTED_TRACK_NUMBER = 0x03;
|
||||
private static final byte SECOND_VIDEO_TRACK_NUMBER = 0x04;
|
||||
private static final byte SECOND_AUDIO_TRACK_NUMBER = 0x05;
|
||||
|
||||
private static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
|
||||
private static final String MATROSKA_DOC_TYPE = "matroska";
|
||||
private static final String WEBM_DOC_TYPE = "webm";
|
||||
|
||||
private FakeExtractorOutput consumeTestData(byte[] data)
|
||||
throws IOException, InterruptedException {
|
||||
return TestUtil.consumeTestData(new MatroskaExtractor(), data);
|
||||
}
|
||||
|
||||
public void testReadInitializationSegment() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1);
|
||||
}
|
||||
|
||||
public void testPrepareOpus() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addOpusTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY,
|
||||
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1);
|
||||
}
|
||||
|
||||
public void testPrepareVorbis() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVorbisTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE,
|
||||
getVorbisCodecPrivate())
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_VORBIS);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1);
|
||||
}
|
||||
|
||||
public void testPrepareH264() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(MATROSKA_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addH264Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, TEST_H264_CODEC_PRIVATE)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertH264VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1);
|
||||
}
|
||||
|
||||
public void testPrepareTwoTracks() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addOpusTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY,
|
||||
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertEquals(2, extractorOutput.numberOfTracks);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1);
|
||||
}
|
||||
|
||||
public void testPrepareThreeTracks() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addUnsupportedTrack(UNSUPPORTED_TRACK_NUMBER)
|
||||
.addOpusTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY,
|
||||
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
// Even though the input stream has 3 tracks, only 2 of them are supported and will be reported.
|
||||
assertEquals(2, extractorOutput.numberOfTracks);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1);
|
||||
}
|
||||
|
||||
public void testPrepareFourTracks() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addVorbisTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE,
|
||||
getVorbisCodecPrivate())
|
||||
.addVp9Track(SECOND_VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addOpusTrack(SECOND_AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE,
|
||||
TEST_CODEC_DELAY, TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertEquals(4, extractorOutput.numberOfTracks);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_VORBIS);
|
||||
assertVp9VideoFormat(extractorOutput, SECOND_VIDEO_TRACK_NUMBER);
|
||||
assertAudioFormat(extractorOutput, SECOND_AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1);
|
||||
}
|
||||
|
||||
public void testPrepareContentEncodingEncryption() throws IOException, InterruptedException {
|
||||
ContentEncodingSettings settings = new StreamBuilder.ContentEncodingSettings(0, 1, 5, 1);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertDrmInitData(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1);
|
||||
}
|
||||
|
||||
public void testPrepareThreeCuePoints() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.build(3);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 3);
|
||||
}
|
||||
|
||||
public void testPrepareCustomTimecodeScaleBeforeDuration()
|
||||
throws IOException, InterruptedException {
|
||||
testPrepareTimecodeScale(1000, false, false);
|
||||
}
|
||||
|
||||
public void testPrepareCustomTimecodeScaleAfterDuration()
|
||||
throws IOException, InterruptedException {
|
||||
testPrepareTimecodeScale(1000, false, true);
|
||||
}
|
||||
|
||||
public void testPrepareImplicitDefaultTimecode()
|
||||
throws IOException, InterruptedException {
|
||||
testPrepareTimecodeScale(1000, false, true);
|
||||
}
|
||||
|
||||
private void testPrepareTimecodeScale(int timecodeScale, boolean omitTimecodeScaleIfDefault,
|
||||
boolean afterDuration) throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(timecodeScale, TEST_DURATION_TIMECODE, omitTimecodeScaleIfDefault, afterDuration)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.build(3);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSeekMap(extractorOutput, timecodeScale, 3);
|
||||
}
|
||||
|
||||
public void testPrepareNoCuesElement() throws IOException, InterruptedException {
|
||||
testPrepareNoCuesElement(DEFAULT_TIMECODE_SCALE);
|
||||
}
|
||||
|
||||
public void testPrepareNoCuesElementCustomTimecodeScale()
|
||||
throws IOException, InterruptedException {
|
||||
testPrepareNoCuesElement(1000);
|
||||
}
|
||||
|
||||
private void testPrepareNoCuesElement(int timecodeScale) throws IOException,
|
||||
InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(timecodeScale, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, media)
|
||||
.build(0);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertSeekMapUnseekable(extractorOutput, timecodeScale);
|
||||
}
|
||||
|
||||
public void testAcceptsWebmDocType() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
}
|
||||
|
||||
public void testAcceptsMatroskaDocType() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(MATROSKA_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
}
|
||||
|
||||
public void testPrepareInvalidDocType() throws IOException, InterruptedException {
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader("webB")
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.build(1);
|
||||
try {
|
||||
consumeTestData(data);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("DocType webB not supported", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testPrepareInvalidContentEncodingOrder() throws IOException, InterruptedException {
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(1, 1, 5, 1);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.build(1);
|
||||
try {
|
||||
consumeTestData(data);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("ContentEncodingOrder 1 not supported", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testPrepareInvalidContentEncodingScope() throws IOException, InterruptedException {
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 0, 5, 1);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.build(1);
|
||||
try {
|
||||
consumeTestData(data);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("ContentEncodingScope 0 not supported", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testPrepareInvalidContentCompAlgo()
|
||||
throws IOException, InterruptedException {
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 0, new byte[0]);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.build(1);
|
||||
try {
|
||||
consumeTestData(data);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("ContentCompAlgo 0 not supported", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testPrepareInvalidContentEncAlgo() throws IOException, InterruptedException {
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 4, 1);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.build(1);
|
||||
try {
|
||||
consumeTestData(data);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("ContentEncAlgo 4 not supported", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testPrepareInvalidAESSettingsCipherMode() throws IOException, InterruptedException {
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 5, 0);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.build(1);
|
||||
try {
|
||||
consumeTestData(data);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("AESSettingsCipherMode 0 not supported", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadSampleKeyframe() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSample(0, media, 0, true, false, null,
|
||||
getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER));
|
||||
}
|
||||
|
||||
public void testReadSampleKeyframeStripped() throws IOException, InterruptedException {
|
||||
byte[] strippedBytes = new byte[] {-1, -1};
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 3, strippedBytes);
|
||||
byte[] sampleBytes = createFrameData(100);
|
||||
byte[] unstrippedSampleBytes = TestUtil.joinByteArrays(strippedBytes, sampleBytes);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, sampleBytes)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSample(0, unstrippedSampleBytes, 0, true, false, null,
|
||||
getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER));
|
||||
}
|
||||
|
||||
public void testReadSampleKeyframeManyBytesStripped() throws IOException, InterruptedException {
|
||||
byte[] strippedBytes = createFrameData(100);
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 3, strippedBytes);
|
||||
byte[] sampleBytes = createFrameData(5);
|
||||
byte[] unstrippedSampleBytes = TestUtil.joinByteArrays(strippedBytes, sampleBytes);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, sampleBytes)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSample(0, unstrippedSampleBytes, 0, true, false, null,
|
||||
getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER));
|
||||
}
|
||||
|
||||
public void testReadTwoTrackSamples() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addOpusTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY,
|
||||
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, media)
|
||||
.addSimpleBlockMedia(2 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertEquals(2, extractorOutput.numberOfTracks);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
assertSample(0, media, 0, true, false, null,
|
||||
getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER));
|
||||
assertSample(0, media, 0, true, false, null,
|
||||
getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER));
|
||||
}
|
||||
|
||||
public void testReadTwoTrackSamplesWithSkippedTrack() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addUnsupportedTrack(UNSUPPORTED_TRACK_NUMBER)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addOpusTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY,
|
||||
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, media)
|
||||
.addSimpleBlockMedia(2 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, media)
|
||||
.addSimpleBlockMedia(17 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertEquals(2, extractorOutput.numberOfTracks);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
assertSample(0, media, 0, true, false, null,
|
||||
getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER));
|
||||
assertSample(0, media, 0, true, false, null,
|
||||
getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER));
|
||||
}
|
||||
|
||||
public void testReadBlock() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addOpusTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY,
|
||||
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE)
|
||||
.addBlockMedia(2 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
true /* keyframe */, false /* invisible */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
assertSample(0, media, 0, true, false, null,
|
||||
getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER));
|
||||
}
|
||||
|
||||
public void testReadBlockNonKeyframe() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addBlockMedia(1 /* trackNumber */, 0 /* clusterTimecode */, 0 /* blockTimecode */,
|
||||
false /* keyframe */, false /* invisible */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSample(0, media, 0, false, false, null, getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER
|
||||
));
|
||||
}
|
||||
|
||||
public void testReadEncryptedFrame() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 5, 1);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.addSimpleBlockEncryptedMedia(1 /* trackNumber */, 0 /* clusterTimecode */,
|
||||
0 /* blockTimecode */, true /* keyframe */, false /* invisible */,
|
||||
true /* validSignalByte */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSample(0, media, 0, true, false, TEST_ENCRYPTION_KEY_ID,
|
||||
getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER));
|
||||
}
|
||||
|
||||
public void testReadEncryptedFrameWithInvalidSignalByte()
|
||||
throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
ContentEncodingSettings settings = new ContentEncodingSettings(0, 1, 5, 1);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings)
|
||||
.addSimpleBlockEncryptedMedia(1 /* trackNumber */, 0 /* clusterTimecode */,
|
||||
0 /* blockTimecode */, true /* keyframe */, false /* invisible */,
|
||||
false /* validSignalByte */, media)
|
||||
.build(1);
|
||||
|
||||
try {
|
||||
consumeTestData(data);
|
||||
fail();
|
||||
} catch (ParserException exception) {
|
||||
assertEquals("Extension bit is set in signal byte", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadSampleInvisible() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 12 /* clusterTimecode */, 13 /* blockTimecode */,
|
||||
false /* keyframe */, true /* invisible */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSample(0, media, 25000, false, true, null, getTrackOutput(extractorOutput,
|
||||
VIDEO_TRACK_NUMBER
|
||||
));
|
||||
}
|
||||
|
||||
public void testReadSampleCustomTimecodeScale() throws IOException, InterruptedException {
|
||||
int timecodeScale = 1000;
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(timecodeScale, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 12 /* clusterTimecode */, 13 /* blockTimecode */,
|
||||
false /* keyframe */, false /* invisible */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSample(0, media, 25, false, false, null, getTrackOutput(extractorOutput,
|
||||
VIDEO_TRACK_NUMBER
|
||||
));
|
||||
}
|
||||
|
||||
public void testReadSampleNegativeSimpleBlockTimecode() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null)
|
||||
.addSimpleBlockMedia(1 /* trackNumber */, 13 /* clusterTimecode */, -12 /* blockTimecode */,
|
||||
true /* keyframe */, true /* invisible */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER);
|
||||
assertSample(0, media, 1000, true, true, null, getTrackOutput(extractorOutput,
|
||||
VIDEO_TRACK_NUMBER
|
||||
));
|
||||
}
|
||||
|
||||
public void testReadSampleWithFixedSizeLacing() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(100);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addOpusTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY,
|
||||
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE, TEST_DEFAULT_DURATION_NS)
|
||||
.addSimpleBlockMediaWithFixedSizeLacing(2 /* trackNumber */, 0 /* clusterTimecode */,
|
||||
0 /* blockTimecode */, 20 /* lacingFrameCount */, media)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
long expectedTimeUs = i * TEST_DEFAULT_DURATION_NS / 1000;
|
||||
assertSample(i, Arrays.copyOfRange(media, i * 5, i * 5 + 5), expectedTimeUs, true, false,
|
||||
null, getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER));
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadSampleWithXiphLacing() throws IOException, InterruptedException {
|
||||
byte[] media = createFrameData(300);
|
||||
byte[] data = new StreamBuilder()
|
||||
.setHeader(WEBM_DOC_TYPE)
|
||||
.setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE)
|
||||
.addOpusTrack(AUDIO_TRACK_NUMBER, TEST_CHANNEL_COUNT, TEST_SAMPLE_RATE, TEST_CODEC_DELAY,
|
||||
TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE, TEST_DEFAULT_DURATION_NS)
|
||||
.addSimpleBlockMediaWithXiphLacing(2 /* trackNumber */, 0 /* clusterTimecode */,
|
||||
0 /* blockTimecode */, media, 256, 1, 243)
|
||||
.build(1);
|
||||
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(data);
|
||||
|
||||
assertTracksEnded(extractorOutput);
|
||||
assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS);
|
||||
assertSample(0, Arrays.copyOfRange(media, 0, 256), 0 * TEST_DEFAULT_DURATION_NS / 1000, true,
|
||||
false, null, getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER));
|
||||
assertSample(1, Arrays.copyOfRange(media, 256, 257), 1 * TEST_DEFAULT_DURATION_NS / 1000, true,
|
||||
false, null, getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER));
|
||||
assertSample(2, Arrays.copyOfRange(media, 257, 300), 2 * TEST_DEFAULT_DURATION_NS / 1000, true,
|
||||
false, null, getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER));
|
||||
}
|
||||
|
||||
private FakeTrackOutput getTrackOutput(FakeExtractorOutput extractorOutput, int trackNumber) {
|
||||
return extractorOutput.trackOutputs.get(trackNumber);
|
||||
}
|
||||
|
||||
private void assertTracksEnded(FakeExtractorOutput extractorOutput) {
|
||||
assertTrue(extractorOutput.tracksEnded);
|
||||
}
|
||||
|
||||
private void assertVp9VideoFormat(FakeExtractorOutput extractorOutput, int trackNumber) {
|
||||
Format format = getTrackOutput(extractorOutput, trackNumber).format;
|
||||
assertEquals(TEST_WIDTH, format.width);
|
||||
assertEquals(TEST_HEIGHT, format.height);
|
||||
assertEquals(MimeTypes.VIDEO_VP9, format.sampleMimeType);
|
||||
}
|
||||
|
||||
private void assertH264VideoFormat(FakeExtractorOutput extractorOutput, int trackNumber) {
|
||||
Format format = getTrackOutput(extractorOutput, trackNumber).format;
|
||||
assertEquals(TEST_WIDTH, format.width);
|
||||
assertEquals(TEST_HEIGHT, format.height);
|
||||
assertEquals(MimeTypes.VIDEO_H264, format.sampleMimeType);
|
||||
}
|
||||
|
||||
private void assertAudioFormat(FakeExtractorOutput extractorOutput, int trackNumber,
|
||||
String expectedMimeType) {
|
||||
Format format = getTrackOutput(extractorOutput, trackNumber).format;
|
||||
assertEquals(TEST_CHANNEL_COUNT, format.channelCount);
|
||||
assertEquals(TEST_SAMPLE_RATE, format.sampleRate);
|
||||
assertEquals(expectedMimeType, format.sampleMimeType);
|
||||
if (MimeTypes.AUDIO_OPUS.equals(expectedMimeType)) {
|
||||
assertEquals(3, format.initializationData.size());
|
||||
android.test.MoreAsserts.assertEquals(TEST_OPUS_CODEC_PRIVATE,
|
||||
format.initializationData.get(0));
|
||||
assertEquals(TEST_CODEC_DELAY, ByteBuffer.wrap(format.initializationData.get(1))
|
||||
.order(ByteOrder.nativeOrder()).getLong());
|
||||
assertEquals(TEST_SEEK_PRE_ROLL, ByteBuffer.wrap(format.initializationData.get(2))
|
||||
.order(ByteOrder.nativeOrder()).getLong());
|
||||
} else if (MimeTypes.AUDIO_VORBIS.equals(expectedMimeType)) {
|
||||
assertEquals(2, format.initializationData.size());
|
||||
assertEquals(TEST_VORBIS_INFO_SIZE, format.initializationData.get(0).length);
|
||||
assertEquals(TEST_VORBIS_BOOKS_SIZE, format.initializationData.get(1).length);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertDrmInitData(FakeExtractorOutput extractorOutput, int trackNumber) {
|
||||
DrmInitData drmInitData = getTrackOutput(extractorOutput, trackNumber).format.drmInitData;
|
||||
assertNotNull(drmInitData);
|
||||
SchemeData widevineInitData = drmInitData.get(WIDEVINE_UUID);
|
||||
assertEquals(MimeTypes.VIDEO_WEBM, widevineInitData.mimeType);
|
||||
android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, widevineInitData.data);
|
||||
SchemeData zeroInitData = drmInitData.get(C.UUID_NIL);
|
||||
assertEquals(MimeTypes.VIDEO_WEBM, zeroInitData.mimeType);
|
||||
android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, zeroInitData.data);
|
||||
}
|
||||
|
||||
private void assertSeekMap(FakeExtractorOutput extractorOutput, int timecodeScale,
|
||||
int cuePointCount) {
|
||||
ChunkIndex index = (ChunkIndex) extractorOutput.seekMap;
|
||||
assertEquals(cuePointCount, index.length);
|
||||
for (int i = 0; i < cuePointCount - 1; i++) {
|
||||
assertEquals(Util.scaleLargeTimestamp(10 * i, timecodeScale, 1000), index.timesUs[i]);
|
||||
assertEquals(Util.scaleLargeTimestamp(10, timecodeScale, 1000), index.durationsUs[i]);
|
||||
assertEquals(0, index.sizes[i]);
|
||||
}
|
||||
int lastIndex = cuePointCount - 1;
|
||||
long lastTimecode = 10 * lastIndex;
|
||||
long lastDurationTimecode = TEST_DURATION_TIMECODE - lastTimecode;
|
||||
assertEquals(Util.scaleLargeTimestamp(lastTimecode, timecodeScale, 1000),
|
||||
index.timesUs[lastIndex]);
|
||||
assertEquals(Util.scaleLargeTimestamp(lastDurationTimecode, timecodeScale, 1000),
|
||||
index.durationsUs[lastIndex]);
|
||||
assertEquals(Util.scaleLargeTimestamp(TEST_DURATION_TIMECODE, timecodeScale, 1000),
|
||||
extractorOutput.seekMap.getDurationUs());
|
||||
}
|
||||
|
||||
private void assertSeekMapUnseekable(FakeExtractorOutput extractorOutput, long timecodeScale) {
|
||||
assertFalse(extractorOutput.seekMap.isSeekable());
|
||||
long expectedDurationUs = Util.scaleLargeTimestamp(TEST_DURATION_TIMECODE, timecodeScale, 1000);
|
||||
assertEquals(expectedDurationUs, extractorOutput.seekMap.getDurationUs());
|
||||
}
|
||||
|
||||
private void assertSample(int index, byte[] expectedMedia, long timeUs, boolean keyframe,
|
||||
boolean invisible, byte[] encryptionKey, FakeTrackOutput output) {
|
||||
if (encryptionKey != null) {
|
||||
expectedMedia = TestUtil.joinByteArrays(
|
||||
new byte[] {(byte) StreamBuilder.TEST_INITIALIZATION_VECTOR.length},
|
||||
StreamBuilder.TEST_INITIALIZATION_VECTOR, expectedMedia);
|
||||
}
|
||||
int flags = 0;
|
||||
flags |= keyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
flags |= invisible ? C.BUFFER_FLAG_DECODE_ONLY : 0;
|
||||
flags |= encryptionKey != null ? C.BUFFER_FLAG_ENCRYPTED : 0;
|
||||
output.assertSample(index, expectedMedia, timeUs, flags, encryptionKey);
|
||||
}
|
||||
|
||||
private byte[] getVorbisCodecPrivate() throws IOException {
|
||||
return TestUtil.getByteArray(getInstrumentation(), TEST_VORBIS_CODEC_PRIVATE);
|
||||
}
|
||||
|
||||
private static byte[] createFrameData(int size) {
|
||||
byte[] data = new byte[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
data[i] = (byte) i;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void testMkvSample() throws Exception {
|
||||
TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
|
||||
@Override
|
||||
@ -805,4 +33,5 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase {
|
||||
}
|
||||
}, "mkv/sample.mkv", getInstrumentation());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,561 +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.extractor.mkv;
|
||||
|
||||
import com.google.android.exoplayer.testutil.TestUtil;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides byte arrays containing Matroska data for {@link MatroskaExtractorTest}.
|
||||
*/
|
||||
/* package */ final class StreamBuilder {
|
||||
|
||||
/** Used by {@link #addVp9Track} to create a track header with encryption/compression. */
|
||||
public static final class ContentEncodingSettings {
|
||||
|
||||
private final int order;
|
||||
private final int scope;
|
||||
private final int type;
|
||||
private final int algorithm;
|
||||
private final int aesCipherMode;
|
||||
private final byte[] strippedBytes;
|
||||
|
||||
public ContentEncodingSettings(int order, int scope, int algorithm, int aesCipherMode) {
|
||||
this.order = order;
|
||||
this.scope = scope;
|
||||
this.type = 1; // Encryption
|
||||
this.algorithm = algorithm;
|
||||
this.aesCipherMode = aesCipherMode;
|
||||
this.strippedBytes = null;
|
||||
}
|
||||
|
||||
public ContentEncodingSettings(int order, int scope, int algorithm, byte[] strippedBytes) {
|
||||
this.order = order;
|
||||
this.scope = scope;
|
||||
this.type = 0; // Compression
|
||||
this.algorithm = algorithm;
|
||||
this.aesCipherMode = 0;
|
||||
this.strippedBytes = strippedBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final byte[] TEST_ENCRYPTION_KEY_ID = { 0x00, 0x01, 0x02, 0x03 };
|
||||
public static final byte[] TEST_INITIALIZATION_VECTOR = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
|
||||
};
|
||||
|
||||
private static final int NO_VALUE = -1;
|
||||
|
||||
private final List<EbmlElement> trackEntries;
|
||||
private final List<EbmlElement> mediaSegments;
|
||||
|
||||
private EbmlElement header;
|
||||
private EbmlElement info;
|
||||
|
||||
public StreamBuilder() {
|
||||
trackEntries = new LinkedList<>();
|
||||
mediaSegments = new LinkedList<>();
|
||||
}
|
||||
|
||||
public StreamBuilder setHeader(String docType) {
|
||||
header = createEbmlElement(1, docType, 2);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder setInfo(int timecodeScale, long durationTimecode) {
|
||||
return setInfo(timecodeScale, durationTimecode, false, false);
|
||||
}
|
||||
|
||||
public StreamBuilder setInfo(int timecodeScale, long durationTimecode,
|
||||
boolean omitTimecodeScaleIfDefault, boolean durationFirst) {
|
||||
info = createInfoElement(timecodeScale, durationTimecode, omitTimecodeScaleIfDefault,
|
||||
durationFirst);
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addVp9Track(byte trackNumber, int width, int height,
|
||||
ContentEncodingSettings contentEncodingSettings) {
|
||||
trackEntries.add(createVideoTrackEntry(trackNumber, "V_VP9", width, height,
|
||||
contentEncodingSettings, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addH264Track(byte trackNumber, int width, int height, byte[] codecPrivate) {
|
||||
trackEntries.add(createVideoTrackEntry(trackNumber, "V_MPEG4/ISO/AVC", width, height, null,
|
||||
codecPrivate));
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addOpusTrack(byte trackNumber, int channelCount, int sampleRate,
|
||||
int codecDelay, int seekPreRoll, byte[] codecPrivate) {
|
||||
trackEntries.add(createAudioTrackEntry(trackNumber, "A_OPUS", channelCount, sampleRate,
|
||||
codecPrivate, codecDelay, seekPreRoll, NO_VALUE));
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addOpusTrack(byte trackNumber, int channelCount, int sampleRate,
|
||||
int codecDelay, int seekPreRoll, byte[] codecPrivate, int defaultDurationNs) {
|
||||
trackEntries.add(createAudioTrackEntry(trackNumber, "A_OPUS", channelCount, sampleRate,
|
||||
codecPrivate, codecDelay, seekPreRoll, defaultDurationNs));
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addVorbisTrack(byte trackNumber, int channelCount, int sampleRate,
|
||||
byte[] codecPrivate) {
|
||||
trackEntries.add(createAudioTrackEntry(trackNumber, "A_VORBIS", channelCount, sampleRate,
|
||||
codecPrivate, NO_VALUE, NO_VALUE, NO_VALUE));
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addUnsupportedTrack(byte trackNumber) {
|
||||
trackEntries.add(element(0xAE, // TrackEntry
|
||||
element(0x86, "D_WEBVTT/metadata".getBytes()), // CodecID
|
||||
element(0xD7, trackNumber), // TrackNumber
|
||||
element(0x83, (byte) 0x11))); // TrackType
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addSimpleBlockEncryptedMedia(int trackNumber, int clusterTimecode,
|
||||
int blockTimecode, boolean keyframe, boolean invisible, boolean validSignalByte,
|
||||
byte[] data) {
|
||||
int flags = (keyframe ? 0x80 : 0x00) | (invisible ? 0x08 : 0x00);
|
||||
EbmlElement simpleBlockElement = createSimpleBlock(trackNumber, blockTimecode, flags, true,
|
||||
validSignalByte, 1, data);
|
||||
mediaSegments.add(createCluster(clusterTimecode, simpleBlockElement));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public StreamBuilder addSimpleBlockMedia(int trackNumber, int clusterTimecode,
|
||||
int blockTimecode, boolean keyframe, boolean invisible, byte[] data) {
|
||||
int flags = (keyframe ? 0x80 : 0x00) | (invisible ? 0x08 : 0x00);
|
||||
EbmlElement simpleBlockElement = createSimpleBlock(trackNumber, blockTimecode, flags, false,
|
||||
true, 1, data);
|
||||
mediaSegments.add(createCluster(clusterTimecode, simpleBlockElement));
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addSimpleBlockMediaWithFixedSizeLacing(int trackNumber, int clusterTimecode,
|
||||
int blockTimecode, int lacingFrameCount, byte[] data) {
|
||||
EbmlElement simpleBlockElement = createSimpleBlock(trackNumber, blockTimecode,
|
||||
0x80 /* flags = keyframe */, false, true, lacingFrameCount, data);
|
||||
mediaSegments.add(createCluster(clusterTimecode, simpleBlockElement));
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addSimpleBlockMediaWithXiphLacing(int trackNumber, int clusterTimecode,
|
||||
int blockTimecode, byte[] data, int... lacingFrameSizes) {
|
||||
EbmlElement simpleBlockElement = createSimpleBlock(trackNumber, blockTimecode,
|
||||
0x80 /* flags = keyframe */, false, true, data, lacingFrameSizes);
|
||||
mediaSegments.add(createCluster(clusterTimecode, simpleBlockElement));
|
||||
return this;
|
||||
}
|
||||
|
||||
public StreamBuilder addBlockMedia(int trackNumber, int clusterTimecode, int blockTimecode,
|
||||
boolean keyframe, boolean invisible, byte[] data) {
|
||||
byte flags = (byte) (invisible ? 0x08 : 0x00);
|
||||
EbmlElement blockElement =
|
||||
createBlock(trackNumber, blockTimecode, keyframe, flags, data);
|
||||
mediaSegments.add(createCluster(clusterTimecode, blockElement));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the constructed stream to a {@code byte[]} using the specified number of cue points.
|
||||
*/
|
||||
public byte[] build(int cuePointCount) {
|
||||
Assertions.checkNotNull(header);
|
||||
Assertions.checkNotNull(info);
|
||||
|
||||
EbmlElement tracks = element(0x1654AE6B,
|
||||
trackEntries.toArray(new EbmlElement[trackEntries.size()]));
|
||||
EbmlElement[] children;
|
||||
|
||||
if (cuePointCount == 0) {
|
||||
children = new EbmlElement[2 + mediaSegments.size()];
|
||||
System.arraycopy(mediaSegments.toArray(), 0, children, 2, mediaSegments.size());
|
||||
children[0] = info;
|
||||
children[1] = tracks;
|
||||
} else {
|
||||
// Get the size of the initialization segment.
|
||||
EbmlElement[] cuePointElements = new EbmlElement[cuePointCount];
|
||||
for (int i = 0; i < cuePointCount; i++) {
|
||||
cuePointElements[i] = createCuePointElement(10 * i, 0);
|
||||
}
|
||||
EbmlElement cues = element(0x1C53BB6B, cuePointElements); // Cues
|
||||
long initializationSegmentSize = info.getSize() + tracks.getSize() + cues.getSize();
|
||||
|
||||
// Recreate the initialization segment using its size as an offset.
|
||||
for (int i = 0; i < cuePointCount; i++) {
|
||||
cuePointElements[i] = createCuePointElement(10 * i, (int) initializationSegmentSize);
|
||||
}
|
||||
cues = element(0x1C53BB6B, cuePointElements); // Cues
|
||||
|
||||
// Build the top-level segment element.
|
||||
children = new EbmlElement[3 + mediaSegments.size()];
|
||||
System.arraycopy(mediaSegments.toArray(), 0, children, 3, mediaSegments.size());
|
||||
children[0] = info;
|
||||
children[1] = tracks;
|
||||
children[2] = cues;
|
||||
}
|
||||
|
||||
EbmlElement segmentElement = element(0x18538067, children); // Segment
|
||||
|
||||
// Serialize the EBML header and the top-level segment element.
|
||||
return EbmlElement.serialize(header, segmentElement);
|
||||
}
|
||||
|
||||
private static EbmlElement createCuePointElement(int cueTime, int cueClusterPosition) {
|
||||
byte[] positionBytes = getLongBytes(cueClusterPosition);
|
||||
return element(0xBB, // CuePoint
|
||||
element(0xB3, (byte) (cueTime & 0xFF)), // CueTime
|
||||
element(0xB7, // CueTrackPositions
|
||||
element(0xF1, positionBytes))); // CueClusterPosition
|
||||
}
|
||||
|
||||
private static EbmlElement createEbmlElement(int ebmlReadVersion, String docType,
|
||||
int docTypeReadVersion) {
|
||||
return element(0x1A45DFA3, // EBML
|
||||
element(0x42F7, (byte) (ebmlReadVersion & 0xFF)), // EBMLReadVersion
|
||||
element(0x4282, docType.getBytes()), // DocType
|
||||
element(0x4285, (byte) (docTypeReadVersion & 0xFF))); // DocTypeReadVersion
|
||||
}
|
||||
|
||||
private EbmlElement createInfoElement(int timecodeScale, long durationTimecode,
|
||||
boolean durationFirst, boolean omitDefaultTimecodeScale) {
|
||||
byte[] timecodeScaleBytes = getIntegerBytes(timecodeScale);
|
||||
byte[] durationBytes = getLongBytes(Double.doubleToLongBits(durationTimecode));
|
||||
EbmlElement durationElement = element(0x4489, durationBytes);
|
||||
EbmlElement timescaleElement = element(0x2AD7B1, timecodeScaleBytes);
|
||||
if (omitDefaultTimecodeScale && timecodeScale == 1000000) {
|
||||
return element(0x1549A966, // Info
|
||||
durationElement);
|
||||
}
|
||||
return element(0x1549A966, // Info
|
||||
durationFirst ? durationElement : timescaleElement,
|
||||
durationFirst ? timescaleElement : durationElement);
|
||||
}
|
||||
|
||||
private static EbmlElement createVideoTrackEntry(byte trackNumber, String codecId, int pixelWidth,
|
||||
int pixelHeight, ContentEncodingSettings contentEncodingSettings, byte[] codecPrivate) {
|
||||
byte[] widthBytes = getIntegerBytes(pixelWidth);
|
||||
byte[] heightBytes = getIntegerBytes(pixelHeight);
|
||||
EbmlElement contentEncodingSettingsElement;
|
||||
if (contentEncodingSettings != null) {
|
||||
EbmlElement encryptionOrCompressionElement;
|
||||
if (contentEncodingSettings.type == 0) {
|
||||
encryptionOrCompressionElement = element(0x5034, // ContentCompression
|
||||
element(0x4254, (byte) (contentEncodingSettings.algorithm & 0xFF)), // ContentCompAlgo
|
||||
element(0x4255, contentEncodingSettings.strippedBytes)); // ContentCompSettings
|
||||
} else if (contentEncodingSettings.type == 1) {
|
||||
encryptionOrCompressionElement = element(0x5035, // ContentEncryption
|
||||
// ContentEncAlgo
|
||||
element(0x47E1, (byte) (contentEncodingSettings.algorithm & 0xFF)),
|
||||
element(0x47E2, TEST_ENCRYPTION_KEY_ID), // ContentEncKeyID
|
||||
element(0x47E7, // ContentEncAESSettings
|
||||
// AESSettingsCipherMode
|
||||
element(0x47E8, (byte) (contentEncodingSettings.aesCipherMode & 0xFF))));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected encoding type.");
|
||||
}
|
||||
|
||||
contentEncodingSettingsElement =
|
||||
element(0x6D80, // ContentEncodings
|
||||
element(0x6240, // ContentEncoding
|
||||
// ContentEncodingOrder
|
||||
element(0x5031, (byte) (contentEncodingSettings.order & 0xFF)),
|
||||
// ContentEncodingScope
|
||||
element(0x5032, (byte) (contentEncodingSettings.scope & 0xFF)),
|
||||
// ContentEncodingType
|
||||
element(0x5033, (byte) (contentEncodingSettings.type & 0xFF)),
|
||||
encryptionOrCompressionElement));
|
||||
} else {
|
||||
contentEncodingSettingsElement = empty();
|
||||
}
|
||||
EbmlElement codecPrivateElement;
|
||||
if (codecPrivate != null) {
|
||||
codecPrivateElement = element(0x63A2, codecPrivate); // CodecPrivate
|
||||
} else {
|
||||
codecPrivateElement = empty();
|
||||
}
|
||||
|
||||
return element(0xAE, // TrackEntry
|
||||
element(0x86, codecId.getBytes()), // CodecID
|
||||
element(0xD7, trackNumber), // TrackNumber
|
||||
element(0x83, (byte) 0x01), // TrackType
|
||||
contentEncodingSettingsElement,
|
||||
element(0xE0, // Video
|
||||
element(0xB0, widthBytes[2], widthBytes[3]),
|
||||
element(0xBA, heightBytes[2], heightBytes[3])),
|
||||
codecPrivateElement);
|
||||
}
|
||||
|
||||
private static EbmlElement createAudioTrackEntry(byte trackNumber, String codecId,
|
||||
int channelCount, int sampleRate, byte[] codecPrivate, int codecDelay, int seekPreRoll,
|
||||
int defaultDurationNs) {
|
||||
byte channelCountByte = (byte) (channelCount & 0xFF);
|
||||
byte[] sampleRateDoubleBytes = getLongBytes(Double.doubleToLongBits(sampleRate));
|
||||
return element(0xAE, // TrackEntry
|
||||
element(0x86, codecId.getBytes()), // CodecID
|
||||
element(0xD7, trackNumber), // TrackNumber
|
||||
element(0x83, (byte) 0x02), // TrackType
|
||||
// CodecDelay
|
||||
codecDelay != NO_VALUE ? element(0x56AA, getIntegerBytes(codecDelay)) : empty(),
|
||||
// SeekPreRoll
|
||||
seekPreRoll != NO_VALUE ? element(0x56BB, getIntegerBytes(seekPreRoll)) : empty(),
|
||||
element(0xE1, // Audio
|
||||
element(0x9F, channelCountByte), // Channels
|
||||
element(0xB5, sampleRateDoubleBytes)), // SamplingFrequency
|
||||
// DefaultDuration
|
||||
defaultDurationNs != NO_VALUE ? element(0x23E383, getIntegerBytes(defaultDurationNs))
|
||||
: empty(),
|
||||
element(0x63A2, codecPrivate)); // CodecPrivate
|
||||
}
|
||||
|
||||
private static EbmlElement createCluster(int timecode, EbmlElement blockGroupOrSimpleBlock) {
|
||||
return element(0x1F43B675, // Cluster
|
||||
element(0xE7, getIntegerBytes(timecode)), // Timecode
|
||||
blockGroupOrSimpleBlock);
|
||||
}
|
||||
|
||||
private static EbmlElement createSimpleBlock(int trackNumber, int timecode, int flags,
|
||||
boolean encrypted, boolean validSignalByte, int lacingFrameCount, byte[] data) {
|
||||
byte[] trackNumberBytes = getIntegerBytes(trackNumber);
|
||||
byte[] timeBytes = getIntegerBytes(timecode);
|
||||
byte[] simpleBlockBytes;
|
||||
if (lacingFrameCount > 1) {
|
||||
flags |= 0x04; // Fixed-size lacing
|
||||
simpleBlockBytes = TestUtil.joinByteArrays(
|
||||
new byte[] {0x40, trackNumberBytes[3], timeBytes[2], timeBytes[3]},
|
||||
TestUtil.createByteArray(flags, lacingFrameCount - 1));
|
||||
} else {
|
||||
simpleBlockBytes = TestUtil.joinByteArrays(
|
||||
new byte[] {0x40, trackNumberBytes[3], timeBytes[2], timeBytes[3]},
|
||||
TestUtil.createByteArray(flags));
|
||||
}
|
||||
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 createSimpleBlock(int trackNumber, int timecode, int flags,
|
||||
boolean encrypted, boolean validSignalByte, byte[] data, int... xiphLacingSampleSizes) {
|
||||
byte[] trackNumberBytes = getIntegerBytes(trackNumber);
|
||||
byte[] timeBytes = getIntegerBytes(timecode);
|
||||
byte[] simpleBlockBytes;
|
||||
flags |= 0x02; // Xiph lacing
|
||||
simpleBlockBytes = TestUtil.createByteArray(
|
||||
0x40, trackNumberBytes[3], // Track number size=2
|
||||
timeBytes[2], timeBytes[3], // Timecode
|
||||
flags, xiphLacingSampleSizes.length - 1); // Flags and lacing.
|
||||
int lacingBufferSize = 0;
|
||||
for (int sampleIndex = 0; sampleIndex < xiphLacingSampleSizes.length - 1; sampleIndex++) {
|
||||
lacingBufferSize += (xiphLacingSampleSizes[sampleIndex] + 254) / 255;
|
||||
}
|
||||
ByteBuffer lacingBytes = ByteBuffer.allocate(lacingBufferSize);
|
||||
for (int sampleIndex = 0; sampleIndex < xiphLacingSampleSizes.length - 1; sampleIndex++) {
|
||||
int sampleSize = xiphLacingSampleSizes[sampleIndex];
|
||||
while (sampleSize > 255) {
|
||||
sampleSize -= 255;
|
||||
lacingBytes.put((byte) 0xFF);
|
||||
}
|
||||
lacingBytes.put((byte) sampleSize);
|
||||
}
|
||||
simpleBlockBytes = TestUtil.joinByteArrays(simpleBlockBytes, lacingBytes.array());
|
||||
|
||||
if (encrypted) {
|
||||
simpleBlockBytes = TestUtil.joinByteArrays(
|
||||
simpleBlockBytes, TestUtil.createByteArray(validSignalByte ? 0x01 : 0x80),
|
||||
Arrays.copyOfRange(TEST_INITIALIZATION_VECTOR, 0, 8));
|
||||
}
|
||||
return element(0xA3, // SimpleBlock
|
||||
TestUtil.joinByteArrays(simpleBlockBytes, data));
|
||||
}
|
||||
|
||||
private static EbmlElement createBlock(int trackNumber, int timecode, boolean keyframe, int flags,
|
||||
byte[] data) {
|
||||
byte[] trackNumberBytes = getIntegerBytes(trackNumber);
|
||||
byte[] timeBytes = getIntegerBytes(timecode);
|
||||
byte[] blockBytes = TestUtil.createByteArray(
|
||||
0x40, trackNumberBytes[3], // Track number size=2
|
||||
timeBytes[2], timeBytes[3], flags); // Timecode and flags
|
||||
EbmlElement block = element(0xA1, // Block
|
||||
TestUtil.joinByteArrays(blockBytes, data));
|
||||
EbmlElement referenceBlock = keyframe ? empty() : element(0xFB, (byte) 0x00); // ReferenceBlock
|
||||
return element(0xA0, // BlockGroup
|
||||
referenceBlock,
|
||||
block);
|
||||
}
|
||||
|
||||
private static byte[] getIntegerBytes(int value) {
|
||||
return TestUtil.createByteArray(
|
||||
(value >> 24) & 0xFF,
|
||||
(value >> 16) & 0xFF,
|
||||
(value >> 8) & 0xFF,
|
||||
(value) & 0xFF);
|
||||
}
|
||||
|
||||
private static byte[] getLongBytes(long value) {
|
||||
byte[] result = new byte[8];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
result[7 - i] = (byte) ((value >> (8 * i)) & 0xFF);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @see EbmlElement#EbmlElement(int, EbmlElement...) */
|
||||
private static EbmlElement element(int type, EbmlElement... childElements) {
|
||||
return new EbmlElement(type, childElements);
|
||||
}
|
||||
|
||||
/** @see EbmlElement#EbmlElement(int, byte...) */
|
||||
private static EbmlElement element(int type, byte... payload) {
|
||||
return new EbmlElement(type, payload);
|
||||
}
|
||||
|
||||
/** @see EbmlElement#EbmlElement() */
|
||||
private static EbmlElement empty() {
|
||||
return new EbmlElement();
|
||||
}
|
||||
|
||||
/** Represents an EBML element that can be serialized as a byte array. */
|
||||
private static final class EbmlElement {
|
||||
|
||||
/** Returns a byte[] containing the concatenation of the data from all {@code elements}. */
|
||||
public static byte[] serialize(EbmlElement... elements) {
|
||||
int size = 0;
|
||||
for (EbmlElement element : elements) {
|
||||
size += element.getSize();
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.allocate(size);
|
||||
for (EbmlElement element : elements) {
|
||||
element.getData(buffer);
|
||||
}
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private final int id;
|
||||
private final EbmlElement[] childElements;
|
||||
private final byte[] payload;
|
||||
|
||||
/** Creates an element containing the specified {@code childElements}. */
|
||||
EbmlElement(int id, EbmlElement... childElements) {
|
||||
this.id = id;
|
||||
this.childElements = childElements;
|
||||
payload = null;
|
||||
}
|
||||
|
||||
/** Creates an element containing the specified {@code payload}. */
|
||||
EbmlElement(int id, byte... payload) {
|
||||
this.id = id;
|
||||
this.payload = payload;
|
||||
childElements = null;
|
||||
}
|
||||
|
||||
/** Creates a completely empty element that will contribute no bytes to the output. */
|
||||
EbmlElement() {
|
||||
id = NO_VALUE;
|
||||
payload = null;
|
||||
childElements = null;
|
||||
}
|
||||
|
||||
private long getSize() {
|
||||
if (id == NO_VALUE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long payloadSize = getPayloadSize();
|
||||
return getIdLength() + getVIntLength(payloadSize) + payloadSize;
|
||||
}
|
||||
|
||||
private long getPayloadSize() {
|
||||
if (payload != null) {
|
||||
return payload.length;
|
||||
}
|
||||
|
||||
int payloadSize = 0;
|
||||
for (EbmlElement element : childElements) {
|
||||
payloadSize += element.getSize();
|
||||
}
|
||||
return payloadSize;
|
||||
}
|
||||
|
||||
private void getData(ByteBuffer byteBuffer) {
|
||||
if (id == NO_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
putId(byteBuffer);
|
||||
putVInt(byteBuffer, getPayloadSize());
|
||||
if (payload != null) {
|
||||
byteBuffer.put(payload);
|
||||
} else {
|
||||
for (EbmlElement atom : childElements) {
|
||||
atom.getData(byteBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getIdLength() {
|
||||
if (id == NO_VALUE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (id < 1 << (7 * i + 8)) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private static int getVIntLength(long vInt) {
|
||||
for (int i = 1; i < 9; i++) {
|
||||
if (vInt < 1 << (7 * i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private void putId(ByteBuffer byteBuffer) {
|
||||
int length = getIdLength();
|
||||
for (int i = length - 1; i >= 0; i--) {
|
||||
byteBuffer.put((byte) ((id >> (i * 8)) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
private static void putVInt(ByteBuffer byteBuffer, long vInt) {
|
||||
int vIntLength = getVIntLength(vInt);
|
||||
vInt |= 1 << (vIntLength * 7);
|
||||
for (int i = vIntLength - 1; i >= 0; i--) {
|
||||
byteBuffer.put((byte) ((vInt >> (i * 8)) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -15,488 +15,18 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.extractor.mp4;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.extractor.SeekMap;
|
||||
import com.google.android.exoplayer.testutil.FakeExtractorOutput;
|
||||
import com.google.android.exoplayer.testutil.FakeTrackOutput;
|
||||
import com.google.android.exoplayer.testutil.TestUtil;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for {@link Mp4Extractor}.
|
||||
*/
|
||||
@TargetApi(16)
|
||||
public final class Mp4ExtractorTest extends InstrumentationTestCase {
|
||||
|
||||
/** String of hexadecimal bytes containing the video stsd payload from an AVC video. */
|
||||
private static final byte[] VIDEO_STSD_PAYLOAD = Util.getBytesFromHexString(
|
||||
"00000000000000010000009961766331000000000000000100000000000000000000000000000000050002d00048"
|
||||
+ "000000480000000000000001000000000000000000000000000000000000000000000000000000000000000000"
|
||||
+ "18ffff0000002f617663430164001fffe100186764001facb402802dd80880000003008000001e078c19500100"
|
||||
+ "0468ee3cb000000014627472740000e35c0042a61000216cb8");
|
||||
private static final byte[] VIDEO_HDLR_PAYLOAD = Util.getBytesFromHexString(
|
||||
"000000000000000076696465");
|
||||
private static final byte[] VIDEO_MDHD_PAYLOAD = Util.getBytesFromHexString(
|
||||
"0000000000000000cf6c48890000001e00001c8a55c40000");
|
||||
private static final int TIMESCALE = 30;
|
||||
private static final int VIDEO_WIDTH = 1280;
|
||||
private static final int VIDEO_HEIGHT = 720;
|
||||
|
||||
/** String of hexadecimal bytes containing the video stsd payload for an mp4v track. */
|
||||
private static final byte[] VIDEO_STSD_MP4V_PAYLOAD = Util.getBytesFromHexString(
|
||||
"0000000000000001000000A36D703476000000000000000100000000000000000000000000000000014000B40048"
|
||||
+ "000000480000000000000001000000000000000000000000000000000000000000000000000000000000000000"
|
||||
+ "18FFFF0000004D6573647300000000033F00000004372011001A400004CF280002F1180528000001B001000001"
|
||||
+ "B58913000001000000012000C48D8800F50A04169463000001B2476F6F676C65060102");
|
||||
private static final int VIDEO_MP4V_WIDTH = 320;
|
||||
private static final int VIDEO_MP4V_HEIGHT = 180;
|
||||
|
||||
/** String of hexadecimal bytes containing the audio stsd payload from an AAC track. */
|
||||
private static final byte[] AUDIO_STSD_PAYLOAD = Util.getBytesFromHexString(
|
||||
"0000000000000001000000596d703461000000000000000100000000000000000001001000000000ac4400000000"
|
||||
+ "003565736473000000000327000000041f401500023e00024bc000023280051012080000000000000000000000"
|
||||
+ "000000060102");
|
||||
private static final byte[] AUDIO_HDLR_PAYLOAD = Util.getBytesFromHexString(
|
||||
"0000000000000000736f756e");
|
||||
private static final byte[] AUDIO_MDHD_PAYLOAD = Util.getBytesFromHexString(
|
||||
"00000000cf6c4889cf6c488a0000ac4400a3e40055c40000");
|
||||
|
||||
/** String of hexadecimal bytes for an ftyp payload with major_brand mp41 and minor_version 0. **/
|
||||
private static final byte[] FTYP_PAYLOAD = Util.getBytesFromHexString("6d70343100000000");
|
||||
|
||||
/** String of hexadecimal bytes containing an mvhd payload from an AVC/AAC video. */
|
||||
private static final byte[] MVHD_PAYLOAD = Util.getBytesFromHexString(
|
||||
"00000000cf6c4888cf6c48880000025800023ad40001000001000000000000000000000000010000000000000000"
|
||||
+ "000000000000000100000000000000000000000000004000000000000000000000000000000000000000000000"
|
||||
+ "000000000000000003");
|
||||
|
||||
/** String of hexadecimal bytes containing a tkhd payload with an unknown duration. */
|
||||
private static final byte[] TKHD_PAYLOAD = Util.getBytesFromHexString(
|
||||
"00000007D1F0C7BFD1F0C7BF0000000000000000FFFFFFFF00000000000000000000000000000000000100000000"
|
||||
+ "0000000000000000000000010000000000000000000000000000400000000780000004380000");
|
||||
|
||||
/** Video frame timestamps in time units. */
|
||||
private static final int[] SAMPLE_TIMESTAMPS = {0, 2, 3, 5, 6, 7};
|
||||
/** Video frame sizes in bytes, including a very large sample. */
|
||||
private static final int[] SAMPLE_SIZES = {100, 20, 20, 44, 100, 1024 * 1024};
|
||||
/** Indices of key-frames. */
|
||||
private static final boolean[] SAMPLE_IS_SYNC = {true, false, false, false, true, true};
|
||||
/** Indices of video frame chunk offsets. */
|
||||
private static final int[] CHUNK_OFFSETS = {1208, 2128, 3128, 4128};
|
||||
/** Numbers of video frames in each chunk. */
|
||||
private static final int[] SAMPLES_IN_CHUNK = {2, 2, 1, 1};
|
||||
/** The mdat box must be large enough to avoid reading chunk sample data out of bounds. */
|
||||
private static final int MDAT_SIZE = 10 * 1024 * 1024;
|
||||
/** Empty byte array. */
|
||||
private static final byte[] EMPTY = new byte[0];
|
||||
|
||||
public void testParsesValidMp4File() throws Exception {
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(true, false);
|
||||
|
||||
// The seek map is correct.
|
||||
assertSeekMap(extractorOutput.seekMap, true);
|
||||
|
||||
// The video and audio formats are set correctly.
|
||||
assertEquals(2, extractorOutput.trackOutputs.size());
|
||||
Format videoFormat = extractorOutput.trackOutputs.get(0).format;
|
||||
Format audioFormat = extractorOutput.trackOutputs.get(1).format;
|
||||
assertEquals(MimeTypes.VIDEO_H264, videoFormat.sampleMimeType);
|
||||
assertEquals(VIDEO_WIDTH, videoFormat.width);
|
||||
assertEquals(VIDEO_HEIGHT, videoFormat.height);
|
||||
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.sampleMimeType);
|
||||
|
||||
// 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.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]);
|
||||
videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void testParsesValidMp4FileWithoutStss() throws Exception {
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(false, false);
|
||||
|
||||
// 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.BUFFER_FLAG_KEY_FRAME;
|
||||
long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]);
|
||||
videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void testParsesValidMp4vFile() throws Exception {
|
||||
FakeExtractorOutput extractorOutput = consumeTestData(true, true);
|
||||
|
||||
// The seek map is correct.
|
||||
assertSeekMap(extractorOutput.seekMap, true);
|
||||
|
||||
// The video and audio formats are set correctly.
|
||||
assertEquals(2, extractorOutput.trackOutputs.size());
|
||||
Format videoFormat = extractorOutput.trackOutputs.get(0).format;
|
||||
Format audioFormat = extractorOutput.trackOutputs.get(1).format;
|
||||
assertEquals(MimeTypes.VIDEO_MP4V, videoFormat.sampleMimeType);
|
||||
assertEquals(VIDEO_MP4V_WIDTH, videoFormat.width);
|
||||
assertEquals(VIDEO_MP4V_HEIGHT, videoFormat.height);
|
||||
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.sampleMimeType);
|
||||
|
||||
// The timestamps and sizes are set correctly.
|
||||
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
|
||||
videoTrackOutput.assertSampleCount(SAMPLE_TIMESTAMPS.length);
|
||||
for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) {
|
||||
byte[] sampleData = getOutputSampleData(i, false);
|
||||
int sampleFlags = SAMPLE_IS_SYNC[i] ? C.BUFFER_FLAG_KEY_FRAME : 0;
|
||||
long sampleTimestampUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]);
|
||||
videoTrackOutput.assertSample(i, sampleData, sampleTimestampUs, sampleFlags, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertSeekMap(SeekMap seekMap, boolean haveStss) {
|
||||
assertNotNull(seekMap);
|
||||
int expectedSeekPosition = getSampleOffset(0);
|
||||
for (int i = 0; i < SAMPLE_TIMESTAMPS.length; i++) {
|
||||
// Seek to just before the current sample.
|
||||
long seekPositionUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]) - 1;
|
||||
assertEquals(expectedSeekPosition, seekMap.getPosition(seekPositionUs));
|
||||
// If the current sample is a sync sample, the expected seek position will change.
|
||||
if (SAMPLE_IS_SYNC[i] || !haveStss) {
|
||||
expectedSeekPosition = getSampleOffset(i);
|
||||
}
|
||||
// Seek to the current sample.
|
||||
seekPositionUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]);
|
||||
assertEquals(expectedSeekPosition, seekMap.getPosition(seekPositionUs));
|
||||
// Seek to just after the current sample.
|
||||
seekPositionUs = getVideoTimestampUs(SAMPLE_TIMESTAMPS[i]) + 1;
|
||||
assertEquals(expectedSeekPosition, seekMap.getPosition(seekPositionUs));
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a video timestamp in microseconds corresponding to {@code timeUnits}. */
|
||||
private static long getVideoTimestampUs(int timeUnits) {
|
||||
return Util.scaleLargeTimestamp(timeUnits, C.MICROS_PER_SECOND, TIMESCALE);
|
||||
}
|
||||
|
||||
private static byte[] getStco() {
|
||||
byte[] result = new byte[4 + 4 + 4 * CHUNK_OFFSETS.length];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(result);
|
||||
buffer.putInt(0); // Version (skipped)
|
||||
buffer.putInt(CHUNK_OFFSETS.length);
|
||||
for (int chunkOffset : CHUNK_OFFSETS) {
|
||||
buffer.putInt(chunkOffset);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] getStsc() {
|
||||
int samplesPerChunk = -1;
|
||||
List<Integer> samplesInChunkChangeIndices = new ArrayList<>();
|
||||
for (int i = 0; i < SAMPLES_IN_CHUNK.length; i++) {
|
||||
if (SAMPLES_IN_CHUNK[i] != samplesPerChunk) {
|
||||
samplesInChunkChangeIndices.add(i);
|
||||
samplesPerChunk = SAMPLES_IN_CHUNK[i];
|
||||
}
|
||||
}
|
||||
|
||||
byte[] result = new byte[4 + 4 + 3 * 4 * samplesInChunkChangeIndices.size()];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(result);
|
||||
buffer.putInt(0); // Version (skipped)
|
||||
buffer.putInt(samplesInChunkChangeIndices.size());
|
||||
for (int index : samplesInChunkChangeIndices) {
|
||||
buffer.putInt(index + 1);
|
||||
buffer.putInt(SAMPLES_IN_CHUNK[index]);
|
||||
buffer.putInt(0); // Sample description index (skipped)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] getStsz() {
|
||||
byte[] result = new byte[4 + 4 + 4 + 4 * SAMPLE_SIZES.length];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(result);
|
||||
buffer.putInt(0); // Version (skipped)
|
||||
buffer.putInt(0); // No fixed sample size.
|
||||
buffer.putInt(SAMPLE_SIZES.length);
|
||||
for (int size : SAMPLE_SIZES) {
|
||||
buffer.putInt(size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] getStss() {
|
||||
int synchronizationSampleCount = 0;
|
||||
for (boolean sampleIsSync : SAMPLE_IS_SYNC) {
|
||||
if (sampleIsSync) {
|
||||
synchronizationSampleCount++;
|
||||
}
|
||||
}
|
||||
byte[] result = new byte[4 + 4 + 4 * synchronizationSampleCount];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(result);
|
||||
buffer.putInt(0); // Version (skipped)
|
||||
buffer.putInt(synchronizationSampleCount);
|
||||
for (int i = 0; i < SAMPLE_IS_SYNC.length; i++) {
|
||||
if (SAMPLE_IS_SYNC[i]) {
|
||||
buffer.putInt(i + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] getStts() {
|
||||
int sampleTimestampDeltaChanges = 0;
|
||||
int currentSampleTimestampDelta = -1;
|
||||
for (int i = 1; i < SAMPLE_TIMESTAMPS.length; i++) {
|
||||
int timestampDelta = SAMPLE_TIMESTAMPS[i] - SAMPLE_TIMESTAMPS[i - 1];
|
||||
if (timestampDelta != currentSampleTimestampDelta) {
|
||||
sampleTimestampDeltaChanges++;
|
||||
currentSampleTimestampDelta = timestampDelta;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] result = new byte[4 + 4 + 2 * 4 * sampleTimestampDeltaChanges];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(result);
|
||||
buffer.putInt(0); // Version (skipped);
|
||||
buffer.putInt(sampleTimestampDeltaChanges);
|
||||
int lastTimestampDeltaChangeIndex = 1;
|
||||
currentSampleTimestampDelta = SAMPLE_TIMESTAMPS[1] - SAMPLE_TIMESTAMPS[0];
|
||||
for (int i = 2; i < SAMPLE_TIMESTAMPS.length; i++) {
|
||||
int timestampDelta = SAMPLE_TIMESTAMPS[i] - SAMPLE_TIMESTAMPS[i - 1];
|
||||
if (timestampDelta != currentSampleTimestampDelta) {
|
||||
buffer.putInt(i - lastTimestampDeltaChangeIndex);
|
||||
lastTimestampDeltaChangeIndex = i;
|
||||
buffer.putInt(currentSampleTimestampDelta);
|
||||
currentSampleTimestampDelta = timestampDelta;
|
||||
}
|
||||
}
|
||||
// The last sample also has a duration, so the number of entries is the number of samples.
|
||||
buffer.putInt(SAMPLE_TIMESTAMPS.length - lastTimestampDeltaChangeIndex + 1);
|
||||
buffer.putInt(currentSampleTimestampDelta);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] getMdat(int mdatOffset, boolean isH264) {
|
||||
ByteBuffer mdat = ByteBuffer.allocate(MDAT_SIZE);
|
||||
int sampleIndex = 0;
|
||||
for (int chunk = 0; chunk < CHUNK_OFFSETS.length; chunk++) {
|
||||
mdat.position(CHUNK_OFFSETS[chunk] - mdatOffset);
|
||||
for (int sample = 0; sample < SAMPLES_IN_CHUNK[chunk]; sample++) {
|
||||
mdat.put(getInputSampleData(sampleIndex++, isH264));
|
||||
}
|
||||
}
|
||||
return mdat.array();
|
||||
}
|
||||
|
||||
private static byte[] getInputSampleData(int index, boolean isH264) {
|
||||
ByteBuffer sample = ByteBuffer.allocate(SAMPLE_SIZES[index]);
|
||||
for (int i = 0; i < SAMPLE_SIZES[index]; i++) {
|
||||
sample.put((byte) i);
|
||||
}
|
||||
if (isH264) {
|
||||
// First four bytes should specify the remaining length of the sample. This assumes that the
|
||||
// sample consists of a single length delimited NAL unit.
|
||||
sample.position(0);
|
||||
sample.putInt(SAMPLE_SIZES[index] - 4);
|
||||
}
|
||||
return sample.array();
|
||||
}
|
||||
|
||||
private static byte[] getOutputSampleData(int index, boolean isH264) {
|
||||
byte[] sampleData = getInputSampleData(index, isH264);
|
||||
if (isH264) {
|
||||
// The output sample should begin with a NAL start code.
|
||||
sampleData[0] = 0;
|
||||
sampleData[1] = 0;
|
||||
sampleData[2] = 0;
|
||||
sampleData[3] = 1;
|
||||
}
|
||||
return sampleData;
|
||||
}
|
||||
|
||||
private static int getSampleOffset(int index) {
|
||||
int sampleCount = 0;
|
||||
int chunkIndex = 0;
|
||||
int samplesLeftInChunk = SAMPLES_IN_CHUNK[chunkIndex];
|
||||
int offsetInChunk = 0;
|
||||
while (sampleCount < index) {
|
||||
offsetInChunk += SAMPLE_SIZES[sampleCount++];
|
||||
samplesLeftInChunk--;
|
||||
if (samplesLeftInChunk == 0) {
|
||||
chunkIndex++;
|
||||
samplesLeftInChunk = SAMPLES_IN_CHUNK[chunkIndex];
|
||||
offsetInChunk = 0;
|
||||
}
|
||||
}
|
||||
return CHUNK_OFFSETS[chunkIndex] + offsetInChunk;
|
||||
}
|
||||
|
||||
private static FakeExtractorOutput consumeTestData(boolean includeStss, boolean mp4vFormat)
|
||||
throws IOException, InterruptedException {
|
||||
byte[] testInputData = includeStss ? getTestMp4File(mp4vFormat)
|
||||
: getTestMp4FileWithoutSynchronizationData(mp4vFormat);
|
||||
return TestUtil.consumeTestData(new Mp4Extractor(), testInputData);
|
||||
}
|
||||
|
||||
/** Gets a valid MP4 file with audio/video tracks and synchronization data. */
|
||||
private static byte[] getTestMp4File(boolean mp4vFormat) {
|
||||
return Mp4Atom.serialize(
|
||||
atom(Atom.TYPE_ftyp, FTYP_PAYLOAD),
|
||||
atom(Atom.TYPE_moov,
|
||||
atom(Atom.TYPE_mvhd, MVHD_PAYLOAD),
|
||||
atom(Atom.TYPE_trak,
|
||||
atom(Atom.TYPE_tkhd, TKHD_PAYLOAD),
|
||||
atom(Atom.TYPE_mdia,
|
||||
atom(Atom.TYPE_mdhd, VIDEO_MDHD_PAYLOAD),
|
||||
atom(Atom.TYPE_hdlr, VIDEO_HDLR_PAYLOAD),
|
||||
atom(Atom.TYPE_minf,
|
||||
atom(Atom.TYPE_vmhd, EMPTY),
|
||||
atom(Atom.TYPE_stbl,
|
||||
atom(Atom.TYPE_stsd,
|
||||
mp4vFormat ? VIDEO_STSD_MP4V_PAYLOAD : VIDEO_STSD_PAYLOAD),
|
||||
atom(Atom.TYPE_stts, getStts()),
|
||||
atom(Atom.TYPE_stss, getStss()),
|
||||
atom(Atom.TYPE_stsc, getStsc()),
|
||||
atom(Atom.TYPE_stsz, getStsz()),
|
||||
atom(Atom.TYPE_stco, getStco()))))),
|
||||
atom(Atom.TYPE_trak,
|
||||
atom(Atom.TYPE_tkhd, TKHD_PAYLOAD),
|
||||
atom(Atom.TYPE_mdia,
|
||||
atom(Atom.TYPE_mdhd, AUDIO_MDHD_PAYLOAD),
|
||||
atom(Atom.TYPE_hdlr, AUDIO_HDLR_PAYLOAD),
|
||||
atom(Atom.TYPE_minf,
|
||||
atom(Atom.TYPE_vmhd, EMPTY),
|
||||
atom(Atom.TYPE_stbl,
|
||||
atom(Atom.TYPE_stsd, AUDIO_STSD_PAYLOAD),
|
||||
atom(Atom.TYPE_stts, getStts()),
|
||||
atom(Atom.TYPE_stss, getStss()),
|
||||
atom(Atom.TYPE_stsc, getStsc()),
|
||||
atom(Atom.TYPE_stsz, getStsz()),
|
||||
atom(Atom.TYPE_stco, getStco())))))),
|
||||
atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 1176 : 1166, !mp4vFormat)));
|
||||
}
|
||||
|
||||
/** Gets a valid MP4 file with audio/video tracks and without a synchronization table. */
|
||||
private static byte[] getTestMp4FileWithoutSynchronizationData(boolean mp4vFormat) {
|
||||
return Mp4Atom.serialize(
|
||||
atom(Atom.TYPE_ftyp, FTYP_PAYLOAD),
|
||||
atom(Atom.TYPE_moov,
|
||||
atom(Atom.TYPE_mvhd, MVHD_PAYLOAD),
|
||||
atom(Atom.TYPE_trak,
|
||||
atom(Atom.TYPE_tkhd, TKHD_PAYLOAD),
|
||||
atom(Atom.TYPE_mdia,
|
||||
atom(Atom.TYPE_mdhd, VIDEO_MDHD_PAYLOAD),
|
||||
atom(Atom.TYPE_hdlr, VIDEO_HDLR_PAYLOAD),
|
||||
atom(Atom.TYPE_minf,
|
||||
atom(Atom.TYPE_vmhd, EMPTY),
|
||||
atom(Atom.TYPE_stbl,
|
||||
atom(Atom.TYPE_stsd,
|
||||
mp4vFormat ? VIDEO_STSD_MP4V_PAYLOAD : VIDEO_STSD_PAYLOAD),
|
||||
atom(Atom.TYPE_stts, getStts()),
|
||||
atom(Atom.TYPE_stsc, getStsc()),
|
||||
atom(Atom.TYPE_stsz, getStsz()),
|
||||
atom(Atom.TYPE_stco, getStco()))))),
|
||||
atom(Atom.TYPE_trak,
|
||||
atom(Atom.TYPE_tkhd, TKHD_PAYLOAD),
|
||||
atom(Atom.TYPE_mdia,
|
||||
atom(Atom.TYPE_mdhd, AUDIO_MDHD_PAYLOAD),
|
||||
atom(Atom.TYPE_hdlr, AUDIO_HDLR_PAYLOAD),
|
||||
atom(Atom.TYPE_minf,
|
||||
atom(Atom.TYPE_vmhd, EMPTY),
|
||||
atom(Atom.TYPE_stbl,
|
||||
atom(Atom.TYPE_stsd, AUDIO_STSD_PAYLOAD),
|
||||
atom(Atom.TYPE_stts, getStts()),
|
||||
atom(Atom.TYPE_stsc, getStsc()),
|
||||
atom(Atom.TYPE_stsz, getStsz()),
|
||||
atom(Atom.TYPE_stco, getStco())))))),
|
||||
atom(Atom.TYPE_mdat, getMdat(mp4vFormat ? 1120 : 1110, !mp4vFormat)));
|
||||
}
|
||||
|
||||
private static Mp4Atom atom(int type, Mp4Atom... containedMp4Atoms) {
|
||||
return new Mp4Atom(type, containedMp4Atoms);
|
||||
}
|
||||
|
||||
private static Mp4Atom atom(int type, byte[] payload) {
|
||||
return new Mp4Atom(type, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* MP4 atom that can be serialized as a byte array.
|
||||
*/
|
||||
private static final class Mp4Atom {
|
||||
|
||||
public static byte[] serialize(Mp4Atom... atoms) {
|
||||
int size = 0;
|
||||
for (Mp4Atom atom : atoms) {
|
||||
size += atom.getSize();
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.allocate(size);
|
||||
for (Mp4Atom atom : atoms) {
|
||||
atom.getData(buffer);
|
||||
}
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private static final int HEADER_SIZE = 8;
|
||||
|
||||
private final int type;
|
||||
private final Mp4Atom[] containedMp4Atoms;
|
||||
private final byte[] payload;
|
||||
|
||||
private Mp4Atom(int type, Mp4Atom... containedMp4Atoms) {
|
||||
this.type = type;
|
||||
this.containedMp4Atoms = containedMp4Atoms;
|
||||
payload = null;
|
||||
}
|
||||
|
||||
private Mp4Atom(int type, byte[] payload) {
|
||||
this.type = type;
|
||||
this.payload = payload;
|
||||
containedMp4Atoms = null;
|
||||
}
|
||||
|
||||
private int getSize() {
|
||||
int size = HEADER_SIZE;
|
||||
if (payload != null) {
|
||||
size += payload.length;
|
||||
} else {
|
||||
for (Mp4Atom atom : containedMp4Atoms) {
|
||||
size += atom.getSize();
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private void getData(ByteBuffer byteBuffer) {
|
||||
byteBuffer.putInt(getSize());
|
||||
byteBuffer.putInt(type);
|
||||
|
||||
if (payload != null) {
|
||||
byteBuffer.put(payload);
|
||||
} else {
|
||||
for (Mp4Atom atom : containedMp4Atoms) {
|
||||
atom.getData(byteBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testMp4Sample() throws Exception {
|
||||
TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
|
||||
@Override
|
||||
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.extractor.ogg;
|
||||
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.testutil.TestUtil;
|
||||
import com.google.android.exoplayer.testutil.TestUtil.ExtractorFactory;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
/**
|
||||
* Unit test for {@link OpusReader}.
|
||||
*/
|
||||
public final class OggExtractorFileTests extends InstrumentationTestCase {
|
||||
|
||||
private static final ExtractorFactory OGG_EXTRACTOR_FACTORY = new ExtractorFactory() {
|
||||
@Override
|
||||
public Extractor create() {
|
||||
return new OggExtractor();
|
||||
}
|
||||
};
|
||||
|
||||
public void testOpus() throws Exception {
|
||||
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear.opus", getInstrumentation());
|
||||
}
|
||||
|
||||
public void testFlac() throws Exception {
|
||||
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac.ogg", getInstrumentation());
|
||||
}
|
||||
|
||||
public void testFlacNoSeektable() throws Exception {
|
||||
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac_noseektable.ogg",
|
||||
getInstrumentation());
|
||||
}
|
||||
|
||||
public void testVorbis() throws Exception {
|
||||
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_vorbis.ogg", getInstrumentation());
|
||||
}
|
||||
|
||||
}
|
@ -15,24 +15,42 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.extractor.ogg;
|
||||
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.testutil.FakeExtractorInput;
|
||||
import com.google.android.exoplayer.testutil.TestUtil;
|
||||
import com.google.android.exoplayer.testutil.TestUtil.ExtractorFactory;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Unit test for {@link OggExtractor}.
|
||||
*/
|
||||
public final class OggExtractorTest extends TestCase {
|
||||
public final class OggExtractorTest extends InstrumentationTestCase {
|
||||
|
||||
private OggExtractor extractor;
|
||||
private static final ExtractorFactory OGG_EXTRACTOR_FACTORY = new ExtractorFactory() {
|
||||
@Override
|
||||
public Extractor create() {
|
||||
return new OggExtractor();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
extractor = new OggExtractor();
|
||||
public void testOpus() throws Exception {
|
||||
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear.opus", getInstrumentation());
|
||||
}
|
||||
|
||||
public void testFlac() throws Exception {
|
||||
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac.ogg", getInstrumentation());
|
||||
}
|
||||
|
||||
public void testFlacNoSeektable() throws Exception {
|
||||
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_flac_noseektable.ogg",
|
||||
getInstrumentation());
|
||||
}
|
||||
|
||||
public void testVorbis() throws Exception {
|
||||
TestUtil.assertOutput(OGG_EXTRACTOR_FACTORY, "ogg/bear_vorbis.ogg", getInstrumentation());
|
||||
}
|
||||
|
||||
public void testSniffVorbis() throws Exception {
|
||||
@ -80,7 +98,7 @@ public final class OggExtractorTest extends TestCase {
|
||||
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data)
|
||||
.setSimulateIOErrors(true).setSimulateUnknownLength(true).setSimulatePartialReads(true)
|
||||
.build();
|
||||
return TestUtil.sniffTestData(extractor, input);
|
||||
return TestUtil.sniffTestData(OGG_EXTRACTOR_FACTORY.create(), input);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.extractor.ogg;
|
||||
|
||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||
import com.google.android.exoplayer.testutil.TestUtil;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@ -25,9 +25,7 @@ import junit.framework.TestCase;
|
||||
public final class VorbisBitArrayTest extends TestCase {
|
||||
|
||||
public void testReadBit() {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0x5c, 0x50
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x5c, 0x50));
|
||||
|
||||
assertFalse(bitArray.readBit());
|
||||
assertFalse(bitArray.readBit());
|
||||
@ -56,9 +54,7 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testSkipBits() {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0xF0, 0x0F
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
||||
|
||||
bitArray.skipBits(10);
|
||||
assertEquals(10, bitArray.getPosition());
|
||||
@ -79,9 +75,7 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
|
||||
|
||||
public void testSkipBitsThrowsErrorIfEOB() {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0xF0, 0x0F
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
||||
|
||||
try {
|
||||
bitArray.skipBits(17);
|
||||
@ -90,9 +84,7 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testGetPosition() throws Exception {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0xF0, 0x0F
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
||||
|
||||
assertEquals(0, bitArray.getPosition());
|
||||
bitArray.readBit();
|
||||
@ -104,9 +96,7 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testSetPosition() throws Exception {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0xF0, 0x0F
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
||||
|
||||
assertEquals(0, bitArray.getPosition());
|
||||
bitArray.setPosition(4);
|
||||
@ -121,9 +111,7 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
|
||||
}
|
||||
public void testSetPositionIllegalPositions() throws Exception {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0xF0, 0x0F
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
||||
|
||||
try {
|
||||
bitArray.setPosition(16);
|
||||
@ -141,19 +129,14 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testReadInt32() {
|
||||
byte[] data = {(byte) 0xF0, 0x0F, (byte) 0xF0, 0x0F};
|
||||
VorbisBitArray lsb = new VorbisBitArray(data);
|
||||
assertEquals(0x0FF00FF0, lsb.readBits(32));
|
||||
|
||||
data = new byte[]{0x0F, (byte) 0xF0, 0x0F, (byte) 0xF0};
|
||||
lsb = new VorbisBitArray(data);
|
||||
assertEquals(0xF00FF00F, lsb.readBits(32));
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F, 0xF0, 0x0F));
|
||||
assertEquals(0x0FF00FF0, bitArray.readBits(32));
|
||||
bitArray = new VorbisBitArray(TestUtil.createByteArray(0x0F, 0xF0, 0x0F, 0xF0));
|
||||
assertEquals(0xF00FF00F, bitArray.readBits(32));
|
||||
}
|
||||
|
||||
public void testReadBits() throws Exception {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0x03, 0x22
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22));
|
||||
|
||||
assertEquals(3, bitArray.readBits(2));
|
||||
bitArray.skipBits(6);
|
||||
@ -166,18 +149,14 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testRead4BitsBeyondBoundary() throws Exception {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
0x2e, 0x10
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x2e, 0x10));
|
||||
assertEquals(0x2e, bitArray.readBits(7));
|
||||
assertEquals(7, bitArray.getPosition());
|
||||
assertEquals(0x0, bitArray.readBits(4));
|
||||
}
|
||||
|
||||
public void testReadBitsBeyondByteBoundaries() throws Exception {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0xFF, (byte) 0x0F, (byte) 0xFF, (byte) 0x0F
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xFF, 0x0F, 0xFF, 0x0F));
|
||||
|
||||
assertEquals(0x0FFF0FFF, bitArray.readBits(32));
|
||||
|
||||
@ -202,9 +181,7 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testReadBitsIllegalLengths() throws Exception {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0x03, 0x22, 0x30
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22, 0x30));
|
||||
|
||||
// reading zero bits gets 0 without advancing position
|
||||
// (like a zero-bit read is defined to yield zer0)
|
||||
@ -222,9 +199,7 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testLimit() {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0xc0, 0x02
|
||||
}, 1);
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xc0, 0x02), 1);
|
||||
|
||||
try {
|
||||
bitArray.skipBits(9);
|
||||
@ -240,7 +215,8 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
assertEquals(0, bitArray.getPosition());
|
||||
}
|
||||
|
||||
bitArray.readBits(8);
|
||||
int byteValue = bitArray.readBits(8);
|
||||
assertEquals(0xc0, byteValue);
|
||||
assertEquals(8, bitArray.getPosition());
|
||||
try {
|
||||
bitArray.readBit();
|
||||
@ -251,9 +227,8 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testBitsLeft() {
|
||||
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{
|
||||
(byte) 0xc0, 0x02
|
||||
});
|
||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xc0, 0x02));
|
||||
|
||||
assertEquals(16, bitArray.bitsLeft());
|
||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
||||
|
||||
@ -293,44 +268,4 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadBitCompareWithMSb() {
|
||||
byte[] data = {0x0F};
|
||||
VorbisBitArray lsb = new VorbisBitArray(data);
|
||||
ParsableBitArray msb = new ParsableBitArray(data);
|
||||
|
||||
assertEquals(lsb.readBit(), !msb.readBit());
|
||||
assertEquals(lsb.readBit(), !msb.readBit());
|
||||
assertEquals(lsb.readBit(), !msb.readBit());
|
||||
assertEquals(lsb.readBit(), !msb.readBit());
|
||||
assertEquals(lsb.readBit(), !msb.readBit());
|
||||
assertEquals(lsb.readBit(), !msb.readBit());
|
||||
assertEquals(lsb.readBit(), !msb.readBit());
|
||||
assertEquals(lsb.readBit(), !msb.readBit());
|
||||
}
|
||||
|
||||
public void testReadBitsCompareWithMSb() {
|
||||
byte[] data = {0x0F};
|
||||
VorbisBitArray lsb = new VorbisBitArray(data);
|
||||
ParsableBitArray msb = new ParsableBitArray(data);
|
||||
|
||||
assertEquals(15, lsb.readBits(4));
|
||||
assertEquals(lsb.readBits(4), msb.readBits(4));
|
||||
assertEquals(15, msb.readBits(4));
|
||||
}
|
||||
|
||||
public void testReadBitsCompareWithMSbBeyondByteBoundary() {
|
||||
byte[] data = {(byte) 0xF0, 0x0F};
|
||||
VorbisBitArray lsb = new VorbisBitArray(data);
|
||||
ParsableBitArray msb = new ParsableBitArray(data);
|
||||
|
||||
assertEquals(0x00, lsb.readBits(4));
|
||||
assertEquals(0x0F, msb.readBits(4));
|
||||
|
||||
assertEquals(0xFF, lsb.readBits(8));
|
||||
assertEquals(0x00, msb.readBits(8));
|
||||
|
||||
assertEquals(0x00, lsb.readBits(4));
|
||||
assertEquals(0x0F, msb.readBits(4));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.extractor.ogg;
|
||||
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.ogg.VorbisReader.VorbisSetup;
|
||||
import com.google.android.exoplayer.testutil.FakeExtractorInput;
|
||||
import com.google.android.exoplayer.testutil.FakeExtractorInput.SimulatedIOException;
|
||||
@ -29,14 +30,6 @@ import java.io.IOException;
|
||||
*/
|
||||
public final class VorbisReaderTest extends TestCase {
|
||||
|
||||
private VorbisReader extractor;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
extractor = new VorbisReader();
|
||||
}
|
||||
|
||||
public void testReadBits() throws Exception {
|
||||
assertEquals(0, VorbisReader.readBits((byte) 0x00, 2, 2));
|
||||
assertEquals(1, VorbisReader.readBits((byte) 0x02, 1, 1));
|
||||
@ -57,7 +50,11 @@ public final class VorbisReaderTest extends TestCase {
|
||||
|
||||
public void testReadSetupHeadersWithIOExceptions() throws IOException, InterruptedException {
|
||||
byte[] data = TestData.getVorbisHeaderPages();
|
||||
VorbisReader.VorbisSetup vorbisSetup = readSetupHeaders(createInput(data));
|
||||
ExtractorInput input = new FakeExtractorInput.Builder().setData(data).setSimulateIOErrors(true)
|
||||
.setSimulateUnknownLength(true).setSimulatePartialReads(true).build();
|
||||
|
||||
VorbisReader reader = new VorbisReader();
|
||||
VorbisReader.VorbisSetup vorbisSetup = readSetupHeaders(reader, input);
|
||||
|
||||
assertNotNull(vorbisSetup.idHeader);
|
||||
assertNotNull(vorbisSetup.commentHeader);
|
||||
@ -88,12 +85,7 @@ public final class VorbisReaderTest extends TestCase {
|
||||
assertTrue(vorbisSetup.modes[1].blockFlag);
|
||||
}
|
||||
|
||||
private static FakeExtractorInput createInput(byte[] data) {
|
||||
return new FakeExtractorInput.Builder().setData(data).setSimulateIOErrors(true)
|
||||
.setSimulateUnknownLength(true).setSimulatePartialReads(true).build();
|
||||
}
|
||||
|
||||
private VorbisSetup readSetupHeaders(FakeExtractorInput input)
|
||||
private static VorbisSetup readSetupHeaders(VorbisReader reader, ExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
OggPacket oggPacket = new OggPacket();
|
||||
while (true) {
|
||||
@ -101,7 +93,7 @@ public final class VorbisReaderTest extends TestCase {
|
||||
if (!oggPacket.populate(input)) {
|
||||
fail();
|
||||
}
|
||||
VorbisSetup vorbisSetup = extractor.readSetupHeaders(oggPacket.getPayload());
|
||||
VorbisSetup vorbisSetup = reader.readSetupHeaders(oggPacket.getPayload());
|
||||
if (vorbisSetup != null) {
|
||||
return vorbisSetup;
|
||||
}
|
||||
|
@ -90,13 +90,13 @@ public final class VorbisUtilTest extends TestCase {
|
||||
|
||||
public void testVerifyVorbisHeaderCapturePattern() throws ParserException {
|
||||
ParsableByteArray header = new ParsableByteArray(
|
||||
new byte[]{0x01, 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
new byte[] {0x01, 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
assertEquals(true, VorbisUtil.verifyVorbisHeaderCapturePattern(0x01, header, false));
|
||||
}
|
||||
|
||||
public void testVerifyVorbisHeaderCapturePatternInvalidHeader() {
|
||||
ParsableByteArray header = new ParsableByteArray(
|
||||
new byte[]{0x01, 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
new byte[] {0x01, 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
try {
|
||||
VorbisUtil.verifyVorbisHeaderCapturePattern(0x99, header, false);
|
||||
fail();
|
||||
@ -107,13 +107,13 @@ public final class VorbisUtilTest extends TestCase {
|
||||
|
||||
public void testVerifyVorbisHeaderCapturePatternInvalidHeaderQuite() throws ParserException {
|
||||
ParsableByteArray header = new ParsableByteArray(
|
||||
new byte[]{0x01, 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
new byte[] {0x01, 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
assertFalse(VorbisUtil.verifyVorbisHeaderCapturePattern(0x99, header, true));
|
||||
}
|
||||
|
||||
public void testVerifyVorbisHeaderCapturePatternInvalidPattern() {
|
||||
ParsableByteArray header = new ParsableByteArray(
|
||||
new byte[]{0x01, 'x', 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
new byte[] {0x01, 'x', 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
try {
|
||||
VorbisUtil.verifyVorbisHeaderCapturePattern(0x01, header, false);
|
||||
fail();
|
||||
@ -125,7 +125,7 @@ public final class VorbisUtilTest extends TestCase {
|
||||
public void testVerifyVorbisHeaderCapturePatternQuiteInvalidPatternQuite()
|
||||
throws ParserException {
|
||||
ParsableByteArray header = new ParsableByteArray(
|
||||
new byte[]{0x01, 'x', 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
new byte[] {0x01, 'x', 'v', 'o', 'r', 'b', 'i', 's'});
|
||||
assertFalse(VorbisUtil.verifyVorbisHeaderCapturePattern(0x01, header, true));
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user