Home | History | Annotate | Download | only in cts
      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