1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.cts;
18 
19 import android.media.cts.R;
20 
21 import android.content.res.AssetFileDescriptor;
22 import android.content.res.Resources;
23 import android.cts.util.MediaUtils;
24 import android.media.MediaCodec;
25 import android.media.MediaCodec.BufferInfo;
26 import android.media.MediaCodecInfo;
27 import android.media.MediaExtractor;
28 import android.media.MediaFormat;
29 import android.media.MediaMetadataRetriever;
30 import android.media.MediaPlayer;
31 import android.net.Uri;
32 import android.os.Environment;
33 import android.os.ParcelFileDescriptor;
34 import android.util.Log;
35 import android.view.Surface;
36 import android.webkit.cts.CtsTestServer;
37 
38 import java.io.BufferedInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.FileDescriptor;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.RandomAccessFile;
45 import java.nio.ByteBuffer;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51 import java.util.UUID;
52 
53 public class NativeDecoderTest extends MediaPlayerTestBase {
54     private static final String TAG = "DecoderTest";
55 
56     private static final int RESET_MODE_NONE = 0;
57     private static final int RESET_MODE_RECONFIGURE = 1;
58     private static final int RESET_MODE_FLUSH = 2;
59     private static final int RESET_MODE_EOS_FLUSH = 3;
60 
61     private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
62 
63     private static final int CONFIG_MODE_NONE = 0;
64     private static final int CONFIG_MODE_QUEUE = 1;
65 
66     private static Resources mResources;
67     short[] mMasterBuffer;
68 
69     /** Load jni on initialization */
70     static {
71         Log.i("@@@", "before loadlibrary");
72         System.loadLibrary("ctsmediacodec_jni");
73         Log.i("@@@", "after loadlibrary");
74     }
75 
76     @Override
setUp()77     protected void setUp() throws Exception {
78         super.setUp();
79         mResources = mContext.getResources();
80 
81     }
82 
83     // check that native extractor behavior matches java extractor
84 
testExtractor()85     public void testExtractor() throws Exception {
86         testExtractor(R.raw.sinesweepogg);
87         testExtractor(R.raw.sinesweepmp3lame);
88         testExtractor(R.raw.sinesweepmp3smpb);
89         testExtractor(R.raw.sinesweepm4a);
90         testExtractor(R.raw.sinesweepflac);
91         testExtractor(R.raw.sinesweepwav);
92 
93         testExtractor(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
94         testExtractor(R.raw.bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz);
95         testExtractor(R.raw.bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz);
96         testExtractor(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
97         testExtractor(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
98 
99         CtsTestServer foo = new CtsTestServer(mContext);
100         testExtractor(foo.getAssetUrl("noiseandchirps.ogg"));
101         testExtractor(foo.getAssetUrl("ringer.mp3"));
102         testExtractor(foo.getRedirectingAssetUrl("ringer.mp3"));
103     }
104 
testExtractor(String path)105     private void testExtractor(String path) throws Exception {
106         int[] jsizes = getSampleSizes(path);
107         int[] nsizes = getSampleSizesNativePath(path);
108 
109         //Log.i("@@@", Arrays.toString(jsizes));
110         assertTrue("different samplesizes", Arrays.equals(jsizes, nsizes));
111     }
112 
testExtractor(int res)113     private void testExtractor(int res) throws Exception {
114         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
115 
116         int[] jsizes = getSampleSizes(
117                 fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
118         int[] nsizes = getSampleSizesNative(
119                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
120 
121         fd.close();
122         //Log.i("@@@", Arrays.toString(jsizes));
123         assertTrue("different samplesizes", Arrays.equals(jsizes, nsizes));
124     }
125 
getSampleSizes(String path)126     private static int[] getSampleSizes(String path) throws IOException {
127         MediaExtractor ex = new MediaExtractor();
128         ex.setDataSource(path);
129         return getSampleSizes(ex);
130     }
131 
getSampleSizes(FileDescriptor fd, long offset, long size)132     private static int[] getSampleSizes(FileDescriptor fd, long offset, long size)
133             throws IOException {
134         MediaExtractor ex = new MediaExtractor();
135         ex.setDataSource(fd, offset, size);
136         return getSampleSizes(ex);
137     }
138 
getSampleSizes(MediaExtractor ex)139     private static int[] getSampleSizes(MediaExtractor ex) {
140         ArrayList<Integer> foo = new ArrayList<Integer>();
141         ByteBuffer buf = ByteBuffer.allocate(1024*1024);
142         int numtracks = ex.getTrackCount();
143         assertTrue("no tracks", numtracks > 0);
144         foo.add(numtracks);
145         for (int i = 0; i < numtracks; i++) {
146             MediaFormat format = ex.getTrackFormat(i);
147             String mime = format.getString(MediaFormat.KEY_MIME);
148             if (mime.startsWith("audio/")) {
149                 foo.add(0);
150                 foo.add(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
151                 foo.add(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
152                 foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
153             } else if (mime.startsWith("video/")) {
154                 foo.add(1);
155                 foo.add(format.getInteger(MediaFormat.KEY_WIDTH));
156                 foo.add(format.getInteger(MediaFormat.KEY_HEIGHT));
157                 foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
158             } else {
159                 fail("unexpected mime type: " + mime);
160             }
161             ex.selectTrack(i);
162         }
163         while(true) {
164             int n = ex.readSampleData(buf, 0);
165             if (n < 0) {
166                 break;
167             }
168             foo.add(n);
169             foo.add(ex.getSampleTrackIndex());
170             foo.add(ex.getSampleFlags());
171             foo.add((int)ex.getSampleTime()); // just the low bits should be OK
172             ex.advance();
173         }
174 
175         int [] ret = new int[foo.size()];
176         for (int i = 0; i < ret.length; i++) {
177             ret[i] = foo.get(i);
178         }
179         return ret;
180     }
181 
getSampleSizesNative(int fd, long offset, long size)182     private static native int[] getSampleSizesNative(int fd, long offset, long size);
getSampleSizesNativePath(String path)183     private static native int[] getSampleSizesNativePath(String path);
184 
185 
testDecoder()186     public void testDecoder() throws Exception {
187         int testsRun =
188             testDecoder(R.raw.sinesweepogg) +
189             testDecoder(R.raw.sinesweepmp3lame) +
190             testDecoder(R.raw.sinesweepmp3smpb) +
191             testDecoder(R.raw.sinesweepm4a) +
192             testDecoder(R.raw.sinesweepflac) +
193             testDecoder(R.raw.sinesweepwav) +
194 
195             testDecoder(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
196             testDecoder(R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz) +
197             testDecoder(R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz) +
198             testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
199             testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
200         if (testsRun == 0) {
201             MediaUtils.skipTest("no decoders found");
202         }
203     }
204 
testDecoder(int res)205     private int testDecoder(int res) throws Exception {
206         if (!MediaUtils.hasCodecsForResource(mContext, res)) {
207             return 0; // skip
208         }
209 
210         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
211 
212         int[] jdata = getDecodedData(
213                 fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
214         int[] ndata = getDecodedDataNative(
215                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
216 
217         fd.close();
218         Log.i("@@@", Arrays.toString(jdata));
219         Log.i("@@@", Arrays.toString(ndata));
220         assertEquals("number of samples differs", jdata.length, ndata.length);
221         assertTrue("different decoded data", Arrays.equals(jdata, ndata));
222         return 1;
223     }
224 
getDecodedData(FileDescriptor fd, long offset, long size)225     private static int[] getDecodedData(FileDescriptor fd, long offset, long size)
226             throws IOException {
227         MediaExtractor ex = new MediaExtractor();
228         ex.setDataSource(fd, offset, size);
229         return getDecodedData(ex);
230     }
getDecodedData(MediaExtractor ex)231     private static int[] getDecodedData(MediaExtractor ex) throws IOException {
232         int numtracks = ex.getTrackCount();
233         assertTrue("no tracks", numtracks > 0);
234         ArrayList<Integer>[] trackdata = new ArrayList[numtracks];
235         MediaCodec[] codec = new MediaCodec[numtracks];
236         MediaFormat[] format = new MediaFormat[numtracks];
237         ByteBuffer[][] inbuffers = new ByteBuffer[numtracks][];
238         ByteBuffer[][] outbuffers = new ByteBuffer[numtracks][];
239         for (int i = 0; i < numtracks; i++) {
240             format[i] = ex.getTrackFormat(i);
241             String mime = format[i].getString(MediaFormat.KEY_MIME);
242             if (mime.startsWith("audio/") || mime.startsWith("video/")) {
243                 codec[i] = MediaCodec.createDecoderByType(mime);
244                 codec[i].configure(format[i], null, null, 0);
245                 codec[i].start();
246                 inbuffers[i] = codec[i].getInputBuffers();
247                 outbuffers[i] = codec[i].getOutputBuffers();
248                 trackdata[i] = new ArrayList<Integer>();
249             } else {
250                 fail("unexpected mime type: " + mime);
251             }
252             ex.selectTrack(i);
253         }
254 
255         boolean[] sawInputEOS = new boolean[numtracks];
256         boolean[] sawOutputEOS = new boolean[numtracks];
257         int eosCount = 0;
258         BufferInfo info = new BufferInfo();
259         while(eosCount < numtracks) {
260             int t = ex.getSampleTrackIndex();
261             if (t >= 0) {
262                 assertFalse("saw input EOS twice", sawInputEOS[t]);
263                 int bufidx = codec[t].dequeueInputBuffer(5000);
264                 if (bufidx >= 0) {
265                     Log.i("@@@@", "track " + t + " buffer " + bufidx);
266                     ByteBuffer buf = inbuffers[t][bufidx];
267                     int sampleSize = ex.readSampleData(buf, 0);
268                     Log.i("@@@@", "read " + sampleSize);
269                     if (sampleSize < 0) {
270                         sampleSize = 0;
271                         sawInputEOS[t] = true;
272                         Log.i("@@@@", "EOS");
273                         //break;
274                     }
275                     long presentationTimeUs = ex.getSampleTime();
276 
277                     codec[t].queueInputBuffer(bufidx, 0, sampleSize, presentationTimeUs,
278                             sawInputEOS[t] ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
279                     ex.advance();
280                 }
281             } else {
282                 Log.i("@@@@", "no more input samples");
283                 for (int tt = 0; tt < codec.length; tt++) {
284                     if (!sawInputEOS[tt]) {
285                         // we ran out of samples without ever signaling EOS to the codec,
286                         // so do that now
287                         int bufidx = codec[tt].dequeueInputBuffer(5000);
288                         if (bufidx >= 0) {
289                             codec[tt].queueInputBuffer(bufidx, 0, 0, 0,
290                                     MediaCodec.BUFFER_FLAG_END_OF_STREAM);
291                             sawInputEOS[tt] = true;
292                         }
293                     }
294                 }
295             }
296 
297             // see if any of the codecs have data available
298             for (int tt = 0; tt < codec.length; tt++) {
299                 if (!sawOutputEOS[tt]) {
300                     int status = codec[tt].dequeueOutputBuffer(info, 1);
301                     if (status >= 0) {
302                         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
303                             Log.i("@@@@", "EOS on track " + tt);
304                             sawOutputEOS[tt] = true;
305                             eosCount++;
306                         }
307                         Log.i("@@@@", "got decoded buffer for track " + tt + ", size " + info.size);
308                         if (info.size > 0) {
309                             addSampleData(trackdata[tt], outbuffers[tt][status], info.size, format[tt]);
310                         }
311                         codec[tt].releaseOutputBuffer(status, false);
312                     } else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
313                         Log.i("@@@@", "output buffers changed for track " + tt);
314                         outbuffers[tt] = codec[tt].getOutputBuffers();
315                     } else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
316                         format[tt] = codec[tt].getOutputFormat();
317                         Log.i("@@@@", "format changed for track " + t + ": " + format[tt].toString());
318                     } else if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
319                         Log.i("@@@@", "no buffer right now for track " + tt);
320                     } else {
321                         Log.i("@@@@", "unexpected info code for track " + tt + ": " + status);
322                     }
323                 } else {
324                     Log.i("@@@@", "already at EOS on track " + tt);
325                 }
326             }
327         }
328 
329         int totalsize = 0;
330         for (int i = 0; i < numtracks; i++) {
331             totalsize += trackdata[i].size();
332         }
333         int[] trackbytes = new int[totalsize];
334         int idx = 0;
335         for (int i = 0; i < numtracks; i++) {
336             ArrayList<Integer> src = trackdata[i];
337             int tracksize = src.size();
338             for (int j = 0; j < tracksize; j++) {
339                 trackbytes[idx++] = src.get(j);
340             }
341         }
342 
343         for (int i = 0; i < codec.length; i++) {
344             codec[i].release();
345         }
346 
347         return trackbytes;
348     }
349 
addSampleData(ArrayList<Integer> dst, ByteBuffer buf, int size, MediaFormat format)350     static void addSampleData(ArrayList<Integer> dst,
351             ByteBuffer buf, int size, MediaFormat format) throws IOException{
352 
353         Log.i("@@@", "addsample " + dst.size() + "/" + size);
354         int width = format.getInteger(MediaFormat.KEY_WIDTH, size);
355         int stride = format.getInteger(MediaFormat.KEY_STRIDE, width);
356         int height = format.getInteger(MediaFormat.KEY_HEIGHT, 1);
357         byte[] bb = new byte[width * height];
358         for (int i = 0; i < height; i++) {
359             buf.position(i * stride);
360             buf.get(bb, i * width, width);
361         }
362         // bb is filled with data
363         long sum = adler32(bb);
364         dst.add( (int) (sum & 0xffffffff));
365     }
366 
367     // simple checksum computed over every decoded buffer
adler32(byte[] input)368     static long adler32(byte[] input) {
369         int a = 1;
370         int b = 0;
371         for (int i = 0; i < input.length; i++) {
372             int unsignedval = input[i];
373             if (unsignedval < 0) {
374                 unsignedval = 256 + unsignedval;
375             }
376             a += unsignedval;
377             b += a;
378         }
379         a = a % 65521;
380         b = b % 65521;
381         long ret = b * 65536 + a;
382         Log.i("@@@", "adler " + input.length + "/" + ret);
383         return ret;
384     }
385 
getDecodedDataNative(int fd, long offset, long size)386     private static native int[] getDecodedDataNative(int fd, long offset, long size)
387             throws IOException;
388 
testVideoPlayback()389     public void testVideoPlayback() throws Exception {
390         int testsRun =
391             testVideoPlayback(
392                     R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
393             testVideoPlayback(
394                     R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz) +
395             testVideoPlayback(
396                     R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz) +
397             testVideoPlayback(
398                     R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
399             testVideoPlayback(
400                     R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
401         if (testsRun == 0) {
402             MediaUtils.skipTest("no decoders found");
403         }
404     }
405 
testVideoPlayback(int res)406     private int testVideoPlayback(int res) throws Exception {
407         if (!MediaUtils.checkCodecsForResource(mContext, res)) {
408             return 0; // skip
409         }
410 
411         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
412 
413         boolean ret = testPlaybackNative(mActivity.getSurfaceHolder().getSurface(),
414                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
415         assertTrue("native playback error", ret);
416         return 1;
417     }
418 
testPlaybackNative(Surface surface, int fd, long startOffset, long length)419     private static native boolean testPlaybackNative(Surface surface,
420             int fd, long startOffset, long length);
421 
testMuxerAvc()422     public void testMuxerAvc() throws Exception {
423         // IMPORTANT: this file must not have B-frames
424         testMuxer(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, false);
425     }
426 
testMuxerH263()427     public void testMuxerH263() throws Exception {
428         // IMPORTANT: this file must not have B-frames
429         testMuxer(R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz, false);
430     }
431 
testMuxerHevc()432     public void testMuxerHevc() throws Exception {
433         // IMPORTANT: this file must not have B-frames
434         testMuxer(R.raw.video_640x360_mp4_hevc_450kbps_no_b, false);
435     }
436 
testMuxerVp8()437     public void testMuxerVp8() throws Exception {
438         testMuxer(R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz, true);
439     }
440 
testMuxerVp9()441     public void testMuxerVp9() throws Exception {
442         testMuxer(
443                 R.raw.video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz,
444                 true);
445     }
446 
testMuxerVp9NoCsd()447     public void testMuxerVp9NoCsd() throws Exception {
448         testMuxer(
449                 R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz,
450                 true);
451     }
452 
testMuxerVp9Hdr()453     public void testMuxerVp9Hdr() throws Exception {
454         testMuxer(R.raw.video_256x144_webm_vp9_hdr_83kbps_24fps, true);
455     }
456 
457     // We do not support MPEG-2 muxing as of yet
SKIP_testMuxerMpeg2()458     public void SKIP_testMuxerMpeg2() throws Exception {
459         // IMPORTANT: this file must not have B-frames
460         testMuxer(R.raw.video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz, false);
461     }
462 
testMuxerMpeg4()463     public void testMuxerMpeg4() throws Exception {
464         // IMPORTANT: this file must not have B-frames
465         testMuxer(R.raw.video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz, false);
466     }
467 
testMuxer(int res, boolean webm)468     private void testMuxer(int res, boolean webm) throws Exception {
469         if (!MediaUtils.checkCodecsForResource(mContext, res)) {
470             return; // skip
471         }
472 
473         AssetFileDescriptor infd = mResources.openRawResourceFd(res);
474 
475         File base = mContext.getExternalFilesDir(null);
476         String tmpFile = base.getPath() + "/tmp.dat";
477         Log.i("@@@", "using tmp file " + tmpFile);
478         new File(tmpFile).delete();
479         ParcelFileDescriptor out = ParcelFileDescriptor.open(new File(tmpFile),
480                 ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
481 
482         assertTrue("muxer failed", testMuxerNative(
483                 infd.getParcelFileDescriptor().getFd(), infd.getStartOffset(), infd.getLength(),
484                 out.getFd(), webm));
485 
486         // compare the original with the remuxed
487         MediaExtractor org = new MediaExtractor();
488         org.setDataSource(infd.getFileDescriptor(),
489                 infd.getStartOffset(), infd.getLength());
490 
491         MediaExtractor remux = new MediaExtractor();
492         remux.setDataSource(out.getFileDescriptor());
493 
494         assertEquals("mismatched numer of tracks", org.getTrackCount(), remux.getTrackCount());
495         // allow duration mismatch for webm files as ffmpeg does not consider the duration of the
496         // last frame while libwebm (and our framework) does.
497         final long maxDurationDiffUs = webm ? 50000 : 0; // 50ms for webm
498         for (int i = 0; i < org.getTrackCount(); i++) {
499             MediaFormat format1 = org.getTrackFormat(i);
500             MediaFormat format2 = remux.getTrackFormat(i);
501             Log.i("@@@", "org: " + format1);
502             Log.i("@@@", "remux: " + format2);
503             assertTrue("different formats", compareFormats(format1, format2, maxDurationDiffUs));
504         }
505 
506         org.release();
507         remux.release();
508 
509         MediaPlayer player1 = MediaPlayer.create(mContext, res);
510         MediaPlayer player2 = MediaPlayer.create(mContext, Uri.parse("file://" + tmpFile));
511         assertEquals("duration is different",
512                      player1.getDuration(), player2.getDuration(), maxDurationDiffUs * 0.001);
513         player1.release();
514         player2.release();
515         new File(tmpFile).delete();
516     }
517 
hexString(ByteBuffer buf)518     private String hexString(ByteBuffer buf) {
519         if (buf == null) {
520             return "(null)";
521         }
522         final char digits[] =
523             { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
524 
525         StringBuilder hex = new StringBuilder();
526         for (int i = buf.position(); i < buf.limit(); ++i) {
527             byte c = buf.get(i);
528             hex.append(digits[(c >> 4) & 0xf]);
529             hex.append(digits[c & 0xf]);
530         }
531         return hex.toString();
532     }
533 
534     /** returns: null if key is in neither formats, true if they match and false otherwise */
compareByteBufferInFormats(MediaFormat f1, MediaFormat f2, String key)535     private Boolean compareByteBufferInFormats(MediaFormat f1, MediaFormat f2, String key) {
536         ByteBuffer bufF1 = f1.containsKey(key) ? f1.getByteBuffer(key) : null;
537         ByteBuffer bufF2 = f2.containsKey(key) ? f2.getByteBuffer(key) : null;
538         if (bufF1 == null && bufF2 == null) {
539             return null;
540         }
541         if (bufF1 == null || !bufF1.equals(bufF2)) {
542             Log.i("@@@", "org " + key + ": " + hexString(bufF1));
543             Log.i("@@@", "rmx " + key + ": " + hexString(bufF2));
544             return false;
545         }
546         return true;
547     }
548 
compareFormats(MediaFormat f1, MediaFormat f2, long maxDurationDiffUs)549     private boolean compareFormats(MediaFormat f1, MediaFormat f2, long maxDurationDiffUs) {
550         final String KEY_DURATION = MediaFormat.KEY_DURATION;
551 
552         // allow some difference in durations
553         if (maxDurationDiffUs > 0
554                 && f1.containsKey(KEY_DURATION) && f2.containsKey(KEY_DURATION)
555                 && Math.abs(f1.getLong(KEY_DURATION)
556                         - f2.getLong(KEY_DURATION)) <= maxDurationDiffUs) {
557             f2.setLong(KEY_DURATION, f1.getLong(KEY_DURATION));
558         }
559 
560         // verify hdr-static-info
561         if (Boolean.FALSE.equals(compareByteBufferInFormats(f1, f2, "hdr-static-info"))) {
562             return false;
563         }
564 
565         // verify CSDs
566         for (int i = 0;; ++i) {
567             String key = "csd-" + i;
568             Boolean match = compareByteBufferInFormats(f1, f2, key);
569             if (match == null) {
570                 break;
571             } else if (match == false) {
572                 return false;
573             }
574         }
575 
576         // there's no good way to compare two MediaFormats, so compare their string
577         // representation
578         return f1.toString().equals(f2.toString());
579     }
580 
testMuxerNative(int in, long inoffset, long insize, int out, boolean webm)581     private static native boolean testMuxerNative(int in, long inoffset, long insize,
582             int out, boolean webm);
583 
testFormat()584     public void testFormat() throws Exception {
585         assertTrue("media format fail, see log for details", testFormatNative());
586     }
587 
testFormatNative()588     private static native boolean testFormatNative();
589 
testPssh()590     public void testPssh() throws Exception {
591         testPssh(R.raw.psshtest);
592     }
593 
testPssh(int res)594     private void testPssh(int res) throws Exception {
595         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
596 
597         MediaExtractor ex = new MediaExtractor();
598         ex.setDataSource(fd.getParcelFileDescriptor().getFileDescriptor(),
599                 fd.getStartOffset(), fd.getLength());
600         testPssh(ex);
601         ex.release();
602 
603         boolean ret = testPsshNative(
604                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
605         assertTrue("native pssh error", ret);
606     }
607 
testPssh(MediaExtractor ex)608     private static void testPssh(MediaExtractor ex) {
609         Map<UUID, byte[]> map = ex.getPsshInfo();
610         Set<UUID> keys = map.keySet();
611         for (UUID uuid: keys) {
612             Log.i("@@@", "uuid: " + uuid + ", data size " +
613                     map.get(uuid).length);
614         }
615     }
616 
testPsshNative(int fd, long offset, long size)617     private static native boolean testPsshNative(int fd, long offset, long size);
618 
testCryptoInfo()619     public void testCryptoInfo() throws Exception {
620         assertTrue("native cryptoinfo failed, see log for details", testCryptoInfoNative());
621     }
622 
testCryptoInfoNative()623     private static native boolean testCryptoInfoNative();
624 }
625 
626