blob: 216a7befc02b77e9963f4b33a9db52fdc3f20e22 [file] [log] [blame]
/*
* Copyright (C) 2019 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.mediav2.cts;
import static android.mediav2.common.cts.CodecTestBase.hasDecoder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaDataSource;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.mediav2.common.cts.CodecDecoderTestBase;
import android.mediav2.common.cts.CodecTestBase;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.util.Log;
import android.webkit.cts.CtsTestServer;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.Preconditions;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.zip.CRC32;
class TestMediaDataSource extends MediaDataSource {
private static final String LOG_TAG = TestMediaDataSource.class.getSimpleName();
private static final boolean ENABLE_LOGS = false;
private byte[] mData;
private boolean mFatalGetSize;
private boolean mFatalReadAt;
private boolean mIsClosed = false;
static TestMediaDataSource fromString(String inpPath, boolean failSize, boolean failRead)
throws IOException {
try (FileInputStream fInp = new FileInputStream(inpPath)) {
int size = (int) new File(inpPath).length();
byte[] data = new byte[size];
fInp.read(data, 0, size);
return new TestMediaDataSource(data, failSize, failRead);
}
}
private TestMediaDataSource(byte[] data, boolean fatalGetSize, boolean fatalReadAt) {
mData = data;
mFatalGetSize = fatalGetSize;
mFatalReadAt = fatalReadAt;
}
@Override
public synchronized int readAt(long srcOffset, byte[] buffer, int dstOffset, int size)
throws IOException {
if (mFatalReadAt) {
throw new IOException("malformed media data source");
}
if (srcOffset >= mData.length) {
return -1;
}
if (srcOffset + size > mData.length) {
size = mData.length - (int) srcOffset;
}
System.arraycopy(mData, (int) srcOffset, buffer, dstOffset, size);
return size;
}
@Override
public synchronized long getSize() throws IOException {
if (mFatalGetSize) {
throw new IOException("malformed media data source");
}
if (ENABLE_LOGS) {
Log.v(LOG_TAG, "getSize: " + mData.length);
}
return mData.length;
}
@Override
public synchronized void close() {
mIsClosed = true;
}
public boolean isClosed() {
return mIsClosed;
}
}
@RunWith(Enclosed.class)
public class ExtractorTest {
private static final String LOG_TAG = ExtractorTest.class.getSimpleName();
private static final boolean ENABLE_LOGS = false;
private static final int MAX_SAMPLE_SIZE = 4 * 1024 * 1024;
private static final String EXT_SEL_KEY = "ext-sel";
static private final List<String> codecListforTypeMp4 =
Arrays.asList(MediaFormat.MIMETYPE_AUDIO_MPEG, MediaFormat.MIMETYPE_AUDIO_AAC,
MediaFormat.MIMETYPE_AUDIO_FLAC, MediaFormat.MIMETYPE_AUDIO_VORBIS,
MediaFormat.MIMETYPE_AUDIO_OPUS, MediaFormat.MIMETYPE_VIDEO_MPEG2,
MediaFormat.MIMETYPE_VIDEO_MPEG4, MediaFormat.MIMETYPE_VIDEO_H263,
MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_VIDEO_HEVC);
static private final List<String> codecListforTypeWebm =
Arrays.asList(MediaFormat.MIMETYPE_AUDIO_VORBIS, MediaFormat.MIMETYPE_AUDIO_OPUS,
MediaFormat.MIMETYPE_VIDEO_VP8, MediaFormat.MIMETYPE_VIDEO_VP9);
static private final List<String> codecListforType3gp =
Arrays.asList(MediaFormat.MIMETYPE_AUDIO_AAC, MediaFormat.MIMETYPE_AUDIO_AMR_NB,
MediaFormat.MIMETYPE_AUDIO_AMR_WB, MediaFormat.MIMETYPE_VIDEO_MPEG4,
MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_VIDEO_AVC);
static private final List<String> codecListforTypeMkv =
Arrays.asList(MediaFormat.MIMETYPE_AUDIO_MPEG, MediaFormat.MIMETYPE_AUDIO_AAC,
MediaFormat.MIMETYPE_AUDIO_FLAC, MediaFormat.MIMETYPE_AUDIO_VORBIS,
MediaFormat.MIMETYPE_AUDIO_OPUS, MediaFormat.MIMETYPE_VIDEO_MPEG2,
MediaFormat.MIMETYPE_VIDEO_MPEG4, MediaFormat.MIMETYPE_VIDEO_H263,
MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_VIDEO_HEVC,
MediaFormat.MIMETYPE_VIDEO_VP8, MediaFormat.MIMETYPE_VIDEO_VP9);
static private final List<String> codecListforTypeOgg =
Arrays.asList(MediaFormat.MIMETYPE_AUDIO_VORBIS, MediaFormat.MIMETYPE_AUDIO_OPUS);
static private final List<String> codecListforTypeTs =
Arrays.asList(MediaFormat.MIMETYPE_AUDIO_AAC, MediaFormat.MIMETYPE_VIDEO_MPEG2,
MediaFormat.MIMETYPE_VIDEO_AVC);
static private final List<String> codecListforTypePs =
Arrays.asList(MediaFormat.MIMETYPE_VIDEO_MPEG2);
static private final List<String> codecListforTypeRaw =
Arrays.asList(MediaFormat.MIMETYPE_AUDIO_AAC, MediaFormat.MIMETYPE_AUDIO_FLAC,
MediaFormat.MIMETYPE_AUDIO_MPEG, MediaFormat.MIMETYPE_AUDIO_AMR_NB,
MediaFormat.MIMETYPE_AUDIO_AMR_WB, MediaFormat.MIMETYPE_AUDIO_RAW);
static private final List<String> codecListforTypeWav =
Arrays.asList(MediaFormat.MIMETYPE_AUDIO_RAW, MediaFormat.MIMETYPE_AUDIO_G711_ALAW,
MediaFormat.MIMETYPE_AUDIO_G711_MLAW, MediaFormat.MIMETYPE_AUDIO_MSGSM);
// List of codecs that are not required to be supported as per CDD but are tested
static private final List<String> codecListSupp =
Arrays.asList(MediaFormat.MIMETYPE_VIDEO_AV1, MediaFormat.MIMETYPE_AUDIO_AC3,
MediaFormat.MIMETYPE_AUDIO_AC4, MediaFormat.MIMETYPE_AUDIO_EAC3);
private static final String MEDIA_DIR = WorkDir.getMediaDirString();
private static String extSel;
static {
android.os.Bundle args = InstrumentationRegistry.getArguments();
final String defSel = "mp4;webm;3gp;mkv;ogg;supp;raw;ts;ps;wav";
extSel = (null == args.getString(EXT_SEL_KEY)) ? defSel : args.getString(EXT_SEL_KEY);
}
private static boolean shouldRunTest(String mediaType) {
boolean result = false;
if ((extSel.contains("mp4") && codecListforTypeMp4.contains(mediaType))
|| (extSel.contains("webm") && codecListforTypeWebm.contains(mediaType))
|| (extSel.contains("3gp") && codecListforType3gp.contains(mediaType))
|| (extSel.contains("mkv") && codecListforTypeMkv.contains(mediaType))
|| (extSel.contains("ogg") && codecListforTypeOgg.contains(mediaType))
|| (extSel.contains("ts") && codecListforTypeTs.contains(mediaType))
|| (extSel.contains("ps") && codecListforTypePs.contains(mediaType))
|| (extSel.contains("raw") && codecListforTypeRaw.contains(mediaType))
|| (extSel.contains("wav") && codecListforTypeWav.contains(mediaType))
|| (extSel.contains("supp") && codecListSupp.contains(mediaType))) {
result = true;
}
return result;
}
private static boolean isExtractorOKonEOS(MediaExtractor extractor) {
return extractor.getSampleTrackIndex() < 0 && extractor.getSampleSize() < 0 &&
extractor.getSampleFlags() < 0 && extractor.getSampleTime() < 0;
}
private static boolean isSampleInfoIdentical(MediaCodec.BufferInfo refSample,
MediaCodec.BufferInfo testSample) {
return refSample.flags == testSample.flags && refSample.size == testSample.size &&
refSample.presentationTimeUs == testSample.presentationTimeUs;
}
private static boolean isSampleInfoValidAndIdentical(MediaCodec.BufferInfo refSample,
MediaCodec.BufferInfo testSample) {
return refSample.flags == testSample.flags && refSample.size == testSample.size &&
Math.abs(refSample.presentationTimeUs - testSample.presentationTimeUs) <= 1 &&
refSample.flags >= 0 && refSample.size >= 0 && refSample.presentationTimeUs >= 0;
}
static boolean isCSDIdentical(MediaFormat refFormat, MediaFormat testFormat) {
String mediaType = refFormat.getString(MediaFormat.KEY_MIME);
for (int i = 0; ; i++) {
String csdKey = "csd-" + i;
boolean refHasCSD = refFormat.containsKey(csdKey);
boolean testHasCSD = testFormat.containsKey(csdKey);
if (refHasCSD != testHasCSD) {
if (ENABLE_LOGS) {
Log.w(LOG_TAG, "error, ref fmt has CSD: " + refHasCSD + " test fmt has CSD: " +
testHasCSD);
}
return false;
}
if (refHasCSD) {
Log.v(LOG_TAG, mediaType + " has " + csdKey);
ByteBuffer r = refFormat.getByteBuffer(csdKey);
ByteBuffer t = testFormat.getByteBuffer(csdKey);
if (!r.equals(t)) {
if (ENABLE_LOGS) {
Log.w(LOG_TAG, "ref CSD and test CSD buffers are not identical");
}
return false;
}
} else break;
}
return true;
}
static boolean isFormatSimilar(MediaFormat refFormat, MediaFormat testFormat) {
String refMediaType = refFormat.getString(MediaFormat.KEY_MIME);
String testMediaType = testFormat.getString(MediaFormat.KEY_MIME);
if (!refMediaType.equals(testMediaType)) return false;
if (refFormat.getLong(MediaFormat.KEY_DURATION) !=
testFormat.getLong(MediaFormat.KEY_DURATION)) {
Log.w(LOG_TAG, "Duration mismatches ref / test = " +
refFormat.getLong(MediaFormat.KEY_DURATION) + " / " +
testFormat.getLong(MediaFormat.KEY_DURATION));
// TODO (b/163477410)(b/163478168)
// return false;
}
if (!isCSDIdentical(refFormat, testFormat)) return false;
if (refMediaType.startsWith("audio/")) {
if (refFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) !=
testFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)) return false;
if (refFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) !=
testFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)) return false;
} else if (refMediaType.startsWith("video/")) {
if (refFormat.getInteger(MediaFormat.KEY_WIDTH) !=
testFormat.getInteger(MediaFormat.KEY_WIDTH)) return false;
if (refFormat.getInteger(MediaFormat.KEY_HEIGHT) !=
testFormat.getInteger(MediaFormat.KEY_HEIGHT)) return false;
}
return true;
}
private static boolean isMediaSimilar(MediaExtractor refExtractor, MediaExtractor testExtractor,
String mediaType, int sampleLimit) {
ByteBuffer refBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
ByteBuffer testBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
int noOfTracksMatched = 0;
for (int refTrackID = 0; refTrackID < refExtractor.getTrackCount(); refTrackID++) {
MediaFormat refFormat = refExtractor.getTrackFormat(refTrackID);
String refMediaType = refFormat.getString(MediaFormat.KEY_MIME);
if (mediaType != null && !refMediaType.equals(mediaType)) {
continue;
}
for (int testTrackID = 0; testTrackID < testExtractor.getTrackCount(); testTrackID++) {
MediaFormat testFormat = testExtractor.getTrackFormat(testTrackID);
if (!isFormatSimilar(refFormat, testFormat)) {
continue;
}
refExtractor.selectTrack(refTrackID);
testExtractor.selectTrack(testTrackID);
MediaCodec.BufferInfo refSampleInfo = new MediaCodec.BufferInfo();
MediaCodec.BufferInfo testSampleInfo = new MediaCodec.BufferInfo();
boolean areTracksIdentical = true;
for (int frameCount = 0; ; frameCount++) {
refSampleInfo.set(0, (int) refExtractor.getSampleSize(),
refExtractor.getSampleTime(), refExtractor.getSampleFlags());
testSampleInfo.set(0, (int) testExtractor.getSampleSize(),
testExtractor.getSampleTime(), testExtractor.getSampleFlags());
if (!isSampleInfoValidAndIdentical(refSampleInfo, testSampleInfo)) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG,
" Mediatype: " + refMediaType + " mismatch for sample: "
+ frameCount);
Log.d(LOG_TAG, " flags exp/got: " + refSampleInfo.flags + '/'
+ testSampleInfo.flags);
Log.d(LOG_TAG, " size exp/got: " + refSampleInfo.size + '/'
+ testSampleInfo.size);
Log.d(LOG_TAG, " ts exp/got: " + refSampleInfo.presentationTimeUs
+ '/' + testSampleInfo.presentationTimeUs);
}
areTracksIdentical = false;
break;
}
int refSz = refExtractor.readSampleData(refBuffer, 0);
if (refSz != refSampleInfo.size) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "Mediatype: " + refMediaType + " Size exp/got: "
+ refSampleInfo.size + '/' + refSz);
}
areTracksIdentical = false;
break;
}
int testSz = testExtractor.readSampleData(testBuffer, 0);
if (testSz != testSampleInfo.size) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "Mediatype: " + refMediaType + " Size exp/got: "
+ testSampleInfo.size + '/' + testSz);
}
areTracksIdentical = false;
break;
}
int trackIndex = refExtractor.getSampleTrackIndex();
if (trackIndex != refTrackID) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "Mediatype: " + refMediaType + " TrackID exp/got: "
+ refTrackID + '/' + trackIndex);
}
areTracksIdentical = false;
break;
}
trackIndex = testExtractor.getSampleTrackIndex();
if (trackIndex != testTrackID) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "Mediatype: " + refMediaType + " TrackID exp/got: "
+ testTrackID + '/' + trackIndex);
}
areTracksIdentical = false;
break;
}
if (!testBuffer.equals(refBuffer)) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG,
"Mediatype: " + refMediaType + " sample data is not identical");
}
areTracksIdentical = false;
break;
}
boolean haveRefSamples = refExtractor.advance();
boolean haveTestSamples = testExtractor.advance();
if (haveRefSamples != haveTestSamples) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "Mediatype: " + refMediaType + " Mismatch "
+ "in sampleCount");
}
areTracksIdentical = false;
break;
}
if (!haveRefSamples && !isExtractorOKonEOS(refExtractor)) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG,
"Mediatype: " + refMediaType
+ " calls post advance() are not OK");
}
areTracksIdentical = false;
break;
}
if (!haveTestSamples && !isExtractorOKonEOS(testExtractor)) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG,
"Mediatype: " + refMediaType
+ " calls post advance() are not OK");
}
areTracksIdentical = false;
break;
}
if (ENABLE_LOGS) {
Log.v(LOG_TAG, "Mediatype: " + refMediaType + " Sample: " + frameCount
+ " flags: " + refSampleInfo.flags + " size: " + refSampleInfo.size
+ " ts: " + refSampleInfo.presentationTimeUs);
}
if (!haveRefSamples || frameCount >= sampleLimit) {
break;
}
}
testExtractor.unselectTrack(testTrackID);
refExtractor.unselectTrack(refTrackID);
if (areTracksIdentical) {
noOfTracksMatched++;
break;
}
}
if (mediaType != null && noOfTracksMatched > 0) break;
}
if (mediaType == null) {
return noOfTracksMatched == refExtractor.getTrackCount();
} else {
return noOfTracksMatched > 0;
}
}
private static long readAllData(MediaExtractor extractor, String mediaType, int sampleLimit) {
CRC32 checksum = new CRC32();
ByteBuffer buffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
int tracksSelected = 0;
for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) {
MediaFormat format = extractor.getTrackFormat(trackID);
String srcMediaType = format.getString(MediaFormat.KEY_MIME);
if (mediaType != null && !srcMediaType.equals(mediaType)) {
continue;
}
extractor.selectTrack(trackID);
tracksSelected++;
if (srcMediaType.startsWith("audio/")) {
buffer.putInt(0);
buffer.putInt(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
buffer.putInt(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
} else if (srcMediaType.startsWith("video/")) {
buffer.putInt(1);
buffer.putInt(format.getInteger(MediaFormat.KEY_WIDTH));
buffer.putInt(format.getInteger(MediaFormat.KEY_HEIGHT));
} else {
buffer.putInt(2);
}
buffer.putLong(format.getLong(MediaFormat.KEY_DURATION));
for (int i = 0; ; i++) {
String csdKey = "csd-" + i;
if (format.containsKey(csdKey)) {
checksum.update(format.getByteBuffer(csdKey));
} else break;
}
}
assertTrue(tracksSelected > 0);
buffer.flip();
checksum.update(buffer);
MediaCodec.BufferInfo sampleInfo = new MediaCodec.BufferInfo();
for (int sampleCount = 0; sampleCount < sampleLimit; sampleCount++) {
sampleInfo.set(0, (int) extractor.getSampleSize(), extractor.getSampleTime(),
extractor.getSampleFlags());
extractor.readSampleData(buffer, 0);
checksum.update(buffer);
assertEquals(sampleInfo.size, buffer.limit());
assertTrue(sampleInfo.flags != -1);
assertTrue(sampleInfo.presentationTimeUs != -1);
buffer.position(0);
buffer.putInt(sampleInfo.size)
.putInt(sampleInfo.flags)
.putLong(sampleInfo.presentationTimeUs);
buffer.flip();
checksum.update(buffer);
sampleCount++;
if (!extractor.advance()) {
assertTrue(isExtractorOKonEOS(extractor));
break;
}
}
for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) {
extractor.unselectTrack(trackID);
}
return checksum.getValue();
}
private static native long nativeReadAllData(String srcPath, String mediaType, int sampleLimit,
String[] keys, String[] values, boolean isSrcUrl);
/**
* Tests setDataSource(...) Api by observing the extractor behavior after its successful
* instantiation using a media stream.
*/
@SmallTest
public static class SetDataSourceTest {
@Rule
public TestName testName = new TestName();
private static final String INPUT_MEDIA = "ForBiggerEscapes.mp4";
private static final String RES_STRING = "raw/forbiggerescapes";
private CtsTestServer mWebServer;
private String mInpMediaUrl;
private MediaExtractor mRefExtractor;
static {
System.loadLibrary("ctsmediav2extractor_jni");
}
@Before
public void setUp() throws IOException {
mRefExtractor = new MediaExtractor();
Preconditions.assertTestFileExists(MEDIA_DIR + INPUT_MEDIA);
mRefExtractor.setDataSource(MEDIA_DIR + INPUT_MEDIA);
try {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
mWebServer = new CtsTestServer(context);
mInpMediaUrl = mWebServer.getAssetUrl(RES_STRING);
} catch (Exception e) {
fail(e.getMessage());
}
}
@After
public void tearDown() {
mRefExtractor.release();
mRefExtractor = null;
mWebServer.shutdown();
}
private static boolean areMetricsIdentical(MediaExtractor refExtractor,
MediaExtractor testExtractor) {
PersistableBundle bundle = refExtractor.getMetrics();
int refNumTracks = bundle.getInt(MediaExtractor.MetricsConstants.TRACKS);
String refFormat = bundle.getString(MediaExtractor.MetricsConstants.FORMAT);
String refMediaType = bundle.getString(MediaExtractor.MetricsConstants.MIME_TYPE);
bundle = testExtractor.getMetrics();
int testNumTracks = bundle.getInt(MediaExtractor.MetricsConstants.TRACKS);
String testFormat = bundle.getString(MediaExtractor.MetricsConstants.FORMAT);
String testMediaType = bundle.getString(MediaExtractor.MetricsConstants.MIME_TYPE);
boolean result = testNumTracks == refNumTracks && testFormat.equals(refFormat) &&
testMediaType.equals(refMediaType);
if (ENABLE_LOGS) {
Log.d(LOG_TAG, " NumTracks exp/got: " + refNumTracks + '/' + testNumTracks);
Log.d(LOG_TAG, " Format exp/got: " + refFormat + '/' + testFormat);
Log.d(LOG_TAG, " Mediatype exp/got: " + refMediaType + '/' + testMediaType);
}
return result;
}
private static boolean isSeekOk(MediaExtractor refExtractor, MediaExtractor testExtractor) {
final long maxEstDuration = 14000000;
final int MAX_SEEK_POINTS = 7;
final long mSeed = 0x12b9b0a1;
final Random randNum = new Random(mSeed);
MediaCodec.BufferInfo refSampleInfo = new MediaCodec.BufferInfo();
MediaCodec.BufferInfo testSampleInfo = new MediaCodec.BufferInfo();
boolean result = true;
for (int trackID = 0; trackID < refExtractor.getTrackCount() && result; trackID++) {
refExtractor.selectTrack(trackID);
testExtractor.selectTrack(trackID);
for (int i = 0; i < MAX_SEEK_POINTS && result; i++) {
long pts = (long) (randNum.nextDouble() * maxEstDuration);
for (int mode = MediaExtractor.SEEK_TO_PREVIOUS_SYNC;
mode <= MediaExtractor.SEEK_TO_CLOSEST_SYNC; mode++) {
refExtractor.seekTo(pts, mode);
testExtractor.seekTo(pts, mode);
refSampleInfo.set(0, (int) refExtractor.getSampleSize(),
refExtractor.getSampleTime(), refExtractor.getSampleFlags());
testSampleInfo.set(0, (int) testExtractor.getSampleSize(),
testExtractor.getSampleTime(), testExtractor.getSampleFlags());
result = isSampleInfoIdentical(refSampleInfo, testSampleInfo);
int refTrackIdx = refExtractor.getSampleTrackIndex();
int testTrackIdx = testExtractor.getSampleTrackIndex();
result &= (refTrackIdx == testTrackIdx);
if (ENABLE_LOGS) {
Log.d(LOG_TAG, " mode/pts/trackId:" + mode + "/" + pts + "/" + trackID);
Log.d(LOG_TAG, " trackId exp/got: " + refTrackIdx + '/' + testTrackIdx);
Log.d(LOG_TAG, " flags exp/got: " +
refSampleInfo.flags + '/' + testSampleInfo.flags);
Log.d(LOG_TAG, " size exp/got: " +
refSampleInfo.size + '/' + testSampleInfo.size);
Log.d(LOG_TAG, " ts exp/got: " + refSampleInfo.presentationTimeUs +
'/' + testSampleInfo.presentationTimeUs);
}
}
}
refExtractor.unselectTrack(trackID);
testExtractor.unselectTrack(trackID);
}
return result;
}
@Test
public void testAssetFD() throws IOException {
Preconditions.assertTestFileExists(MEDIA_DIR + INPUT_MEDIA);
File inpFile = new File(MEDIA_DIR + INPUT_MEDIA);
MediaExtractor testExtractor = new MediaExtractor();
try (ParcelFileDescriptor parcelFD = ParcelFileDescriptor
.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0,
AssetFileDescriptor.UNKNOWN_LENGTH)) {
testExtractor.setDataSource(afd);
}
assertTrue(testExtractor.getCachedDuration() < 0);
if (!isMediaSimilar(mRefExtractor, testExtractor, null, Integer.MAX_VALUE) ||
!areMetricsIdentical(mRefExtractor, testExtractor) ||
!isSeekOk(mRefExtractor, testExtractor)) {
fail("setDataSource failed: " + testName.getMethodName());
}
testExtractor.release();
}
@Test
public void testFileDescriptor() throws IOException {
Preconditions.assertTestFileExists(MEDIA_DIR + INPUT_MEDIA);
File inpFile = new File(MEDIA_DIR + INPUT_MEDIA);
MediaExtractor testExtractor = new MediaExtractor();
try (FileInputStream fInp = new FileInputStream(inpFile)) {
testExtractor.setDataSource(fInp.getFD());
}
assertTrue(testExtractor.getCachedDuration() < 0);
if (!isMediaSimilar(mRefExtractor, testExtractor, null, Integer.MAX_VALUE) ||
!areMetricsIdentical(mRefExtractor, testExtractor) ||
!isSeekOk(mRefExtractor, testExtractor)) {
fail("setDataSource failed: " + testName.getMethodName());
}
long sdkChecksum = readAllData(testExtractor, null, Integer.MAX_VALUE);
long ndkChecksum = nativeReadAllData(MEDIA_DIR + INPUT_MEDIA, "",
Integer.MAX_VALUE, null, null, false);
testExtractor.release();
assertEquals("SDK and NDK checksums mismatch", sdkChecksum, ndkChecksum);
}
@Test
public void testFileDescriptorLenOffset() throws IOException {
Preconditions.assertTestFileExists(MEDIA_DIR + INPUT_MEDIA);
File inpFile = new File(MEDIA_DIR + INPUT_MEDIA);
File outFile = File.createTempFile("temp", ".out");
byte[] garbageAppend = "PrefixGarbage".getBytes();
try (FileInputStream fInp = new FileInputStream(inpFile);
FileOutputStream fOut = new FileOutputStream(outFile)) {
fOut.write(garbageAppend);
byte[] data = new byte[(int) new File(inpFile.toString()).length()];
if (fInp.read(data) == -1) {
fail("Failed to read input file");
}
fOut.write(data);
fOut.write(garbageAppend);
}
MediaExtractor testExtractor = new MediaExtractor();
try (FileInputStream fInp = new FileInputStream(outFile)) {
testExtractor.setDataSource(fInp.getFD(), garbageAppend.length,
inpFile.length());
}
assertTrue(testExtractor.getCachedDuration() < 0);
if (!isMediaSimilar(mRefExtractor, testExtractor, null, Integer.MAX_VALUE) ||
!areMetricsIdentical(mRefExtractor, testExtractor) ||
!isSeekOk(mRefExtractor, testExtractor)) {
fail("setDataSource failed: " + testName.getMethodName());
}
testExtractor.release();
outFile.delete();
}
@Test
public void testMediaDataSource() throws Exception {
Preconditions.assertTestFileExists(MEDIA_DIR + INPUT_MEDIA);
TestMediaDataSource dataSource =
TestMediaDataSource.fromString(MEDIA_DIR + INPUT_MEDIA, false, false);
MediaExtractor testExtractor = new MediaExtractor();
testExtractor.setDataSource(dataSource);
assertTrue(testExtractor.getCachedDuration() < 0);
if (!isMediaSimilar(mRefExtractor, testExtractor, null, Integer.MAX_VALUE) ||
!areMetricsIdentical(mRefExtractor, testExtractor) ||
!isSeekOk(mRefExtractor, testExtractor)) {
fail("setDataSource failed: " + testName.getMethodName());
}
testExtractor.release();
assertTrue(dataSource.isClosed());
}
@Test
public void testContextUri() throws IOException {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
String path = "android.resource://android.mediav2.cts/" + RES_STRING;
MediaExtractor testExtractor = new MediaExtractor();
testExtractor.setDataSource(context, Uri.parse(path), null);
assertTrue(testExtractor.getCachedDuration() < 0);
if (!isMediaSimilar(mRefExtractor, testExtractor, null, Integer.MAX_VALUE) ||
!areMetricsIdentical(mRefExtractor, testExtractor) ||
!isSeekOk(mRefExtractor, testExtractor)) {
fail("setDataSource failed: " + testName.getMethodName());
}
testExtractor.release();
}
private void checkExtractorOkForUrlDS(Map<String, String> headers) throws Exception {
MediaExtractor testExtractor = new MediaExtractor();
testExtractor.setDataSource(mInpMediaUrl, headers);
HttpRequest req = mWebServer.getLastRequest(RES_STRING);
if (headers != null) {
for (String key : headers.keySet()) {
String value = headers.get(key);
Header[] header = req.getHeaders(key);
assertTrue(
"expecting " + key + ":" + value + ", saw " + Arrays.toString(header),
header.length == 1 && header[0].getValue().equals(value));
}
}
if (!isMediaSimilar(mRefExtractor, testExtractor, null, Integer.MAX_VALUE) ||
!areMetricsIdentical(mRefExtractor, testExtractor) ||
!isSeekOk(mRefExtractor, testExtractor)) {
fail("setDataSource failed: " + testName.getMethodName());
}
testExtractor.selectTrack(0);
for (int idx = 0; ; idx++) {
if ((idx & (idx - 1)) == 0) {
long cachedDuration = testExtractor.getCachedDuration();
if (ENABLE_LOGS) {
Log.v(LOG_TAG, "cachedDuration at frame: " + idx + " is:" + cachedDuration);
}
assertTrue("cached duration should be non-negative", cachedDuration >= 0);
}
if (!testExtractor.advance()) break;
}
assertTrue(testExtractor.hasCacheReachedEndOfStream());
testExtractor.unselectTrack(0);
testExtractor.release();
}
@Test
public void testUrlDataSource() throws Exception {
checkExtractorOkForUrlDS(null);
Map<String, String> headers = new HashMap<>();
checkExtractorOkForUrlDS(headers);
String[] keys = new String[]{"From", "Client", "Location"};
String[] values = new String[]{"alcor@bigdipper.asm", "CtsTestServer", "UrsaMajor"};
for (int i = 0; i < keys.length; i++) {
headers.put(keys[i], values[i]);
}
checkExtractorOkForUrlDS(headers);
MediaExtractor testExtractor = new MediaExtractor();
testExtractor.setDataSource(mInpMediaUrl, headers);
long sdkChecksum = readAllData(testExtractor, null, Integer.MAX_VALUE);
testExtractor.release();
long ndkChecksum = nativeReadAllData(mInpMediaUrl, "", Integer.MAX_VALUE, keys,
values, true);
assertEquals("SDK and NDK checksums mismatch", sdkChecksum, ndkChecksum);
ndkChecksum = nativeReadAllData(mInpMediaUrl, "", Integer.MAX_VALUE, new String[0],
new String[0], true);
assertEquals("SDK and NDK checksums mismatch", sdkChecksum, ndkChecksum);
}
private native boolean nativeTestDataSource(String srcPath, String srcUrl);
@Test
public void testDataSourceNative() {
Preconditions.assertTestFileExists(MEDIA_DIR + INPUT_MEDIA);
assertTrue(testName.getMethodName() + " failed ",
nativeTestDataSource(MEDIA_DIR + INPUT_MEDIA, mInpMediaUrl));
}
}
/**
* Encloses extractor functionality tests
*/
@RunWith(Parameterized.class)
public static class FunctionalityTest {
private static final int MAX_SEEK_POINTS = 7;
private static final long SEED = 0x12b9b0a1;
private final Random mRandNum = new Random(SEED);
private String[] mSrcFiles;
private String mMediaType;
static {
System.loadLibrary("ctsmediav2extractor_jni");
}
@Rule
public TestName testName = new TestName();
@Parameterized.Parameters(name = "{index}({0})")
public static Collection<Object[]> input() {
return Arrays.asList(new Object[][]{
{MediaFormat.MIMETYPE_VIDEO_MPEG2, new String[]{
"bbb_cif_768kbps_30fps_mpeg2_stereo_48kHz_192kbps_mp3.mp4",
"bbb_cif_768kbps_30fps_mpeg2.mkv",
/* TODO(b/162919907)
"bbb_cif_768kbps_30fps_mpeg2.vob",*/
/* TODO(b/162715861)
"bbb_cif_768kbps_30fps_mpeg2.ts" */}},
{MediaFormat.MIMETYPE_VIDEO_H263, new String[]{
"bbb_cif_768kbps_30fps_h263.mp4",
"bbb_cif_768kbps_30fps_h263_stereo_48kHz_192kbps_flac.mkv",
"bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp",}},
{MediaFormat.MIMETYPE_VIDEO_MPEG4, new String[]{
"bbb_cif_768kbps_30fps_mpeg4.mkv",
"bbb_cif_768kbps_30fps_mpeg4_stereo_48kHz_192kbps_flac.mp4",
"bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp",}},
{MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
"bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_vorbis.mp4",
"bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_aac.mkv",
"bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_aac.3gp",
/* TODO(b/162715861)
"bbb_cif_768kbps_30fps_avc.ts",*/}},
{MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{
"bbb_cif_768kbps_30fps_hevc_stereo_48kHz_192kbps_opus.mp4",
"bbb_cif_768kbps_30fps_hevc_stereo_48kHz_192kbps_mp3.mkv",}},
{MediaFormat.MIMETYPE_VIDEO_VP8, new String[]{
"bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm",
"bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.mkv"}},
{MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{
"bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm",
"bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.mkv",}},
{MediaFormat.MIMETYPE_VIDEO_AV1, new String[]{
"bbb_cif_768kbps_30fps_av1.mp4",
"bbb_cif_768kbps_30fps_av1.webm",
"bbb_cif_768kbps_30fps_av1.mkv",}},
{MediaFormat.MIMETYPE_AUDIO_VORBIS, new String[]{
"bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_vorbis.mp4",
"bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.mkv",
"bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm",
"bbb_stereo_48kHz_192kbps_vorbis.ogg",}},
{MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
"bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm",
"bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.mkv",
"bbb_cif_768kbps_30fps_hevc_stereo_48kHz_192kbps_opus.mp4",
"bbb_stereo_48kHz_192kbps_opus.ogg",}},
{MediaFormat.MIMETYPE_AUDIO_MPEG, new String[]{
"bbb_stereo_48kHz_192kbps_mp3.mp3",
"bbb_cif_768kbps_30fps_mpeg2_stereo_48kHz_192kbps_mp3.mp4",
"bbb_cif_768kbps_30fps_hevc_stereo_48kHz_192kbps_mp3.mkv",}},
{MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
"bbb_stereo_48kHz_192kbps_aac.mp4",
"bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_aac.3gp",
"bbb_cif_768kbps_30fps_avc_stereo_48kHz_192kbps_aac.mkv",
"bbb_stereo_48kHz_128kbps_aac.ts",}},
{MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
"bbb_cif_768kbps_30fps_h263_mono_8kHz_12kbps_amrnb.3gp",
"bbb_mono_8kHz_12kbps_amrnb.amr",}},
{MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
"bbb_cif_768kbps_30fps_mpeg4_mono_16kHz_20kbps_amrwb.3gp",
"bbb_mono_16kHz_20kbps_amrwb.amr"}},
{MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{
"bbb_cif_768kbps_30fps_mpeg4_stereo_48kHz_192kbps_flac.mp4",
"bbb_cif_768kbps_30fps_h263_stereo_48kHz_192kbps_flac.mkv",}},
{MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"canon.mid",}},
{MediaFormat.MIMETYPE_AUDIO_AC3, new String[]{
"testac3mp4.mp4", "testac3ts.ts",}},
{MediaFormat.MIMETYPE_AUDIO_AC4, new String[]{"multi0.mp4",}},
{MediaFormat.MIMETYPE_AUDIO_EAC3, new String[]{
"testeac3mp4.mp4", "testeac3ts.ts",}},
{MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"bbb_1ch_16kHz.wav",}},
{MediaFormat.MIMETYPE_AUDIO_G711_ALAW, new String[]{"bbb_2ch_8kHz_alaw.wav",}},
{MediaFormat.MIMETYPE_AUDIO_G711_MLAW, new String[]{"bbb_2ch_8kHz_mulaw.wav",}},
{MediaFormat.MIMETYPE_AUDIO_MSGSM, new String[]{"bbb_1ch_8kHz_gsm.wav",}},
});
}
private native boolean nativeTestExtract(String srcPath, String refPath, String mediaType);
private native boolean nativeTestSeek(String srcPath, String mediaType);
private native boolean nativeTestSeekFlakiness(String srcPath, String mediaType);
private native boolean nativeTestSeekToZero(String srcPath, String mediaType);
private native boolean nativeTestFileFormat(String srcPath);
public FunctionalityTest(String mediaType, String[] srcFiles) {
mMediaType = mediaType;
mSrcFiles = srcFiles;
}
// content necessary for testing seek are grouped in this class
private class SeekTestParams {
MediaCodec.BufferInfo mExpected;
long mTimeStamp;
int mMode;
SeekTestParams(MediaCodec.BufferInfo expected, long timeStamp, int mode) {
mExpected = expected;
mTimeStamp = timeStamp;
mMode = mode;
}
}
private ArrayList<MediaCodec.BufferInfo> getSeekablePoints(String srcFile, String mediaType)
throws IOException {
ArrayList<MediaCodec.BufferInfo> bookmarks = null;
if (mediaType == null) return null;
MediaExtractor extractor = new MediaExtractor();
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
extractor.setDataSource(MEDIA_DIR + srcFile);
for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) {
MediaFormat format = extractor.getTrackFormat(trackID);
if (!mediaType.equals(format.getString(MediaFormat.KEY_MIME))) continue;
extractor.selectTrack(trackID);
bookmarks = new ArrayList<>();
do {
int sampleFlags = extractor.getSampleFlags();
if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
MediaCodec.BufferInfo sampleInfo = new MediaCodec.BufferInfo();
sampleInfo.set(0, (int) extractor.getSampleSize(),
extractor.getSampleTime(), extractor.getSampleFlags());
bookmarks.add(sampleInfo);
}
} while (extractor.advance());
extractor.unselectTrack(trackID);
break;
}
extractor.release();
return bookmarks;
}
private ArrayList<SeekTestParams> generateSeekTestArgs(String srcFile, String mediaType,
boolean isRandom) throws IOException {
ArrayList<SeekTestParams> testArgs = new ArrayList<>();
if (mediaType == null) return null;
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
if (isRandom) {
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(MEDIA_DIR + srcFile);
final long maxEstDuration = 4000000;
for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) {
MediaFormat format = extractor.getTrackFormat(trackID);
if (!mediaType.equals(format.getString(MediaFormat.KEY_MIME))) continue;
extractor.selectTrack(trackID);
for (int i = 0; i < MAX_SEEK_POINTS; i++) {
long pts = (long) (mRandNum.nextDouble() * maxEstDuration);
for (int mode = MediaExtractor.SEEK_TO_PREVIOUS_SYNC;
mode <= MediaExtractor.SEEK_TO_CLOSEST_SYNC; mode++) {
MediaCodec.BufferInfo currInfo = new MediaCodec.BufferInfo();
extractor.seekTo(pts, mode);
currInfo.set(0, (int) extractor.getSampleSize(),
extractor.getSampleTime(), extractor.getSampleFlags());
testArgs.add(new SeekTestParams(currInfo, pts, mode));
}
}
extractor.unselectTrack(trackID);
break;
}
extractor.release();
} else {
ArrayList<MediaCodec.BufferInfo> bookmarks = getSeekablePoints(srcFile, mediaType);
if (bookmarks == null) return null;
int size = bookmarks.size();
int[] indices;
if (size > MAX_SEEK_POINTS) {
indices = new int[MAX_SEEK_POINTS];
indices[0] = 0;
indices[MAX_SEEK_POINTS - 1] = size - 1;
for (int i = 1; i < MAX_SEEK_POINTS - 1; i++) {
indices[i] = (int) (mRandNum.nextDouble() * (MAX_SEEK_POINTS - 1) + 1);
}
} else {
indices = new int[size];
for (int i = 0; i < size; i++) indices[i] = i;
}
// closest sync : Seek to the sync sample CLOSEST to the specified time
// previous sync : Seek to a sync sample AT or AFTER the specified time
// next sync : Seek to a sync sample AT or BEFORE the specified time
for (int i : indices) {
MediaCodec.BufferInfo currInfo = bookmarks.get(i);
long pts = currInfo.presentationTimeUs;
testArgs.add(
new SeekTestParams(currInfo, pts, MediaExtractor.SEEK_TO_CLOSEST_SYNC));
testArgs.add(
new SeekTestParams(currInfo, pts, MediaExtractor.SEEK_TO_NEXT_SYNC));
testArgs.add(
new SeekTestParams(currInfo, pts,
MediaExtractor.SEEK_TO_PREVIOUS_SYNC));
if (i > 0) {
MediaCodec.BufferInfo prevInfo = bookmarks.get(i - 1);
long ptsMinus = prevInfo.presentationTimeUs;
ptsMinus = pts - ((pts - ptsMinus) >> 3);
testArgs.add(new SeekTestParams(currInfo, ptsMinus,
MediaExtractor.SEEK_TO_CLOSEST_SYNC));
testArgs.add(new SeekTestParams(currInfo, ptsMinus,
MediaExtractor.SEEK_TO_NEXT_SYNC));
testArgs.add(new SeekTestParams(prevInfo, ptsMinus,
MediaExtractor.SEEK_TO_PREVIOUS_SYNC));
}
if (i < size - 1) {
MediaCodec.BufferInfo nextInfo = bookmarks.get(i + 1);
long ptsPlus = nextInfo.presentationTimeUs;
ptsPlus = pts + ((ptsPlus - pts) >> 3);
testArgs.add(new SeekTestParams(currInfo, ptsPlus,
MediaExtractor.SEEK_TO_CLOSEST_SYNC));
testArgs.add(new SeekTestParams(nextInfo, ptsPlus,
MediaExtractor.SEEK_TO_NEXT_SYNC));
testArgs.add(new SeekTestParams(currInfo, ptsPlus,
MediaExtractor.SEEK_TO_PREVIOUS_SYNC));
}
}
}
return testArgs;
}
int checkSeekPoints(String srcFile, String mediaType,
ArrayList<SeekTestParams> seekTestArgs) throws IOException {
int errCnt = 0;
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(MEDIA_DIR + srcFile);
for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) {
MediaFormat format = extractor.getTrackFormat(trackID);
if (!format.getString(MediaFormat.KEY_MIME).equals(mediaType)) continue;
extractor.selectTrack(trackID);
MediaCodec.BufferInfo received = new MediaCodec.BufferInfo();
for (SeekTestParams arg : seekTestArgs) {
extractor.seekTo(arg.mTimeStamp, arg.mMode);
received.set(0, (int) extractor.getSampleSize(), extractor.getSampleTime(),
extractor.getSampleFlags());
if (!isSampleInfoIdentical(arg.mExpected, received)) {
errCnt++;
if (ENABLE_LOGS) {
Log.d(LOG_TAG, " flags exp/got: " + arg.mExpected.flags + '/' +
received.flags);
Log.d(LOG_TAG,
" size exp/got: " + arg.mExpected.size + '/' + received.size);
Log.d(LOG_TAG,
" ts exp/got: " + arg.mExpected.presentationTimeUs + '/' +
received.presentationTimeUs);
}
}
}
extractor.unselectTrack(trackID);
break;
}
extractor.release();
return errCnt;
}
private boolean isFileSeekable(String srcFile) throws IOException {
MediaExtractor ext = new MediaExtractor();
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
ext.setDataSource(MEDIA_DIR + srcFile);
String format = ext.getMetrics().getString(MediaExtractor.MetricsConstants.FORMAT);
ext.release();
// MPEG2TS and MPEG2PS files are non-seekable
return !(format.equalsIgnoreCase("MPEG2TSExtractor") ||
format.equalsIgnoreCase("MPEG2PSExtractor"));
}
/**
* Audio, Video codecs support a variety of file-types/container formats. For example,
* Vorbis supports OGG, MP4, WEBM and MKV. H.263 supports 3GPP, WEBM and MKV. For every
* mediaType, a list of test vectors are provided one for each container) but underlying
* elementary stream is the same for all. The streams of a mediaType are extracted and
* compared with each other for similarity.
*/
@LargeTest
@Test
public void testExtract() throws IOException {
assumeTrue(shouldRunTest(mMediaType));
Preconditions.assertTestFileExists(MEDIA_DIR + mSrcFiles[0]);
MediaExtractor refExtractor = new MediaExtractor();
refExtractor.setDataSource(MEDIA_DIR + mSrcFiles[0]);
long sdkChecksum = readAllData(refExtractor, mMediaType, Integer.MAX_VALUE);
long ndkChecksum = nativeReadAllData(MEDIA_DIR + mSrcFiles[0], mMediaType,
Integer.MAX_VALUE, null, null, false);
assertEquals("SDK and NDK checksums mismatch", sdkChecksum, ndkChecksum);
if (mSrcFiles.length == 1) {
refExtractor.release();
return;
}
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_OPUS));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_MPEG));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC));
boolean isOk = true;
for (int i = 1; i < mSrcFiles.length && isOk; i++) {
MediaExtractor testExtractor = new MediaExtractor();
Preconditions.assertTestFileExists(MEDIA_DIR + mSrcFiles[i]);
testExtractor.setDataSource(MEDIA_DIR + mSrcFiles[i]);
if (!isMediaSimilar(refExtractor, testExtractor, mMediaType, Integer.MAX_VALUE)) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "Files: " + mSrcFiles[0] + ", " + mSrcFiles[i] +
" are different from extractor perspective");
}
if (!codecListSupp.contains(mMediaType)) {
isOk = false;
}
}
testExtractor.release();
}
refExtractor.release();
assertTrue(testName.getMethodName() + " failed for Mediatype: " + mMediaType, isOk);
}
/**
* Tests seek functionality, verifies if we seek to most accurate point for a given
* choice of timestamp and mode.
*/
@LargeTest
@Test
@Ignore("TODO(b/146420831)")
public void testSeek() throws IOException {
assumeTrue(shouldRunTest(mMediaType)
&& !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_RAW));
boolean isOk = true;
for (String srcFile : mSrcFiles) {
if (!isFileSeekable(srcFile)) continue;
ArrayList<SeekTestParams> seekTestArgs =
generateSeekTestArgs(srcFile, mMediaType, false);
assertTrue("mediaType is null.", seekTestArgs != null);
assertTrue("No sync samples found.", !seekTestArgs.isEmpty());
Collections.shuffle(seekTestArgs, mRandNum);
int seekAccErrCnt = checkSeekPoints(srcFile, mMediaType, seekTestArgs);
if (seekAccErrCnt != 0) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "For " + srcFile + " seek chose inaccurate Sync point in: " +
seekAccErrCnt + "/" + seekTestArgs.size());
}
if (!codecListSupp.contains(mMediaType)) {
isOk = false;
break;
}
}
}
assertTrue(testName.getMethodName() + " failed for mediaType: " + mMediaType, isOk);
}
/**
* Tests if we get the same content each time after a call to seekto;
*/
@LargeTest
@Test
public void testSeekFlakiness() throws IOException {
assumeTrue(shouldRunTest(mMediaType));
boolean isOk = true;
for (String srcFile : mSrcFiles) {
if (!isFileSeekable(srcFile)) continue;
ArrayList<SeekTestParams> seekTestArgs =
generateSeekTestArgs(srcFile, mMediaType, true);
assertTrue("mediaType is null.", seekTestArgs != null);
assertTrue("No samples found.", !seekTestArgs.isEmpty());
Collections.shuffle(seekTestArgs, mRandNum);
int flakyErrCnt = checkSeekPoints(srcFile, mMediaType, seekTestArgs);
if (flakyErrCnt != 0) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG,
"No. of Samples where seek showed flakiness is: " + flakyErrCnt);
}
if (!codecListSupp.contains(mMediaType)) {
isOk = false;
break;
}
}
}
assertTrue(testName.getMethodName() + " failed for Mediatype: " + mMediaType, isOk);
}
/**
* Test if seekTo(0) yields the same content as if we had just opened the file and started
* reading.
*/
@SmallTest
@Test
public void testSeekToZero() throws IOException {
assumeTrue(shouldRunTest(mMediaType));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_MPEG));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC));
boolean isOk = true;
for (String srcFile : mSrcFiles) {
if (!isFileSeekable(srcFile)) continue;
MediaExtractor extractor = new MediaExtractor();
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
extractor.setDataSource(MEDIA_DIR + srcFile);
MediaCodec.BufferInfo sampleInfoAtZero = new MediaCodec.BufferInfo();
MediaCodec.BufferInfo currInfo = new MediaCodec.BufferInfo();
final long randomSeekPts = 1 << 20;
for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) {
MediaFormat format = extractor.getTrackFormat(trackID);
if (!mMediaType.equals(format.getString(MediaFormat.KEY_MIME))) continue;
extractor.selectTrack(trackID);
sampleInfoAtZero.set(0, (int) extractor.getSampleSize(),
extractor.getSampleTime(), extractor.getSampleFlags());
extractor.seekTo(randomSeekPts, MediaExtractor.SEEK_TO_NEXT_SYNC);
extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
currInfo.set(0, (int) extractor.getSampleSize(),
extractor.getSampleTime(), extractor.getSampleFlags());
if (!isSampleInfoIdentical(sampleInfoAtZero, currInfo)) {
if (!codecListSupp.contains(mMediaType)) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "seen mismatch seekTo(0, SEEK_TO_CLOSEST_SYNC)");
Log.d(LOG_TAG, " flags exp/got: " + sampleInfoAtZero.flags + '/' +
currInfo.flags);
Log.d(LOG_TAG, " size exp/got: " + sampleInfoAtZero.size + '/' +
currInfo.size);
Log.d(LOG_TAG,
" ts exp/got: " + sampleInfoAtZero.presentationTimeUs +
'/' + currInfo.presentationTimeUs);
}
isOk = false;
break;
}
}
extractor.seekTo(-1L, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
currInfo.set(0, (int) extractor.getSampleSize(),
extractor.getSampleTime(), extractor.getSampleFlags());
if (!isSampleInfoIdentical(sampleInfoAtZero, currInfo)) {
if (!codecListSupp.contains(mMediaType)) {
if (ENABLE_LOGS) {
Log.d(LOG_TAG, "seen mismatch seekTo(-1, SEEK_TO_CLOSEST_SYNC)");
Log.d(LOG_TAG, " flags exp/got: " + sampleInfoAtZero.flags + '/' +
currInfo.flags);
Log.d(LOG_TAG, " size exp/got: " + sampleInfoAtZero.size + '/' +
currInfo.size);
Log.d(LOG_TAG,
" ts exp/got: " + sampleInfoAtZero.presentationTimeUs +
'/' + currInfo.presentationTimeUs);
}
isOk = false;
break;
}
}
extractor.unselectTrack(trackID);
}
extractor.release();
}
assertTrue(testName.getMethodName() + " failed for Mediatype: " + mMediaType, isOk);
}
@SmallTest
@Test
public void testMetrics() throws IOException {
assumeTrue(shouldRunTest(mMediaType));
for (String srcFile : mSrcFiles) {
MediaExtractor extractor = new MediaExtractor();
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
extractor.setDataSource(MEDIA_DIR + srcFile);
PersistableBundle bundle = extractor.getMetrics();
int numTracks = bundle.getInt(MediaExtractor.MetricsConstants.TRACKS);
String format = bundle.getString(MediaExtractor.MetricsConstants.FORMAT);
String mediaType = bundle.getString(MediaExtractor.MetricsConstants.MIME_TYPE);
assertTrue(numTracks == extractor.getTrackCount() && format != null &&
mediaType != null);
extractor.release();
}
}
@LargeTest
@Test
public void testExtractNative() {
assumeTrue(shouldRunTest(mMediaType));
if (mSrcFiles.length == 1) return;
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_OPUS));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_MPEG));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC));
boolean isOk = true;
Preconditions.assertTestFileExists(MEDIA_DIR + mSrcFiles[0]);
for (int i = 1; i < mSrcFiles.length; i++) {
Preconditions.assertTestFileExists(MEDIA_DIR + mSrcFiles[i]);
if (!nativeTestExtract(MEDIA_DIR + mSrcFiles[0], MEDIA_DIR + mSrcFiles[i],
mMediaType)) {
Log.d(LOG_TAG, "Files: " + mSrcFiles[0] + ", " + mSrcFiles[i] +
" are different from extractor perpsective");
if (!codecListSupp.contains(mMediaType)) {
isOk = false;
break;
}
}
}
assertTrue(testName.getMethodName() + " failed for Mediatype: " + mMediaType, isOk);
}
@LargeTest
@Test
@Ignore("TODO(b/146420831)")
public void testSeekNative() throws IOException {
assumeTrue(shouldRunTest(mMediaType)
&& !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_RAW));
boolean isOk = true;
for (String srcFile : mSrcFiles) {
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
if (!isFileSeekable(srcFile)) continue;
if (!nativeTestSeek(MEDIA_DIR + srcFile, mMediaType)) {
if (!codecListSupp.contains(mMediaType)) {
isOk = false;
break;
}
}
}
assertTrue(testName.getMethodName() + " failed for Mediatype: " + mMediaType, isOk);
}
@LargeTest
@Test
public void testSeekFlakinessNative() throws IOException {
assumeTrue(shouldRunTest(mMediaType));
boolean isOk = true;
for (String srcFile : mSrcFiles) {
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
if (!isFileSeekable(srcFile)) continue;
if (!nativeTestSeekFlakiness(MEDIA_DIR + srcFile, mMediaType)) {
if (!codecListSupp.contains(mMediaType)) {
isOk = false;
break;
}
}
}
assertTrue(testName.getMethodName() + " failed for Mediatype: " + mMediaType, isOk);
}
@SmallTest
@Test
public void testSeekToZeroNative() throws IOException {
assumeTrue(shouldRunTest(mMediaType));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_VORBIS));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_MPEG));
assumeTrue("TODO(b/146925481)", !mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC));
boolean isOk = true;
for (String srcFile : mSrcFiles) {
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
if (!isFileSeekable(srcFile)) continue;
if (!nativeTestSeekToZero(MEDIA_DIR + srcFile, mMediaType)) {
if (!codecListSupp.contains(mMediaType)) {
isOk = false;
break;
}
}
}
assertTrue(testName.getMethodName() + " failed for Mediatype: " + mMediaType, isOk);
}
@SmallTest
@Test
public void testFileFormatNative() {
assumeTrue(shouldRunTest(mMediaType));
boolean isOk = true;
for (String srcFile : mSrcFiles) {
Preconditions.assertTestFileExists(MEDIA_DIR + srcFile);
if (!nativeTestFileFormat(MEDIA_DIR + srcFile)) {
isOk = false;
break;
}
}
assertTrue(testName.getMethodName() + " failed for Mediatype: " + mMediaType, isOk);
}
}
/**
* Encloses extractor test for validating extractor output for extractors which directly
* decode instead of extracting.
*/
@RunWith(Parameterized.class)
public static class FusedExtractorDecoderTest {
private final String mMediaType;
private final String mRefFile;
private final String mTestFile;
public FusedExtractorDecoderTest(String mediaType, String refFile, String testFile) {
mMediaType = mediaType;
mRefFile = refFile;
mTestFile = testFile;
}
@Parameterized.Parameters(name = "{index}({0})")
public static Collection<Object[]> input() {
return Arrays.asList(new Object[][]{
{MediaFormat.MIMETYPE_AUDIO_FLAC,
"bbb_cif_768kbps_30fps_mpeg4_stereo_48kHz_192kbps_flac.mp4",
"bbb_stereo_48kHz_192kbps_flac.flac"},
/* TODO(b/163566531)
{MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_1ch_16kHz.mkv", "bbb_1ch_16kHz.wav"},*/
});
}
@LargeTest
@Test
public void testExtractDecodeAndValidate() throws IOException, InterruptedException {
MediaExtractor testExtractor = new MediaExtractor();
testExtractor.setDataSource(MEDIA_DIR + mTestFile);
MediaFormat format = testExtractor.getTrackFormat(0);
String mediaType = format.getString(MediaFormat.KEY_MIME);
if (mediaType.equals(MediaFormat.MIMETYPE_AUDIO_RAW)) {
ArrayList<String> listOfDecoders =
CodecTestBase.selectCodecs(mMediaType, null, null, false);
assertTrue("no suitable codecs found for Mediatype: " + mMediaType,
!listOfDecoders.isEmpty());
CodecDecoderTestBase cdtb =
new CodecDecoderTestBase(listOfDecoders.get(0), mMediaType, mRefFile,
"invalid");
cdtb.decodeToMemory(MEDIA_DIR + mRefFile, listOfDecoders.get(0), 0,
MediaExtractor.SEEK_TO_CLOSEST_SYNC, Integer.MAX_VALUE);
String log = String.format("test file: %s, ref file: %s:: ", mTestFile, mRefFile);
final ByteBuffer refBuffer = cdtb.getOutputManager().getBuffer();
testExtractor.selectTrack(0);
ByteBuffer testBuffer = ByteBuffer.allocate(refBuffer.limit());
int bufOffset = 0;
while (true) {
long bytesRead = testExtractor.readSampleData(testBuffer, bufOffset);
if (bytesRead == -1) break;
bufOffset += bytesRead;
testExtractor.advance();
}
testBuffer.rewind();
assertEquals(log + "Output mismatch", 0, refBuffer.compareTo(testBuffer));
assertTrue(log + "Output formats mismatch",
cdtb.isFormatSimilar(cdtb.getOutputFormat(), format));
} else if (mediaType.equals(mMediaType)) {
MediaExtractor refExtractor = new MediaExtractor();
refExtractor.setDataSource(MEDIA_DIR + mRefFile);
if (!isMediaSimilar(refExtractor, testExtractor, mMediaType, Integer.MAX_VALUE)) {
fail("Files: " + mRefFile + ", " + mTestFile +
" are different from extractor perspective");
}
refExtractor.release();
} else {
fail("unexpected Mediatype: " + mediaType);
}
testExtractor.release();
}
}
/**
* Test if extractor populates key-value pairs correctly
*/
@RunWith(Parameterized.class)
public static class ValidateKeyValuePairs {
private static final String MEDIA_DIR = WorkDir.getMediaDirString();
private final String mMediaType;
private final String[] mInpFiles;
private final int mProfile;
private final int mLevel;
private final int mWR;
private final int mHCh;
public ValidateKeyValuePairs(String mediaType, String[] inpFiles, int profile, int level,
int wr, int hCh) {
mMediaType = mediaType;
mInpFiles = inpFiles;
mProfile = profile;
mLevel = level;
mWR = wr;
mHCh = hCh;
}
@Parameterized.Parameters(name = "{index}({0})")
public static Collection<Object[]> input() {
// mediaType, clips, profile, level, width/sample rate, height/channel count
List<Object[]> exhaustiveArgsList = new ArrayList<>();
if (hasDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG2)) {
// profile and level constraints as per sec 2.3.2 of cdd
/* TODO(b/159582475)
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_MPEG2, new String[]{
"bbb_1920x1080_mpeg2_main_high.mp4",
"bbb_1920x1080_mpeg2_main_high.mkv"},
MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain,
MediaCodecInfo.CodecProfileLevel.MPEG2LevelHL, 1920, 1080});*/
}
if (hasDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
// profile and level constraints as per sec 2.3.2 of cdd
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
"bbb_1920x1080_avc_baseline_l42.mp4",
"bbb_1920x1080_avc_baseline_l42.mkv",
"bbb_1920x1080_avc_baseline_l42.3gp"},
MediaCodecInfo.CodecProfileLevel.AVCProfileConstrainedBaseline,
MediaCodecInfo.CodecProfileLevel.AVCLevel42, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
"bbb_1920x1080_avc_main_l42.mp4",
"bbb_1920x1080_avc_main_l42.mkv",
"bbb_1920x1080_avc_main_l42.3gp"},
MediaCodecInfo.CodecProfileLevel.AVCProfileMain,
MediaCodecInfo.CodecProfileLevel.AVCLevel42, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
"bbb_1920x1080_avc_high_l42.mp4",
"bbb_1920x1080_avc_high_l42.mkv",
"bbb_1920x1080_avc_high_l42.3gp"},
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
MediaCodecInfo.CodecProfileLevel.AVCLevel42, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
"video_dovi_1920x1080_60fps_dvav_09.mp4"},
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
MediaCodecInfo.CodecProfileLevel.AVCLevel42, 1920, 1080});
// profile/level constraints for avc as per sec 5.3.4 of cdd
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
"bbb_1920x1080_avc_baseline_l40.mp4",
"bbb_1920x1080_avc_baseline_l40.mkv",
"bbb_1920x1080_avc_baseline_l40.3gp"},
MediaCodecInfo.CodecProfileLevel.AVCProfileConstrainedBaseline,
MediaCodecInfo.CodecProfileLevel.AVCLevel4, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
"bbb_1920x1080_avc_main_l40.mp4",
"bbb_1920x1080_avc_main_l40.mkv",
"bbb_1920x1080_avc_main_l40.3gp"},
MediaCodecInfo.CodecProfileLevel.AVCProfileMain,
MediaCodecInfo.CodecProfileLevel.AVCLevel4, 1920, 1080});
}
if (hasDecoder(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
// profile and level constraints as per sec 2.3.2 of cdd
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{
"bbb_1920x1080_hevc_main_l41.mp4",
"bbb_1920x1080_hevc_main_l41.mkv"},
MediaCodecInfo.CodecProfileLevel.HEVCProfileMain,
MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel41, 1920, 1080});
// profile/level constraints for hevc as per sec 5.3.5 of cdd
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{
"bbb_1920x1080_hevc_main_l40.mp4",
"bbb_1920x1080_hevc_main_l40.mkv"},
MediaCodecInfo.CodecProfileLevel.HEVCProfileMain,
MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel4, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{
"video_dovi_1920x1080_30fps_dvhe_04.mp4"},
MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10,
MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel4, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{
"video_dovi_1920x1080_60fps_dvhe_08.mp4"},
MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10,
MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel41, 1920, 1080});
}
if (hasDecoder(MediaFormat.MIMETYPE_VIDEO_VP9)) {
// profile and level constraints as per sec 2.3.2 of cdd
/* TODO(b/159582475)
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{
"bbb_1920x1080_vp9_main_l41.webm",
"bbb_1920x1080_vp9_main_l41.mkv"},
MediaCodecInfo.CodecProfileLevel.VP9Profile0,
MediaCodecInfo.CodecProfileLevel.VP9Level41, 1920, 1080});*/
// profile/level constraints for vp9 as per sec 5.3.6 of cdd
/* TODO(b/159582475)
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{
"bbb_1920x1080_vp9_main_l40.webm",
"bbb_1920x1080_vp9_main_l40.mkv"},
MediaCodecInfo.CodecProfileLevel.VP9Profile0,
MediaCodecInfo.CodecProfileLevel.VP9Level4, 1920, 1080});*/
}
if (hasDecoder(MediaFormat.MIMETYPE_VIDEO_H263)) {
// profile/level constraints for h263 as per sec 5.3.2 of cdd
/* TODO(b/159582475)
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_H263, new String[]{
"bbb_352x288_384kbps_30fps_h263_baseline_l3.3gp",
"bbb_352x288_384kbps_30fps_h263_baseline_l3.mp4",
"bbb_352x288_384kbps_30fps_h263_baseline_l3.mkv"},
MediaCodecInfo.CodecProfileLevel.H263ProfileBaseline,
MediaCodecInfo.CodecProfileLevel.H263Level30, 352, 288});*/
}
if (hasDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
// profile/level constraints for mpeg4 as per sec 5.3.3 of cdd
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_MPEG4, new String[]{
"bbb_352x288_384kbps_30fps_mpeg4_simple_l3.mp4",
"bbb_352x288_384kbps_30fps_mpeg4_simple_l3.3gp",
"bbb_352x288_384kbps_30fps_mpeg4_simple_l3.mkv"},
MediaCodecInfo.CodecProfileLevel.MPEG4ProfileSimple,
MediaCodecInfo.CodecProfileLevel.MPEG4Level3, 352, 288});
}
if (hasDecoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
// profile and level constraints for devices that have audio output as per sec 2.2.2,
// sec 2.3.2, sec 2.5.2, sec 5.1.2 of cdd
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
"bbb_stereo_44kHz_192kbps_aac_lc.mp4",
"bbb_stereo_44kHz_192kbps_aac_lc.3gp",
"bbb_stereo_44kHz_192kbps_aac_lc.mkv"},
MediaCodecInfo.CodecProfileLevel.AACObjectLC, 0, 44100, 2});
/* TODO(b/159582475)
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
"bbb_stereo_44kHz_192kbps_aac_he.mp4",
"bbb_stereo_44kHz_192kbps_aac_he.3gp",
"bbb_stereo_44kHz_192kbps_aac_he.mkv"},
MediaCodecInfo.CodecProfileLevel.AACObjectHE, 0, 44100, 2});*/
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_AUDIO_AAC, new String[] {
"bbb_stereo_44kHz_192kbps_aac_eld.mp4",
"bbb_stereo_44kHz_192kbps_aac_eld.3gp",
"bbb_stereo_44kHz_192kbps_aac_eld.mkv"},
MediaCodecInfo.CodecProfileLevel.AACObjectELD, 0, 44100, 2});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
"bbb_stereo_44kHz_192kbps_aac_ld.mp4",
"bbb_stereo_44kHz_192kbps_aac_ld.3gp",
"bbb_stereo_44kHz_192kbps_aac_ld.mkv"},
MediaCodecInfo.CodecProfileLevel.AACObjectLD, 0, 44100, 2});
/*TODO(b/159582475)
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
"bbb_stereo_44kHz_192kbps_aac_hev2.mp4",
"bbb_stereo_44kHz_192kbps_aac_hev2.3gp",
"bbb_stereo_44kHz_192kbps_aac_hev2.mkv"},
MediaCodecInfo.CodecProfileLevel.AACObjectHE_PS, 0, 44100, 2});*/
}
// Miscellaneous
if (hasDecoder(MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION)) {
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION,
new String[]{"video_dovi_1920x1080_30fps_dvhe_04.mp4"},
MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDtr,
MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd30, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION,
new String[]{"video_dovi_1920x1080_60fps_dvhe_05.mp4"},
MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn,
MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION,
new String[]{"video_dovi_1920x1080_60fps_dvhe_08.mp4"},
MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt,
MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, 1920, 1080});
exhaustiveArgsList.add(new Object[]{MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION,
new String[]{"video_dovi_1920x1080_60fps_dvav_09.mp4"},
MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe,
MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, 1920, 1080});
}
return exhaustiveArgsList;
}
@Test
public void validateKeyValuePairs() throws IOException {
for (String file : mInpFiles) {
MediaFormat format = null;
MediaExtractor extractor = new MediaExtractor();
Preconditions.assertTestFileExists(MEDIA_DIR + file);
extractor.setDataSource(MEDIA_DIR + file);
for (int trackID = 0; trackID < extractor.getTrackCount(); trackID++) {
MediaFormat fmt = extractor.getTrackFormat(trackID);
if (mMediaType.equalsIgnoreCase(fmt.getString(MediaFormat.KEY_MIME))) {
format = fmt;
break;
}
}
extractor.release();
assertTrue("missing track format from file " + file, format != null);
if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC)) {
assertTrue("neither KEY_AAC_PROFILE nor KEY_PROFILE found in file " + file,
format.containsKey(MediaFormat.KEY_AAC_PROFILE) ||
format.containsKey(MediaFormat.KEY_PROFILE));
if (format.containsKey(MediaFormat.KEY_AAC_PROFILE)) {
int profile = format.getInteger(MediaFormat.KEY_AAC_PROFILE, -1);
assertEquals("mismatched KEY_AAC_PROFILE in file " + file,
mProfile, profile);
}
if (format.containsKey(MediaFormat.KEY_PROFILE)) {
int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1);
assertEquals("mismatched KEY_PROFILE in file " + file, mProfile, profile);
}
} else {
int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1);
assertEquals("mismatched KEY_PROFILE in file " + file, mProfile, profile);
int level = format.getInteger(MediaFormat.KEY_LEVEL, -1);
assertEquals("mismatched KEY_LEVEL in file " + file, mLevel, level);
}
if (mMediaType.startsWith("audio/")) {
int sample_rate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1);
assertEquals("mismatched KEY_SAMPLE_RATE in file " + file,
mWR, sample_rate);
int channel_count = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -1);
assertEquals("mismatched KEY_CHANNEL_COUNT in file " + file,
mHCh, channel_count);
} else if (mMediaType.startsWith("video/")) {
int width = format.getInteger(MediaFormat.KEY_WIDTH, -1);
assertEquals("mismatched KEY_WIDTH in file " + file, mWR, width);
int height = format.getInteger(MediaFormat.KEY_HEIGHT, -1);
assertEquals("mismatched KEY_HEIGHT in file " + file, mHCh, height);
}
}
}
}
/**
* Makes sure if PTS(order) of a file matches the expected values in the corresponding text
* file with just PTS values.
*/
@RunWith(Parameterized.class)
public static class ExtractorTimeStampTest {
private final String mRefFile;
private final String mPTSListFile;
private int mTrackIndex;
// Allowing tolerance of +1/-1 for rounding error.
private static final int PTS_TOLERANCE = 1;
public ExtractorTimeStampTest(String refFile, String textFile, int trackIndex) {
mRefFile = refFile;
mPTSListFile = textFile;
mTrackIndex = trackIndex;
}
@Parameterized.Parameters
public static Collection<Object[]> input() {
final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
{"bbb_384x216_768kbps_30fps_avc_2b.mp4",
"pts_bbb_384x216_768kbps_30fps_avc_2b.txt", 0},
{"bbb_384x216_768kbps_25fps_avc_7b.mp4",
"pts_bbb_384x216_768kbps_25fps_avc_7b.txt", 0},
{"bbb_384x216_768kbps_24fps_avc_5b.mkv",
"pts_bbb_384x216_768kbps_24fps_avc_5b.txt", 0},
{"bbb_384x216_768kbps_30fps_avc_badapt.mkv",
"pts_bbb_384x216_768kbps_30fps_avc_badapt.txt", 0},
{"bbb_384x216_768kbps_30fps_avc_2b.3gp",
"pts_bbb_384x216_768kbps_30fps_avc_2b.txt", 0},
{"bbb_384x216_768kbps_25fps_avc_7b.3gp",
"pts_bbb_384x216_768kbps_25fps_avc_7b.txt", 0},
{"bbb_384x216_768kbps_30fps_avc_badapt_bbb_480x360_768kbps_24fps_avc_5b.mkv",
"pts_bbb_384x216_768kbps_30fps_avc_badapt.txt", 0},
{"bbb_384x216_768kbps_30fps_avc_badapt_bbb_480x360_768kbps_24fps_avc_5b.mkv",
"pts_bbb_480x360_768kbps_24fps_avc_5b.txt", 1},
{"bbb_384x216_768kbps_30fps_avc_2b_bbb_cif_768kbps_25fps_avc_7b.mp4",
"pts_bbb_384x216_768kbps_30fps_avc_2b.txt", 0},
{"bbb_384x216_768kbps_30fps_avc_2b_bbb_cif_768kbps_25fps_avc_7b.mp4",
"pts_bbb_cif_768kbps_25fps_avc_7b.txt", 1},
{"bbb_384x216_768kbps_30fps_hevc_2b.mp4",
"pts_bbb_384x216_768kbps_30fps_hevc_2b.txt", 0},
{"bbb_384x216_768kbps_25fps_hevc_7b.mp4",
"pts_bbb_384x216_768kbps_25fps_hevc_7b.txt", 0},
{"bbb_384x216_768kbps_24fps_hevc_5b.mkv",
"pts_bbb_384x216_768kbps_24fps_hevc_5b.txt", 0},
{"bbb_384x216_768kbps_30fps_hevc_badapt.mkv",
"pts_bbb_384x216_768kbps_30fps_hevc_badapt.txt", 0},
{"bbb_384x216_768kbps_30fps_hevc_badapt_bbb_480x360_768kbps_24fps_hevc_5b.mkv",
"pts_bbb_384x216_768kbps_30fps_hevc_badapt.txt", 0},
{"bbb_384x216_768kbps_30fps_hevc_badapt_bbb_480x360_768kbps_24fps_hevc_5b.mkv",
"pts_bbb_480x360_768kbps_24fps_hevc_5b.txt", 1},
{"bbb_384x216_768kbps_30fps_hevc_2b_bbb_cif_768kbps_25fps_hevc_7b.mp4",
"pts_bbb_384x216_768kbps_30fps_hevc_2b.txt", 0},
{"bbb_384x216_768kbps_30fps_hevc_2b_bbb_cif_768kbps_25fps_hevc_7b.mp4",
"pts_bbb_cif_768kbps_25fps_hevc_7b.txt", 1},
{"bbb_384x216_768kbps_30fps_mpeg2_2b.mp4",
"pts_bbb_384x216_768kbps_30fps_mpeg2_2b.txt", 0},
{"bbb_384x216_768kbps_25fps_mpeg2_5b.mp4",
"pts_bbb_384x216_768kbps_25fps_mpeg2_5b.txt", 0},
{"bbb_384x216_768kbps_24fps_mpeg2_5b.mkv",
"pts_bbb_384x216_768kbps_24fps_mpeg2_5b.txt", 0},
{"bbb_384x216_768kbps_30fps_mpeg2_2b.ts",
"pts_bbb_384x216_768kbps_30fps_mpeg2_2b.txt", 0},
{"bbb_384x216_768kbps_25fps_mpeg2_7b.ts",
"pts_bbb_384x216_768kbps_25fps_mpeg2_7b.txt", 0},
{"bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm",
"pts_bbb_cif_768kbps_30fps_vp8.txt", 0},
{"bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.mkv",
"pts_bbb_cif_768kbps_30fps_vp8.txt", 0},
{"bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.webm",
"pts_stereo_48kHz_192kbps_vorbis.txt", 1},
{"bbb_cif_768kbps_30fps_vp8_stereo_48kHz_192kbps_vorbis.mkv",
"pts_stereo_48kHz_192kbps_vorbis.txt", 1},
{"bbb_340x280_768kbps_30fps_split_non_display_frame_vp9.webm",
"pts_bbb_340x280_768kbps_30fps_split_non_display_frame_vp9.txt", 0},
{"bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.webm",
"pts_bbb_cif_768kbps_30fps_vp9.txt", 0},
{"bbb_cif_768kbps_30fps_vp9_stereo_48kHz_192kbps_opus.mkv",
"pts_bbb_cif_768kbps_30fps_vp9.txt", 0},
{"bbb_cif_768kbps_30fps_av1.mp4",
"pts_bbb_cif_768kbps_30fps_av1.txt", 0},
{"bbb_cif_768kbps_30fps_av1.mkv",
"pts_bbb_cif_768kbps_30fps_av1.txt", 0},
{"bbb_cif_768kbps_30fps_av1.webm",
"pts_bbb_cif_768kbps_30fps_av1.txt", 0},
{"binary_counter_320x240_30fps_600frames.mp4",
"pts_binary_counter_320x240_30fps_600frames.txt", 0},
});
return exhaustiveArgsList;
}
@LargeTest
@Test
public void testPresentationTimeStampsMatch() throws IOException {
try (FileInputStream file = new FileInputStream(MEDIA_DIR + mPTSListFile);
InputStreamReader input = new InputStreamReader(file);
Reader txtRdr = new BufferedReader(input)) {
StreamTokenizer strTok = new StreamTokenizer(txtRdr);
strTok.parseNumbers();
MediaExtractor extractor = new MediaExtractor();
Preconditions.assertTestFileExists(MEDIA_DIR + mRefFile);
extractor.setDataSource(MEDIA_DIR + mRefFile);
assertTrue(mTrackIndex < extractor.getTrackCount());
extractor.selectTrack(mTrackIndex);
while (true) {
if (strTok.nextToken() == StreamTokenizer.TT_EOF) break;
assertTrue("PTS mismatch exp/got: " + (long) strTok.nval + "/" +
extractor.getSampleTime(),
Math.abs(extractor.getSampleTime() - (long) strTok.nval) <=
PTS_TOLERANCE);
if (!extractor.advance()) break;
}
assertEquals(StreamTokenizer.TT_EOF, strTok.nextToken());
assertTrue(!extractor.advance());
extractor.release();
}
}
}
}