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