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:
olly 2016-06-20 06:16:40 -07:00 committed by Oliver Woodman
parent 53cc88e2dc
commit 4c434005d4
9 changed files with 61 additions and 1971 deletions

View File

@ -24,7 +24,7 @@ import junit.framework.TestCase;
*/ */
public class ExtractorTest extends 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}. // Sanity check that constant values match those defined by {@link C}.
assertEquals(C.RESULT_END_OF_INPUT, Extractor.RESULT_END_OF_INPUT); assertEquals(C.RESULT_END_OF_INPUT, Extractor.RESULT_END_OF_INPUT);
// Sanity check that the other constant values don't overlap. // Sanity check that the other constant values don't overlap.

View File

@ -15,788 +15,16 @@
*/ */
package com.google.android.exoplayer.extractor.mkv; 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.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.testutil.TestUtil;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
import android.test.InstrumentationTestCase; 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}. * Tests for {@link MatroskaExtractor}.
*/ */
public final class MatroskaExtractorTest extends InstrumentationTestCase { 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 { public void testMkvSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
@Override @Override
@ -805,4 +33,5 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase {
} }
}, "mkv/sample.mkv", getInstrumentation()); }, "mkv/sample.mkv", getInstrumentation());
} }
} }

View File

@ -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));
}
}
}
}

View File

@ -15,488 +15,18 @@
*/ */
package com.google.android.exoplayer.extractor.mp4; 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.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.testutil.TestUtil;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/** /**
* Tests for {@link Mp4Extractor}. * Tests for {@link Mp4Extractor}.
*/ */
@TargetApi(16) @TargetApi(16)
public final class Mp4ExtractorTest extends InstrumentationTestCase { 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 { public void testMp4Sample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() { TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
@Override @Override

View File

@ -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());
}
}

View File

@ -15,24 +15,42 @@
*/ */
package com.google.android.exoplayer.extractor.ogg; 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.FakeExtractorInput;
import com.google.android.exoplayer.testutil.TestUtil; 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; import java.io.IOException;
/** /**
* Unit test for {@link OggExtractor}. * 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 @Override
public void setUp() throws Exception { public Extractor create() {
super.setUp(); return new OggExtractor();
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 { public void testSniffVorbis() throws Exception {
@ -80,7 +98,7 @@ public final class OggExtractorTest extends TestCase {
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data) FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data)
.setSimulateIOErrors(true).setSimulateUnknownLength(true).setSimulatePartialReads(true) .setSimulateIOErrors(true).setSimulateUnknownLength(true).setSimulatePartialReads(true)
.build(); .build();
return TestUtil.sniffTestData(extractor, input); return TestUtil.sniffTestData(OGG_EXTRACTOR_FACTORY.create(), input);
} }
} }

View File

@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer.extractor.ogg; 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; import junit.framework.TestCase;
@ -25,9 +25,7 @@ import junit.framework.TestCase;
public final class VorbisBitArrayTest extends TestCase { public final class VorbisBitArrayTest extends TestCase {
public void testReadBit() { public void testReadBit() {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x5c, 0x50));
(byte) 0x5c, 0x50
});
assertFalse(bitArray.readBit()); assertFalse(bitArray.readBit());
assertFalse(bitArray.readBit()); assertFalse(bitArray.readBit());
@ -56,9 +54,7 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testSkipBits() { public void testSkipBits() {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
(byte) 0xF0, 0x0F
});
bitArray.skipBits(10); bitArray.skipBits(10);
assertEquals(10, bitArray.getPosition()); assertEquals(10, bitArray.getPosition());
@ -79,9 +75,7 @@ public final class VorbisBitArrayTest extends TestCase {
public void testSkipBitsThrowsErrorIfEOB() { public void testSkipBitsThrowsErrorIfEOB() {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
(byte) 0xF0, 0x0F
});
try { try {
bitArray.skipBits(17); bitArray.skipBits(17);
@ -90,9 +84,7 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testGetPosition() throws Exception { public void testGetPosition() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
(byte) 0xF0, 0x0F
});
assertEquals(0, bitArray.getPosition()); assertEquals(0, bitArray.getPosition());
bitArray.readBit(); bitArray.readBit();
@ -104,9 +96,7 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testSetPosition() throws Exception { public void testSetPosition() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
(byte) 0xF0, 0x0F
});
assertEquals(0, bitArray.getPosition()); assertEquals(0, bitArray.getPosition());
bitArray.setPosition(4); bitArray.setPosition(4);
@ -121,9 +111,7 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testSetPositionIllegalPositions() throws Exception { public void testSetPositionIllegalPositions() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
(byte) 0xF0, 0x0F
});
try { try {
bitArray.setPosition(16); bitArray.setPosition(16);
@ -141,19 +129,14 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testReadInt32() { public void testReadInt32() {
byte[] data = {(byte) 0xF0, 0x0F, (byte) 0xF0, 0x0F}; VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F, 0xF0, 0x0F));
VorbisBitArray lsb = new VorbisBitArray(data); assertEquals(0x0FF00FF0, bitArray.readBits(32));
assertEquals(0x0FF00FF0, lsb.readBits(32)); bitArray = new VorbisBitArray(TestUtil.createByteArray(0x0F, 0xF0, 0x0F, 0xF0));
assertEquals(0xF00FF00F, bitArray.readBits(32));
data = new byte[]{0x0F, (byte) 0xF0, 0x0F, (byte) 0xF0};
lsb = new VorbisBitArray(data);
assertEquals(0xF00FF00F, lsb.readBits(32));
} }
public void testReadBits() throws Exception { public void testReadBits() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22));
(byte) 0x03, 0x22
});
assertEquals(3, bitArray.readBits(2)); assertEquals(3, bitArray.readBits(2));
bitArray.skipBits(6); bitArray.skipBits(6);
@ -166,18 +149,14 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testRead4BitsBeyondBoundary() throws Exception { public void testRead4BitsBeyondBoundary() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x2e, 0x10));
0x2e, 0x10
});
assertEquals(0x2e, bitArray.readBits(7)); assertEquals(0x2e, bitArray.readBits(7));
assertEquals(7, bitArray.getPosition()); assertEquals(7, bitArray.getPosition());
assertEquals(0x0, bitArray.readBits(4)); assertEquals(0x0, bitArray.readBits(4));
} }
public void testReadBitsBeyondByteBoundaries() throws Exception { public void testReadBitsBeyondByteBoundaries() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xFF, 0x0F, 0xFF, 0x0F));
(byte) 0xFF, (byte) 0x0F, (byte) 0xFF, (byte) 0x0F
});
assertEquals(0x0FFF0FFF, bitArray.readBits(32)); assertEquals(0x0FFF0FFF, bitArray.readBits(32));
@ -202,9 +181,7 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testReadBitsIllegalLengths() throws Exception { public void testReadBitsIllegalLengths() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22, 0x30));
(byte) 0x03, 0x22, 0x30
});
// reading zero bits gets 0 without advancing position // reading zero bits gets 0 without advancing position
// (like a zero-bit read is defined to yield zer0) // (like a zero-bit read is defined to yield zer0)
@ -222,9 +199,7 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testLimit() { public void testLimit() {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xc0, 0x02), 1);
(byte) 0xc0, 0x02
}, 1);
try { try {
bitArray.skipBits(9); bitArray.skipBits(9);
@ -240,7 +215,8 @@ public final class VorbisBitArrayTest extends TestCase {
assertEquals(0, bitArray.getPosition()); assertEquals(0, bitArray.getPosition());
} }
bitArray.readBits(8); int byteValue = bitArray.readBits(8);
assertEquals(0xc0, byteValue);
assertEquals(8, bitArray.getPosition()); assertEquals(8, bitArray.getPosition());
try { try {
bitArray.readBit(); bitArray.readBit();
@ -251,9 +227,8 @@ public final class VorbisBitArrayTest extends TestCase {
} }
public void testBitsLeft() { public void testBitsLeft() {
VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xc0, 0x02));
(byte) 0xc0, 0x02
});
assertEquals(16, bitArray.bitsLeft()); assertEquals(16, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + 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));
}
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.extractor.ogg; 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.extractor.ogg.VorbisReader.VorbisSetup;
import com.google.android.exoplayer.testutil.FakeExtractorInput; import com.google.android.exoplayer.testutil.FakeExtractorInput;
import com.google.android.exoplayer.testutil.FakeExtractorInput.SimulatedIOException; import com.google.android.exoplayer.testutil.FakeExtractorInput.SimulatedIOException;
@ -29,14 +30,6 @@ import java.io.IOException;
*/ */
public final class VorbisReaderTest extends TestCase { 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 { public void testReadBits() throws Exception {
assertEquals(0, VorbisReader.readBits((byte) 0x00, 2, 2)); assertEquals(0, VorbisReader.readBits((byte) 0x00, 2, 2));
assertEquals(1, VorbisReader.readBits((byte) 0x02, 1, 1)); assertEquals(1, VorbisReader.readBits((byte) 0x02, 1, 1));
@ -57,7 +50,11 @@ public final class VorbisReaderTest extends TestCase {
public void testReadSetupHeadersWithIOExceptions() throws IOException, InterruptedException { public void testReadSetupHeadersWithIOExceptions() throws IOException, InterruptedException {
byte[] data = TestData.getVorbisHeaderPages(); 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.idHeader);
assertNotNull(vorbisSetup.commentHeader); assertNotNull(vorbisSetup.commentHeader);
@ -88,12 +85,7 @@ public final class VorbisReaderTest extends TestCase {
assertTrue(vorbisSetup.modes[1].blockFlag); assertTrue(vorbisSetup.modes[1].blockFlag);
} }
private static FakeExtractorInput createInput(byte[] data) { private static VorbisSetup readSetupHeaders(VorbisReader reader, ExtractorInput input)
return new FakeExtractorInput.Builder().setData(data).setSimulateIOErrors(true)
.setSimulateUnknownLength(true).setSimulatePartialReads(true).build();
}
private VorbisSetup readSetupHeaders(FakeExtractorInput input)
throws IOException, InterruptedException { throws IOException, InterruptedException {
OggPacket oggPacket = new OggPacket(); OggPacket oggPacket = new OggPacket();
while (true) { while (true) {
@ -101,7 +93,7 @@ public final class VorbisReaderTest extends TestCase {
if (!oggPacket.populate(input)) { if (!oggPacket.populate(input)) {
fail(); fail();
} }
VorbisSetup vorbisSetup = extractor.readSetupHeaders(oggPacket.getPayload()); VorbisSetup vorbisSetup = reader.readSetupHeaders(oggPacket.getPayload());
if (vorbisSetup != null) { if (vorbisSetup != null) {
return vorbisSetup; return vorbisSetup;
} }