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.content.res.AssetFileDescriptor;
20 import android.content.res.Resources;
21 import android.media.MediaCodec;
22 import android.media.MediaCodec.BufferInfo;
23 import android.media.MediaExtractor;
24 import android.media.MediaFormat;
25 import android.media.MediaPlayer;
26 import android.media.cts.R;
27 import android.media.cts.TestUtils.Monitor;
28 import android.net.Uri;
29 import android.os.ParcelFileDescriptor;
30 import android.platform.test.annotations.AppModeFull;
31 import android.platform.test.annotations.RequiresDevice;
32 import android.util.Log;
33 import android.view.Surface;
34 import android.webkit.cts.CtsTestServer;
35 
36 import androidx.test.filters.SmallTest;
37 
38 import com.android.compatibility.common.util.MediaUtils;
39 
40 import org.apache.http.Header;
41 import org.apache.http.HttpRequest;
42 import org.apache.http.impl.DefaultHttpServerConnection;
43 import org.apache.http.impl.io.SocketOutputBuffer;
44 import org.apache.http.io.SessionOutputBuffer;
45 import org.apache.http.params.HttpParams;
46 import org.apache.http.util.CharArrayBuffer;
47 
48 import java.io.File;
49 import java.io.FileDescriptor;
50 import java.io.IOException;
51 import java.net.Socket;
52 import java.nio.ByteBuffer;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.HashMap;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.UUID;
59 import java.util.concurrent.CountDownLatch;
60 import java.util.concurrent.TimeUnit;
61 import java.util.zip.Adler32;
62 
63 @SmallTest
64 @RequiresDevice
65 @AppModeFull(reason = "TODO: evaluate and port to instant")
66 public class NativeDecoderTest extends MediaPlayerTestBase {
67     private static final String TAG = "DecoderTest";
68 
69     private static final int RESET_MODE_NONE = 0;
70     private static final int RESET_MODE_RECONFIGURE = 1;
71     private static final int RESET_MODE_FLUSH = 2;
72     private static final int RESET_MODE_EOS_FLUSH = 3;
73 
74     private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
75 
76     private static final int CONFIG_MODE_NONE = 0;
77     private static final int CONFIG_MODE_QUEUE = 1;
78 
79     private static Resources mResources;
80     short[] mMasterBuffer;
81 
82     /** Load jni on initialization */
83     static {
84         Log.i("@@@", "before loadlibrary");
85         System.loadLibrary("ctsmediacodec_jni");
86         Log.i("@@@", "after loadlibrary");
87     }
88 
89     @Override
setUp()90     protected void setUp() throws Exception {
91         super.setUp();
92         mResources = mContext.getResources();
93 
94     }
95 
96     // check that native extractor behavior matches java extractor
97 
compareArrays(String message, int[] a1, int[] a2)98     private void compareArrays(String message, int[] a1, int[] a2) {
99         if (a1 == a2) {
100             return;
101         }
102 
103         assertNotNull(message + ": array 1 is null", a1);
104         assertNotNull(message + ": array 2 is null", a2);
105 
106         assertEquals(message + ": arraylengths differ", a1.length, a2.length);
107         int length = a1.length;
108 
109         for (int i = 0; i < length; i++)
110             if (a1[i] != a2[i]) {
111                 Log.i("@@@@", Arrays.toString(a1));
112                 Log.i("@@@@", Arrays.toString(a2));
113                 fail(message + ": at index " + i);
114             }
115     }
116 
testExtractor()117     public void testExtractor() throws Exception {
118         testExtractor(R.raw.sinesweepogg);
119         testExtractor(R.raw.sinesweepoggmkv);
120         testExtractor(R.raw.sinesweepoggmp4);
121         testExtractor(R.raw.sinesweepmp3lame);
122         testExtractor(R.raw.sinesweepmp3smpb);
123         testExtractor(R.raw.sinesweepopus);
124         testExtractor(R.raw.sinesweepopusmp4);
125         testExtractor(R.raw.sinesweepm4a);
126         testExtractor(R.raw.sinesweepflacmkv);
127         testExtractor(R.raw.sinesweepflac);
128         testExtractor(R.raw.sinesweepflacmp4);
129         testExtractor(R.raw.sinesweepwav);
130 
131         testExtractor(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
132         testExtractor(R.raw.bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz);
133         testExtractor(R.raw.bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz);
134         testExtractor(R.raw.video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz);
135         testExtractor(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
136         testExtractor(R.raw.video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz);
137         testExtractor(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
138 
139         CtsTestServer foo = new CtsTestServer(mContext);
140         testExtractor(foo.getAssetUrl("noiseandchirps.ogg"));
141         testExtractor(foo.getAssetUrl("ringer.mp3"));
142         testExtractor(foo.getRedirectingAssetUrl("ringer.mp3"));
143 
144         String[] keys = new String[] {"header0", "header1"};
145         String[] values = new String[] {"value0", "value1"};
146         testExtractor(foo.getAssetUrl("noiseandchirps.ogg"), keys, values);
147         HttpRequest req = foo.getLastRequest("noiseandchirps.ogg");
148         for (int i = 0; i < keys.length; i++) {
149             String key = keys[i];
150             String value = values[i];
151             Header[] header = req.getHeaders(key);
152             assertTrue("expecting " + key + ":" + value + ", saw " + Arrays.toString(header),
153                     header.length == 1 && header[0].getValue().equals(value));
154         }
155 
156         String[] emptyArray = new String[0];
157         testExtractor(foo.getAssetUrl("noiseandchirps.ogg"), emptyArray, emptyArray);
158     }
159 
testExtractor(String path)160     private void testExtractor(String path) throws Exception {
161         testExtractor(path, null, null);
162     }
163 
164     /**
165      * |keys| and |values| should be arrays of the same length.
166      *
167      * If keys or values is null, test {@link MediaExtractor#setDataSource(String)}
168      * and NDK counter part, i.e. set data source without headers.
169      *
170      * If keys or values is zero length, test {@link MediaExtractor#setDataSource(String, Map))}
171      * and NDK counter part with null headers.
172      *
173      */
testExtractor(String path, String[] keys, String[] values)174     private void testExtractor(String path, String[] keys, String[] values) throws Exception {
175         int[] jsizes = getSampleSizes(path, keys, values);
176         int[] nsizes = getSampleSizesNativePath(path, keys, values, /* testNativeSource = */ false);
177         int[] nsizes2 = getSampleSizesNativePath(path, keys, values, /* testNativeSource = */ true);
178 
179         compareArrays("different samplesizes", jsizes, nsizes);
180         compareArrays("different samplesizes native source", jsizes, nsizes2);
181     }
182 
testExtractor(int res)183     private void testExtractor(int res) throws Exception {
184         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
185 
186         int[] jsizes = getSampleSizes(
187                 fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
188         int[] nsizes = getSampleSizesNative(
189                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
190 
191         fd.close();
192         compareArrays("different samples", jsizes, nsizes);
193     }
194 
getSampleSizes(String path, String[] keys, String[] values)195     private static int[] getSampleSizes(String path, String[] keys, String[] values) throws IOException {
196         MediaExtractor ex = new MediaExtractor();
197         if (keys == null || values == null) {
198             ex.setDataSource(path);
199         } else {
200             Map<String, String> headers = null;
201             int numheaders = Math.min(keys.length, values.length);
202             for (int i = 0; i < numheaders; i++) {
203                 if (headers == null) {
204                     headers = new HashMap<>();
205                 }
206                 String key = keys[i];
207                 String value = values[i];
208                 headers.put(key, value);
209             }
210             ex.setDataSource(path, headers);
211         }
212 
213         return getSampleSizes(ex);
214     }
215 
getSampleSizes(FileDescriptor fd, long offset, long size)216     private static int[] getSampleSizes(FileDescriptor fd, long offset, long size)
217             throws IOException {
218         MediaExtractor ex = new MediaExtractor();
219         ex.setDataSource(fd, offset, size);
220         return getSampleSizes(ex);
221     }
222 
getSampleSizes(MediaExtractor ex)223     private static int[] getSampleSizes(MediaExtractor ex) {
224         ArrayList<Integer> foo = new ArrayList<Integer>();
225         ByteBuffer buf = ByteBuffer.allocate(1024*1024);
226         int numtracks = ex.getTrackCount();
227         assertTrue("no tracks", numtracks > 0);
228         foo.add(numtracks);
229         for (int i = 0; i < numtracks; i++) {
230             MediaFormat format = ex.getTrackFormat(i);
231             String mime = format.getString(MediaFormat.KEY_MIME);
232             if (mime.startsWith("audio/")) {
233                 foo.add(0);
234                 foo.add(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
235                 foo.add(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
236                 foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
237             } else if (mime.startsWith("video/")) {
238                 foo.add(1);
239                 foo.add(format.getInteger(MediaFormat.KEY_WIDTH));
240                 foo.add(format.getInteger(MediaFormat.KEY_HEIGHT));
241                 foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
242             } else {
243                 fail("unexpected mime type: " + mime);
244             }
245             ex.selectTrack(i);
246         }
247         while(true) {
248             int n = ex.readSampleData(buf, 0);
249             if (n < 0) {
250                 break;
251             }
252             foo.add(n);
253             foo.add(ex.getSampleTrackIndex());
254             foo.add(ex.getSampleFlags());
255             foo.add((int)ex.getSampleTime()); // just the low bits should be OK
256             byte foobar[] = new byte[n];
257             buf.get(foobar, 0, n);
258             foo.add((int)adler32(foobar));
259             ex.advance();
260         }
261 
262         int [] ret = new int[foo.size()];
263         for (int i = 0; i < ret.length; i++) {
264             ret[i] = foo.get(i);
265         }
266         return ret;
267     }
268 
getSampleSizesNative(int fd, long offset, long size)269     private static native int[] getSampleSizesNative(int fd, long offset, long size);
getSampleSizesNativePath( String path, String[] keys, String[] values, boolean testNativeSource)270     private static native int[] getSampleSizesNativePath(
271             String path, String[] keys, String[] values, boolean testNativeSource);
272 
testExtractorFileDurationNative()273     public void testExtractorFileDurationNative() throws Exception {
274         int res = R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz;
275         testExtractorFileDurationNative(res);
276     }
277 
testExtractorFileDurationNative(int res)278     private void testExtractorFileDurationNative(int res) throws Exception {
279 
280         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
281         long durationUs = getExtractorFileDurationNative(
282                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
283 
284         MediaExtractor ex = new MediaExtractor();
285         ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
286 
287         int numtracks = ex.getTrackCount();
288         long aDurationUs = -1, vDurationUs = -1;
289         for (int i = 0; i < numtracks; i++) {
290             MediaFormat format = ex.getTrackFormat(i);
291             String mime = format.getString(MediaFormat.KEY_MIME);
292             if (mime.startsWith("audio/")) {
293                 aDurationUs = format.getLong(MediaFormat.KEY_DURATION);
294             } else if (mime.startsWith("video/")) {
295                 vDurationUs = format.getLong(MediaFormat.KEY_DURATION);
296             }
297         }
298 
299         assertTrue("duration inconsistency",
300                 durationUs < 0 || durationUs >= aDurationUs && durationUs >= vDurationUs);
301 
302     }
303 
getExtractorFileDurationNative(int fd, long offset, long size)304     private static native long getExtractorFileDurationNative(int fd, long offset, long size);
305 
testExtractorCachedDurationNative()306     public void testExtractorCachedDurationNative() throws Exception {
307         CtsTestServer foo = new CtsTestServer(mContext);
308         String url = foo.getAssetUrl("ringer.mp3");
309         long cachedDurationUs = getExtractorCachedDurationNative(url, /* testNativeSource = */ false);
310         assertTrue("cached duration negative", cachedDurationUs >= 0);
311         cachedDurationUs = getExtractorCachedDurationNative(url, /* testNativeSource = */ true);
312         assertTrue("cached duration negative native source", cachedDurationUs >= 0);
313     }
314 
getExtractorCachedDurationNative(String uri, boolean testNativeSource)315     private static native long getExtractorCachedDurationNative(String uri, boolean testNativeSource);
316 
testDecoder()317     public void testDecoder() throws Exception {
318         int testsRun =
319             testDecoder(R.raw.sinesweepogg) +
320             testDecoder(R.raw.sinesweepoggmkv) +
321             testDecoder(R.raw.sinesweepoggmp4) +
322             testDecoder(R.raw.sinesweepmp3lame) +
323             testDecoder(R.raw.sinesweepmp3smpb) +
324             testDecoder(R.raw.sinesweepopus) +
325             testDecoder(R.raw.sinesweepopusmp4) +
326             testDecoder(R.raw.sinesweepm4a) +
327             testDecoder(R.raw.sinesweepflacmkv) +
328             testDecoder(R.raw.sinesweepflac) +
329             testDecoder(R.raw.sinesweepflacmp4) +
330             testDecoder(R.raw.sinesweepwav) +
331 
332             testDecoder(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
333             testDecoder(R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz) +
334             testDecoder(R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz) +
335             testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
336             testDecoder(R.raw.video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz);
337             testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
338         if (testsRun == 0) {
339             MediaUtils.skipTest("no decoders found");
340         }
341     }
342 
testDataSource()343     public void testDataSource() throws Exception {
344         int testsRun = testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz,
345                 /* wrapFd */ true, /* useCallback */ false);
346         if (testsRun == 0) {
347             MediaUtils.skipTest("no decoders found");
348         }
349     }
350 
testDataSourceAudioOnly()351     public void testDataSourceAudioOnly() throws Exception {
352         int testsRun = testDecoder(
353                 R.raw.loudsoftmp3,
354                 /* wrapFd */ true, /* useCallback */ false) +
355                 testDecoder(
356                         R.raw.loudsoftaac,
357                         /* wrapFd */ false, /* useCallback */ false);
358         if (testsRun == 0) {
359             MediaUtils.skipTest("no decoders found");
360         }
361     }
362 
testDataSourceWithCallback()363     public void testDataSourceWithCallback() throws Exception {
364         int testsRun = testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz,
365                 /* wrapFd */ true, /* useCallback */ true);
366         if (testsRun == 0) {
367             MediaUtils.skipTest("no decoders found");
368         }
369     }
370 
testDecoder(int res)371     private int testDecoder(int res) throws Exception {
372         return testDecoder(res, /* wrapFd */ false, /* useCallback */ false);
373     }
374 
testDecoder(int res, boolean wrapFd, boolean useCallback)375     private int testDecoder(int res, boolean wrapFd, boolean useCallback) throws Exception {
376         if (!MediaUtils.hasCodecsForResource(mContext, res)) {
377             return 0; // skip
378         }
379 
380         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
381 
382         int[] jdata1 = getDecodedData(
383                 fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
384         int[] jdata2 = getDecodedData(
385                 fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
386         int[] ndata1 = getDecodedDataNative(
387                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd,
388                 useCallback);
389         int[] ndata2 = getDecodedDataNative(
390                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd,
391                 useCallback);
392 
393         fd.close();
394         compareArrays("inconsistent java decoder", jdata1, jdata2);
395         compareArrays("inconsistent native decoder", ndata1, ndata2);
396         compareArrays("different decoded data", jdata1, ndata1);
397         return 1;
398     }
399 
getDecodedData(FileDescriptor fd, long offset, long size)400     private static int[] getDecodedData(FileDescriptor fd, long offset, long size)
401             throws IOException {
402         MediaExtractor ex = new MediaExtractor();
403         ex.setDataSource(fd, offset, size);
404         return getDecodedData(ex);
405     }
getDecodedData(MediaExtractor ex)406     private static int[] getDecodedData(MediaExtractor ex) throws IOException {
407         int numtracks = ex.getTrackCount();
408         assertTrue("no tracks", numtracks > 0);
409         ArrayList<Integer>[] trackdata = new ArrayList[numtracks];
410         MediaCodec[] codec = new MediaCodec[numtracks];
411         MediaFormat[] format = new MediaFormat[numtracks];
412         ByteBuffer[][] inbuffers = new ByteBuffer[numtracks][];
413         ByteBuffer[][] outbuffers = new ByteBuffer[numtracks][];
414         for (int i = 0; i < numtracks; i++) {
415             format[i] = ex.getTrackFormat(i);
416             String mime = format[i].getString(MediaFormat.KEY_MIME);
417             if (mime.startsWith("audio/") || mime.startsWith("video/")) {
418                 codec[i] = MediaCodec.createDecoderByType(mime);
419                 codec[i].configure(format[i], null, null, 0);
420                 codec[i].start();
421                 inbuffers[i] = codec[i].getInputBuffers();
422                 outbuffers[i] = codec[i].getOutputBuffers();
423                 trackdata[i] = new ArrayList<Integer>();
424             } else {
425                 fail("unexpected mime type: " + mime);
426             }
427             ex.selectTrack(i);
428         }
429 
430         boolean[] sawInputEOS = new boolean[numtracks];
431         boolean[] sawOutputEOS = new boolean[numtracks];
432         int eosCount = 0;
433         BufferInfo info = new BufferInfo();
434         while(eosCount < numtracks) {
435             int t = ex.getSampleTrackIndex();
436             if (t >= 0) {
437                 assertFalse("saw input EOS twice", sawInputEOS[t]);
438                 int bufidx = codec[t].dequeueInputBuffer(5000);
439                 if (bufidx >= 0) {
440                     Log.i("@@@@", "track " + t + " buffer " + bufidx);
441                     ByteBuffer buf = inbuffers[t][bufidx];
442                     int sampleSize = ex.readSampleData(buf, 0);
443                     Log.i("@@@@", "read " + sampleSize + " @ " + ex.getSampleTime());
444                     if (sampleSize < 0) {
445                         sampleSize = 0;
446                         sawInputEOS[t] = true;
447                         Log.i("@@@@", "EOS");
448                         //break;
449                     }
450                     long presentationTimeUs = ex.getSampleTime();
451 
452                     codec[t].queueInputBuffer(bufidx, 0, sampleSize, presentationTimeUs,
453                             sawInputEOS[t] ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
454                     ex.advance();
455                 }
456             } else {
457                 Log.i("@@@@", "no more input samples");
458                 for (int tt = 0; tt < codec.length; tt++) {
459                     if (!sawInputEOS[tt]) {
460                         // we ran out of samples without ever signaling EOS to the codec,
461                         // so do that now
462                         int bufidx = codec[tt].dequeueInputBuffer(5000);
463                         if (bufidx >= 0) {
464                             codec[tt].queueInputBuffer(bufidx, 0, 0, 0,
465                                     MediaCodec.BUFFER_FLAG_END_OF_STREAM);
466                             sawInputEOS[tt] = true;
467                         }
468                     }
469                 }
470             }
471 
472             // see if any of the codecs have data available
473             for (int tt = 0; tt < codec.length; tt++) {
474                 if (!sawOutputEOS[tt]) {
475                     int status = codec[tt].dequeueOutputBuffer(info, 1);
476                     if (status >= 0) {
477                         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
478                             Log.i("@@@@", "EOS on track " + tt);
479                             sawOutputEOS[tt] = true;
480                             eosCount++;
481                         }
482                         Log.i("@@@@", "got decoded buffer for track " + tt + ", size " + info.size);
483                         if (info.size > 0) {
484                             addSampleData(trackdata[tt], outbuffers[tt][status], info.size, format[tt]);
485                         }
486                         codec[tt].releaseOutputBuffer(status, false);
487                     } else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
488                         Log.i("@@@@", "output buffers changed for track " + tt);
489                         outbuffers[tt] = codec[tt].getOutputBuffers();
490                     } else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
491                         format[tt] = codec[tt].getOutputFormat();
492                         Log.i("@@@@", "format changed for track " + t + ": " + format[tt].toString());
493                     } else if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
494                         Log.i("@@@@", "no buffer right now for track " + tt);
495                     } else {
496                         Log.i("@@@@", "unexpected info code for track " + tt + ": " + status);
497                     }
498                 } else {
499                     Log.i("@@@@", "already at EOS on track " + tt);
500                 }
501             }
502         }
503 
504         int totalsize = 0;
505         for (int i = 0; i < numtracks; i++) {
506             totalsize += trackdata[i].size();
507         }
508         int[] trackbytes = new int[totalsize];
509         int idx = 0;
510         for (int i = 0; i < numtracks; i++) {
511             ArrayList<Integer> src = trackdata[i];
512             int tracksize = src.size();
513             for (int j = 0; j < tracksize; j++) {
514                 trackbytes[idx++] = src.get(j);
515             }
516         }
517 
518         for (int i = 0; i < codec.length; i++) {
519             codec[i].release();
520         }
521 
522         return trackbytes;
523     }
524 
addSampleData(ArrayList<Integer> dst, ByteBuffer buf, int size, MediaFormat format)525     static void addSampleData(ArrayList<Integer> dst,
526             ByteBuffer buf, int size, MediaFormat format) throws IOException{
527 
528         Log.i("@@@", "addsample " + dst.size() + "/" + size);
529         int width = format.getInteger(MediaFormat.KEY_WIDTH, size);
530         int stride = format.getInteger(MediaFormat.KEY_STRIDE, width);
531         int height = format.getInteger(MediaFormat.KEY_HEIGHT, 1);
532         byte[] bb = new byte[width * height];
533         int offset = buf.position();
534         for (int i = 0; i < height; i++) {
535             buf.position(i * stride + offset);
536             buf.get(bb, i * width, width);
537         }
538         // bb is filled with data
539         long sum = adler32(bb);
540         dst.add( (int) (sum & 0xffffffff));
541     }
542 
543     private final static Adler32 checksummer = new Adler32();
544     // simple checksum computed over every decoded buffer
adler32(byte[] input)545     static int adler32(byte[] input) {
546         checksummer.reset();
547         checksummer.update(input);
548         int ret = (int) checksummer.getValue();
549         Log.i("@@@", "adler " + input.length + "/" + ret);
550         return ret;
551     }
552 
getDecodedDataNative(int fd, long offset, long size, boolean wrapFd, boolean useCallback)553     private static native int[] getDecodedDataNative(int fd, long offset, long size, boolean wrapFd,
554             boolean useCallback)
555             throws IOException;
556 
testVideoPlayback()557     public void testVideoPlayback() throws Exception {
558         int testsRun =
559             testVideoPlayback(
560                     R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
561             testVideoPlayback(
562                     R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz) +
563             testVideoPlayback(
564                     R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz) +
565             testVideoPlayback(
566                     R.raw.video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz) +
567             testVideoPlayback(
568                     R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
569             testVideoPlayback(
570                     R.raw.video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz) +
571             testVideoPlayback(
572                     R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
573         if (testsRun == 0) {
574             MediaUtils.skipTest("no decoders found");
575         }
576     }
577 
testVideoPlayback(int res)578     private int testVideoPlayback(int res) throws Exception {
579         if (!MediaUtils.checkCodecsForResource(mContext, res)) {
580             return 0; // skip
581         }
582 
583         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
584 
585         boolean ret = testPlaybackNative(mActivity.getSurfaceHolder().getSurface(),
586                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
587         assertTrue("native playback error", ret);
588         return 1;
589     }
590 
testPlaybackNative(Surface surface, int fd, long startOffset, long length)591     private static native boolean testPlaybackNative(Surface surface,
592             int fd, long startOffset, long length);
593 
testMuxerAvc()594     public void testMuxerAvc() throws Exception {
595         // IMPORTANT: this file must not have B-frames
596         testMuxer(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, false);
597     }
598 
testMuxerH263()599     public void testMuxerH263() throws Exception {
600         // IMPORTANT: this file must not have B-frames
601         testMuxer(R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz, false);
602     }
603 
testMuxerHevc()604     public void testMuxerHevc() throws Exception {
605         // IMPORTANT: this file must not have B-frames
606         testMuxer(R.raw.video_640x360_mp4_hevc_450kbps_no_b, false);
607     }
608 
testMuxerVp8()609     public void testMuxerVp8() throws Exception {
610         testMuxer(R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz, true);
611     }
612 
testMuxerVp9()613     public void testMuxerVp9() throws Exception {
614         testMuxer(
615                 R.raw.video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz,
616                 true);
617     }
618 
testMuxerVp9NoCsd()619     public void testMuxerVp9NoCsd() throws Exception {
620         testMuxer(
621                 R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz,
622                 true);
623     }
624 
testMuxerVp9Hdr()625     public void testMuxerVp9Hdr() throws Exception {
626         testMuxer(R.raw.video_256x144_webm_vp9_hdr_83kbps_24fps, true);
627     }
628 
629     // We do not support MPEG-2 muxing as of yet
SKIP_testMuxerMpeg2()630     public void SKIP_testMuxerMpeg2() throws Exception {
631         // IMPORTANT: this file must not have B-frames
632         testMuxer(R.raw.video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz, false);
633     }
634 
testMuxerMpeg4()635     public void testMuxerMpeg4() throws Exception {
636         // IMPORTANT: this file must not have B-frames
637         testMuxer(R.raw.video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz, false);
638     }
639 
testMuxer(int res, boolean webm)640     private void testMuxer(int res, boolean webm) throws Exception {
641         if (!MediaUtils.checkCodecsForResource(mContext, res)) {
642             return; // skip
643         }
644 
645         AssetFileDescriptor infd = mResources.openRawResourceFd(res);
646 
647         File base = mContext.getExternalFilesDir(null);
648         String tmpFile = base.getPath() + "/tmp.dat";
649         Log.i("@@@", "using tmp file " + tmpFile);
650         new File(tmpFile).delete();
651         ParcelFileDescriptor out = ParcelFileDescriptor.open(new File(tmpFile),
652                 ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
653 
654         assertTrue("muxer failed", testMuxerNative(
655                 infd.getParcelFileDescriptor().getFd(), infd.getStartOffset(), infd.getLength(),
656                 out.getFd(), webm));
657 
658         // compare the original with the remuxed
659         MediaExtractor org = new MediaExtractor();
660         org.setDataSource(infd.getFileDescriptor(),
661                 infd.getStartOffset(), infd.getLength());
662 
663         MediaExtractor remux = new MediaExtractor();
664         remux.setDataSource(out.getFileDescriptor());
665 
666         assertEquals("mismatched numer of tracks", org.getTrackCount(), remux.getTrackCount());
667         // allow duration mismatch for webm files as ffmpeg does not consider the duration of the
668         // last frame while libwebm (and our framework) does.
669         final long maxDurationDiffUs = webm ? 50000 : 0; // 50ms for webm
670         for (int i = 0; i < org.getTrackCount(); i++) {
671             MediaFormat format1 = org.getTrackFormat(i);
672             MediaFormat format2 = remux.getTrackFormat(i);
673             Log.i("@@@", "org: " + format1);
674             Log.i("@@@", "remux: " + format2);
675             assertTrue("different formats", compareFormats(format1, format2, maxDurationDiffUs));
676         }
677 
678         org.release();
679         remux.release();
680 
681         MediaPlayer player1 = MediaPlayer.create(mContext, res);
682         MediaPlayer player2 = MediaPlayer.create(mContext, Uri.parse("file://" + tmpFile));
683         assertEquals("duration is different",
684                      player1.getDuration(), player2.getDuration(), maxDurationDiffUs * 0.001);
685         player1.release();
686         player2.release();
687         new File(tmpFile).delete();
688     }
689 
hexString(ByteBuffer buf)690     private String hexString(ByteBuffer buf) {
691         if (buf == null) {
692             return "(null)";
693         }
694         final char digits[] =
695             { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
696 
697         StringBuilder hex = new StringBuilder();
698         for (int i = buf.position(); i < buf.limit(); ++i) {
699             byte c = buf.get(i);
700             hex.append(digits[(c >> 4) & 0xf]);
701             hex.append(digits[c & 0xf]);
702         }
703         return hex.toString();
704     }
705 
706     /** returns: null if key is in neither formats, true if they match and false otherwise */
compareByteBufferInFormats(MediaFormat f1, MediaFormat f2, String key)707     private Boolean compareByteBufferInFormats(MediaFormat f1, MediaFormat f2, String key) {
708         ByteBuffer bufF1 = f1.containsKey(key) ? f1.getByteBuffer(key) : null;
709         ByteBuffer bufF2 = f2.containsKey(key) ? f2.getByteBuffer(key) : null;
710         if (bufF1 == null && bufF2 == null) {
711             return null;
712         }
713         if (bufF1 == null || !bufF1.equals(bufF2)) {
714             Log.i("@@@", "org " + key + ": " + hexString(bufF1));
715             Log.i("@@@", "rmx " + key + ": " + hexString(bufF2));
716             return false;
717         }
718         return true;
719     }
720 
compareFormats(MediaFormat f1, MediaFormat f2, long maxDurationDiffUs)721     private boolean compareFormats(MediaFormat f1, MediaFormat f2, long maxDurationDiffUs) {
722         final String KEY_DURATION = MediaFormat.KEY_DURATION;
723 
724         // allow some difference in durations
725         if (maxDurationDiffUs > 0
726                 && f1.containsKey(KEY_DURATION) && f2.containsKey(KEY_DURATION)
727                 && Math.abs(f1.getLong(KEY_DURATION)
728                         - f2.getLong(KEY_DURATION)) <= maxDurationDiffUs) {
729             f2.setLong(KEY_DURATION, f1.getLong(KEY_DURATION));
730         }
731 
732         // verify hdr-static-info
733         if (Boolean.FALSE.equals(compareByteBufferInFormats(f1, f2, "hdr-static-info"))) {
734             return false;
735         }
736 
737         // verify CSDs
738         for (int i = 0;; ++i) {
739             String key = "csd-" + i;
740             Boolean match = compareByteBufferInFormats(f1, f2, key);
741             if (match == null) {
742                 break;
743             } else if (match == false) {
744                 return false;
745             }
746         }
747 
748         // there's no good way to compare two MediaFormats, so compare their string
749         // representation
750         return f1.toString().equals(f2.toString());
751     }
752 
testMuxerNative(int in, long inoffset, long insize, int out, boolean webm)753     private static native boolean testMuxerNative(int in, long inoffset, long insize,
754             int out, boolean webm);
755 
testFormat()756     public void testFormat() throws Exception {
757         assertTrue("media format fail, see log for details", testFormatNative());
758     }
759 
testFormatNative()760     private static native boolean testFormatNative();
761 
testPssh()762     public void testPssh() throws Exception {
763         testPssh(R.raw.psshtest);
764     }
765 
testPssh(int res)766     private void testPssh(int res) throws Exception {
767         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
768 
769         MediaExtractor ex = new MediaExtractor();
770         ex.setDataSource(fd.getParcelFileDescriptor().getFileDescriptor(),
771                 fd.getStartOffset(), fd.getLength());
772         testPssh(ex);
773         ex.release();
774 
775         boolean ret = testPsshNative(
776                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
777         assertTrue("native pssh error", ret);
778     }
779 
testPssh(MediaExtractor ex)780     private static void testPssh(MediaExtractor ex) {
781         Map<UUID, byte[]> map = ex.getPsshInfo();
782         Set<UUID> keys = map.keySet();
783         for (UUID uuid: keys) {
784             Log.i("@@@", "uuid: " + uuid + ", data size " +
785                     map.get(uuid).length);
786         }
787     }
788 
testPsshNative(int fd, long offset, long size)789     private static native boolean testPsshNative(int fd, long offset, long size);
790 
testCryptoInfo()791     public void testCryptoInfo() throws Exception {
792         assertTrue("native cryptoinfo failed, see log for details", testCryptoInfoNative());
793     }
794 
testCryptoInfoNative()795     private static native boolean testCryptoInfoNative();
796 
testMediaFormat()797     public void testMediaFormat() throws Exception {
798         assertTrue("native mediaformat failed, see log for details", testMediaFormatNative());
799     }
800 
testMediaFormatNative()801     private static native boolean testMediaFormatNative();
802 
testAMediaDataSourceClose()803     public void testAMediaDataSourceClose() throws Throwable {
804 
805         final CtsTestServer slowServer = new SlowCtsTestServer();
806         final String url = slowServer.getAssetUrl("noiseandchirps.ogg");
807         final long ds = createAMediaDataSource(url);
808         final long ex = createAMediaExtractor();
809 
810         try {
811             setAMediaExtractorDataSourceAndFailIfAnr(ex, ds);
812         } finally {
813             slowServer.shutdown();
814             deleteAMediaExtractor(ex);
815             deleteAMediaDataSource(ds);
816         }
817 
818     }
819 
setAMediaExtractorDataSourceAndFailIfAnr(final long ex, final long ds)820     private void setAMediaExtractorDataSourceAndFailIfAnr(final long ex, final long ds)
821             throws Throwable {
822         final Monitor setAMediaExtractorDataSourceDone = new Monitor();
823         final int HEAD_START_MILLIS = 1000;
824         final int ANR_TIMEOUT_MILLIS = 2500;
825         final int JOIN_TIMEOUT_MILLIS = 1500;
826 
827         Thread setAMediaExtractorDataSourceThread = new Thread() {
828             public void run() {
829                 setAMediaExtractorDataSource(ex, ds);
830                 setAMediaExtractorDataSourceDone.signal();
831             }
832         };
833 
834         try {
835             setAMediaExtractorDataSourceThread.start();
836             Thread.sleep(HEAD_START_MILLIS);
837             closeAMediaDataSource(ds);
838             boolean closed = setAMediaExtractorDataSourceDone.waitForSignal(ANR_TIMEOUT_MILLIS);
839             assertTrue("close took longer than " + ANR_TIMEOUT_MILLIS, closed);
840         } finally {
841             setAMediaExtractorDataSourceThread.join(JOIN_TIMEOUT_MILLIS);
842         }
843 
844     }
845 
846     private class SlowCtsTestServer extends CtsTestServer {
847 
848         private static final int SERVER_DELAY_MILLIS = 5000;
849         private final CountDownLatch mDisconnected = new CountDownLatch(1);
850 
SlowCtsTestServer()851         SlowCtsTestServer() throws Exception {
852             super(mContext);
853         }
854 
855         @Override
createHttpServerConnection()856         protected DefaultHttpServerConnection createHttpServerConnection() {
857             return new SlowHttpServerConnection(mDisconnected, SERVER_DELAY_MILLIS);
858         }
859 
860         @Override
shutdown()861         public void shutdown() {
862             mDisconnected.countDown();
863             super.shutdown();
864         }
865     }
866 
867     private static class SlowHttpServerConnection extends DefaultHttpServerConnection {
868 
869         private final CountDownLatch mDisconnected;
870         private final int mDelayMillis;
871 
SlowHttpServerConnection(CountDownLatch disconnected, int delayMillis)872         public SlowHttpServerConnection(CountDownLatch disconnected, int delayMillis) {
873             mDisconnected = disconnected;
874             mDelayMillis = delayMillis;
875         }
876 
877         @Override
createHttpDataTransmitter( Socket socket, int buffersize, HttpParams params)878         protected SessionOutputBuffer createHttpDataTransmitter(
879                 Socket socket, int buffersize, HttpParams params) throws IOException {
880             return createSessionOutputBuffer(socket, buffersize, params);
881         }
882 
createSessionOutputBuffer( Socket socket, int buffersize, HttpParams params)883         SessionOutputBuffer createSessionOutputBuffer(
884                 Socket socket, int buffersize, HttpParams params) throws IOException {
885             return new SocketOutputBuffer(socket, buffersize, params) {
886                 @Override
887                 public void write(byte[] b) throws IOException {
888                     write(b, 0, b.length);
889                 }
890 
891                 @Override
892                 public void write(byte[] b, int off, int len) throws IOException {
893                     while (len-- > 0) {
894                         write(b[off++]);
895                     }
896                 }
897 
898                 @Override
899                 public void writeLine(String s) throws IOException {
900                     delay();
901                     super.writeLine(s);
902                 }
903 
904                 @Override
905                 public void writeLine(CharArrayBuffer buffer) throws IOException {
906                     delay();
907                     super.writeLine(buffer);
908                 }
909 
910                 @Override
911                 public void write(int b) throws IOException {
912                     delay();
913                     super.write(b);
914                 }
915 
916                 private void delay() throws IOException {
917                     try {
918                         mDisconnected.await(mDelayMillis, TimeUnit.MILLISECONDS);
919                     } catch (InterruptedException e) {
920                         // Ignored
921                     }
922                 }
923 
924             };
925         }
926     }
927 
928     private static native long createAMediaExtractor();
929     private static native long createAMediaDataSource(String url);
930     private static native int  setAMediaExtractorDataSource(long ex, long ds);
931     private static native void closeAMediaDataSource(long ds);
932     private static native void deleteAMediaExtractor(long ex);
933     private static native void deleteAMediaDataSource(long ds);
934 
935 }
936 
937