blob: aed00296b4956fc180e8634270e9148313a5705a [file] [log] [blame]
/*
* 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 android.media.cts;
import com.android.cts.media.R;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.cts.util.MediaUtils;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.view.Surface;
import android.webkit.cts.CtsTestServer;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class NativeDecoderTest extends MediaPlayerTestBase {
private static final String TAG = "DecoderTest";
private static final int RESET_MODE_NONE = 0;
private static final int RESET_MODE_RECONFIGURE = 1;
private static final int RESET_MODE_FLUSH = 2;
private static final int RESET_MODE_EOS_FLUSH = 3;
private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
private static final int CONFIG_MODE_NONE = 0;
private static final int CONFIG_MODE_QUEUE = 1;
private static Resources mResources;
short[] mMasterBuffer;
/** Load jni on initialization */
static {
Log.i("@@@", "before loadlibrary");
System.loadLibrary("ctsmediacodec_jni");
Log.i("@@@", "after loadlibrary");
}
@Override
protected void setUp() throws Exception {
super.setUp();
mResources = mContext.getResources();
}
// check that native extractor behavior matches java extractor
public void testExtractor() throws Exception {
testExtractor(R.raw.sinesweepogg);
testExtractor(R.raw.sinesweepmp3lame);
testExtractor(R.raw.sinesweepmp3smpb);
testExtractor(R.raw.sinesweepm4a);
testExtractor(R.raw.sinesweepflac);
testExtractor(R.raw.sinesweepwav);
testExtractor(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
testExtractor(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
testExtractor(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz);
testExtractor(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
testExtractor(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
CtsTestServer foo = new CtsTestServer(mContext);
testExtractor(foo.getAssetUrl("noiseandchirps.ogg"));
testExtractor(foo.getAssetUrl("ringer.mp3"));
testExtractor(foo.getRedirectingAssetUrl("ringer.mp3"));
}
private void testExtractor(String path) throws Exception {
int[] jsizes = getSampleSizes(path);
int[] nsizes = getSampleSizesNativePath(path);
//Log.i("@@@", Arrays.toString(jsizes));
assertTrue("different samplesizes", Arrays.equals(jsizes, nsizes));
}
private void testExtractor(int res) throws Exception {
AssetFileDescriptor fd = mResources.openRawResourceFd(res);
int[] jsizes = getSampleSizes(
fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
int[] nsizes = getSampleSizesNative(
fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
fd.close();
//Log.i("@@@", Arrays.toString(jsizes));
assertTrue("different samplesizes", Arrays.equals(jsizes, nsizes));
}
private static int[] getSampleSizes(String path) throws IOException {
MediaExtractor ex = new MediaExtractor();
ex.setDataSource(path);
return getSampleSizes(ex);
}
private static int[] getSampleSizes(FileDescriptor fd, long offset, long size)
throws IOException {
MediaExtractor ex = new MediaExtractor();
ex.setDataSource(fd, offset, size);
return getSampleSizes(ex);
}
private static int[] getSampleSizes(MediaExtractor ex) {
ArrayList<Integer> foo = new ArrayList<Integer>();
ByteBuffer buf = ByteBuffer.allocate(1024*1024);
int numtracks = ex.getTrackCount();
assertTrue("no tracks", numtracks > 0);
foo.add(numtracks);
for (int i = 0; i < numtracks; i++) {
MediaFormat format = ex.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("audio/")) {
foo.add(0);
foo.add(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
foo.add(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
} else if (mime.startsWith("video/")) {
foo.add(1);
foo.add(format.getInteger(MediaFormat.KEY_WIDTH));
foo.add(format.getInteger(MediaFormat.KEY_HEIGHT));
foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
} else {
fail("unexpected mime type: " + mime);
}
ex.selectTrack(i);
}
while(true) {
int n = ex.readSampleData(buf, 0);
if (n < 0) {
break;
}
foo.add(n);
foo.add(ex.getSampleTrackIndex());
foo.add(ex.getSampleFlags());
foo.add((int)ex.getSampleTime()); // just the low bits should be OK
ex.advance();
}
int [] ret = new int[foo.size()];
for (int i = 0; i < ret.length; i++) {
ret[i] = foo.get(i);
}
return ret;
}
private static native int[] getSampleSizesNative(int fd, long offset, long size);
private static native int[] getSampleSizesNativePath(String path);
public void testDecoder() throws Exception {
int testsRun =
testDecoder(R.raw.sinesweepogg) +
testDecoder(R.raw.sinesweepmp3lame) +
testDecoder(R.raw.sinesweepmp3smpb) +
testDecoder(R.raw.sinesweepm4a) +
testDecoder(R.raw.sinesweepflac) +
testDecoder(R.raw.sinesweepwav) +
testDecoder(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
testDecoder(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz) +
testDecoder(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz) +
testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
if (testsRun == 0) {
MediaUtils.skipTest("no decoders found");
}
}
private int testDecoder(int res) throws Exception {
if (!MediaUtils.hasCodecsForResource(mContext, res)) {
return 0; // skip
}
AssetFileDescriptor fd = mResources.openRawResourceFd(res);
int[] jdata = getDecodedData(
fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
int[] ndata = getDecodedDataNative(
fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
fd.close();
Log.i("@@@", Arrays.toString(jdata));
Log.i("@@@", Arrays.toString(ndata));
assertEquals("number of samples differs", jdata.length, ndata.length);
assertTrue("different decoded data", Arrays.equals(jdata, ndata));
return 1;
}
private static int[] getDecodedData(FileDescriptor fd, long offset, long size)
throws IOException {
MediaExtractor ex = new MediaExtractor();
ex.setDataSource(fd, offset, size);
return getDecodedData(ex);
}
private static int[] getDecodedData(MediaExtractor ex) throws IOException {
int numtracks = ex.getTrackCount();
assertTrue("no tracks", numtracks > 0);
ArrayList<Integer>[] trackdata = new ArrayList[numtracks];
MediaCodec[] codec = new MediaCodec[numtracks];
MediaFormat[] format = new MediaFormat[numtracks];
ByteBuffer[][] inbuffers = new ByteBuffer[numtracks][];
ByteBuffer[][] outbuffers = new ByteBuffer[numtracks][];
for (int i = 0; i < numtracks; i++) {
format[i] = ex.getTrackFormat(i);
String mime = format[i].getString(MediaFormat.KEY_MIME);
if (mime.startsWith("audio/") || mime.startsWith("video/")) {
codec[i] = MediaCodec.createDecoderByType(mime);
codec[i].configure(format[i], null, null, 0);
codec[i].start();
inbuffers[i] = codec[i].getInputBuffers();
outbuffers[i] = codec[i].getOutputBuffers();
trackdata[i] = new ArrayList<Integer>();
} else {
fail("unexpected mime type: " + mime);
}
ex.selectTrack(i);
}
boolean[] sawInputEOS = new boolean[numtracks];
boolean[] sawOutputEOS = new boolean[numtracks];
int eosCount = 0;
BufferInfo info = new BufferInfo();
while(eosCount < numtracks) {
int t = ex.getSampleTrackIndex();
if (t >= 0) {
assertFalse("saw input EOS twice", sawInputEOS[t]);
int bufidx = codec[t].dequeueInputBuffer(5000);
if (bufidx >= 0) {
Log.i("@@@@", "track " + t + " buffer " + bufidx);
ByteBuffer buf = inbuffers[t][bufidx];
int sampleSize = ex.readSampleData(buf, 0);
Log.i("@@@@", "read " + sampleSize);
if (sampleSize < 0) {
sampleSize = 0;
sawInputEOS[t] = true;
Log.i("@@@@", "EOS");
//break;
}
long presentationTimeUs = ex.getSampleTime();
codec[t].queueInputBuffer(bufidx, 0, sampleSize, presentationTimeUs,
sawInputEOS[t] ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
ex.advance();
}
} else {
Log.i("@@@@", "no more input samples");
for (int tt = 0; tt < codec.length; tt++) {
if (!sawInputEOS[tt]) {
// we ran out of samples without ever signaling EOS to the codec,
// so do that now
int bufidx = codec[tt].dequeueInputBuffer(5000);
if (bufidx >= 0) {
codec[tt].queueInputBuffer(bufidx, 0, 0, 0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
sawInputEOS[tt] = true;
}
}
}
}
// see if any of the codecs have data available
for (int tt = 0; tt < codec.length; tt++) {
if (!sawOutputEOS[tt]) {
int status = codec[tt].dequeueOutputBuffer(info, 1);
if (status >= 0) {
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.i("@@@@", "EOS on track " + tt);
sawOutputEOS[tt] = true;
eosCount++;
}
Log.i("@@@@", "got decoded buffer for track " + tt + ", size " + info.size);
if (info.size > 0) {
addSampleData(trackdata[tt], outbuffers[tt][status], info.size, format[tt]);
}
codec[tt].releaseOutputBuffer(status, false);
} else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
Log.i("@@@@", "output buffers changed for track " + tt);
outbuffers[tt] = codec[tt].getOutputBuffers();
} else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
format[tt] = codec[tt].getOutputFormat();
Log.i("@@@@", "format changed for track " + t + ": " + format[tt].toString());
} else if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
Log.i("@@@@", "no buffer right now for track " + tt);
} else {
Log.i("@@@@", "unexpected info code for track " + tt + ": " + status);
}
} else {
Log.i("@@@@", "already at EOS on track " + tt);
}
}
}
int totalsize = 0;
for (int i = 0; i < numtracks; i++) {
totalsize += trackdata[i].size();
}
int[] trackbytes = new int[totalsize];
int idx = 0;
for (int i = 0; i < numtracks; i++) {
ArrayList<Integer> src = trackdata[i];
int tracksize = src.size();
for (int j = 0; j < tracksize; j++) {
trackbytes[idx++] = src.get(j);
}
}
return trackbytes;
}
static void addSampleData(ArrayList<Integer> dst,
ByteBuffer buf, int size, MediaFormat format) throws IOException{
Log.i("@@@", "addsample " + dst.size() + "/" + size);
int width = format.getInteger(MediaFormat.KEY_WIDTH, size);
int stride = format.getInteger(MediaFormat.KEY_STRIDE, width);
int height = format.getInteger(MediaFormat.KEY_HEIGHT, 1);
byte[] bb = new byte[width * height];
for (int i = 0; i < height; i++) {
buf.position(i * stride);
buf.get(bb, i * width, width);
}
// bb is filled with data
long sum = adler32(bb);
dst.add( (int) (sum & 0xffffffff));
}
// simple checksum computed over every decoded buffer
static long adler32(byte[] input) {
int a = 1;
int b = 0;
for (int i = 0; i < input.length; i++) {
int unsignedval = input[i];
if (unsignedval < 0) {
unsignedval = 256 + unsignedval;
}
a += unsignedval;
b += a;
}
a = a % 65521;
b = b % 65521;
long ret = b * 65536 + a;
Log.i("@@@", "adler " + input.length + "/" + ret);
return ret;
}
private static native int[] getDecodedDataNative(int fd, long offset, long size)
throws IOException;
public void testVideoPlayback() throws Exception {
int testsRun =
testVideoPlayback(
R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
testVideoPlayback(
R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz) +
testVideoPlayback(
R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz) +
testVideoPlayback(
R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
testVideoPlayback(
R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
if (testsRun == 0) {
MediaUtils.skipTest("no decoders found");
}
}
private int testVideoPlayback(int res) throws Exception {
if (!MediaUtils.checkCodecsForResource(mContext, res)) {
return 0; // skip
}
AssetFileDescriptor fd = mResources.openRawResourceFd(res);
boolean ret = testPlaybackNative(mActivity.getSurfaceHolder().getSurface(),
fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
assertTrue("native playback error", ret);
return 1;
}
private static native boolean testPlaybackNative(Surface surface,
int fd, long startOffset, long length);
public void testMuxer() throws Exception {
testMuxer(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, false);
}
private void testMuxer(int res, boolean webm) throws Exception {
if (!MediaUtils.checkCodecsForResource(mContext, res)) {
return; // skip
}
AssetFileDescriptor infd = mResources.openRawResourceFd(res);
File base = mContext.getExternalFilesDir(null);
String tmpFile = base.getPath() + "/tmp.dat";
Log.i("@@@", "using tmp file " + tmpFile);
new File(tmpFile).delete();
ParcelFileDescriptor out = ParcelFileDescriptor.open(new File(tmpFile),
ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
assertTrue("muxer failed", testMuxerNative(
infd.getParcelFileDescriptor().getFd(), infd.getStartOffset(), infd.getLength(),
out.getFd(), webm));
// compare the original with the remuxed
MediaExtractor org = new MediaExtractor();
org.setDataSource(infd.getFileDescriptor(),
infd.getStartOffset(), infd.getLength());
MediaExtractor remux = new MediaExtractor();
remux.setDataSource(out.getFileDescriptor());
assertEquals("mismatched numer of tracks", org.getTrackCount(), remux.getTrackCount());
for (int i = 0; i < 2; i++) {
MediaFormat format1 = org.getTrackFormat(i);
MediaFormat format2 = remux.getTrackFormat(i);
Log.i("@@@", "org: " + format1);
Log.i("@@@", "remux: " + format2);
assertTrue("different formats", compareFormats(format1, format2));
}
org.release();
remux.release();
MediaPlayer player1 = MediaPlayer.create(mContext, res);
MediaPlayer player2 = MediaPlayer.create(mContext, Uri.parse("file://" + tmpFile));
assertEquals("duration is different", player1.getDuration(), player2.getDuration());
player1.release();
player2.release();
new File(tmpFile).delete();
}
boolean compareFormats(MediaFormat f1, MediaFormat f2) {
// there's no good way to compare two MediaFormats, so compare their string
// representation
return f1.toString().equals(f2.toString());
}
private static native boolean testMuxerNative(int in, long inoffset, long insize,
int out, boolean webm);
public void testFormat() throws Exception {
assertTrue("media format fail, see log for details", testFormatNative());
}
private static native boolean testFormatNative();
public void testPssh() throws Exception {
testPssh(R.raw.psshtest);
}
private void testPssh(int res) throws Exception {
AssetFileDescriptor fd = mResources.openRawResourceFd(res);
MediaExtractor ex = new MediaExtractor();
ex.setDataSource(fd.getParcelFileDescriptor().getFileDescriptor(),
fd.getStartOffset(), fd.getLength());
testPssh(ex);
ex.release();
boolean ret = testPsshNative(
fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
assertTrue("native pssh error", ret);
}
private static void testPssh(MediaExtractor ex) {
Map<UUID, byte[]> map = ex.getPsshInfo();
Set<UUID> keys = map.keySet();
for (UUID uuid: keys) {
Log.i("@@@", "uuid: " + uuid + ", data size " +
map.get(uuid).length);
}
}
private static native boolean testPsshNative(int fd, long offset, long size);
public void testCryptoInfo() throws Exception {
assertTrue("native cryptoinfo failed, see log for details", testCryptoInfoNative());
}
private static native boolean testCryptoInfoNative();
}