Add initial MediaParser tests
Bug: 147324074
Bug: 147308781
Test: Not applicable.
Change-Id: Ia77749122dc74f31d30abb9af03330e0ae50d334
diff --git a/tests/tests/mediaparser/Android.bp b/tests/tests/mediaparser/Android.bp
index 08b646c..8b3697c 100644
--- a/tests/tests/mediaparser/Android.bp
+++ b/tests/tests/mediaparser/Android.bp
@@ -18,6 +18,7 @@
static_libs: [
"ctstestrunner-axt",
"androidx.test.ext.junit",
+ "exoplayer2-extractor-test-utils",
// TODO: Remove the assets folder once b/146655310 is fixed.
// Current assets version in the asset folder from ExoPlayer SHA
// 2fc426214f5cfdde2e15a9f08c397a961fa0e249.
diff --git a/tests/tests/mediaparser/AndroidTest.xml b/tests/tests/mediaparser/AndroidTest.xml
index ea39d58..905e7e4 100644
--- a/tests/tests/mediaparser/AndroidTest.xml
+++ b/tests/tests/mediaparser/AndroidTest.xml
@@ -26,8 +26,8 @@
<option name="package" value="android.media.mediaparser.cts" />
<!-- setup can be expensive so limit the number of shards -->
<option name="ajur-max-shard" value="5" />
- <!-- test-timeout unit is ms, value = 30 min -->
- <option name="test-timeout" value="10000" />
- <option name="runtime-hint" value="10m" />
+ <!-- test-timeout unit is ms -->
+ <option name="test-timeout" value="30000" />
+ <option name="runtime-hint" value="30ms" />
</test>
</configuration>
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
index b3f59d1..818ce18 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
@@ -16,10 +16,361 @@
package android.media.mediaparser.cts;
-import junit.framework.TestCase;
+import static com.google.common.truth.Truth.assertThat;
-public class MediaParserTest extends TestCase {
+import android.media.MediaParser;
- // TODO: Implement tests.
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.android.exoplayer2.testutil.FakeExtractorInput;
+import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
+import com.google.android.exoplayer2.testutil.TestUtil;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaParserTest {
+
+ // OGG.
+
+ @Test
+ public void testBearVorbisOgg() throws IOException, InterruptedException {
+ testExtractAsset("ogg/bear_vorbis.ogg");
+ }
+
+ @Test
+ public void testBearOgg() throws IOException, InterruptedException {
+ testExtractAsset("ogg/bear.opus");
+ }
+
+ @Test
+ public void testBearFlacOgg() throws IOException, InterruptedException {
+ testExtractAsset("ogg/bear_flac.ogg");
+ }
+
+ @Test
+ public void testNoFlacSeekTableOgg() throws IOException, InterruptedException {
+ testExtractAsset("ogg/bear_flac_noseektable.ogg");
+ }
+
+ @Test
+ public void testFlacHeaderOggSniff() throws IOException, InterruptedException {
+ testSniffAsset("ogg/flac_header", /* expectedExtractorName= */ "exo.OggExtractor");
+ }
+
+ @Test
+ public void testOpusHeaderOggSniff() throws IOException, InterruptedException {
+ try {
+ testSniffAsset("ogg/opus_header", /* expectedExtractorName= */ "exo.OggExtractor");
+ Assert.fail();
+ } catch (MediaParser.UnrecognizedInputFormatException e) {
+ // Expected.
+ }
+ }
+
+ @Test
+ public void testInvalidHeaderOggSniff() throws IOException, InterruptedException {
+ try {
+ testSniffAsset(
+ "ogg/invalid_ogg_header", /* expectedExtractorName= */ "exo.OggExtractor");
+ Assert.fail();
+ } catch (MediaParser.UnrecognizedInputFormatException e) {
+ // Expected.
+ }
+ }
+
+ @Test
+ public void testInvalidHeaderSniff() throws IOException, InterruptedException {
+ try {
+ testSniffAsset("ogg/invalid_header", /* expectedExtractorName= */ "exo.OggExtractor");
+ Assert.fail();
+ } catch (MediaParser.UnrecognizedInputFormatException e) {
+ // Expected.
+ }
+ }
+
+ // FLAC.
+
+ @Test
+ public void testBearUncommonSampleRateFlac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_uncommon_sample_rate.flac");
+ }
+
+ @Test
+ public void testBearNoSeekTableAndNoNumSamplesFlac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_no_seek_table_no_num_samples.flac");
+ }
+
+ @Test
+ public void testBearWithPictureFlac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_with_picture.flac");
+ }
+
+ @Test
+ public void testBearWithVorbisCommentsFlac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_with_vorbis_comments.flac");
+ }
+
+ @Test
+ public void testOneMetadataBlockFlac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_one_metadata_block.flac");
+ }
+
+ @Test
+ public void testBearNoMinMaxFrameSizeFlac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_no_min_max_frame_size.flac");
+ }
+
+ @Test
+ public void testNoNumSamplesFlac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_no_num_samples.flac");
+ }
+
+ @Test
+ public void testBearNoId3Flac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_with_id3_disabled.flac");
+ }
+
+ @Test
+ public void testBearWithId3Flac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear_with_id3_enabled.flac");
+ }
+
+ @Test
+ public void testBearFlac() throws IOException, InterruptedException {
+ testExtractAsset("flac/bear.flac");
+ }
+
+ // MP3.
+
+ @Test
+ public void testTrimmedMp3() throws IOException, InterruptedException {
+ testExtractAsset("mp3/play-trimmed.mp3");
+ }
+
+ @Test
+ public void testBearMp3() throws IOException, InterruptedException {
+ testExtractAsset("mp3/bear.mp3");
+ }
+
+ // WAV.
+
+ @Test
+ public void testWavWithImaAdpcm() throws IOException, InterruptedException {
+ testExtractAsset("wav/sample_ima_adpcm.wav");
+ }
+
+ @Test
+ public void testWav() throws IOException, InterruptedException {
+ testExtractAsset("wav/sample.wav");
+ }
+
+ // AMR.
+
+ @Test
+ public void testNarrowBandSamplesWithConstantBitrateSeeking()
+ throws IOException, InterruptedException {
+ testExtractAsset("amr/sample_nb_cbr.amr");
+ }
+
+ @Test
+ public void testNarrowBandSamples() throws IOException, InterruptedException {
+ testExtractAsset("amr/sample_nb.amr");
+ }
+
+ @Test
+ public void testWideBandSamples() throws IOException, InterruptedException {
+ testExtractAsset("amr/sample_wb.amr");
+ }
+
+ @Test
+ public void testWideBandSamplesWithConstantBitrateSeeking()
+ throws IOException, InterruptedException {
+ testExtractAsset("amr/sample_wb_cbr.amr");
+ }
+
+ // FLV.
+
+ @Test
+ public void testFlv() throws IOException, InterruptedException {
+ testExtractAsset("flv/sample.flv");
+ }
+
+ // PS.
+
+ // TODO: Enable once the timeout is fixed.
+ @Test
+ @Ignore
+ public void testElphantsDreamPs() throws IOException, InterruptedException {
+ testExtractAsset("ts/elephants_dream.mpg");
+ }
+
+ @Test
+ public void testProgramStream() throws IOException, InterruptedException {
+ testExtractAsset("ts/sample.ps");
+ }
+
+ // ADTS.
+
+ @Test
+ public void testTruncatedAdtsWithConstantBitrateSeeking()
+ throws IOException, InterruptedException {
+ testExtractAsset("ts/sample_cbs_truncated.adts");
+ }
+
+ @Test
+ public void testAdts() throws IOException, InterruptedException {
+ testExtractAsset("ts/sample.adts");
+ }
+
+ @Test
+ public void testAdtsWithConstantBitrateSeeking() throws IOException, InterruptedException {
+ testExtractAsset("ts/sample_cbs.adts");
+ }
+
+ // AC-3.
+
+ @Test
+ public void testAc3() throws IOException, InterruptedException {
+ testExtractAsset("ts/sample.ac3");
+ }
+
+ // AC-4.
+
+ @Test
+ public void testAc4() throws IOException, InterruptedException {
+ testExtractAsset("ts/sample.ac4");
+ }
+
+ // EAC-3.
+
+ @Test
+ public void testEac3() throws IOException, InterruptedException {
+ testExtractAsset("ts/sample.eac3");
+ }
+
+ // TS.
+
+ @Test
+ public void testBigBuckBunnyTs() throws IOException, InterruptedException {
+ testExtractAsset("ts/bbb_2500ms.ts");
+ }
+
+ @Test
+ public void testTransportStream() throws IOException, InterruptedException {
+ testExtractAsset("ts/sample.ts");
+ }
+
+ @Test
+ public void testTransportStreamWithSdt() throws IOException, InterruptedException {
+ testExtractAsset("ts/sample_with_sdt.ts");
+ }
+
+ // MKV.
+
+ @Test
+ public void testSubsampleEncryptedNoAltref() throws IOException, InterruptedException {
+ testExtractAsset("mkv/subsample_encrypted_noaltref.webm");
+ }
+
+ @Test
+ public void testMatroskaFile() throws IOException, InterruptedException {
+ testExtractAsset("mkv/sample.mkv");
+ }
+
+ @Test
+ public void testFullBlocks() throws IOException, InterruptedException {
+ testExtractAsset("mkv/full_blocks.mkv");
+ }
+
+ @Test
+ public void testSubsampleEncryptedAltref() throws IOException, InterruptedException {
+ testExtractAsset("mkv/subsample_encrypted_altref.webm");
+ }
+
+ // MP4.
+
+ @Test
+ public void testAc4Fragmented() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample_ac4_fragmented.mp4");
+ }
+
+ @Test
+ public void testAndrdoidSlowMotion() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample_android_slow_motion.mp4");
+ }
+
+ @Test
+ public void testFragmentedSei() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample_fragmented_sei.mp4");
+ }
+
+ @Test
+ public void testMp4WithAc4() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample_ac4.mp4");
+ }
+
+ @Test
+ public void testFragmentedSeekable() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample_fragmented_seekable.mp4");
+ }
+
+ @Test
+ public void testAc4Protected() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample_ac4_protected.mp4");
+ }
+
+ @Test
+ public void testMp4() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample.mp4");
+ }
+
+ @Test
+ public void testMdatTooLong() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample_mdat_too_long.mp4");
+ }
+
+ @Test
+ public void testFragmented() throws IOException, InterruptedException {
+ testExtractAsset("mp4/sample_fragmented.mp4");
+ }
+
+ private static void testSniffAsset(String assetPath, String expectedExtractorName)
+ throws IOException, InterruptedException {
+ extractAsset(assetPath, expectedExtractorName);
+ }
+
+ private static void testExtractAsset(String assetPath)
+ throws IOException, InterruptedException {
+ extractAsset(assetPath, /* expectedExtractorName= */ null);
+ }
+
+ private static void extractAsset(String assetPath, String expectedExtractorName)
+ throws IOException, InterruptedException {
+ byte[] assetBytes =
+ TestUtil.getByteArray(
+ InstrumentationRegistry.getInstrumentation().getContext(), assetPath);
+ MockMediaParserInputReader mockInput =
+ new MockMediaParserInputReader(
+ new FakeExtractorInput.Builder().setData(assetBytes).build());
+ MediaParser mediaParser =
+ MediaParser.create(new MockMediaParserOutputConsumer(new FakeExtractorOutput()));
+
+ mediaParser.advance(mockInput);
+ if (expectedExtractorName != null) {
+ assertThat(expectedExtractorName).isEqualTo(mediaParser.getExtractorName());
+ // We are only checking that the extractor is the right one.
+ return;
+ }
+
+ while (mediaParser.advance(mockInput)) {
+ // Do nothing.
+ }
+ }
}
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserInputReader.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserInputReader.java
new file mode 100644
index 0000000..d7fe90d
--- /dev/null
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserInputReader.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 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 android.media.mediaparser.cts;
+
+import android.media.MediaParser;
+
+import com.google.android.exoplayer2.testutil.FakeExtractorInput;
+
+import java.io.IOException;
+
+public class MockMediaParserInputReader implements MediaParser.SeekableInputReader {
+
+ private final FakeExtractorInput mFakeExtractorInput;
+
+ public MockMediaParserInputReader(FakeExtractorInput fakeExtractorInput) throws IOException {
+ mFakeExtractorInput = fakeExtractorInput;
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int readLength)
+ throws IOException, InterruptedException {
+ return mFakeExtractorInput.read(buffer, offset, readLength);
+ }
+
+ @Override
+ public long getPosition() {
+ return mFakeExtractorInput.getPosition();
+ }
+
+ @Override
+ public long getLength() {
+ return mFakeExtractorInput.getLength();
+ }
+
+ @Override
+ public void seekToPosition(long position) {
+ mFakeExtractorInput.setPosition((int) position);
+ }
+}
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
new file mode 100644
index 0000000..1bef85e
--- /dev/null
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2020 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 android.media.mediaparser.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaParser;
+import android.util.Pair;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.ExtractorInput;
+import com.google.android.exoplayer2.extractor.SeekMap;
+import com.google.android.exoplayer2.extractor.SeekPoint;
+import com.google.android.exoplayer2.extractor.TrackOutput;
+import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class MockMediaParserOutputConsumer implements MediaParser.OutputConsumer {
+
+ private final FakeExtractorOutput mFakeExtractorOutput;
+ private final ArrayList<TrackOutput> mTrackOutputs;
+
+ public MockMediaParserOutputConsumer(FakeExtractorOutput fakeExtractorOutput) {
+ mFakeExtractorOutput = fakeExtractorOutput;
+ mTrackOutputs = new ArrayList<>();
+ }
+
+ @Override
+ public void onSeekMap(MediaParser.SeekMap seekMap) {
+ mFakeExtractorOutput.seekMap(
+ new SeekMap() {
+ @Override
+ public boolean isSeekable() {
+ return seekMap.isSeekable();
+ }
+
+ @Override
+ public long getDurationUs() {
+ return seekMap.getDurationUs();
+ }
+
+ @Override
+ public SeekPoints getSeekPoints(long timeUs) {
+ return toExoPlayerSeekPoints(seekMap.getSeekPoints(timeUs));
+ }
+ });
+ }
+
+ private static SeekMap.SeekPoints toExoPlayerSeekPoints(
+ Pair<MediaParser.SeekPoint, MediaParser.SeekPoint> seekPoints) {
+ return new SeekMap.SeekPoints(
+ toExoPlayerSeekPoint(seekPoints.first), toExoPlayerSeekPoint(seekPoints.second));
+ }
+
+ private static SeekPoint toExoPlayerSeekPoint(MediaParser.SeekPoint seekPoint) {
+ return new SeekPoint(seekPoint.timeUs, seekPoint.position);
+ }
+
+ @Override
+ public void onTracksFound(int numberOfTracks) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTrackData(int trackIndex, MediaParser.TrackData trackData) {
+ while (mTrackOutputs.size() < trackIndex) {
+ mTrackOutputs.add(mFakeExtractorOutput.track(trackIndex, C.TRACK_TYPE_UNKNOWN));
+ }
+ }
+
+ @Override
+ public void onSampleData(int trackIndex, MediaParser.InputReader inputReader)
+ throws IOException, InterruptedException {
+ mFakeExtractorOutput
+ .track(trackIndex, C.TRACK_TYPE_UNKNOWN)
+ .sampleData(
+ new ExtractorInputAdapter(inputReader),
+ (int) inputReader.getLength(),
+ false);
+ }
+
+ @Override
+ public void onSampleCompleted(
+ int trackIndex,
+ long timeUs,
+ int flags,
+ int size,
+ int offset,
+ MediaCodec.CryptoInfo cryptoData) {}
+
+ private class ExtractorInputAdapter implements ExtractorInput {
+
+ private final MediaParser.InputReader mInputReader;
+
+ private ExtractorInputAdapter(MediaParser.InputReader inputReader) {
+ mInputReader = inputReader;
+ }
+
+ @Override
+ public int read(byte[] target, int offset, int length)
+ throws IOException, InterruptedException {
+ return mInputReader.read(target, offset, length);
+ }
+
+ @Override
+ public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
+ throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void readFully(byte[] target, int offset, int length)
+ throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int skip(int length) throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean skipFully(int length, boolean allowEndOfInput)
+ throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void skipFully(int length) throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int peek(byte[] target, int offset, int length)
+ throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
+ throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void peekFully(byte[] target, int offset, int length)
+ throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean advancePeekPosition(int length, boolean allowEndOfInput)
+ throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void advancePeekPosition(int length) throws IOException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resetPeekPosition() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getPeekPosition() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getPosition() {
+ return mInputReader.getPosition();
+ }
+
+ @Override
+ public long getLength() {
+ return mInputReader.getLength();
+ }
+
+ @Override
+ public <E extends Throwable> void setRetryPosition(long position, E e) throws E {
+ throw new UnsupportedOperationException();
+ }
+ }
+}