1 /* 2 * Copyright (C) 2013 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.media.MediaCodec; 23 import android.media.MediaCodec.BufferInfo; 24 import android.media.MediaCodec.CodecException; 25 import android.media.MediaCodec.CryptoInfo; 26 import android.media.MediaCodec.CryptoInfo.Pattern; 27 import android.media.MediaCodecInfo; 28 import android.media.MediaCodecList; 29 import android.media.MediaCrypto; 30 import android.media.MediaDrm; 31 import android.media.MediaExtractor; 32 import android.media.MediaFormat; 33 import android.media.MediaCodecInfo.CodecCapabilities; 34 import android.media.MediaCodecInfo.CodecProfileLevel; 35 import android.opengl.GLES20; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.PersistableBundle; 39 import android.support.test.filters.SmallTest; 40 import android.platform.test.annotations.RequiresDevice; 41 import android.test.AndroidTestCase; 42 import android.util.Log; 43 import android.view.Surface; 44 45 import com.android.compatibility.common.util.MediaUtils; 46 47 import java.io.IOException; 48 import java.nio.ByteBuffer; 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.UUID; 52 import java.util.concurrent.CountDownLatch; 53 import java.util.concurrent.TimeUnit; 54 import java.util.concurrent.atomic.AtomicBoolean; 55 import java.util.concurrent.atomic.AtomicInteger; 56 57 /** 58 * General MediaCodec tests. 59 * 60 * In particular, check various API edge cases. 61 * 62 * <p>The file in res/raw used by testDecodeShortInput are (c) copyright 2008, 63 * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons 64 * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/. 65 */ 66 @SmallTest 67 @RequiresDevice 68 public class MediaCodecTest extends AndroidTestCase { 69 private static final String TAG = "MediaCodecTest"; 70 private static final boolean VERBOSE = false; // lots of logging 71 72 // parameters for the video encoder 73 // H.264 Advanced Video Coding 74 private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC; 75 private static final int BIT_RATE = 2000000; // 2Mbps 76 private static final int FRAME_RATE = 15; // 15fps 77 private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames 78 private static final int WIDTH = 1280; 79 private static final int HEIGHT = 720; 80 // parameters for the audio encoder 81 private static final String MIME_TYPE_AUDIO = MediaFormat.MIMETYPE_AUDIO_AAC; 82 private static final int AUDIO_SAMPLE_RATE = 44100; 83 private static final int AUDIO_AAC_PROFILE = 2; /* OMX_AUDIO_AACObjectLC */ 84 private static final int AUDIO_CHANNEL_COUNT = 2; // mono 85 private static final int AUDIO_BIT_RATE = 128000; 86 87 private static final int TIMEOUT_USEC = 100000; 88 private static final int TIMEOUT_USEC_SHORT = 100; 89 90 private boolean mVideoEncoderHadError = false; 91 private boolean mAudioEncoderHadError = false; 92 private volatile boolean mVideoEncodingOngoing = false; 93 94 private static final int INPUT_RESOURCE_ID = 95 R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz; 96 97 // The test should fail if the decoder never produces output frames for the input. 98 // Time out decoding, as we have no way to query whether the decoder will produce output. 99 private static final int DECODING_TIMEOUT_MS = 10000; 100 101 /** 102 * Tests: 103 * <br> Exceptions for MediaCodec factory methods 104 * <br> Exceptions for MediaCodec methods when called in the incorrect state. 105 * 106 * A selective test to ensure proper exceptions are thrown from MediaCodec 107 * methods when called in incorrect operational states. 108 */ 109 public void testException() throws Exception { 110 boolean tested = false; 111 // audio decoder (MP3 should be present on all Android devices) 112 MediaFormat format = MediaFormat.createAudioFormat( 113 MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */); 114 tested = verifyException(format, false /* isEncoder */) || tested; 115 116 // audio encoder (AMR-WB may not be present on some Android devices) 117 format = MediaFormat.createAudioFormat( 118 MediaFormat.MIMETYPE_AUDIO_AMR_WB, 16000 /* sampleRate */, 1 /* channelCount */); 119 format.setInteger(MediaFormat.KEY_BIT_RATE, 19850); 120 tested = verifyException(format, true /* isEncoder */) || tested; 121 122 // video decoder (H.264/AVC may not be present on some Android devices) 123 format = createMediaFormat(); 124 tested = verifyException(format, false /* isEncoder */) || tested; 125 126 // video encoder (H.264/AVC may not be present on some Android devices) 127 tested = verifyException(format, true /* isEncoder */) || tested; 128 129 // signal test is skipped due to no device media codecs. 130 if (!tested) { 131 MediaUtils.skipTest(TAG, "cannot find any compatible device codecs"); 132 } 133 } 134 135 // wrap MediaCodec encoder and decoder creation 136 private static MediaCodec createCodecByType(String type, boolean isEncoder) 137 throws IOException { 138 if (isEncoder) { 139 return MediaCodec.createEncoderByType(type); 140 } 141 return MediaCodec.createDecoderByType(type); 142 } 143 144 private static void logMediaCodecException(MediaCodec.CodecException ex) { 145 if (ex.isRecoverable()) { 146 Log.w(TAG, "CodecException Recoverable: " + ex.getErrorCode()); 147 } else if (ex.isTransient()) { 148 Log.w(TAG, "CodecException Transient: " + ex.getErrorCode()); 149 } else { 150 Log.w(TAG, "CodecException Fatal: " + ex.getErrorCode()); 151 } 152 } 153 154 private static boolean verifyException(MediaFormat format, boolean isEncoder) 155 throws IOException { 156 String mimeType = format.getString(MediaFormat.KEY_MIME); 157 if (!supportsCodec(mimeType, isEncoder)) { 158 Log.i(TAG, "No " + (isEncoder ? "encoder" : "decoder") 159 + " found for mimeType= " + mimeType); 160 return false; 161 } 162 163 final boolean isVideoEncoder = isEncoder && mimeType.startsWith("video/"); 164 165 // create codec (enter Initialized State) 166 MediaCodec codec; 167 168 // create improperly 169 final String methodName = isEncoder ? "createEncoderByType" : "createDecoderByType"; 170 try { 171 codec = createCodecByType(null, isEncoder); 172 fail(methodName + " should return NullPointerException on null"); 173 } catch (NullPointerException e) { // expected 174 } 175 try { 176 codec = createCodecByType("foobarplan9", isEncoder); // invalid type 177 fail(methodName + " should return IllegalArgumentException on invalid type"); 178 } catch (IllegalArgumentException e) { // expected 179 } 180 try { 181 codec = MediaCodec.createByCodecName("foobarplan9"); // invalid name 182 fail(methodName + " should return IllegalArgumentException on invalid name"); 183 } catch (IllegalArgumentException e) { // expected 184 } 185 // correct 186 codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder); 187 188 // test a few commands 189 try { 190 codec.start(); 191 fail("start should return IllegalStateException when in Initialized state"); 192 } catch (MediaCodec.CodecException e) { 193 logMediaCodecException(e); 194 fail("start should not return MediaCodec.CodecException on wrong state"); 195 } catch (IllegalStateException e) { // expected 196 } 197 try { 198 codec.flush(); 199 fail("flush should return IllegalStateException when in Initialized state"); 200 } catch (MediaCodec.CodecException e) { 201 logMediaCodecException(e); 202 fail("flush should not return MediaCodec.CodecException on wrong state"); 203 } catch (IllegalStateException e) { // expected 204 } 205 MediaCodecInfo codecInfo = codec.getCodecInfo(); // obtaining the codec info now is fine. 206 try { 207 int bufIndex = codec.dequeueInputBuffer(0); 208 fail("dequeueInputBuffer should return IllegalStateException" 209 + " when in the Initialized state"); 210 } catch (MediaCodec.CodecException e) { 211 logMediaCodecException(e); 212 fail("dequeueInputBuffer should not return MediaCodec.CodecException" 213 + " on wrong state"); 214 } catch (IllegalStateException e) { // expected 215 } 216 try { 217 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 218 int bufIndex = codec.dequeueOutputBuffer(info, 0); 219 fail("dequeueOutputBuffer should return IllegalStateException" 220 + " when in the Initialized state"); 221 } catch (MediaCodec.CodecException e) { 222 logMediaCodecException(e); 223 fail("dequeueOutputBuffer should not return MediaCodec.CodecException" 224 + " on wrong state"); 225 } catch (IllegalStateException e) { // expected 226 } 227 228 // configure (enter Configured State) 229 230 // configure improperly 231 try { 232 codec.configure(format, null /* surface */, null /* crypto */, 233 isEncoder ? 0 : MediaCodec.CONFIGURE_FLAG_ENCODE /* flags */); 234 fail("configure needs MediaCodec.CONFIGURE_FLAG_ENCODE for encoders only"); 235 } catch (MediaCodec.CodecException e) { // expected 236 logMediaCodecException(e); 237 } catch (IllegalStateException e) { 238 fail("configure should not return IllegalStateException when improperly configured"); 239 } 240 // correct 241 codec.configure(format, null /* surface */, null /* crypto */, 242 isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0 /* flags */); 243 244 // test a few commands 245 try { 246 codec.flush(); 247 fail("flush should return IllegalStateException when in Configured state"); 248 } catch (MediaCodec.CodecException e) { 249 logMediaCodecException(e); 250 fail("flush should not return MediaCodec.CodecException on wrong state"); 251 } catch (IllegalStateException e) { // expected 252 } 253 try { 254 Surface surface = codec.createInputSurface(); 255 if (!isEncoder) { 256 fail("createInputSurface should not work on a decoder"); 257 } 258 } catch (IllegalStateException e) { // expected for decoder and audio encoder 259 if (isVideoEncoder) { 260 throw e; 261 } 262 } 263 264 // test getInputBuffers before start() 265 try { 266 ByteBuffer[] buffers = codec.getInputBuffers(); 267 fail("getInputBuffers called before start() should throw exception"); 268 } catch (IllegalStateException e) { // expected 269 } 270 271 // start codec (enter Executing state) 272 codec.start(); 273 274 // test getInputBuffers after start() 275 try { 276 ByteBuffer[] buffers = codec.getInputBuffers(); 277 if (buffers == null) { 278 fail("getInputBuffers called after start() should not return null"); 279 } 280 if (isVideoEncoder && buffers.length > 0) { 281 fail("getInputBuffers returned non-zero length array with input surface"); 282 } 283 } catch (IllegalStateException e) { 284 fail("getInputBuffers called after start() shouldn't throw exception"); 285 } 286 287 // test a few commands 288 try { 289 codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */); 290 fail("configure should return IllegalStateException when in Executing state"); 291 } catch (MediaCodec.CodecException e) { 292 logMediaCodecException(e); 293 // TODO: consider configuring after a flush. 294 fail("configure should not return MediaCodec.CodecException on wrong state"); 295 } catch (IllegalStateException e) { // expected 296 } 297 298 // two flushes should be fine. 299 codec.flush(); 300 codec.flush(); 301 302 // stop codec (enter Initialized state) 303 // two stops should be fine. 304 codec.stop(); 305 codec.stop(); 306 307 // release codec (enter Uninitialized state) 308 // two releases should be fine. 309 codec.release(); 310 codec.release(); 311 312 try { 313 codecInfo = codec.getCodecInfo(); 314 fail("getCodecInfo should should return IllegalStateException" + 315 " when in Uninitialized state"); 316 } catch (MediaCodec.CodecException e) { 317 logMediaCodecException(e); 318 fail("getCodecInfo should not return MediaCodec.CodecException on wrong state"); 319 } catch (IllegalStateException e) { // expected 320 } 321 try { 322 codec.stop(); 323 fail("stop should return IllegalStateException when in Uninitialized state"); 324 } catch (MediaCodec.CodecException e) { 325 logMediaCodecException(e); 326 fail("stop should not return MediaCodec.CodecException on wrong state"); 327 } catch (IllegalStateException e) { // expected 328 } 329 return true; 330 } 331 332 /** 333 * Tests: 334 * <br> calling createInputSurface() before configure() throws exception 335 * <br> calling createInputSurface() after start() throws exception 336 * <br> calling createInputSurface() with a non-Surface color format is not required to throw exception 337 */ 338 public void testCreateInputSurfaceErrors() { 339 if (!supportsCodec(MIME_TYPE, true)) { 340 Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE); 341 return; 342 } 343 344 MediaFormat format = createMediaFormat(); 345 MediaCodec encoder = null; 346 Surface surface = null; 347 348 // Replace color format with something that isn't COLOR_FormatSurface. 349 MediaCodecInfo codecInfo = selectCodec(MIME_TYPE); 350 int colorFormat = findNonSurfaceColorFormat(codecInfo, MIME_TYPE); 351 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); 352 353 try { 354 try { 355 encoder = MediaCodec.createByCodecName(codecInfo.getName()); 356 } catch (IOException e) { 357 fail("failed to create codec " + codecInfo.getName()); 358 } 359 try { 360 surface = encoder.createInputSurface(); 361 fail("createInputSurface should not work pre-configure"); 362 } catch (IllegalStateException ise) { 363 // good 364 } 365 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 366 encoder.start(); 367 try { 368 surface = encoder.createInputSurface(); 369 fail("createInputSurface should not work post-start"); 370 } catch (IllegalStateException ise) { 371 // good 372 } 373 } finally { 374 if (encoder != null) { 375 encoder.stop(); 376 encoder.release(); 377 } 378 } 379 assertNull(surface); 380 } 381 382 /** 383 * Tests: 384 * <br> signaling end-of-stream before any data is sent works 385 * <br> signaling EOS twice throws exception 386 * <br> submitting a frame after EOS throws exception [TODO] 387 */ 388 public void testSignalSurfaceEOS() { 389 if (!supportsCodec(MIME_TYPE, true)) { 390 Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE); 391 return; 392 } 393 394 MediaFormat format = createMediaFormat(); 395 MediaCodec encoder = null; 396 InputSurface inputSurface = null; 397 398 try { 399 try { 400 encoder = MediaCodec.createEncoderByType(MIME_TYPE); 401 } catch (IOException e) { 402 fail("failed to create " + MIME_TYPE + " encoder"); 403 } 404 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 405 inputSurface = new InputSurface(encoder.createInputSurface()); 406 inputSurface.makeCurrent(); 407 encoder.start(); 408 409 // send an immediate EOS 410 encoder.signalEndOfInputStream(); 411 412 try { 413 encoder.signalEndOfInputStream(); 414 fail("should not be able to signal EOS twice"); 415 } catch (IllegalStateException ise) { 416 // good 417 } 418 419 // submit a frame post-EOS 420 GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f); 421 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 422 try { 423 inputSurface.swapBuffers(); 424 if (false) { // TODO 425 fail("should not be able to submit frame after EOS"); 426 } 427 } catch (Exception ex) { 428 // good 429 } 430 } finally { 431 if (encoder != null) { 432 encoder.stop(); 433 encoder.release(); 434 } 435 if (inputSurface != null) { 436 inputSurface.release(); 437 } 438 } 439 } 440 441 /** 442 * Tests: 443 * <br> stopping with buffers in flight doesn't crash or hang 444 */ 445 public void testAbruptStop() { 446 if (!supportsCodec(MIME_TYPE, true)) { 447 Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE); 448 return; 449 } 450 451 // There appears to be a race, so run it several times with a short delay between runs 452 // to allow any previous activity to shut down. 453 for (int i = 0; i < 50; i++) { 454 Log.d(TAG, "testAbruptStop " + i); 455 doTestAbruptStop(); 456 try { Thread.sleep(400); } catch (InterruptedException ignored) {} 457 } 458 } 459 private void doTestAbruptStop() { 460 MediaFormat format = createMediaFormat(); 461 MediaCodec encoder = null; 462 InputSurface inputSurface = null; 463 464 try { 465 try { 466 encoder = MediaCodec.createEncoderByType(MIME_TYPE); 467 } catch (IOException e) { 468 fail("failed to create " + MIME_TYPE + " encoder"); 469 } 470 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 471 inputSurface = new InputSurface(encoder.createInputSurface()); 472 inputSurface.makeCurrent(); 473 encoder.start(); 474 475 int totalBuffers = encoder.getOutputBuffers().length; 476 if (VERBOSE) Log.d(TAG, "Total buffers: " + totalBuffers); 477 478 // Submit several frames quickly, without draining the encoder output, to try to 479 // ensure that we've got some queued up when we call stop(). If we do too many 480 // we'll block in swapBuffers(). 481 for (int i = 0; i < totalBuffers; i++) { 482 GLES20.glClearColor(0.0f, (i % 8) / 8.0f, 0.0f, 1.0f); 483 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 484 inputSurface.swapBuffers(); 485 } 486 Log.d(TAG, "stopping"); 487 encoder.stop(); 488 Log.d(TAG, "stopped"); 489 } finally { 490 if (encoder != null) { 491 encoder.stop(); 492 encoder.release(); 493 } 494 if (inputSurface != null) { 495 inputSurface.release(); 496 } 497 } 498 } 499 500 public void testReleaseAfterFlush() throws IOException, InterruptedException { 501 String mimes[] = new String[] { MIME_TYPE, MIME_TYPE_AUDIO}; 502 for (String mime : mimes) { 503 if (!MediaUtils.checkEncoder(mime)) { 504 continue; 505 } 506 testReleaseAfterFlush(mime); 507 } 508 } 509 510 private void testReleaseAfterFlush(String mime) throws IOException, InterruptedException { 511 CountDownLatch buffersExhausted = null; 512 CountDownLatch codecFlushed = null; 513 AtomicInteger numBuffers = null; 514 515 // sync flush from same thread 516 MediaCodec encoder = MediaCodec.createEncoderByType(mime); 517 runReleaseAfterFlush(mime, encoder, buffersExhausted, codecFlushed, numBuffers); 518 519 // sync flush from different thread 520 encoder = MediaCodec.createEncoderByType(mime); 521 buffersExhausted = new CountDownLatch(1); 522 codecFlushed = new CountDownLatch(1); 523 numBuffers = new AtomicInteger(); 524 Thread flushThread = new FlushThread(encoder, buffersExhausted, codecFlushed); 525 flushThread.start(); 526 runReleaseAfterFlush(mime, encoder, buffersExhausted, codecFlushed, numBuffers); 527 flushThread.join(); 528 529 // async 530 // This value is calculated in getOutputBufferIndices by calling dequeueOutputBuffer 531 // with a fixed timeout until buffers are exhausted; it is possible that random timing 532 // in dequeueOutputBuffer can result in a smaller `nBuffs` than the max possible value. 533 int nBuffs = numBuffers.get(); 534 HandlerThread callbackThread = new HandlerThread("ReleaseAfterFlushCallbackThread"); 535 callbackThread.start(); 536 Handler handler = new Handler(callbackThread.getLooper()); 537 538 // async flush from same thread 539 encoder = MediaCodec.createEncoderByType(mime); 540 buffersExhausted = null; 541 codecFlushed = null; 542 ReleaseAfterFlushCallback callback = 543 new ReleaseAfterFlushCallback(mime, encoder, buffersExhausted, codecFlushed, nBuffs); 544 encoder.setCallback(callback, handler); // setCallback before configure, which is called in run 545 callback.run(); // drive input on main thread 546 547 // async flush from different thread 548 encoder = MediaCodec.createEncoderByType(mime); 549 buffersExhausted = new CountDownLatch(1); 550 codecFlushed = new CountDownLatch(1); 551 callback = new ReleaseAfterFlushCallback(mime, encoder, buffersExhausted, codecFlushed, nBuffs); 552 encoder.setCallback(callback, handler); 553 flushThread = new FlushThread(encoder, buffersExhausted, codecFlushed); 554 flushThread.start(); 555 callback.run(); 556 flushThread.join(); 557 558 callbackThread.quitSafely(); 559 callbackThread.join(); 560 } 561 562 public void testAsyncFlushAndReset() throws Exception, InterruptedException { 563 testAsyncReset(false /* testStop */); 564 } 565 566 public void testAsyncStopAndReset() throws Exception, InterruptedException { 567 testAsyncReset(true /* testStop */); 568 } 569 570 private void testAsyncReset(boolean testStop) throws Exception, InterruptedException { 571 // Test video and audio 10x each 572 for (int i = 0; i < 10; i++) { 573 testAsyncReset(false /* audio */, (i % 2) == 0 /* swap */, testStop); 574 } 575 for (int i = 0; i < 10; i++) { 576 testAsyncReset(true /* audio */, (i % 2) == 0 /* swap */, testStop); 577 } 578 } 579 580 /* 581 * This method simulates a race between flush (or stop) and reset() called from 582 * two threads. Neither call should get stuck. This should be run multiple rounds. 583 */ 584 private void testAsyncReset(boolean audio, boolean swap, final boolean testStop) 585 throws Exception, InterruptedException { 586 String mimeTypePrefix = audio ? "audio/" : "video/"; 587 final MediaExtractor mediaExtractor = getMediaExtractorForMimeType( 588 INPUT_RESOURCE_ID, mimeTypePrefix); 589 MediaFormat mediaFormat = mediaExtractor.getTrackFormat( 590 mediaExtractor.getSampleTrackIndex()); 591 if (!MediaUtils.checkDecoderForFormat(mediaFormat)) { 592 return; // skip 593 } 594 595 OutputSurface outputSurface = audio ? null : new OutputSurface(1, 1); 596 final Surface surface = outputSurface == null ? null : outputSurface.getSurface(); 597 598 String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); 599 final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mimeType); 600 601 try { 602 mediaCodec.configure(mediaFormat, surface, null /* crypto */, 0 /* flags */); 603 604 mediaCodec.start(); 605 606 assertTrue(runDecodeTillFirstOutput(mediaCodec, mediaExtractor)); 607 608 Thread flushingThread = new Thread(new Runnable() { 609 @Override 610 public void run() { 611 try { 612 if (testStop) { 613 mediaCodec.stop(); 614 } else { 615 mediaCodec.flush(); 616 } 617 } catch (IllegalStateException e) { 618 // This is okay, since we're simulating a race between flush and reset. 619 // If reset executed first, flush could fail. 620 } 621 } 622 }); 623 624 Thread resettingThread = new Thread(new Runnable() { 625 @Override 626 public void run() { 627 mediaCodec.reset(); 628 } 629 }); 630 631 // start flushing (or stopping) and resetting in two threads 632 if (swap) { 633 flushingThread.start(); 634 resettingThread.start(); 635 } else { 636 resettingThread.start(); 637 flushingThread.start(); 638 } 639 640 // wait for at most 5 sec, and check if the thread exits properly 641 flushingThread.join(5000); 642 assertFalse(flushingThread.isAlive()); 643 644 resettingThread.join(5000); 645 assertFalse(resettingThread.isAlive()); 646 } finally { 647 if (mediaCodec != null) { 648 mediaCodec.release(); 649 } 650 if (mediaExtractor != null) { 651 mediaExtractor.release(); 652 } 653 if (outputSurface != null) { 654 outputSurface.release(); 655 } 656 } 657 } 658 659 private static class FlushThread extends Thread { 660 final MediaCodec mEncoder; 661 final CountDownLatch mBuffersExhausted; 662 final CountDownLatch mCodecFlushed; 663 664 FlushThread(MediaCodec encoder, CountDownLatch buffersExhausted, 665 CountDownLatch codecFlushed) { 666 mEncoder = encoder; 667 mBuffersExhausted = buffersExhausted; 668 mCodecFlushed = codecFlushed; 669 } 670 671 @Override 672 public void run() { 673 try { 674 mBuffersExhausted.await(); 675 } catch (InterruptedException e) { 676 Thread.currentThread().interrupt(); 677 Log.w(TAG, "buffersExhausted wait interrupted; flushing immediately.", e); 678 } 679 mEncoder.flush(); 680 mCodecFlushed.countDown(); 681 } 682 } 683 684 private static class ReleaseAfterFlushCallback extends MediaCodec.Callback implements Runnable { 685 final String mMime; 686 final MediaCodec mEncoder; 687 final CountDownLatch mBuffersExhausted, mCodecFlushed; 688 final int mNumBuffersBeforeFlush; 689 690 CountDownLatch mStopInput = new CountDownLatch(1); 691 List<Integer> mInputBufferIndices = new ArrayList<>(); 692 List<Integer> mOutputBufferIndices = new ArrayList<>(); 693 694 ReleaseAfterFlushCallback(String mime, 695 MediaCodec encoder, 696 CountDownLatch buffersExhausted, 697 CountDownLatch codecFlushed, 698 int numBuffersBeforeFlush) { 699 mMime = mime; 700 mEncoder = encoder; 701 mBuffersExhausted = buffersExhausted; 702 mCodecFlushed = codecFlushed; 703 mNumBuffersBeforeFlush = numBuffersBeforeFlush; 704 } 705 706 @Override 707 public void onInputBufferAvailable(MediaCodec codec, int index) { 708 assertTrue("video onInputBufferAvailable " + index, mMime.startsWith("audio/")); 709 synchronized (mInputBufferIndices) { 710 mInputBufferIndices.add(index); 711 }; 712 } 713 714 @Override 715 public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) { 716 mOutputBufferIndices.add(index); 717 if (mOutputBufferIndices.size() == mNumBuffersBeforeFlush) { 718 releaseAfterFlush(codec, mOutputBufferIndices, mBuffersExhausted, mCodecFlushed); 719 mStopInput.countDown(); 720 } 721 } 722 723 @Override 724 public void onError(MediaCodec codec, CodecException e) { 725 Log.e(TAG, codec + " onError", e); 726 fail(codec + " onError " + e.getMessage()); 727 } 728 729 @Override 730 public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { 731 Log.v(TAG, codec + " onOutputFormatChanged " + format); 732 } 733 734 @Override 735 public void run() { 736 InputSurface inputSurface = null; 737 try { 738 inputSurface = initCodecAndSurface(mMime, mEncoder); 739 do { 740 int inputIndex = -1; 741 if (inputSurface == null) { 742 // asynchronous audio codec 743 synchronized (mInputBufferIndices) { 744 if (mInputBufferIndices.isEmpty()) { 745 continue; 746 } else { 747 inputIndex = mInputBufferIndices.remove(0); 748 } 749 } 750 } 751 feedEncoder(mEncoder, inputSurface, inputIndex); 752 } while (!mStopInput.await(TIMEOUT_USEC, TimeUnit.MICROSECONDS)); 753 } catch (InterruptedException e) { 754 Thread.currentThread().interrupt(); 755 Log.w(TAG, "mEncoder input frames interrupted/stopped", e); 756 } finally { 757 cleanupCodecAndSurface(mEncoder, inputSurface); 758 } 759 } 760 } 761 762 private static void runReleaseAfterFlush( 763 String mime, 764 MediaCodec encoder, 765 CountDownLatch buffersExhausted, 766 CountDownLatch codecFlushed, 767 AtomicInteger numBuffers) { 768 InputSurface inputSurface = null; 769 try { 770 inputSurface = initCodecAndSurface(mime, encoder); 771 List<Integer> outputBufferIndices = getOutputBufferIndices(encoder, inputSurface); 772 if (numBuffers != null) { 773 numBuffers.set(outputBufferIndices.size()); 774 } 775 releaseAfterFlush(encoder, outputBufferIndices, buffersExhausted, codecFlushed); 776 } finally { 777 cleanupCodecAndSurface(encoder, inputSurface); 778 } 779 } 780 781 private static InputSurface initCodecAndSurface(String mime, MediaCodec encoder) { 782 MediaFormat format; 783 InputSurface inputSurface = null; 784 if (mime.startsWith("audio/")) { 785 format = MediaFormat.createAudioFormat(mime, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT); 786 format.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE); 787 format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE); 788 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 789 } else if (MIME_TYPE.equals(mime)) { 790 CodecInfo info = getAvcSupportedFormatInfo(); 791 format = MediaFormat.createVideoFormat(mime, info.mMaxW, info.mMaxH); 792 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 793 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 794 format.setInteger(MediaFormat.KEY_BIT_RATE, info.mBitRate); 795 format.setInteger(MediaFormat.KEY_FRAME_RATE, info.mFps); 796 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 797 OutputSurface outputSurface = new OutputSurface(1, 1); 798 encoder.configure(format, outputSurface.getSurface(), null, MediaCodec.CONFIGURE_FLAG_ENCODE); 799 inputSurface = new InputSurface(encoder.createInputSurface()); 800 inputSurface.makeCurrent(); 801 } else { 802 throw new IllegalArgumentException("unsupported mime type: " + mime); 803 } 804 encoder.start(); 805 return inputSurface; 806 } 807 808 private static void cleanupCodecAndSurface(MediaCodec encoder, InputSurface inputSurface) { 809 if (encoder != null) { 810 encoder.stop(); 811 encoder.release(); 812 } 813 814 if (inputSurface != null) { 815 inputSurface.release(); 816 } 817 } 818 819 private static List<Integer> getOutputBufferIndices(MediaCodec encoder, InputSurface inputSurface) { 820 boolean feedMoreFrames; 821 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 822 List<Integer> indices = new ArrayList<>(); 823 do { 824 feedMoreFrames = indices.isEmpty(); 825 feedEncoder(encoder, inputSurface, -1); 826 // dequeue buffers until not available 827 int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); 828 while (index >= 0) { 829 indices.add(index); 830 index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT); 831 } 832 } while (feedMoreFrames); 833 assertFalse(indices.isEmpty()); 834 return indices; 835 } 836 837 /** 838 * @param encoder audio/video encoder 839 * @param inputSurface null for and only for audio encoders 840 * @param inputIndex only used for audio; if -1 the function would attempt to dequeue from encoder; 841 * do not use -1 for asynchronous encoders 842 */ 843 private static void feedEncoder(MediaCodec encoder, InputSurface inputSurface, int inputIndex) { 844 if (inputSurface == null) { 845 // audio 846 while (inputIndex == -1) { 847 inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); 848 } 849 ByteBuffer inputBuffer = encoder.getInputBuffer(inputIndex);; 850 for (int i = 0; i < inputBuffer.capacity() / 2; i++) { 851 inputBuffer.putShort((short)i); 852 } 853 encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0); 854 } else { 855 // video 856 GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f); 857 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 858 inputSurface.swapBuffers(); 859 } 860 } 861 862 private static void releaseAfterFlush( 863 MediaCodec encoder, 864 List<Integer> outputBufferIndices, 865 CountDownLatch buffersExhausted, 866 CountDownLatch codecFlushed) { 867 if (buffersExhausted == null) { 868 // flush from same thread 869 encoder.flush(); 870 } else { 871 assertNotNull(codecFlushed); 872 buffersExhausted.countDown(); 873 try { 874 codecFlushed.await(); 875 } catch (InterruptedException e) { 876 Thread.currentThread().interrupt(); 877 Log.w(TAG, "codecFlushed wait interrupted; releasing buffers immediately.", e); 878 } 879 } 880 881 for (int index : outputBufferIndices) { 882 try { 883 encoder.releaseOutputBuffer(index, true); 884 fail("MediaCodec releaseOutputBuffer after flush() does not throw exception"); 885 } catch (MediaCodec.CodecException e) { 886 // Expected 887 } 888 } 889 } 890 891 /** 892 * Tests: 893 * <br> dequeueInputBuffer() fails when encoder configured with an input Surface 894 */ 895 public void testDequeueSurface() { 896 if (!supportsCodec(MIME_TYPE, true)) { 897 Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE); 898 return; 899 } 900 901 MediaFormat format = createMediaFormat(); 902 MediaCodec encoder = null; 903 Surface surface = null; 904 905 try { 906 try { 907 encoder = MediaCodec.createEncoderByType(MIME_TYPE); 908 } catch (IOException e) { 909 fail("failed to create " + MIME_TYPE + " encoder"); 910 } 911 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 912 surface = encoder.createInputSurface(); 913 encoder.start(); 914 915 try { 916 encoder.dequeueInputBuffer(-1); 917 fail("dequeueInputBuffer should fail on encoder with input surface"); 918 } catch (IllegalStateException ise) { 919 // good 920 } 921 922 PersistableBundle metrics = encoder.getMetrics(); 923 if (metrics == null) { 924 fail("getMetrics() returns null"); 925 } else if (metrics.isEmpty()) { 926 fail("getMetrics() returns empty results"); 927 } 928 int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1); 929 if (encoding != 1) { 930 fail("getMetrics() returns bad encoder value " + encoding); 931 } 932 String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null); 933 if (theCodec == null) { 934 fail("getMetrics() returns null codec value "); 935 } 936 937 } finally { 938 if (encoder != null) { 939 encoder.stop(); 940 encoder.release(); 941 } 942 if (surface != null) { 943 surface.release(); 944 } 945 } 946 } 947 948 /** 949 * Tests: 950 * <br> configure() encoder with Surface, re-configure() without Surface works 951 * <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails 952 */ 953 public void testReconfigureWithoutSurface() { 954 if (!supportsCodec(MIME_TYPE, true)) { 955 Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE); 956 return; 957 } 958 959 MediaFormat format = createMediaFormat(); 960 MediaCodec encoder = null; 961 Surface surface = null; 962 963 try { 964 try { 965 encoder = MediaCodec.createEncoderByType(MIME_TYPE); 966 } catch (IOException e) { 967 fail("failed to create " + MIME_TYPE + " encoder"); 968 } 969 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 970 surface = encoder.createInputSurface(); 971 encoder.start(); 972 973 encoder.getOutputBuffers(); 974 975 // re-configure, this time without an input surface 976 if (VERBOSE) Log.d(TAG, "reconfiguring"); 977 encoder.stop(); 978 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 979 encoder.start(); 980 if (VERBOSE) Log.d(TAG, "reconfigured"); 981 982 encoder.getOutputBuffers(); 983 encoder.dequeueInputBuffer(-1); 984 985 try { 986 encoder.signalEndOfInputStream(); 987 fail("signalEndOfInputStream only works on surface input"); 988 } catch (IllegalStateException ise) { 989 // good 990 } 991 992 PersistableBundle metrics = encoder.getMetrics(); 993 if (metrics == null) { 994 fail("getMetrics() returns null"); 995 } else if (metrics.isEmpty()) { 996 fail("getMetrics() returns empty results"); 997 } 998 int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1); 999 if (encoding != 1) { 1000 fail("getMetrics() returns bad encoder value " + encoding); 1001 } 1002 String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null); 1003 if (theCodec == null) { 1004 fail("getMetrics() returns null codec value "); 1005 } 1006 1007 } finally { 1008 if (encoder != null) { 1009 encoder.stop(); 1010 encoder.release(); 1011 } 1012 if (surface != null) { 1013 surface.release(); 1014 } 1015 } 1016 } 1017 1018 public void testDecodeAfterFlush() throws InterruptedException { 1019 testDecodeAfterFlush(true /* audio */); 1020 testDecodeAfterFlush(false /* audio */); 1021 } 1022 1023 private void testDecodeAfterFlush(final boolean audio) throws InterruptedException { 1024 final AtomicBoolean completed = new AtomicBoolean(false); 1025 Thread decodingThread = new Thread(new Runnable() { 1026 @Override 1027 public void run() { 1028 OutputSurface outputSurface = null; 1029 MediaExtractor mediaExtractor = null; 1030 MediaCodec mediaCodec = null; 1031 try { 1032 String mimeTypePrefix = audio ? "audio/" : "video/"; 1033 if (!audio) { 1034 outputSurface = new OutputSurface(1, 1); 1035 } 1036 mediaExtractor = getMediaExtractorForMimeType(INPUT_RESOURCE_ID, mimeTypePrefix); 1037 MediaFormat mediaFormat = 1038 mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex()); 1039 if (!MediaUtils.checkDecoderForFormat(mediaFormat)) { 1040 completed.set(true); 1041 return; // skip 1042 } 1043 String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); 1044 mediaCodec = MediaCodec.createDecoderByType(mimeType); 1045 mediaCodec.configure(mediaFormat, outputSurface == null ? null : outputSurface.getSurface(), 1046 null /* crypto */, 0 /* flags */); 1047 mediaCodec.start(); 1048 1049 if (!runDecodeTillFirstOutput(mediaCodec, mediaExtractor)) { 1050 throw new RuntimeException("decoder does not generate non-empty output."); 1051 } 1052 1053 PersistableBundle metrics = mediaCodec.getMetrics(); 1054 if (metrics == null) { 1055 fail("getMetrics() returns null"); 1056 } else if (metrics.isEmpty()) { 1057 fail("getMetrics() returns empty results"); 1058 } 1059 int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1); 1060 if (encoder != 0) { 1061 fail("getMetrics() returns bad encoder value " + encoder); 1062 } 1063 String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null); 1064 if (theCodec == null) { 1065 fail("getMetrics() returns null codec value "); 1066 } 1067 1068 1069 // simulate application flush. 1070 mediaExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC); 1071 mediaCodec.flush(); 1072 1073 completed.set(runDecodeTillFirstOutput(mediaCodec, mediaExtractor)); 1074 metrics = mediaCodec.getMetrics(); 1075 if (metrics == null) { 1076 fail("getMetrics() returns null"); 1077 } else if (metrics.isEmpty()) { 1078 fail("getMetrics() returns empty results"); 1079 } 1080 int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1); 1081 if (encoding != 0) { 1082 fail("getMetrics() returns bad encoder value " + encoding); 1083 } 1084 String theCodec2 = metrics.getString(MediaCodec.MetricsConstants.CODEC, null); 1085 if (theCodec2 == null) { 1086 fail("getMetrics() returns null codec value "); 1087 } 1088 1089 } catch (IOException e) { 1090 throw new RuntimeException("error setting up decoding", e); 1091 } finally { 1092 if (mediaCodec != null) { 1093 mediaCodec.stop(); 1094 1095 PersistableBundle metrics = mediaCodec.getMetrics(); 1096 if (metrics == null) { 1097 fail("getMetrics() returns null"); 1098 } else if (metrics.isEmpty()) { 1099 fail("getMetrics() returns empty results"); 1100 } 1101 int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1); 1102 if (encoder != 0) { 1103 fail("getMetrics() returns bad encoder value " + encoder); 1104 } 1105 String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null); 1106 if (theCodec == null) { 1107 fail("getMetrics() returns null codec value "); 1108 } 1109 1110 mediaCodec.release(); 1111 } 1112 if (mediaExtractor != null) { 1113 mediaExtractor.release(); 1114 } 1115 if (outputSurface != null) { 1116 outputSurface.release(); 1117 } 1118 } 1119 } 1120 }); 1121 decodingThread.start(); 1122 decodingThread.join(DECODING_TIMEOUT_MS); 1123 // In case it's timed out, need to stop the thread and have all resources released. 1124 decodingThread.interrupt(); 1125 if (!completed.get()) { 1126 throw new RuntimeException("timed out decoding to end-of-stream"); 1127 } 1128 } 1129 1130 // Run the decoder till it generates an output buffer. 1131 // Return true when that output buffer is not empty, false otherwise. 1132 private static boolean runDecodeTillFirstOutput( 1133 MediaCodec mediaCodec, MediaExtractor mediaExtractor) { 1134 final int TIME_OUT_US = 10000; 1135 1136 assertTrue("Wrong test stream which has no data.", 1137 mediaExtractor.getSampleTrackIndex() != -1); 1138 boolean signaledEos = false; 1139 MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo(); 1140 while (!Thread.interrupted()) { 1141 // Try to feed more data into the codec. 1142 if (!signaledEos) { 1143 int bufferIndex = mediaCodec.dequeueInputBuffer(TIME_OUT_US /* timeoutUs */); 1144 if (bufferIndex != -1) { 1145 ByteBuffer buffer = mediaCodec.getInputBuffer(bufferIndex); 1146 int size = mediaExtractor.readSampleData(buffer, 0 /* offset */); 1147 long timestampUs = mediaExtractor.getSampleTime(); 1148 mediaExtractor.advance(); 1149 signaledEos = mediaExtractor.getSampleTrackIndex() == -1; 1150 mediaCodec.queueInputBuffer(bufferIndex, 1151 0 /* offset */, 1152 size, 1153 timestampUs, 1154 signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 1155 Log.i("DEBUG", "queue with " + signaledEos); 1156 } 1157 } 1158 1159 int outputBufferIndex = mediaCodec.dequeueOutputBuffer( 1160 outputBufferInfo, TIME_OUT_US /* timeoutUs */); 1161 1162 if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED 1163 || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 1164 || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { 1165 continue; 1166 } 1167 assertTrue("Wrong output buffer index", outputBufferIndex >= 0); 1168 1169 PersistableBundle metrics = mediaCodec.getMetrics(); 1170 Log.d(TAG, "getMetrics after first buffer metrics says: " + metrics); 1171 1172 int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1); 1173 if (encoder != 0) { 1174 fail("getMetrics() returns bad encoder value " + encoder); 1175 } 1176 String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null); 1177 if (theCodec == null) { 1178 fail("getMetrics() returns null codec value "); 1179 } 1180 1181 mediaCodec.releaseOutputBuffer(outputBufferIndex, false /* render */); 1182 boolean eos = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; 1183 Log.i("DEBUG", "Got a frame with eos=" + eos); 1184 if (eos && outputBufferInfo.size == 0) { 1185 return false; 1186 } else { 1187 return true; 1188 } 1189 } 1190 1191 return false; 1192 } 1193 1194 /** 1195 * Tests whether decoding a short group-of-pictures succeeds. The test queues a few video frames 1196 * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames. 1197 */ 1198 public void testDecodeShortInput() throws InterruptedException { 1199 // Input buffers from this input video are queued up to and including the video frame with 1200 // timestamp LAST_BUFFER_TIMESTAMP_US. 1201 final int INPUT_RESOURCE_ID = 1202 R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz; 1203 final long LAST_BUFFER_TIMESTAMP_US = 166666; 1204 1205 // The test should fail if the decoder never produces output frames for the truncated input. 1206 // Time out decoding, as we have no way to query whether the decoder will produce output. 1207 final int DECODING_TIMEOUT_MS = 2000; 1208 1209 final AtomicBoolean completed = new AtomicBoolean(); 1210 Thread videoDecodingThread = new Thread(new Runnable() { 1211 @Override 1212 public void run() { 1213 completed.set(runDecodeShortInput(INPUT_RESOURCE_ID, LAST_BUFFER_TIMESTAMP_US)); 1214 } 1215 }); 1216 videoDecodingThread.start(); 1217 videoDecodingThread.join(DECODING_TIMEOUT_MS); 1218 if (!completed.get()) { 1219 throw new RuntimeException("timed out decoding to end-of-stream"); 1220 } 1221 } 1222 1223 private boolean runDecodeShortInput(int inputResourceId, long lastBufferTimestampUs) { 1224 final int NO_BUFFER_INDEX = -1; 1225 1226 OutputSurface outputSurface = null; 1227 MediaExtractor mediaExtractor = null; 1228 MediaCodec mediaCodec = null; 1229 try { 1230 outputSurface = new OutputSurface(1, 1); 1231 mediaExtractor = getMediaExtractorForMimeType(inputResourceId, "video/"); 1232 MediaFormat mediaFormat = 1233 mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex()); 1234 String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); 1235 if (!supportsCodec(mimeType, false)) { 1236 Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE); 1237 return true; 1238 } 1239 mediaCodec = 1240 MediaCodec.createDecoderByType(mimeType); 1241 mediaCodec.configure(mediaFormat, outputSurface.getSurface(), null, 0); 1242 mediaCodec.start(); 1243 boolean eos = false; 1244 boolean signaledEos = false; 1245 MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo(); 1246 int outputBufferIndex = NO_BUFFER_INDEX; 1247 while (!eos && !Thread.interrupted()) { 1248 // Try to feed more data into the codec. 1249 if (mediaExtractor.getSampleTrackIndex() != -1 && !signaledEos) { 1250 int bufferIndex = mediaCodec.dequeueInputBuffer(0); 1251 if (bufferIndex != NO_BUFFER_INDEX) { 1252 ByteBuffer buffer = mediaCodec.getInputBuffers()[bufferIndex]; 1253 int size = mediaExtractor.readSampleData(buffer, 0); 1254 long timestampUs = mediaExtractor.getSampleTime(); 1255 mediaExtractor.advance(); 1256 signaledEos = mediaExtractor.getSampleTrackIndex() == -1 1257 || timestampUs == lastBufferTimestampUs; 1258 mediaCodec.queueInputBuffer(bufferIndex, 1259 0, 1260 size, 1261 timestampUs, 1262 signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 1263 } 1264 } 1265 1266 // If we don't have an output buffer, try to get one now. 1267 if (outputBufferIndex == NO_BUFFER_INDEX) { 1268 outputBufferIndex = mediaCodec.dequeueOutputBuffer(outputBufferInfo, 0); 1269 } 1270 1271 if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED 1272 || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 1273 || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { 1274 outputBufferIndex = NO_BUFFER_INDEX; 1275 } else if (outputBufferIndex != NO_BUFFER_INDEX) { 1276 eos = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; 1277 1278 boolean render = outputBufferInfo.size > 0; 1279 mediaCodec.releaseOutputBuffer(outputBufferIndex, render); 1280 if (render) { 1281 outputSurface.awaitNewImage(); 1282 } 1283 1284 outputBufferIndex = NO_BUFFER_INDEX; 1285 } 1286 } 1287 1288 return eos; 1289 } catch (IOException e) { 1290 throw new RuntimeException("error reading input resource", e); 1291 } finally { 1292 if (mediaCodec != null) { 1293 mediaCodec.stop(); 1294 mediaCodec.release(); 1295 } 1296 if (mediaExtractor != null) { 1297 mediaExtractor.release(); 1298 } 1299 if (outputSurface != null) { 1300 outputSurface.release(); 1301 } 1302 } 1303 } 1304 1305 /** 1306 * Tests creating two decoders for {@link #MIME_TYPE_AUDIO} at the same time. 1307 */ 1308 public void testCreateTwoAudioDecoders() { 1309 final MediaFormat format = MediaFormat.createAudioFormat( 1310 MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT); 1311 1312 MediaCodec audioDecoderA = null; 1313 MediaCodec audioDecoderB = null; 1314 try { 1315 try { 1316 audioDecoderA = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO); 1317 } catch (IOException e) { 1318 fail("failed to create first " + MIME_TYPE_AUDIO + " decoder"); 1319 } 1320 audioDecoderA.configure(format, null, null, 0); 1321 audioDecoderA.start(); 1322 1323 try { 1324 audioDecoderB = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO); 1325 } catch (IOException e) { 1326 fail("failed to create second " + MIME_TYPE_AUDIO + " decoder"); 1327 } 1328 audioDecoderB.configure(format, null, null, 0); 1329 audioDecoderB.start(); 1330 } finally { 1331 if (audioDecoderB != null) { 1332 try { 1333 audioDecoderB.stop(); 1334 audioDecoderB.release(); 1335 } catch (RuntimeException e) { 1336 Log.w(TAG, "exception stopping/releasing codec", e); 1337 } 1338 } 1339 1340 if (audioDecoderA != null) { 1341 try { 1342 audioDecoderA.stop(); 1343 audioDecoderA.release(); 1344 } catch (RuntimeException e) { 1345 Log.w(TAG, "exception stopping/releasing codec", e); 1346 } 1347 } 1348 } 1349 } 1350 1351 /** 1352 * Tests creating an encoder and decoder for {@link #MIME_TYPE_AUDIO} at the same time. 1353 */ 1354 public void testCreateAudioDecoderAndEncoder() { 1355 if (!supportsCodec(MIME_TYPE_AUDIO, true)) { 1356 Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO); 1357 return; 1358 } 1359 1360 if (!supportsCodec(MIME_TYPE_AUDIO, false)) { 1361 Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE_AUDIO); 1362 return; 1363 } 1364 1365 final MediaFormat encoderFormat = MediaFormat.createAudioFormat( 1366 MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT); 1367 encoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE); 1368 encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE); 1369 final MediaFormat decoderFormat = MediaFormat.createAudioFormat( 1370 MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT); 1371 1372 MediaCodec audioEncoder = null; 1373 MediaCodec audioDecoder = null; 1374 try { 1375 try { 1376 audioEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO); 1377 } catch (IOException e) { 1378 fail("failed to create " + MIME_TYPE_AUDIO + " encoder"); 1379 } 1380 audioEncoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 1381 audioEncoder.start(); 1382 1383 try { 1384 audioDecoder = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO); 1385 } catch (IOException e) { 1386 fail("failed to create " + MIME_TYPE_AUDIO + " decoder"); 1387 } 1388 audioDecoder.configure(decoderFormat, null, null, 0); 1389 audioDecoder.start(); 1390 } finally { 1391 if (audioDecoder != null) { 1392 try { 1393 audioDecoder.stop(); 1394 audioDecoder.release(); 1395 } catch (RuntimeException e) { 1396 Log.w(TAG, "exception stopping/releasing codec", e); 1397 } 1398 } 1399 1400 if (audioEncoder != null) { 1401 try { 1402 audioEncoder.stop(); 1403 audioEncoder.release(); 1404 } catch (RuntimeException e) { 1405 Log.w(TAG, "exception stopping/releasing codec", e); 1406 } 1407 } 1408 } 1409 } 1410 1411 public void testConcurrentAudioVideoEncodings() throws InterruptedException { 1412 if (!supportsCodec(MIME_TYPE_AUDIO, true)) { 1413 Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO); 1414 return; 1415 } 1416 1417 if (!supportsCodec(MIME_TYPE, true)) { 1418 Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE); 1419 return; 1420 } 1421 1422 final int VIDEO_NUM_SWAPS = 100; 1423 // audio only checks this and stop 1424 mVideoEncodingOngoing = true; 1425 final CodecInfo info = getAvcSupportedFormatInfo(); 1426 long start = System.currentTimeMillis(); 1427 Thread videoEncodingThread = new Thread(new Runnable() { 1428 @Override 1429 public void run() { 1430 runVideoEncoding(VIDEO_NUM_SWAPS, info); 1431 } 1432 }); 1433 Thread audioEncodingThread = new Thread(new Runnable() { 1434 @Override 1435 public void run() { 1436 runAudioEncoding(); 1437 } 1438 }); 1439 videoEncodingThread.start(); 1440 audioEncodingThread.start(); 1441 videoEncodingThread.join(); 1442 mVideoEncodingOngoing = false; 1443 audioEncodingThread.join(); 1444 assertFalse("Video encoding error. Chekc logcat", mVideoEncoderHadError); 1445 assertFalse("Audio encoding error. Chekc logcat", mAudioEncoderHadError); 1446 long end = System.currentTimeMillis(); 1447 Log.w(TAG, "Concurrent AV encoding took " + (end - start) + " ms for " + VIDEO_NUM_SWAPS + 1448 " video frames"); 1449 } 1450 1451 private static class CodecInfo { 1452 public int mMaxW; 1453 public int mMaxH; 1454 public int mFps; 1455 public int mBitRate; 1456 }; 1457 1458 public void testCryptoInfoPattern() { 1459 CryptoInfo info = new CryptoInfo(); 1460 Pattern pattern = new Pattern(1 /*blocksToEncrypt*/, 2 /*blocksToSkip*/); 1461 if (pattern.getEncryptBlocks() != 1) { 1462 fail("Incorrect number of encrypt blocks in pattern"); 1463 } 1464 if (pattern.getSkipBlocks() != 2) { 1465 fail("Incorrect number of skip blocks in pattern"); 1466 } 1467 pattern.set(3 /*blocksToEncrypt*/, 4 /*blocksToSkip*/); 1468 if (pattern.getEncryptBlocks() != 3) { 1469 fail("Incorrect number of encrypt blocks in pattern"); 1470 } 1471 if (pattern.getSkipBlocks() != 4) { 1472 fail("Incorrect number of skip blocks in pattern"); 1473 } 1474 info.setPattern(pattern); 1475 } 1476 1477 private static CodecInfo getAvcSupportedFormatInfo() { 1478 MediaCodecInfo mediaCodecInfo = selectCodec(MIME_TYPE); 1479 CodecCapabilities cap = mediaCodecInfo.getCapabilitiesForType(MIME_TYPE); 1480 if (cap == null) { // not supported 1481 return null; 1482 } 1483 CodecInfo info = new CodecInfo(); 1484 int highestLevel = 0; 1485 for (CodecProfileLevel lvl : cap.profileLevels) { 1486 if (lvl.level > highestLevel) { 1487 highestLevel = lvl.level; 1488 } 1489 } 1490 int maxW = 0; 1491 int maxH = 0; 1492 int bitRate = 0; 1493 int fps = 0; // frame rate for the max resolution 1494 switch(highestLevel) { 1495 // Do not support Level 1 to 2. 1496 case CodecProfileLevel.AVCLevel1: 1497 case CodecProfileLevel.AVCLevel11: 1498 case CodecProfileLevel.AVCLevel12: 1499 case CodecProfileLevel.AVCLevel13: 1500 case CodecProfileLevel.AVCLevel1b: 1501 case CodecProfileLevel.AVCLevel2: 1502 return null; 1503 case CodecProfileLevel.AVCLevel21: 1504 maxW = 352; 1505 maxH = 576; 1506 bitRate = 4000000; 1507 fps = 25; 1508 break; 1509 case CodecProfileLevel.AVCLevel22: 1510 maxW = 720; 1511 maxH = 480; 1512 bitRate = 4000000; 1513 fps = 15; 1514 break; 1515 case CodecProfileLevel.AVCLevel3: 1516 maxW = 720; 1517 maxH = 480; 1518 bitRate = 10000000; 1519 fps = 30; 1520 break; 1521 case CodecProfileLevel.AVCLevel31: 1522 maxW = 1280; 1523 maxH = 720; 1524 bitRate = 14000000; 1525 fps = 30; 1526 break; 1527 case CodecProfileLevel.AVCLevel32: 1528 maxW = 1280; 1529 maxH = 720; 1530 bitRate = 20000000; 1531 fps = 60; 1532 break; 1533 case CodecProfileLevel.AVCLevel4: // only try up to 1080p 1534 default: 1535 maxW = 1920; 1536 maxH = 1080; 1537 bitRate = 20000000; 1538 fps = 30; 1539 break; 1540 } 1541 info.mMaxW = maxW; 1542 info.mMaxH = maxH; 1543 info.mFps = fps; 1544 info.mBitRate = bitRate; 1545 Log.i(TAG, "AVC Level 0x" + Integer.toHexString(highestLevel) + " bit rate " + bitRate + 1546 " fps " + info.mFps + " w " + maxW + " h " + maxH); 1547 1548 return info; 1549 } 1550 1551 private void runVideoEncoding(int numSwap, CodecInfo info) { 1552 MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, info.mMaxW, info.mMaxH); 1553 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 1554 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 1555 format.setInteger(MediaFormat.KEY_BIT_RATE, info.mBitRate); 1556 format.setInteger(MediaFormat.KEY_FRAME_RATE, info.mFps); 1557 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 1558 MediaCodec encoder = null; 1559 InputSurface inputSurface = null; 1560 mVideoEncoderHadError = false; 1561 try { 1562 encoder = MediaCodec.createEncoderByType(MIME_TYPE); 1563 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 1564 inputSurface = new InputSurface(encoder.createInputSurface()); 1565 inputSurface.makeCurrent(); 1566 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 1567 encoder.start(); 1568 for (int i = 0; i < numSwap; i++) { 1569 GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f); 1570 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 1571 inputSurface.swapBuffers(); 1572 // dequeue buffers until not available 1573 int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); 1574 while (index >= 0) { 1575 encoder.releaseOutputBuffer(index, false); 1576 // just throw away output 1577 // allow shorter wait for 2nd round to move on quickly. 1578 index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT); 1579 } 1580 } 1581 encoder.signalEndOfInputStream(); 1582 } catch (Throwable e) { 1583 Log.w(TAG, "runVideoEncoding got error: " + e); 1584 mVideoEncoderHadError = true; 1585 } finally { 1586 if (encoder != null) { 1587 encoder.stop(); 1588 encoder.release(); 1589 } 1590 if (inputSurface != null) { 1591 inputSurface.release(); 1592 } 1593 } 1594 } 1595 1596 private void runAudioEncoding() { 1597 MediaFormat format = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, 1598 AUDIO_CHANNEL_COUNT); 1599 format.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE); 1600 format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE); 1601 MediaCodec encoder = null; 1602 mAudioEncoderHadError = false; 1603 try { 1604 encoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO); 1605 encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 1606 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 1607 encoder.start(); 1608 ByteBuffer[] inputBuffers = encoder.getInputBuffers(); 1609 ByteBuffer source = ByteBuffer.allocate(inputBuffers[0].capacity()); 1610 for (int i = 0; i < source.capacity()/2; i++) { 1611 source.putShort((short)i); 1612 } 1613 source.rewind(); 1614 int currentInputBufferIndex = 0; 1615 long encodingLatencySum = 0; 1616 int totalEncoded = 0; 1617 int numRepeat = 0; 1618 while (mVideoEncodingOngoing) { 1619 numRepeat++; 1620 int inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); 1621 while (inputIndex == -1) { 1622 inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); 1623 } 1624 ByteBuffer inputBuffer = inputBuffers[inputIndex]; 1625 inputBuffer.rewind(); 1626 inputBuffer.put(source); 1627 long start = System.currentTimeMillis(); 1628 totalEncoded += inputBuffers[inputIndex].limit(); 1629 encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0); 1630 source.rewind(); 1631 int index = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC); 1632 long end = System.currentTimeMillis(); 1633 encodingLatencySum += (end - start); 1634 while (index >= 0) { 1635 encoder.releaseOutputBuffer(index, false); 1636 // just throw away output 1637 // allow shorter wait for 2nd round to move on quickly. 1638 index = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC_SHORT); 1639 } 1640 } 1641 Log.w(TAG, "Audio encoding average latency " + encodingLatencySum / numRepeat + 1642 " ms for average write size " + totalEncoded / numRepeat + 1643 " total latency " + encodingLatencySum + " ms for total bytes " + totalEncoded); 1644 } catch (Throwable e) { 1645 Log.w(TAG, "runAudioEncoding got error: " + e); 1646 mAudioEncoderHadError = true; 1647 } finally { 1648 if (encoder != null) { 1649 encoder.stop(); 1650 encoder.release(); 1651 } 1652 } 1653 } 1654 1655 /** 1656 * Creates a MediaFormat with the basic set of values. 1657 */ 1658 private static MediaFormat createMediaFormat() { 1659 MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT); 1660 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 1661 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 1662 format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); 1663 format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 1664 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 1665 return format; 1666 } 1667 1668 /** 1669 * Returns the first codec capable of encoding the specified MIME type, or null if no 1670 * match was found. 1671 */ 1672 private static MediaCodecInfo selectCodec(String mimeType) { 1673 // FIXME: select codecs based on the complete use-case, not just the mime 1674 MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 1675 for (MediaCodecInfo info : mcl.getCodecInfos()) { 1676 if (!info.isEncoder()) { 1677 continue; 1678 } 1679 1680 String[] types = info.getSupportedTypes(); 1681 for (int j = 0; j < types.length; j++) { 1682 if (types[j].equalsIgnoreCase(mimeType)) { 1683 return info; 1684 } 1685 } 1686 } 1687 return null; 1688 } 1689 1690 /** 1691 * Returns a color format that is supported by the codec and isn't COLOR_FormatSurface. Throws 1692 * an exception if none found. 1693 */ 1694 private static int findNonSurfaceColorFormat(MediaCodecInfo codecInfo, String mimeType) { 1695 MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType); 1696 for (int i = 0; i < capabilities.colorFormats.length; i++) { 1697 int colorFormat = capabilities.colorFormats[i]; 1698 if (colorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) { 1699 return colorFormat; 1700 } 1701 } 1702 fail("couldn't find a good color format for " + codecInfo.getName() + " / " + MIME_TYPE); 1703 return 0; // not reached 1704 } 1705 1706 private MediaExtractor getMediaExtractorForMimeType(int resourceId, String mimeTypePrefix) 1707 throws IOException { 1708 MediaExtractor mediaExtractor = new MediaExtractor(); 1709 AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(resourceId); 1710 try { 1711 mediaExtractor.setDataSource( 1712 afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); 1713 } finally { 1714 afd.close(); 1715 } 1716 int trackIndex; 1717 for (trackIndex = 0; trackIndex < mediaExtractor.getTrackCount(); trackIndex++) { 1718 MediaFormat trackMediaFormat = mediaExtractor.getTrackFormat(trackIndex); 1719 if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) { 1720 mediaExtractor.selectTrack(trackIndex); 1721 break; 1722 } 1723 } 1724 if (trackIndex == mediaExtractor.getTrackCount()) { 1725 throw new IllegalStateException("couldn't get a video track"); 1726 } 1727 1728 return mediaExtractor; 1729 } 1730 1731 private static boolean supportsCodec(String mimeType, boolean encoder) { 1732 MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS); 1733 for (MediaCodecInfo info : list.getCodecInfos()) { 1734 if (encoder && !info.isEncoder()) { 1735 continue; 1736 } 1737 if (!encoder && info.isEncoder()) { 1738 continue; 1739 } 1740 1741 for (String type : info.getSupportedTypes()) { 1742 if (type.equalsIgnoreCase(mimeType)) { 1743 return true; 1744 } 1745 } 1746 } 1747 return false; 1748 } 1749 1750 private static final UUID CLEARKEY_SCHEME_UUID = 1751 new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL); 1752 1753 /** 1754 * Tests: 1755 * <br> queueSecureInputBuffer() with erroneous input throws CryptoException 1756 * <br> getInputBuffer() after the failed queueSecureInputBuffer() succeeds. 1757 */ 1758 public void testCryptoError() throws Exception { 1759 MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID); 1760 byte[] sessionId = drm.openSession(); 1761 MediaCrypto crypto = new MediaCrypto(CLEARKEY_SCHEME_UUID, new byte[0]); 1762 MediaCodec codec = MediaCodec.createDecoderByType(MIME_TYPE); 1763 1764 try { 1765 crypto.setMediaDrmSession(sessionId); 1766 1767 MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo(); 1768 MediaFormat format = createMediaFormat(); 1769 1770 codec.configure(format, null, crypto, 0); 1771 codec.start(); 1772 int index = codec.dequeueInputBuffer(-1); 1773 assertTrue(index >= 0); 1774 ByteBuffer buffer = codec.getInputBuffer(index); 1775 cryptoInfo.set( 1776 1, 1777 new int[] { 0 }, 1778 new int[] { buffer.capacity() }, 1779 new byte[16], 1780 new byte[16], 1781 // Trying to decrypt encrypted data in unencrypted mode 1782 MediaCodec.CRYPTO_MODE_UNENCRYPTED); 1783 try { 1784 codec.queueSecureInputBuffer(index, 0, cryptoInfo, 0, 0); 1785 fail("queueSecureInputBuffer should fail when trying to decrypt " + 1786 "encrypted data in unencrypted mode."); 1787 } catch (MediaCodec.CryptoException e) { 1788 // expected 1789 } 1790 buffer = codec.getInputBuffer(index); 1791 codec.stop(); 1792 } finally { 1793 codec.release(); 1794 crypto.release(); 1795 drm.closeSession(sessionId); 1796 } 1797 } 1798 } 1799