xref: /aosp_15_r20/cts/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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.codec.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.assertThrows;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 
28 import android.content.res.AssetFileDescriptor;
29 import android.hardware.HardwareBuffer;
30 import android.media.AudioFormat;
31 import android.media.AudioPresentation;
32 import android.media.MediaCodec;
33 import android.media.MediaCodec.BufferInfo;
34 import android.media.MediaCodec.CodecException;
35 import android.media.MediaCodec.CryptoException;
36 import android.media.MediaCodec.CryptoInfo;
37 import android.media.MediaCodec.CryptoInfo.Pattern;
38 import android.media.MediaCodecInfo;
39 import android.media.MediaCodecInfo.AudioCapabilities;
40 import android.media.MediaCodecInfo.CodecCapabilities;
41 import android.media.MediaCodecInfo.CodecProfileLevel;
42 import android.media.MediaCodecInfo.EncoderCapabilities;
43 import android.media.MediaCodecInfo.VideoCapabilities;
44 import android.media.MediaCodecList;
45 import android.media.MediaExtractor;
46 import android.media.MediaFormat;
47 import android.media.cts.InputSurface;
48 import android.media.cts.OutputSurface;
49 import android.media.cts.StreamUtils;
50 import android.media.cts.TestUtils;
51 import android.opengl.GLES20;
52 import android.os.Build;
53 import android.os.ConditionVariable;
54 import android.os.Handler;
55 import android.os.HandlerThread;
56 import android.os.ParcelFileDescriptor;
57 import android.os.PersistableBundle;
58 import android.platform.test.annotations.AppModeFull;
59 import android.platform.test.annotations.Presubmit;
60 import android.util.Log;
61 import android.util.Range;
62 import android.view.Surface;
63 
64 import androidx.test.ext.junit.runners.AndroidJUnit4;
65 import androidx.test.filters.MediumTest;
66 import androidx.test.filters.SmallTest;
67 
68 import com.android.compatibility.common.util.ApiLevelUtil;
69 import com.android.compatibility.common.util.ApiTest;
70 import com.android.compatibility.common.util.FrameworkSpecificTest;
71 import com.android.compatibility.common.util.MediaUtils;
72 import com.android.compatibility.common.util.Preconditions;
73 
74 import org.junit.Test;
75 import org.junit.runner.RunWith;
76 
77 import java.io.File;
78 import java.io.IOException;
79 import java.nio.ByteBuffer;
80 import java.util.ArrayList;
81 import java.util.Arrays;
82 import java.util.List;
83 import java.util.UUID;
84 import java.util.concurrent.CountDownLatch;
85 import java.util.concurrent.LinkedBlockingQueue;
86 import java.util.concurrent.TimeUnit;
87 import java.util.concurrent.atomic.AtomicBoolean;
88 import java.util.concurrent.atomic.AtomicInteger;
89 
90 /**
91  * General MediaCodec tests.
92  *
93  * In particular, check various API edge cases.
94  */
95 @Presubmit
96 @SmallTest
97 @AppModeFull(reason = "Instant apps cannot access the SD card")
98 @RunWith(AndroidJUnit4.class)
99 public class MediaCodecTest {
100     private static final String TAG = "MediaCodecTest";
101     private static final boolean VERBOSE = false;           // lots of logging
102 
103     static final String mInpPrefix = WorkDir.getMediaDirString();
104     // parameters for the video encoder
105                                                             // H.264 Advanced Video Coding
106     private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
107     private static final int BIT_RATE = 2000000;            // 2Mbps
108     private static final int FRAME_RATE = 15;               // 15fps
109     private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
110     private static final int WIDTH = 1280;
111     private static final int HEIGHT = 720;
112     // parameters for the audio encoder
113     private static final String MIME_TYPE_AUDIO = MediaFormat.MIMETYPE_AUDIO_AAC;
114     private static final int AUDIO_SAMPLE_RATE = 44100;
115     private static final int AUDIO_AAC_PROFILE = 2; /* OMX_AUDIO_AACObjectLC */
116     private static final int AUDIO_CHANNEL_COUNT = 2; // mono
117     private static final int AUDIO_BIT_RATE = 128000;
118 
119     private static final int TIMEOUT_USEC = 100000;
120     private static final int TIMEOUT_USEC_SHORT = 100;
121 
122     private boolean mVideoEncoderHadError = false;
123     private boolean mAudioEncoderHadError = false;
124     private volatile boolean mVideoEncodingOngoing = false;
125 
126     private static final String INPUT_RESOURCE =
127             "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
128 
129     // The test should fail if the decoder never produces output frames for the input.
130     // Time out decoding, as we have no way to query whether the decoder will produce output.
131     private static final int DECODING_TIMEOUT_MS = 10000;
132 
133     private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
134     private static boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
135     private static boolean mIsAtLeastU = ApiLevelUtil.isAfter(Build.VERSION_CODES.TIRAMISU);
136 
137     /**
138      * Tests:
139      * <br> Exceptions for MediaCodec factory methods
140      * <br> Exceptions for MediaCodec methods when called in the incorrect state.
141      *
142      * A selective test to ensure proper exceptions are thrown from MediaCodec
143      * methods when called in incorrect operational states.
144      */
145     @ApiTest(apis = {"MediaCodec#createByCodecName", "MediaCodec#createDecoderByType",
146             "MediaCodec#createEncoderByType", "MediaCodec#start", "MediaCodec#flush",
147             "MediaCodec#configure", "MediaCodec#dequeueInputBuffer",
148             "MediaCodec#dequeueOutputBuffer", "MediaCodec#createInputSurface",
149             "MediaCodec#getInputBuffers", "MediaCodec#getQueueRequest",
150             "MediaCodec#getOutputFrame", "MediaCodec#stop", "MediaCodec#release",
151             "MediaCodec#getCodecInfo", "MediaCodec#getSupportedVendorParameters",
152             "MediaCodec#getParameterDescriptor",
153             "MediaCodec#subscribeToVendorParameters",
154             "MediaCodec#unsubscribeFromVendorParameters",
155             "MediaCodec#getInputBuffer", "MediaCodec#getOutputBuffer",
156             "MediaCodec#setCallback", "MediaCodec#getName"})
157     @Test
testException()158     public void testException() throws Exception {
159         boolean tested = false;
160         // audio decoder (MP3 should be present on all Android devices)
161         MediaFormat format = MediaFormat.createAudioFormat(
162                 MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */);
163         tested = verifyException(format, false /* isEncoder */) || tested;
164 
165         // audio encoder (AMR-WB may not be present on some Android devices)
166         format = MediaFormat.createAudioFormat(
167                 MediaFormat.MIMETYPE_AUDIO_AMR_WB, 16000 /* sampleRate */, 1 /* channelCount */);
168         format.setInteger(MediaFormat.KEY_BIT_RATE, 19850);
169         tested = verifyException(format, true /* isEncoder */) || tested;
170 
171         // video decoder (H.264/AVC may not be present on some Android devices)
172         format = createMediaFormat();
173         tested = verifyException(format, false /* isEncoder */) || tested;
174 
175         // video encoder (H.264/AVC may not be present on some Android devices)
176         tested = verifyException(format, true /* isEncoder */) || tested;
177 
178         // signal test is skipped due to no device media codecs.
179         if (!tested) {
180             MediaUtils.skipTest(TAG, "cannot find any compatible device codecs");
181         }
182     }
183 
184     // wrap MediaCodec encoder and decoder creation
createCodecByType(String type, boolean isEncoder)185     private static MediaCodec createCodecByType(String type, boolean isEncoder)
186             throws IOException {
187         if (isEncoder) {
188             return MediaCodec.createEncoderByType(type);
189         }
190         return MediaCodec.createDecoderByType(type);
191     }
192 
logMediaCodecException(MediaCodec.CodecException ex)193     private static void logMediaCodecException(MediaCodec.CodecException ex) {
194         if (ex.isRecoverable()) {
195             Log.w(TAG, "CodecException Recoverable: " + ex.getErrorCode());
196         } else if (ex.isTransient()) {
197             Log.w(TAG, "CodecException Transient: " + ex.getErrorCode());
198         } else {
199             Log.w(TAG, "CodecException Fatal: " + ex.getErrorCode());
200         }
201     }
202 
verifyException(MediaFormat format, boolean isEncoder)203     private static boolean verifyException(MediaFormat format, boolean isEncoder)
204             throws IOException {
205         String mimeType = format.getString(MediaFormat.KEY_MIME);
206         if (!supportsCodec(mimeType, isEncoder)) {
207             Log.i(TAG, "No " + (isEncoder ? "encoder" : "decoder")
208                     + " found for mimeType= " + mimeType);
209             return false;
210         }
211 
212         final boolean isVideoEncoder = isEncoder && mimeType.startsWith("video/");
213 
214         if (isVideoEncoder) {
215             format = new MediaFormat(format);
216             format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
217                     MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
218         }
219 
220         // create codec (enter Initialized State)
221         MediaCodec codec;
222 
223         // create improperly
224         final String methodName = isEncoder ? "createEncoderByType" : "createDecoderByType";
225         try {
226             codec = createCodecByType(null, isEncoder);
227             fail(methodName + " should return NullPointerException on null");
228         } catch (NullPointerException e) { // expected
229         }
230         try {
231             codec = createCodecByType("foobarplan9", isEncoder); // invalid type
232             fail(methodName + " should return IllegalArgumentException on invalid type");
233         } catch (IllegalArgumentException e) { // expected
234         }
235         try {
236             codec = MediaCodec.createByCodecName("foobarplan9"); // invalid name
237             fail(methodName + " should return IllegalArgumentException on invalid name");
238         } catch (IllegalArgumentException e) { // expected
239         }
240         // correct
241         codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder);
242 
243         // test a few commands
244         try {
245             codec.start();
246             fail("start should return IllegalStateException when in Initialized state");
247         } catch (MediaCodec.CodecException e) {
248             logMediaCodecException(e);
249             fail("start should not return MediaCodec.CodecException on wrong state");
250         } catch (IllegalStateException e) { // expected
251         }
252         try {
253             codec.flush();
254             fail("flush should return IllegalStateException when in Initialized state");
255         } catch (MediaCodec.CodecException e) {
256             logMediaCodecException(e);
257             fail("flush should not return MediaCodec.CodecException on wrong state");
258         } catch (IllegalStateException e) { // expected
259         }
260 
261         MediaCodecInfo codecInfo = codec.getCodecInfo(); // obtaining the codec info now is fine.
262         try {
263             int bufIndex = codec.dequeueInputBuffer(0);
264             fail("dequeueInputBuffer should return IllegalStateException"
265                     + " when in the Initialized state");
266         } catch (MediaCodec.CodecException e) {
267             logMediaCodecException(e);
268             fail("dequeueInputBuffer should not return MediaCodec.CodecException"
269                     + " on wrong state");
270         } catch (IllegalStateException e) { // expected
271         }
272         try {
273             MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
274             int bufIndex = codec.dequeueOutputBuffer(info, 0);
275             fail("dequeueOutputBuffer should return IllegalStateException"
276                     + " when in the Initialized state");
277         } catch (MediaCodec.CodecException e) {
278             logMediaCodecException(e);
279             fail("dequeueOutputBuffer should not return MediaCodec.CodecException"
280                     + " on wrong state");
281         } catch (IllegalStateException e) { // expected
282         }
283 
284         // configure (enter Configured State)
285 
286         // configure improperly
287         try {
288             codec.configure(format, null /* surface */, null /* crypto */,
289                     isEncoder ? 0 : MediaCodec.CONFIGURE_FLAG_ENCODE /* flags */);
290             fail("configure needs MediaCodec.CONFIGURE_FLAG_ENCODE for encoders only");
291         } catch (MediaCodec.CodecException e) { // expected
292             logMediaCodecException(e);
293         } catch (IllegalStateException e) {
294             fail("configure should not return IllegalStateException when improperly configured");
295         }
296         if (mIsAtLeastU) {
297             try {
298                 int flags = MediaCodec.CONFIGURE_FLAG_USE_CRYPTO_ASYNC |
299                         (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
300                 codec.configure(format, null /* surface */, null /* crypto */, flags /* flags */);
301                 fail("At the minimum, CONFIGURE_FLAG_USE_CRYPTO_ASYNC requires setting callback");
302             } catch(IllegalStateException e) { //expected
303                 // Need to set callbacks
304             }
305         }
306         // correct
307         codec.configure(format, null /* surface */, null /* crypto */,
308                 isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0 /* flags */);
309 
310         // test a few commands
311         try {
312             codec.flush();
313             fail("flush should return IllegalStateException when in Configured state");
314         } catch (MediaCodec.CodecException e) {
315             logMediaCodecException(e);
316             fail("flush should not return MediaCodec.CodecException on wrong state");
317         } catch (IllegalStateException e) { // expected
318         }
319         try {
320             Surface surface = codec.createInputSurface();
321             if (!isEncoder) {
322                 fail("createInputSurface should not work on a decoder");
323             }
324         } catch (IllegalStateException |
325                  IllegalArgumentException e) { // expected for decoder and audio encoder
326             if (isVideoEncoder) {
327                 throw e;
328             }
329         }
330 
331         // test getInputBuffers before start()
332         try {
333             ByteBuffer[] buffers = codec.getInputBuffers();
334             fail("getInputBuffers called before start() should throw exception");
335         } catch (IllegalStateException e) { // expected
336         }
337 
338         // start codec (enter Executing state)
339         codec.start();
340 
341         // test getInputBuffers after start()
342         try {
343             ByteBuffer[] buffers = codec.getInputBuffers();
344             if (buffers == null) {
345                 fail("getInputBuffers called after start() should not return null");
346             }
347             if (isVideoEncoder && buffers.length > 0) {
348                 fail("getInputBuffers returned non-zero length array with input surface");
349             }
350         } catch (IllegalStateException e) {
351             fail("getInputBuffers called after start() shouldn't throw exception");
352         }
353 
354         // test a few commands
355         try {
356             codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
357             fail("configure should return IllegalStateException when in Executing state");
358         } catch (MediaCodec.CodecException e) {
359             logMediaCodecException(e);
360             // TODO: consider configuring after a flush.
361             fail("configure should not return MediaCodec.CodecException on wrong state");
362         } catch (IllegalStateException e) { // expected
363         }
364         if (mIsAtLeastR) {
365             try {
366                 codec.getQueueRequest(0);
367                 fail("getQueueRequest should throw IllegalStateException when not configured with " +
368                         "CONFIGURE_FLAG_USE_BLOCK_MODEL");
369             } catch (MediaCodec.CodecException e) {
370                 logMediaCodecException(e);
371                 fail("getQueueRequest should not return " +
372                         "MediaCodec.CodecException on wrong configuration");
373             } catch (IllegalStateException e) { // expected
374             }
375             try {
376                 codec.getOutputFrame(0);
377                 fail("getOutputFrame should throw IllegalStateException when not configured with " +
378                         "CONFIGURE_FLAG_USE_BLOCK_MODEL");
379             } catch (MediaCodec.CodecException e) {
380                 logMediaCodecException(e);
381                 fail("getOutputFrame should not return MediaCodec.CodecException on wrong " +
382                         "configuration");
383             } catch (IllegalStateException e) { // expected
384             }
385         }
386 
387         // two flushes should be fine.
388         codec.flush();
389         codec.flush();
390 
391         // stop codec (enter Initialized state)
392         // two stops should be fine.
393         codec.stop();
394         codec.stop();
395 
396         // release codec (enter Uninitialized state)
397         // two releases should be fine.
398         codec.release();
399         codec.release();
400 
401         try {
402             codecInfo = codec.getCodecInfo();
403             fail("getCodecInfo should should return IllegalStateException" +
404                     " when in Uninitialized state");
405         } catch (MediaCodec.CodecException e) {
406             logMediaCodecException(e);
407             fail("getCodecInfo should not return MediaCodec.CodecException on wrong state");
408         } catch (IllegalStateException e) { // expected
409         }
410         try {
411             codec.stop();
412             fail("stop should return IllegalStateException when in Uninitialized state");
413         } catch (MediaCodec.CodecException e) {
414             logMediaCodecException(e);
415             fail("stop should not return MediaCodec.CodecException on wrong state");
416         } catch (IllegalStateException e) { // expected
417         }
418 
419         if (mIsAtLeastS) {
420             try {
421                 codec.getSupportedVendorParameters();
422                 fail("getSupportedVendorParameters should throw IllegalStateException" +
423                         " when in Uninitialized state");
424             } catch (IllegalStateException e) { // expected
425             } catch (Exception e) {
426                 fail("unexpected exception: " + e.toString());
427             }
428             try {
429                 codec.getParameterDescriptor("");
430                 fail("getParameterDescriptor should throw IllegalStateException" +
431                         " when in Uninitialized state");
432             } catch (IllegalStateException e) { // expected
433             } catch (Exception e) {
434                 fail("unexpected exception: " + e.toString());
435             }
436             try {
437                 codec.subscribeToVendorParameters(List.of(""));
438                 fail("subscribeToVendorParameters should throw IllegalStateException" +
439                         " when in Uninitialized state");
440             } catch (IllegalStateException e) { // expected
441             } catch (Exception e) {
442                 fail("unexpected exception: " + e.toString());
443             }
444             try {
445                 codec.unsubscribeFromVendorParameters(List.of(""));
446                 fail("unsubscribeFromVendorParameters should throw IllegalStateException" +
447                         " when in Uninitialized state");
448             } catch (IllegalStateException e) { // expected
449             } catch (Exception e) {
450                 fail("unexpected exception: " + e.toString());
451             }
452         }
453 
454         if (mIsAtLeastR) {
455             // recreate
456             codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder);
457 
458             if (isVideoEncoder) {
459                 format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
460                         MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
461             }
462 
463             // configure improperly
464             try {
465                 codec.configure(format, null /* surface */, null /* crypto */,
466                         MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
467                         (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
468                 fail("configure with detached buffer mode should be done after setCallback");
469             } catch (MediaCodec.CodecException e) {
470                 logMediaCodecException(e);
471                 fail("configure should not return IllegalStateException when improperly configured");
472             } catch (IllegalStateException e) { // expected
473             }
474 
475             final LinkedBlockingQueue<Integer> inputQueue = new LinkedBlockingQueue<>();
476             codec.setCallback(new MediaCodec.Callback() {
477                 @Override
478                 public void onInputBufferAvailable(MediaCodec codec, int index) {
479                     inputQueue.offer(index);
480                 }
481                 @Override
482                 public void onOutputBufferAvailable(
483                         MediaCodec codec, int index, MediaCodec.BufferInfo info) { }
484                 @Override
485                 public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { }
486                 @Override
487                 public void onError(MediaCodec codec, CodecException e) { }
488             });
489 
490             // configure with CONFIGURE_FLAG_USE_BLOCK_MODEL (enter Configured State)
491             codec.configure(format, null /* surface */, null /* crypto */,
492                     MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
493                     (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
494 
495             // start codec (enter Executing state)
496             codec.start();
497 
498             // grab input index (this should happen immediately)
499             Integer index = null;
500             try {
501                 index = inputQueue.poll(2, TimeUnit.SECONDS);
502             } catch (InterruptedException e) {
503             }
504             assertNotNull(index);
505 
506             // test a few commands
507             try {
508                 codec.getInputBuffers();
509                 fail("getInputBuffers called in detached buffer mode should throw exception");
510             } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
511             }
512             try {
513                 codec.getOutputBuffers();
514                 fail("getOutputBuffers called in detached buffer mode should throw exception");
515             } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
516             }
517             try {
518                 codec.getInputBuffer(index);
519                 fail("getInputBuffer called in detached buffer mode should throw exception");
520             } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
521             }
522             try {
523                 codec.dequeueInputBuffer(0);
524                 fail("dequeueInputBuffer called in detached buffer mode should throw exception");
525             } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
526             }
527             MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
528             try {
529                 codec.dequeueOutputBuffer(info, 0);
530                 fail("dequeueOutputBuffer called in detached buffer mode should throw exception");
531             } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
532             }
533 
534             // test getQueueRequest
535             MediaCodec.QueueRequest request = codec.getQueueRequest(index);
536             try {
537                 request.queue();
538                 fail("QueueRequest should throw IllegalStateException when no buffer is set");
539             } catch (IllegalStateException e) { // expected
540             }
541             // setting a block
542             String[] names = new String[]{ codec.getName() };
543             request.setLinearBlock(MediaCodec.LinearBlock.obtain(1, names), 0, 0);
544             // setting additional block should fail
545             try (HardwareBuffer buffer = HardwareBuffer.create(
546                     16 /* width */,
547                     16 /* height */,
548                     HardwareBuffer.YCBCR_420_888,
549                     1 /* layers */,
550                     HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_CPU_WRITE_OFTEN)) {
551                 request.setHardwareBuffer(buffer);
552                 fail("QueueRequest should throw IllegalStateException multiple blocks are set.");
553             } catch (IllegalStateException e) { // expected
554             }
555         }
556 
557         // release codec
558         codec.release();
559 
560         return true;
561     }
562 
563     /**
564      * Tests:
565      * <br> calling createInputSurface() before configure() throws exception
566      * <br> calling createInputSurface() after start() throws exception
567      * <br> calling createInputSurface() with a non-Surface color format is not required to throw exception
568      */
569     @ApiTest(apis = "MediaCodec#createInputSurface")
570     @Test
571     @FrameworkSpecificTest // tests only the first MIME_TYPE codec, which is usually hardware
testCreateInputSurfaceErrors()572     public void testCreateInputSurfaceErrors() {
573         if (!supportsCodec(MIME_TYPE, true)) {
574             Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
575             return;
576         }
577 
578         MediaFormat format = createMediaFormat();
579         MediaCodec encoder = null;
580         Surface surface = null;
581 
582         // Replace color format with something that isn't COLOR_FormatSurface.
583         MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
584         int colorFormat = findNonSurfaceColorFormat(codecInfo, MIME_TYPE);
585         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
586 
587         try {
588             try {
589                 encoder = MediaCodec.createByCodecName(codecInfo.getName());
590             } catch (IOException e) {
591                 fail("failed to create codec " + codecInfo.getName());
592             }
593             try {
594                 surface = encoder.createInputSurface();
595                 fail("createInputSurface should not work pre-configure");
596             } catch (IllegalStateException ise) {
597                 // good
598             }
599             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
600             encoder.start();
601             try {
602                 surface = encoder.createInputSurface();
603                 fail("createInputSurface should not work post-start");
604             } catch (IllegalStateException ise) {
605                 // good
606             }
607         } finally {
608             if (encoder != null) {
609                 encoder.stop();
610                 encoder.release();
611             }
612         }
613         assertNull(surface);
614     }
615 
616     /**
617      * Tests:
618      * <br> signaling end-of-stream before any data is sent works
619      * <br> signaling EOS twice throws exception
620      * <br> submitting a frame after EOS throws exception [TODO]
621      */
622     @ApiTest(apis = "MediaCodec#signalEndOfInputStream")
623     @Test
624     @FrameworkSpecificTest // tests only the first MIME_TYPE codec, which is usually hardware
testSignalSurfaceEOS()625     public void testSignalSurfaceEOS() {
626         if (!supportsCodec(MIME_TYPE, true)) {
627             Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
628             return;
629         }
630 
631         MediaFormat format = createMediaFormat();
632         MediaCodec encoder = null;
633         InputSurface inputSurface = null;
634 
635         try {
636             try {
637                 encoder = MediaCodec.createEncoderByType(MIME_TYPE);
638             } catch (IOException e) {
639                 fail("failed to create " + MIME_TYPE + " encoder");
640             }
641             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
642             inputSurface = new InputSurface(encoder.createInputSurface());
643             inputSurface.makeCurrent();
644             encoder.start();
645 
646             // send an immediate EOS
647             encoder.signalEndOfInputStream();
648 
649             try {
650                 encoder.signalEndOfInputStream();
651                 fail("should not be able to signal EOS twice");
652             } catch (IllegalStateException ise) {
653                 // good
654             }
655 
656             // submit a frame post-EOS
657             GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
658             GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
659             try {
660                 inputSurface.swapBuffers();
661                 if (false) {    // TODO
662                     fail("should not be able to submit frame after EOS");
663                 }
664             } catch (Exception ex) {
665                 // good
666             }
667         } finally {
668             if (encoder != null) {
669                 encoder.stop();
670                 encoder.release();
671             }
672             if (inputSurface != null) {
673                 inputSurface.release();
674             }
675         }
676     }
677 
678     /**
679      * Tests:
680      * <br> stopping with buffers in flight doesn't crash or hang
681      */
682     @ApiTest(apis = "MediaCodec#stop")
683     @Test
684     @FrameworkSpecificTest // tests only the first MIME_TYPE codec, which is usually hardware
testAbruptStop()685     public void testAbruptStop() {
686         if (!supportsCodec(MIME_TYPE, true)) {
687             Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
688             return;
689         }
690 
691         // There appears to be a race, so run it several times with a short delay between runs
692         // to allow any previous activity to shut down.
693         for (int i = 0; i < 50; i++) {
694             Log.d(TAG, "testAbruptStop " + i);
695             doTestAbruptStop();
696             try { Thread.sleep(400); } catch (InterruptedException ignored) {}
697         }
698     }
doTestAbruptStop()699     private void doTestAbruptStop() {
700         MediaFormat format = createMediaFormat();
701         MediaCodec encoder = null;
702         InputSurface inputSurface = null;
703 
704         try {
705             try {
706                 encoder = MediaCodec.createEncoderByType(MIME_TYPE);
707             } catch (IOException e) {
708                 fail("failed to create " + MIME_TYPE + " encoder");
709             }
710             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
711             inputSurface = new InputSurface(encoder.createInputSurface());
712             inputSurface.makeCurrent();
713             encoder.start();
714 
715             int totalBuffers = encoder.getOutputBuffers().length;
716             if (VERBOSE) Log.d(TAG, "Total buffers: " + totalBuffers);
717 
718             // Submit several frames quickly, without draining the encoder output, to try to
719             // ensure that we've got some queued up when we call stop().  If we do too many
720             // we'll block in swapBuffers().
721             for (int i = 0; i < totalBuffers; i++) {
722                 GLES20.glClearColor(0.0f, (i % 8) / 8.0f, 0.0f, 1.0f);
723                 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
724                 inputSurface.swapBuffers();
725             }
726             Log.d(TAG, "stopping");
727             encoder.stop();
728             Log.d(TAG, "stopped");
729         } finally {
730             if (encoder != null) {
731                 encoder.stop();
732                 encoder.release();
733             }
734             if (inputSurface != null) {
735                 inputSurface.release();
736             }
737         }
738     }
739 
740     @ApiTest(apis = {"MediaCodec#flush", "MediaCodec#release"})
741     @Test
testReleaseAfterFlush()742     public void testReleaseAfterFlush() throws IOException, InterruptedException {
743         String mimes[] = new String[] { MIME_TYPE, MIME_TYPE_AUDIO};
744         for (String mime : mimes) {
745             if (!MediaUtils.checkEncoder(mime)) {
746                 continue;
747             }
748             testReleaseAfterFlush(mime);
749         }
750     }
751 
testReleaseAfterFlush(String mime)752     private void testReleaseAfterFlush(String mime) throws IOException, InterruptedException {
753         CountDownLatch buffersExhausted = null;
754         CountDownLatch codecFlushed = null;
755         AtomicInteger numBuffers = null;
756 
757         // sync flush from same thread
758         MediaCodec encoder = MediaCodec.createEncoderByType(mime);
759         runReleaseAfterFlush(mime, encoder, buffersExhausted, codecFlushed, numBuffers);
760 
761         // sync flush from different thread
762         encoder = MediaCodec.createEncoderByType(mime);
763         buffersExhausted = new CountDownLatch(1);
764         codecFlushed = new CountDownLatch(1);
765         numBuffers = new AtomicInteger();
766         Thread flushThread = new FlushThread(encoder, buffersExhausted, codecFlushed);
767         flushThread.start();
768         runReleaseAfterFlush(mime, encoder, buffersExhausted, codecFlushed, numBuffers);
769         flushThread.join();
770 
771         // async
772         // This value is calculated in getOutputBufferIndices by calling dequeueOutputBuffer
773         // with a fixed timeout until buffers are exhausted; it is possible that random timing
774         // in dequeueOutputBuffer can result in a smaller `nBuffs` than the max possible value.
775         int nBuffs = numBuffers.get();
776         HandlerThread callbackThread = new HandlerThread("ReleaseAfterFlushCallbackThread");
777         callbackThread.start();
778         Handler handler = new Handler(callbackThread.getLooper());
779 
780         // async flush from same thread
781         encoder = MediaCodec.createEncoderByType(mime);
782         buffersExhausted = null;
783         codecFlushed = null;
784         ReleaseAfterFlushCallback callback =
785                 new ReleaseAfterFlushCallback(mime, encoder, buffersExhausted, codecFlushed, nBuffs);
786         encoder.setCallback(callback, handler); // setCallback before configure, which is called in run
787         callback.run(); // drive input on main thread
788 
789         // async flush from different thread
790         encoder = MediaCodec.createEncoderByType(mime);
791         buffersExhausted = new CountDownLatch(1);
792         codecFlushed = new CountDownLatch(1);
793         callback = new ReleaseAfterFlushCallback(mime, encoder, buffersExhausted, codecFlushed, nBuffs);
794         encoder.setCallback(callback, handler);
795         flushThread = new FlushThread(encoder, buffersExhausted, codecFlushed);
796         flushThread.start();
797         callback.run();
798         flushThread.join();
799 
800         callbackThread.quitSafely();
801         callbackThread.join();
802     }
803 
804     @ApiTest(apis = {"MediaCodec#setCallback", "MediaCodec#flush", "MediaCodec#reset"})
805     @Test
testAsyncFlushAndReset()806     public void testAsyncFlushAndReset() throws Exception, InterruptedException {
807         testAsyncReset(false /* testStop */);
808     }
809 
810     @ApiTest(apis = {"MediaCodec#setCallback", "MediaCodec#stop", "MediaCodec#reset"})
811     @Test
testAsyncStopAndReset()812     public void testAsyncStopAndReset() throws Exception, InterruptedException {
813         testAsyncReset(true /* testStop */);
814     }
815 
testAsyncReset(boolean testStop)816     private void testAsyncReset(boolean testStop) throws Exception, InterruptedException {
817         // Test video and audio 10x each
818         for (int i = 0; i < 10; i++) {
819             testAsyncReset(false /* audio */, (i % 2) == 0 /* swap */, testStop);
820         }
821         for (int i = 0; i < 10; i++) {
822             testAsyncReset(true /* audio */, (i % 2) == 0 /* swap */, testStop);
823         }
824     }
825 
826     /*
827      * This method simulates a race between flush (or stop) and reset() called from
828      * two threads. Neither call should get stuck. This should be run multiple rounds.
829      */
testAsyncReset(boolean audio, boolean swap, final boolean testStop)830     private void testAsyncReset(boolean audio, boolean swap, final boolean testStop)
831             throws Exception, InterruptedException {
832         String mimeTypePrefix  = audio ? "audio/" : "video/";
833         final MediaExtractor mediaExtractor = getMediaExtractorForMimeType(
834                 INPUT_RESOURCE, mimeTypePrefix);
835         MediaFormat mediaFormat = mediaExtractor.getTrackFormat(
836                 mediaExtractor.getSampleTrackIndex());
837         if (!MediaUtils.checkDecoderForFormat(mediaFormat)) {
838             return; // skip
839         }
840 
841         OutputSurface outputSurface = audio ? null : new OutputSurface(1, 1);
842         final Surface surface = outputSurface == null ? null : outputSurface.getSurface();
843 
844         String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
845         final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mimeType);
846 
847         try {
848             mediaCodec.configure(mediaFormat, surface, null /* crypto */, 0 /* flags */);
849 
850             mediaCodec.start();
851 
852             assertTrue(runDecodeTillFirstOutput(mediaCodec, mediaExtractor));
853 
854             Thread flushingThread = new Thread(new Runnable() {
855                 @Override
856                 public void run() {
857                     try {
858                         if (testStop) {
859                             mediaCodec.stop();
860                         } else {
861                             mediaCodec.flush();
862                         }
863                     } catch (IllegalStateException e) {
864                         // This is okay, since we're simulating a race between flush and reset.
865                         // If reset executed first, flush could fail.
866                     }
867                 }
868             });
869 
870             Thread resettingThread = new Thread(new Runnable() {
871                 @Override
872                 public void run() {
873                     mediaCodec.reset();
874                 }
875             });
876 
877             // start flushing (or stopping) and resetting in two threads
878             if (swap) {
879                 flushingThread.start();
880                 resettingThread.start();
881             } else {
882                 resettingThread.start();
883                 flushingThread.start();
884             }
885 
886             // wait for at most 5 sec, and check if the thread exits properly
887             flushingThread.join(5000);
888             assertFalse(flushingThread.isAlive());
889 
890             resettingThread.join(5000);
891             assertFalse(resettingThread.isAlive());
892         } finally {
893             if (mediaCodec != null) {
894                 mediaCodec.release();
895             }
896             if (mediaExtractor != null) {
897                 mediaExtractor.release();
898             }
899             if (outputSurface != null) {
900                 outputSurface.release();
901             }
902         }
903     }
904 
905     private static class FlushThread extends Thread {
906         final MediaCodec mEncoder;
907         final CountDownLatch mBuffersExhausted;
908         final CountDownLatch mCodecFlushed;
909 
FlushThread(MediaCodec encoder, CountDownLatch buffersExhausted, CountDownLatch codecFlushed)910         FlushThread(MediaCodec encoder, CountDownLatch buffersExhausted,
911                 CountDownLatch codecFlushed) {
912             mEncoder = encoder;
913             mBuffersExhausted = buffersExhausted;
914             mCodecFlushed = codecFlushed;
915         }
916 
917         @Override
run()918         public void run() {
919             try {
920                 mBuffersExhausted.await();
921             } catch (InterruptedException e) {
922                 Thread.currentThread().interrupt();
923                 Log.w(TAG, "buffersExhausted wait interrupted; flushing immediately.", e);
924             }
925             mEncoder.flush();
926             mCodecFlushed.countDown();
927         }
928     }
929 
930     private static class ReleaseAfterFlushCallback extends MediaCodec.Callback implements Runnable {
931         final String mMime;
932         final MediaCodec mEncoder;
933         final CountDownLatch mBuffersExhausted, mCodecFlushed;
934         final int mNumBuffersBeforeFlush;
935 
936         CountDownLatch mStopInput = new CountDownLatch(1);
937         List<Integer> mInputBufferIndices = new ArrayList<>();
938         List<Integer> mOutputBufferIndices = new ArrayList<>();
939 
ReleaseAfterFlushCallback(String mime, MediaCodec encoder, CountDownLatch buffersExhausted, CountDownLatch codecFlushed, int numBuffersBeforeFlush)940         ReleaseAfterFlushCallback(String mime,
941                 MediaCodec encoder,
942                 CountDownLatch buffersExhausted,
943                 CountDownLatch codecFlushed,
944                 int numBuffersBeforeFlush) {
945             mMime = mime;
946             mEncoder = encoder;
947             mBuffersExhausted = buffersExhausted;
948             mCodecFlushed = codecFlushed;
949             mNumBuffersBeforeFlush = numBuffersBeforeFlush;
950         }
951 
952         @Override
onInputBufferAvailable(MediaCodec codec, int index)953         public void onInputBufferAvailable(MediaCodec codec, int index) {
954             assertTrue("video onInputBufferAvailable " + index, mMime.startsWith("audio/"));
955             synchronized (mInputBufferIndices) {
956                 mInputBufferIndices.add(index);
957             };
958         }
959 
960         @Override
onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info)961         public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) {
962             mOutputBufferIndices.add(index);
963             if (mOutputBufferIndices.size() == mNumBuffersBeforeFlush) {
964                 releaseAfterFlush(codec, mOutputBufferIndices, mBuffersExhausted, mCodecFlushed);
965                 mStopInput.countDown();
966             }
967         }
968 
969         @Override
onError(MediaCodec codec, CodecException e)970         public void onError(MediaCodec codec, CodecException e) {
971             Log.e(TAG, codec + " onError", e);
972             fail(codec + " onError " + e.getMessage());
973         }
974 
975         @Override
onOutputFormatChanged(MediaCodec codec, MediaFormat format)976         public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
977             Log.v(TAG, codec + " onOutputFormatChanged " + format);
978         }
979 
980         @Override
run()981         public void run() {
982             InputSurface inputSurface = null;
983             try {
984                 inputSurface = initCodecAndSurface(mMime, mEncoder);
985                 do {
986                     int inputIndex = -1;
987                     if (inputSurface == null) {
988                         // asynchronous audio codec
989                         synchronized (mInputBufferIndices) {
990                             if (mInputBufferIndices.isEmpty()) {
991                                 continue;
992                             } else {
993                                 inputIndex = mInputBufferIndices.remove(0);
994                             }
995                         }
996                     }
997                     feedEncoder(mEncoder, inputSurface, inputIndex);
998                 } while (!mStopInput.await(TIMEOUT_USEC, TimeUnit.MICROSECONDS));
999             } catch (InterruptedException e) {
1000                 Thread.currentThread().interrupt();
1001                 Log.w(TAG, "mEncoder input frames interrupted/stopped", e);
1002             } finally {
1003                 cleanupCodecAndSurface(mEncoder, inputSurface);
1004             }
1005         }
1006     }
1007 
runReleaseAfterFlush( String mime, MediaCodec encoder, CountDownLatch buffersExhausted, CountDownLatch codecFlushed, AtomicInteger numBuffers)1008     private static void runReleaseAfterFlush(
1009             String mime,
1010             MediaCodec encoder,
1011             CountDownLatch buffersExhausted,
1012             CountDownLatch codecFlushed,
1013             AtomicInteger numBuffers) {
1014         InputSurface inputSurface = null;
1015         try {
1016             inputSurface = initCodecAndSurface(mime, encoder);
1017             List<Integer> outputBufferIndices = getOutputBufferIndices(encoder, inputSurface);
1018             if (numBuffers != null) {
1019                 numBuffers.set(outputBufferIndices.size());
1020             }
1021             releaseAfterFlush(encoder, outputBufferIndices, buffersExhausted, codecFlushed);
1022         } finally {
1023             cleanupCodecAndSurface(encoder, inputSurface);
1024         }
1025     }
1026 
initCodecAndSurface(String mime, MediaCodec encoder)1027     private static InputSurface initCodecAndSurface(String mime, MediaCodec encoder) {
1028         MediaFormat format;
1029         InputSurface inputSurface = null;
1030         if (mime.startsWith("audio/")) {
1031             format = MediaFormat.createAudioFormat(mime, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
1032             format.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
1033             format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
1034             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1035         } else if (MIME_TYPE.equals(mime)) {
1036             CodecInfo info = getAvcSupportedFormatInfo();
1037             format = MediaFormat.createVideoFormat(mime, info.mMaxW, info.mMaxH);
1038             format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
1039                     MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
1040             format.setInteger(MediaFormat.KEY_BIT_RATE, info.mBitRate);
1041             format.setInteger(MediaFormat.KEY_FRAME_RATE, info.mFps);
1042             format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
1043             OutputSurface outputSurface = new OutputSurface(1, 1);
1044             encoder.configure(format, outputSurface.getSurface(), null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1045             inputSurface = new InputSurface(encoder.createInputSurface());
1046             inputSurface.makeCurrent();
1047         } else {
1048             throw new IllegalArgumentException("unsupported mime type: " + mime);
1049         }
1050         encoder.start();
1051         return inputSurface;
1052     }
1053 
cleanupCodecAndSurface(MediaCodec encoder, InputSurface inputSurface)1054     private static void cleanupCodecAndSurface(MediaCodec encoder, InputSurface inputSurface) {
1055         if (encoder != null) {
1056             encoder.stop();
1057             encoder.release();
1058         }
1059 
1060         if (inputSurface != null) {
1061             inputSurface.release();
1062         }
1063     }
1064 
getOutputBufferIndices(MediaCodec encoder, InputSurface inputSurface)1065     private static List<Integer> getOutputBufferIndices(MediaCodec encoder, InputSurface inputSurface) {
1066         boolean feedMoreFrames;
1067         MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
1068         List<Integer> indices = new ArrayList<>();
1069         do {
1070             feedMoreFrames = indices.isEmpty();
1071             feedEncoder(encoder, inputSurface, -1);
1072             // dequeue buffers until not available
1073             int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
1074             while (index >= 0) {
1075                 indices.add(index);
1076                 index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT);
1077             }
1078         } while (feedMoreFrames);
1079         assertFalse(indices.isEmpty());
1080         return indices;
1081     }
1082 
1083     /**
1084      * @param encoder audio/video encoder
1085      * @param inputSurface null for and only for audio encoders
1086      * @param inputIndex only used for audio; if -1 the function would attempt to dequeue from encoder;
1087      * do not use -1 for asynchronous encoders
1088      */
feedEncoder(MediaCodec encoder, InputSurface inputSurface, int inputIndex)1089     private static void feedEncoder(MediaCodec encoder, InputSurface inputSurface, int inputIndex) {
1090         if (inputSurface == null) {
1091             // audio
1092             while (inputIndex == -1) {
1093                 inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
1094             }
1095             ByteBuffer inputBuffer = encoder.getInputBuffer(inputIndex);;
1096             for (int i = 0; i < inputBuffer.capacity() / 2; i++) {
1097                 inputBuffer.putShort((short)i);
1098             }
1099             encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0);
1100         } else {
1101             // video
1102             GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
1103             GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
1104             inputSurface.swapBuffers();
1105         }
1106     }
1107 
releaseAfterFlush( MediaCodec encoder, List<Integer> outputBufferIndices, CountDownLatch buffersExhausted, CountDownLatch codecFlushed)1108     private static void releaseAfterFlush(
1109             MediaCodec encoder,
1110             List<Integer> outputBufferIndices,
1111             CountDownLatch buffersExhausted,
1112             CountDownLatch codecFlushed) {
1113         if (buffersExhausted == null) {
1114             // flush from same thread
1115             encoder.flush();
1116         } else {
1117             assertNotNull(codecFlushed);
1118             buffersExhausted.countDown();
1119             try {
1120                 codecFlushed.await();
1121             } catch (InterruptedException e) {
1122                 Thread.currentThread().interrupt();
1123                 Log.w(TAG, "codecFlushed wait interrupted; releasing buffers immediately.", e);
1124             }
1125         }
1126 
1127         for (int index : outputBufferIndices) {
1128             try {
1129                 encoder.releaseOutputBuffer(index, true);
1130                 fail("MediaCodec releaseOutputBuffer after flush() does not throw exception");
1131             } catch (MediaCodec.CodecException e) {
1132                 // Expected
1133             }
1134         }
1135     }
1136 
1137     /**
1138      * Tests:
1139      * <br> dequeueInputBuffer() fails when encoder configured with an input Surface
1140      */
1141     @ApiTest(apis = {"MediaCodec#dequeueInputBuffer", "MediaCodec#getMetrics"})
1142     @Test
1143     @FrameworkSpecificTest // tests only the first MIME_TYPE codec, which is usually hardware
testDequeueSurface()1144     public void testDequeueSurface() {
1145         if (!supportsCodec(MIME_TYPE, true)) {
1146             Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
1147             return;
1148         }
1149 
1150         MediaFormat format = createMediaFormat();
1151         MediaCodec encoder = null;
1152         Surface surface = null;
1153 
1154         try {
1155             try {
1156                 encoder = MediaCodec.createEncoderByType(MIME_TYPE);
1157             } catch (IOException e) {
1158                 fail("failed to create " + MIME_TYPE + " encoder");
1159             }
1160             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1161             surface = encoder.createInputSurface();
1162             encoder.start();
1163 
1164             try {
1165                 encoder.dequeueInputBuffer(-1);
1166                 fail("dequeueInputBuffer should fail on encoder with input surface");
1167             } catch (IllegalStateException ise) {
1168                 // good
1169             }
1170 
1171             PersistableBundle metrics = encoder.getMetrics();
1172             if (metrics == null) {
1173                 fail("getMetrics() returns null");
1174             } else if (metrics.isEmpty()) {
1175                 fail("getMetrics() returns empty results");
1176             }
1177             int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
1178             if (encoding != 1) {
1179                 fail("getMetrics() returns bad encoder value " + encoding);
1180             }
1181             String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
1182             if (theCodec == null) {
1183                 fail("getMetrics() returns null codec value ");
1184             }
1185 
1186         } finally {
1187             if (encoder != null) {
1188                 encoder.stop();
1189                 encoder.release();
1190             }
1191             if (surface != null) {
1192                 surface.release();
1193             }
1194         }
1195     }
1196 
1197     /**
1198      * Tests:
1199      * <br> configure() encoder with Surface, re-configure() without Surface works
1200      * <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails
1201      */
1202     @ApiTest(apis = {"MediaCodec#configure", "MediaCodec#signalEndOfInputStream",
1203             "MediaCodec#getMetrics"})
1204     @Test
1205     @FrameworkSpecificTest // tests only the first MIME_TYPE codec, which is usually hardware
testReconfigureWithoutSurface()1206     public void testReconfigureWithoutSurface() {
1207         if (!supportsCodec(MIME_TYPE, true)) {
1208             Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
1209             return;
1210         }
1211 
1212         MediaFormat format = createMediaFormat();
1213         MediaCodec encoder = null;
1214         Surface surface = null;
1215 
1216         try {
1217             try {
1218                 encoder = MediaCodec.createEncoderByType(MIME_TYPE);
1219             } catch (IOException e) {
1220                 fail("failed to create " + MIME_TYPE + " encoder");
1221             }
1222             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1223             surface = encoder.createInputSurface();
1224             encoder.start();
1225 
1226             encoder.getOutputBuffers();
1227 
1228             // re-configure, this time without an input surface
1229             if (VERBOSE) Log.d(TAG, "reconfiguring");
1230             encoder.stop();
1231             // Use non-opaque color format for byte buffer mode.
1232             format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
1233                     MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
1234             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1235             encoder.start();
1236             if (VERBOSE) Log.d(TAG, "reconfigured");
1237 
1238             encoder.getOutputBuffers();
1239             encoder.dequeueInputBuffer(-1);
1240 
1241             try {
1242                 encoder.signalEndOfInputStream();
1243                 fail("signalEndOfInputStream only works on surface input");
1244             } catch (IllegalStateException ise) {
1245                 // good
1246             }
1247 
1248             PersistableBundle metrics = encoder.getMetrics();
1249             if (metrics == null) {
1250                 fail("getMetrics() returns null");
1251             } else if (metrics.isEmpty()) {
1252                 fail("getMetrics() returns empty results");
1253             }
1254             int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
1255             if (encoding != 1) {
1256                 fail("getMetrics() returns bad encoder value " + encoding);
1257             }
1258             String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
1259             if (theCodec == null) {
1260                 fail("getMetrics() returns null codec value ");
1261             }
1262 
1263         } finally {
1264             if (encoder != null) {
1265                 encoder.stop();
1266                 encoder.release();
1267             }
1268             if (surface != null) {
1269                 surface.release();
1270             }
1271         }
1272     }
1273 
1274     @ApiTest(apis = "MediaCodec#flush")
1275     @Test
testDecodeAfterFlush()1276     public void testDecodeAfterFlush() throws InterruptedException {
1277         testDecodeAfterFlush(true /* audio */);
1278         testDecodeAfterFlush(false /* audio */);
1279     }
1280 
testDecodeAfterFlush(final boolean audio)1281     private void testDecodeAfterFlush(final boolean audio) throws InterruptedException {
1282         final AtomicBoolean completed = new AtomicBoolean(false);
1283         Thread decodingThread = new Thread(new Runnable() {
1284             @Override
1285             public void run() {
1286                 OutputSurface outputSurface = null;
1287                 MediaExtractor mediaExtractor = null;
1288                 MediaCodec mediaCodec = null;
1289                 try {
1290                     String mimeTypePrefix  = audio ? "audio/" : "video/";
1291                     if (!audio) {
1292                         outputSurface = new OutputSurface(1, 1);
1293                     }
1294                     mediaExtractor = getMediaExtractorForMimeType(INPUT_RESOURCE, mimeTypePrefix);
1295                     MediaFormat mediaFormat =
1296                             mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
1297                     if (!MediaUtils.checkDecoderForFormat(mediaFormat)) {
1298                         completed.set(true);
1299                         return; // skip
1300                     }
1301                     String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
1302                     mediaCodec = MediaCodec.createDecoderByType(mimeType);
1303                     mediaCodec.configure(mediaFormat, outputSurface == null ? null : outputSurface.getSurface(),
1304                             null /* crypto */, 0 /* flags */);
1305                     mediaCodec.start();
1306 
1307                     if (!runDecodeTillFirstOutput(mediaCodec, mediaExtractor)) {
1308                         throw new RuntimeException("decoder does not generate non-empty output.");
1309                     }
1310 
1311                     PersistableBundle metrics = mediaCodec.getMetrics();
1312                     if (metrics == null) {
1313                         fail("getMetrics() returns null");
1314                     } else if (metrics.isEmpty()) {
1315                         fail("getMetrics() returns empty results");
1316                     }
1317                     int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
1318                     if (encoder != 0) {
1319                         fail("getMetrics() returns bad encoder value " + encoder);
1320                     }
1321                     String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
1322                     if (theCodec == null) {
1323                         fail("getMetrics() returns null codec value ");
1324                     }
1325 
1326 
1327                     // simulate application flush.
1328                     mediaExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
1329                     mediaCodec.flush();
1330 
1331                     completed.set(runDecodeTillFirstOutput(mediaCodec, mediaExtractor));
1332                     metrics = mediaCodec.getMetrics();
1333                     if (metrics == null) {
1334                         fail("getMetrics() returns null");
1335                     } else if (metrics.isEmpty()) {
1336                         fail("getMetrics() returns empty results");
1337                     }
1338                     int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
1339                     if (encoding != 0) {
1340                         fail("getMetrics() returns bad encoder value " + encoding);
1341                     }
1342                     String theCodec2 = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
1343                     if (theCodec2 == null) {
1344                         fail("getMetrics() returns null codec value ");
1345                     }
1346 
1347                 } catch (IOException e) {
1348                     throw new RuntimeException("error setting up decoding", e);
1349                 } finally {
1350                     if (mediaCodec != null) {
1351                         mediaCodec.stop();
1352 
1353                         PersistableBundle metrics = mediaCodec.getMetrics();
1354                         if (metrics == null) {
1355                             fail("getMetrics() returns null");
1356                         } else if (metrics.isEmpty()) {
1357                             fail("getMetrics() returns empty results");
1358                         }
1359                         int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
1360                         if (encoder != 0) {
1361                             fail("getMetrics() returns bad encoder value " + encoder);
1362                         }
1363                         String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
1364                         if (theCodec == null) {
1365                             fail("getMetrics() returns null codec value ");
1366                         }
1367 
1368                         mediaCodec.release();
1369                     }
1370                     if (mediaExtractor != null) {
1371                         mediaExtractor.release();
1372                     }
1373                     if (outputSurface != null) {
1374                         outputSurface.release();
1375                     }
1376                 }
1377             }
1378         });
1379         decodingThread.start();
1380         decodingThread.join(DECODING_TIMEOUT_MS);
1381         // In case it's timed out, need to stop the thread and have all resources released.
1382         decodingThread.interrupt();
1383         if (!completed.get()) {
1384             throw new RuntimeException("timed out decoding to end-of-stream");
1385         }
1386     }
1387 
1388     // Run the decoder till it generates an output buffer.
1389     // Return true when that output buffer is not empty, false otherwise.
runDecodeTillFirstOutput( MediaCodec mediaCodec, MediaExtractor mediaExtractor)1390     private static boolean runDecodeTillFirstOutput(
1391             MediaCodec mediaCodec, MediaExtractor mediaExtractor) {
1392         final int TIME_OUT_US = 10000;
1393 
1394         assertTrue("Wrong test stream which has no data.",
1395                 mediaExtractor.getSampleTrackIndex() != -1);
1396         boolean signaledEos = false;
1397         MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
1398         while (!Thread.interrupted()) {
1399             // Try to feed more data into the codec.
1400             if (!signaledEos) {
1401                 int bufferIndex = mediaCodec.dequeueInputBuffer(TIME_OUT_US /* timeoutUs */);
1402                 if (bufferIndex != -1) {
1403                     ByteBuffer buffer = mediaCodec.getInputBuffer(bufferIndex);
1404                     int size = mediaExtractor.readSampleData(buffer, 0 /* offset */);
1405                     long timestampUs = mediaExtractor.getSampleTime();
1406                     mediaExtractor.advance();
1407                     signaledEos = mediaExtractor.getSampleTrackIndex() == -1;
1408                     mediaCodec.queueInputBuffer(bufferIndex,
1409                             0 /* offset */,
1410                             size,
1411                             timestampUs,
1412                             signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
1413                     Log.i("DEBUG", "queue with " + signaledEos);
1414                 }
1415             }
1416 
1417             int outputBufferIndex = mediaCodec.dequeueOutputBuffer(
1418                     outputBufferInfo, TIME_OUT_US /* timeoutUs */);
1419 
1420             if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
1421                     || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
1422                     || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
1423                 continue;
1424             }
1425             assertTrue("Wrong output buffer index", outputBufferIndex >= 0);
1426 
1427             PersistableBundle metrics = mediaCodec.getMetrics();
1428             Log.d(TAG, "getMetrics after first buffer metrics says: " + metrics);
1429 
1430             int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
1431             if (encoder != 0) {
1432                 fail("getMetrics() returns bad encoder value " + encoder);
1433             }
1434             String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
1435             if (theCodec == null) {
1436                 fail("getMetrics() returns null codec value ");
1437             }
1438 
1439             mediaCodec.releaseOutputBuffer(outputBufferIndex, false /* render */);
1440             boolean eos = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
1441             Log.i("DEBUG", "Got a frame with eos=" + eos);
1442             if (eos && outputBufferInfo.size == 0) {
1443                 return false;
1444             } else {
1445                 return true;
1446             }
1447         }
1448 
1449         return false;
1450     }
1451 
1452     /**
1453      * Tests whether decoding a short group-of-pictures succeeds. The test queues a few video frames
1454      * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
1455      */
1456     @ApiTest(apis = {"MediaCodecInfo.CodecCapabilities#COLOR_FormatSurface"})
1457     @Test
testDecodeShortInput()1458     public void testDecodeShortInput() throws InterruptedException {
1459         // Input buffers from this input video are queued up to and including the video frame with
1460         // timestamp LAST_BUFFER_TIMESTAMP_US.
1461         final String INPUT_RESOURCE =
1462                 "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
1463         final long LAST_BUFFER_TIMESTAMP_US = 166666;
1464 
1465         // The test should fail if the decoder never produces output frames for the truncated input.
1466         // Time out decoding, as we have no way to query whether the decoder will produce output.
1467         final int DECODING_TIMEOUT_MS = 2000;
1468 
1469         final AtomicBoolean completed = new AtomicBoolean();
1470         Thread videoDecodingThread = new Thread(new Runnable() {
1471             @Override
1472             public void run() {
1473                 completed.set(runDecodeShortInput(INPUT_RESOURCE, LAST_BUFFER_TIMESTAMP_US));
1474             }
1475         });
1476         videoDecodingThread.start();
1477         videoDecodingThread.join(DECODING_TIMEOUT_MS);
1478         if (!completed.get()) {
1479             throw new RuntimeException("timed out decoding to end-of-stream");
1480         }
1481     }
1482 
runDecodeShortInput(final String inputResource, long lastBufferTimestampUs)1483     private boolean runDecodeShortInput(final String inputResource, long lastBufferTimestampUs) {
1484         final int NO_BUFFER_INDEX = -1;
1485 
1486         OutputSurface outputSurface = null;
1487         MediaExtractor mediaExtractor = null;
1488         MediaCodec mediaCodec = null;
1489         try {
1490             outputSurface = new OutputSurface(1, 1);
1491             mediaExtractor = getMediaExtractorForMimeType(inputResource, "video/");
1492             MediaFormat mediaFormat =
1493                     mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
1494             String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
1495             if (!supportsCodec(mimeType, false)) {
1496                 Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
1497                 return true;
1498             }
1499             mediaCodec =
1500                     MediaCodec.createDecoderByType(mimeType);
1501             mediaCodec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
1502             mediaCodec.start();
1503             boolean eos = false;
1504             boolean signaledEos = false;
1505             MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
1506             int outputBufferIndex = NO_BUFFER_INDEX;
1507             while (!eos && !Thread.interrupted()) {
1508                 // Try to feed more data into the codec.
1509                 if (mediaExtractor.getSampleTrackIndex() != -1 && !signaledEos) {
1510                     int bufferIndex = mediaCodec.dequeueInputBuffer(0);
1511                     if (bufferIndex != NO_BUFFER_INDEX) {
1512                         ByteBuffer buffer = mediaCodec.getInputBuffers()[bufferIndex];
1513                         int size = mediaExtractor.readSampleData(buffer, 0);
1514                         long timestampUs = mediaExtractor.getSampleTime();
1515                         mediaExtractor.advance();
1516                         signaledEos = mediaExtractor.getSampleTrackIndex() == -1
1517                                 || timestampUs == lastBufferTimestampUs;
1518                         mediaCodec.queueInputBuffer(bufferIndex,
1519                                 0,
1520                                 size,
1521                                 timestampUs,
1522                                 signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
1523                     }
1524                 }
1525 
1526                 // If we don't have an output buffer, try to get one now.
1527                 if (outputBufferIndex == NO_BUFFER_INDEX) {
1528                     outputBufferIndex = mediaCodec.dequeueOutputBuffer(outputBufferInfo, 0);
1529                 }
1530 
1531                 if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
1532                         || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
1533                         || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
1534                     outputBufferIndex = NO_BUFFER_INDEX;
1535                 } else if (outputBufferIndex != NO_BUFFER_INDEX) {
1536                     eos = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
1537 
1538                     boolean render = outputBufferInfo.size > 0;
1539                     mediaCodec.releaseOutputBuffer(outputBufferIndex, render);
1540                     if (render) {
1541                         outputSurface.awaitNewImage();
1542                     }
1543 
1544                     outputBufferIndex = NO_BUFFER_INDEX;
1545                 }
1546             }
1547 
1548             return eos;
1549         } catch (IOException e) {
1550             throw new RuntimeException("error reading input resource", e);
1551         } finally {
1552             if (mediaCodec != null) {
1553                 mediaCodec.stop();
1554                 mediaCodec.release();
1555             }
1556             if (mediaExtractor != null) {
1557                 mediaExtractor.release();
1558             }
1559             if (outputSurface != null) {
1560                 outputSurface.release();
1561             }
1562         }
1563     }
1564 
1565     /**
1566      * Tests creating two decoders for {@link #MIME_TYPE_AUDIO} at the same time.
1567      */
1568     @ApiTest(apis = {"MediaCodec#createDecoderByType",
1569             "android.media.MediaFormat#KEY_MIME",
1570             "android.media.MediaFormat#KEY_SAMPLE_RATE",
1571             "android.media.MediaFormat#KEY_CHANNEL_COUNT"})
1572     @Test
testCreateTwoAudioDecoders()1573     public void testCreateTwoAudioDecoders() {
1574         final MediaFormat format = MediaFormat.createAudioFormat(
1575                 MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
1576 
1577         MediaCodec audioDecoderA = null;
1578         MediaCodec audioDecoderB = null;
1579         try {
1580             try {
1581                 audioDecoderA = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
1582             } catch (IOException e) {
1583                 fail("failed to create first " + MIME_TYPE_AUDIO + " decoder");
1584             }
1585             audioDecoderA.configure(format, null, null, 0);
1586             audioDecoderA.start();
1587 
1588             try {
1589                 audioDecoderB = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
1590             } catch (IOException e) {
1591                 fail("failed to create second " + MIME_TYPE_AUDIO + " decoder");
1592             }
1593             audioDecoderB.configure(format, null, null, 0);
1594             audioDecoderB.start();
1595         } finally {
1596             if (audioDecoderB != null) {
1597                 try {
1598                     audioDecoderB.stop();
1599                     audioDecoderB.release();
1600                 } catch (RuntimeException e) {
1601                     Log.w(TAG, "exception stopping/releasing codec", e);
1602                 }
1603             }
1604 
1605             if (audioDecoderA != null) {
1606                 try {
1607                     audioDecoderA.stop();
1608                     audioDecoderA.release();
1609                 } catch (RuntimeException e) {
1610                     Log.w(TAG, "exception stopping/releasing codec", e);
1611                 }
1612             }
1613         }
1614     }
1615 
1616     /**
1617      * Tests creating an encoder and decoder for {@link #MIME_TYPE_AUDIO} at the same time.
1618      */
1619     @ApiTest(apis = {"MediaCodec#createDecoderByType", "MediaCodec#createEncoderByType",
1620             "android.media.MediaFormat#KEY_MIME",
1621             "android.media.MediaFormat#KEY_SAMPLE_RATE",
1622             "android.media.MediaFormat#KEY_CHANNEL_COUNT"})
1623     @Test
testCreateAudioDecoderAndEncoder()1624     public void testCreateAudioDecoderAndEncoder() {
1625         if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
1626             Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
1627             return;
1628         }
1629 
1630         if (!supportsCodec(MIME_TYPE_AUDIO, false)) {
1631             Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE_AUDIO);
1632             return;
1633         }
1634 
1635         final MediaFormat encoderFormat = MediaFormat.createAudioFormat(
1636                 MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
1637         encoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
1638         encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
1639         final MediaFormat decoderFormat = MediaFormat.createAudioFormat(
1640                 MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
1641 
1642         MediaCodec audioEncoder = null;
1643         MediaCodec audioDecoder = null;
1644         try {
1645             try {
1646                 audioEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
1647             } catch (IOException e) {
1648                 fail("failed to create " + MIME_TYPE_AUDIO + " encoder");
1649             }
1650             audioEncoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1651             audioEncoder.start();
1652 
1653             try {
1654                 audioDecoder = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
1655             } catch (IOException e) {
1656                 fail("failed to create " + MIME_TYPE_AUDIO + " decoder");
1657             }
1658             audioDecoder.configure(decoderFormat, null, null, 0);
1659             audioDecoder.start();
1660         } finally {
1661             if (audioDecoder != null) {
1662                 try {
1663                     audioDecoder.stop();
1664                     audioDecoder.release();
1665                 } catch (RuntimeException e) {
1666                     Log.w(TAG, "exception stopping/releasing codec", e);
1667                 }
1668             }
1669 
1670             if (audioEncoder != null) {
1671                 try {
1672                     audioEncoder.stop();
1673                     audioEncoder.release();
1674                 } catch (RuntimeException e) {
1675                     Log.w(TAG, "exception stopping/releasing codec", e);
1676                 }
1677             }
1678         }
1679     }
1680 
1681     @ApiTest(apis = {"MediaCodec#createEncoderByType",
1682             "android.media.MediaFormat#KEY_MIME",
1683             "android.media.MediaFormat#KEY_SAMPLE_RATE",
1684             "android.media.MediaFormat#KEY_CHANNEL_COUNT",
1685             "android.media.MediaFormat#KEY_WIDTH",
1686             "android.media.MediaFormat#KEY_HEIGHT"})
1687     @Test
testConcurrentAudioVideoEncodings()1688     public void testConcurrentAudioVideoEncodings() throws InterruptedException {
1689         if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
1690             Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
1691             return;
1692         }
1693 
1694         if (!supportsCodec(MIME_TYPE, true)) {
1695             Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
1696             return;
1697         }
1698 
1699         final int VIDEO_NUM_SWAPS = 100;
1700         // audio only checks this and stop
1701         mVideoEncodingOngoing = true;
1702         final CodecInfo info = getAvcSupportedFormatInfo();
1703         long start = System.currentTimeMillis();
1704         Thread videoEncodingThread = new Thread(new Runnable() {
1705             @Override
1706             public void run() {
1707                 runVideoEncoding(VIDEO_NUM_SWAPS, info);
1708             }
1709         });
1710         Thread audioEncodingThread = new Thread(new Runnable() {
1711             @Override
1712             public void run() {
1713                 runAudioEncoding();
1714             }
1715         });
1716         videoEncodingThread.start();
1717         audioEncodingThread.start();
1718         videoEncodingThread.join();
1719         mVideoEncodingOngoing = false;
1720         audioEncodingThread.join();
1721         assertFalse("Video encoding error. Chekc logcat", mVideoEncoderHadError);
1722         assertFalse("Audio encoding error. Chekc logcat", mAudioEncoderHadError);
1723         long end = System.currentTimeMillis();
1724         Log.w(TAG, "Concurrent AV encoding took " + (end - start) + " ms for " + VIDEO_NUM_SWAPS +
1725                 " video frames");
1726     }
1727 
1728     private static class CodecInfo {
1729         public int mMaxW;
1730         public int mMaxH;
1731         public int mFps;
1732         public int mBitRate;
1733     }
1734 
1735     @ApiTest(apis = {"MediaCodec#CryptoInfo", "MediaCodec#CryptoInfo#Pattern"})
1736     @Test
1737     @FrameworkSpecificTest // media mainline doesn't update crypto
testCryptoInfoPattern()1738     public void testCryptoInfoPattern() {
1739         CryptoInfo info = new CryptoInfo();
1740         Pattern pattern = new Pattern(1 /*blocksToEncrypt*/, 2 /*blocksToSkip*/);
1741         assertEquals(1, pattern.getEncryptBlocks());
1742         assertEquals(2, pattern.getSkipBlocks());
1743         pattern.set(3 /*blocksToEncrypt*/, 4 /*blocksToSkip*/);
1744         assertEquals(3, pattern.getEncryptBlocks());
1745         assertEquals(4, pattern.getSkipBlocks());
1746         info.setPattern(pattern);
1747         // Check that CryptoInfo does not leak access to the underlying pattern.
1748         if (mIsAtLeastS) {
1749             // getPattern() availability SDK>=S
1750             pattern.set(10, 10);
1751             info.getPattern().set(10, 10);
1752             assertSame(3, info.getPattern().getEncryptBlocks());
1753             assertSame(4, info.getPattern().getSkipBlocks());
1754         }
1755     }
1756 
getAvcSupportedFormatInfo()1757     private static CodecInfo getAvcSupportedFormatInfo() {
1758         MediaCodecInfo mediaCodecInfo = selectCodec(MIME_TYPE);
1759         CodecCapabilities cap = mediaCodecInfo.getCapabilitiesForType(MIME_TYPE);
1760         if (cap == null) { // not supported
1761             return null;
1762         }
1763         CodecInfo info = new CodecInfo();
1764         int highestLevel = 0;
1765         for (CodecProfileLevel lvl : cap.profileLevels) {
1766             if (lvl.level > highestLevel) {
1767                 highestLevel = lvl.level;
1768             }
1769         }
1770         int maxW = 0;
1771         int maxH = 0;
1772         int bitRate = 0;
1773         int fps = 0; // frame rate for the max resolution
1774         switch(highestLevel) {
1775             // Do not support Level 1 to 2.
1776             case CodecProfileLevel.AVCLevel1:
1777             case CodecProfileLevel.AVCLevel11:
1778             case CodecProfileLevel.AVCLevel12:
1779             case CodecProfileLevel.AVCLevel13:
1780             case CodecProfileLevel.AVCLevel1b:
1781             case CodecProfileLevel.AVCLevel2:
1782                 return null;
1783             case CodecProfileLevel.AVCLevel21:
1784                 maxW = 352;
1785                 maxH = 576;
1786                 bitRate = 4000000;
1787                 fps = 25;
1788                 break;
1789             case CodecProfileLevel.AVCLevel22:
1790                 maxW = 720;
1791                 maxH = 480;
1792                 bitRate = 4000000;
1793                 fps = 15;
1794                 break;
1795             case CodecProfileLevel.AVCLevel3:
1796                 maxW = 720;
1797                 maxH = 480;
1798                 bitRate = 10000000;
1799                 fps = 30;
1800                 break;
1801             case CodecProfileLevel.AVCLevel31:
1802                 maxW = 1280;
1803                 maxH = 720;
1804                 bitRate = 14000000;
1805                 fps = 30;
1806                 break;
1807             case CodecProfileLevel.AVCLevel32:
1808                 maxW = 1280;
1809                 maxH = 720;
1810                 bitRate = 20000000;
1811                 fps = 60;
1812                 break;
1813             case CodecProfileLevel.AVCLevel4: // only try up to 1080p
1814             default:
1815                 maxW = 1920;
1816                 maxH = 1080;
1817                 bitRate = 20000000;
1818                 fps = 30;
1819                 break;
1820         }
1821         info.mMaxW = maxW;
1822         info.mMaxH = maxH;
1823         info.mFps = fps;
1824         info.mBitRate = bitRate;
1825         Log.i(TAG, "AVC Level 0x" + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
1826                 " fps " + info.mFps + " w " + maxW + " h " + maxH);
1827 
1828         return info;
1829     }
1830 
runVideoEncoding(int numSwap, CodecInfo info)1831     private void runVideoEncoding(int numSwap, CodecInfo info) {
1832         MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, info.mMaxW, info.mMaxH);
1833         format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
1834                 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
1835         format.setInteger(MediaFormat.KEY_BIT_RATE, info.mBitRate);
1836         format.setInteger(MediaFormat.KEY_FRAME_RATE, info.mFps);
1837         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
1838         MediaCodec encoder = null;
1839         InputSurface inputSurface = null;
1840         mVideoEncoderHadError = false;
1841         try {
1842             encoder = MediaCodec.createEncoderByType(MIME_TYPE);
1843             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1844             inputSurface = new InputSurface(encoder.createInputSurface());
1845             inputSurface.makeCurrent();
1846             MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
1847             encoder.start();
1848             for (int i = 0; i < numSwap; i++) {
1849                 GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
1850                 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
1851                 inputSurface.swapBuffers();
1852                 // dequeue buffers until not available
1853                 int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
1854                 while (index >= 0) {
1855                     encoder.releaseOutputBuffer(index, false);
1856                     // just throw away output
1857                     // allow shorter wait for 2nd round to move on quickly.
1858                     index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT);
1859                 }
1860             }
1861             encoder.signalEndOfInputStream();
1862         } catch (Throwable e) {
1863             Log.w(TAG, "runVideoEncoding got error: " + e);
1864             mVideoEncoderHadError = true;
1865         } finally {
1866             if (encoder != null) {
1867                 encoder.stop();
1868                 encoder.release();
1869             }
1870             if (inputSurface != null) {
1871                 inputSurface.release();
1872             }
1873         }
1874     }
1875 
runAudioEncoding()1876     private void runAudioEncoding() {
1877         MediaFormat format = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE,
1878                 AUDIO_CHANNEL_COUNT);
1879         format.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
1880         format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
1881         MediaCodec encoder = null;
1882         mAudioEncoderHadError = false;
1883         try {
1884             encoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
1885             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
1886             MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
1887             encoder.start();
1888             ByteBuffer[] inputBuffers = encoder.getInputBuffers();
1889             ByteBuffer source = ByteBuffer.allocate(inputBuffers[0].capacity());
1890             for (int i = 0; i < source.capacity()/2; i++) {
1891                 source.putShort((short)i);
1892             }
1893             source.rewind();
1894             int currentInputBufferIndex = 0;
1895             long encodingLatencySum = 0;
1896             int totalEncoded = 0;
1897             int numRepeat = 0;
1898             while (mVideoEncodingOngoing) {
1899                 numRepeat++;
1900                 int inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
1901                 while (inputIndex == -1) {
1902                     inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
1903                 }
1904                 ByteBuffer inputBuffer = inputBuffers[inputIndex];
1905                 inputBuffer.rewind();
1906                 inputBuffer.put(source);
1907                 long start = System.currentTimeMillis();
1908                 totalEncoded += inputBuffers[inputIndex].limit();
1909                 encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0);
1910                 source.rewind();
1911                 int index = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
1912                 long end = System.currentTimeMillis();
1913                 encodingLatencySum += (end - start);
1914                 while (index >= 0) {
1915                     encoder.releaseOutputBuffer(index, false);
1916                     // just throw away output
1917                     // allow shorter wait for 2nd round to move on quickly.
1918                     index = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC_SHORT);
1919                 }
1920             }
1921             Log.w(TAG, "Audio encoding average latency " + encodingLatencySum / numRepeat +
1922                     " ms for average write size " + totalEncoded / numRepeat +
1923                     " total latency " + encodingLatencySum + " ms for total bytes " + totalEncoded);
1924         } catch (Throwable e) {
1925             Log.w(TAG, "runAudioEncoding got error: " + e);
1926             mAudioEncoderHadError = true;
1927         } finally {
1928             if (encoder != null) {
1929                 encoder.stop();
1930                 encoder.release();
1931             }
1932         }
1933     }
1934 
1935     /**
1936      * Creates a MediaFormat with the basic set of values.
1937      */
createMediaFormat()1938     private static MediaFormat createMediaFormat() {
1939         MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
1940         format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
1941                 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
1942         format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
1943         format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
1944         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
1945         return format;
1946     }
1947 
1948     /**
1949      * Returns the first codec capable of encoding the specified MIME type, or null if no
1950      * match was found.
1951      */
selectCodec(String mimeType)1952     private static MediaCodecInfo selectCodec(String mimeType) {
1953         // FIXME: select codecs based on the complete use-case, not just the mime
1954         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
1955         for (MediaCodecInfo info : mcl.getCodecInfos()) {
1956             if (!info.isEncoder()) {
1957                 continue;
1958             }
1959 
1960             String[] types = info.getSupportedTypes();
1961             for (int j = 0; j < types.length; j++) {
1962                 if (types[j].equalsIgnoreCase(mimeType)) {
1963                     return info;
1964                 }
1965             }
1966         }
1967         return null;
1968     }
1969 
1970     /**
1971      * Returns a color format that is supported by the codec and isn't COLOR_FormatSurface.  Throws
1972      * an exception if none found.
1973      */
findNonSurfaceColorFormat(MediaCodecInfo codecInfo, String mimeType)1974     private static int findNonSurfaceColorFormat(MediaCodecInfo codecInfo, String mimeType) {
1975         MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
1976         for (int i = 0; i < capabilities.colorFormats.length; i++) {
1977             int colorFormat = capabilities.colorFormats[i];
1978             if (colorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) {
1979                 return colorFormat;
1980             }
1981         }
1982         fail("couldn't find a good color format for " + codecInfo.getName() + " / " + MIME_TYPE);
1983         return 0;   // not reached
1984     }
1985 
getMediaExtractorForMimeType(final String resource, String mimeTypePrefix)1986     private MediaExtractor getMediaExtractorForMimeType(final String resource,
1987             String mimeTypePrefix) throws IOException {
1988         Preconditions.assertTestFileExists(mInpPrefix + resource);
1989         MediaExtractor mediaExtractor = new MediaExtractor();
1990         File inpFile = new File(mInpPrefix + resource);
1991         ParcelFileDescriptor parcelFD =
1992                 ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
1993         AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
1994         try {
1995             mediaExtractor.setDataSource(
1996                     afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
1997         } finally {
1998             afd.close();
1999         }
2000         int trackIndex;
2001         for (trackIndex = 0; trackIndex < mediaExtractor.getTrackCount(); trackIndex++) {
2002             MediaFormat trackMediaFormat = mediaExtractor.getTrackFormat(trackIndex);
2003             if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
2004                 mediaExtractor.selectTrack(trackIndex);
2005                 break;
2006             }
2007         }
2008         if (trackIndex == mediaExtractor.getTrackCount()) {
2009             throw new IllegalStateException("couldn't get a video track");
2010         }
2011 
2012         return mediaExtractor;
2013     }
2014 
supportsCodec(String mimeType, boolean encoder)2015     private static boolean supportsCodec(String mimeType, boolean encoder) {
2016         MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
2017         for (MediaCodecInfo info : list.getCodecInfos()) {
2018             if (encoder != info.isEncoder()) {
2019                 continue;
2020             }
2021 
2022             for (String type : info.getSupportedTypes()) {
2023                 if (type.equalsIgnoreCase(mimeType)) {
2024                     return true;
2025                 }
2026             }
2027         }
2028         return false;
2029     }
2030 
2031     private static final UUID CLEARKEY_SCHEME_UUID =
2032             new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
2033 
2034     /**
2035      * Tests MediaCodec.CryptoException
2036      */
2037     @ApiTest(apis = "MediaCodec#CryptoException")
2038     @Test
2039     @FrameworkSpecificTest // media mainline doesn't update crypto
testCryptoException()2040     public void testCryptoException() {
2041         int errorCode = CryptoException.ERROR_KEY_EXPIRED;
2042         String errorMessage = "key_expired";
2043         CryptoException exception = new CryptoException(errorCode, errorMessage);
2044 
2045         assertEquals(errorCode, exception.getErrorCode());
2046         assertEquals(errorMessage, exception.getMessage());
2047     }
2048 
2049     /**
2050      * PCM encoding configuration test.
2051      *
2052      * If not specified in configure(), PCM encoding if it exists must be 16 bit.
2053      * If specified float in configure(), PCM encoding if it exists must be 16 bit, or float.
2054      *
2055      * As of Q, any codec of type "audio/raw" must support PCM encoding float.
2056      */
2057     @ApiTest(apis = {"android.media.AudioFormat#ENCODING_PCM_16BIT",
2058             "android.media.AudioFormat#ENCODING_PCM_FLOAT"})
2059     @MediumTest
2060     @Test
testPCMEncoding()2061     public void testPCMEncoding() throws Exception {
2062         final MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
2063         for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
2064             final boolean isEncoder = codecInfo.isEncoder();
2065             final String name = codecInfo.getName();
2066 
2067             for (String type : codecInfo.getSupportedTypes()) {
2068                 final MediaCodecInfo.CodecCapabilities ccaps =
2069                         codecInfo.getCapabilitiesForType(type);
2070                 final MediaCodecInfo.AudioCapabilities acaps =
2071                         ccaps.getAudioCapabilities();
2072                 if (acaps == null) {
2073                     break; // not an audio codec
2074                 }
2075 
2076                 // Deduce the minimum channel count (though prefer stereo over mono).
2077                 final int channelCount = Math.min(acaps.getMaxInputChannelCount(), 2);
2078 
2079                 // Deduce the minimum sample rate.
2080                 final int[] sampleRates = acaps.getSupportedSampleRates();
2081                 final Range<Integer>[] sampleRateRanges = acaps.getSupportedSampleRateRanges();
2082                 assertNotNull("supported sample rate ranges must be non-null", sampleRateRanges);
2083                 final int sampleRate = sampleRateRanges[0].getLower();
2084 
2085                 // If sample rate array exists (it may not),
2086                 // the minimum value must be equal with the minimum from the sample rate range.
2087                 if (sampleRates != null) {
2088                     assertEquals("sample rate range and array should have equal minimum",
2089                             sampleRate, sampleRates[0]);
2090                     Log.d(TAG, "codec: " + name + " type: " + type
2091                             + " has both sampleRate array and ranges");
2092                 } else {
2093                     Log.d(TAG, "codec: " + name + " type: " + type
2094                             + " returns null getSupportedSampleRates()");
2095                 }
2096 
2097                 // We create one format here for both tests below.
2098                 final MediaFormat format = MediaFormat.createAudioFormat(
2099                     type, sampleRate, channelCount);
2100 
2101                 // Bitrate field is mandatory for encoders (except FLAC).
2102                 if (isEncoder) {
2103                     final int bitRate = acaps.getBitrateRange().getLower();
2104                     format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
2105                 }
2106 
2107                 // First test: An audio codec must be createable from a format
2108                 // with the minimum sample rate and channel count.
2109                 // The PCM encoding must be null (doesn't exist) or 16 bit.
2110                 {
2111                     // Check encoding of codec.
2112                     final Integer actualEncoding = encodingOfAudioCodec(name, format, isEncoder);
2113                     if (actualEncoding != null) {
2114                         assertEquals("returned audio encoding must be 16 bit for codec: "
2115                                 + name + " type: " + type + " encoding: " + actualEncoding,
2116                                 AudioFormat.ENCODING_PCM_16BIT, actualEncoding.intValue());
2117                     }
2118                 }
2119 
2120                 // Second test: An audio codec configured with PCM encoding float must return
2121                 // either an encoding of null (doesn't exist), 16 bit, or float.
2122                 {
2123                     // Reuse the original format, and add float specifier.
2124                     format.setInteger(
2125                             MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
2126 
2127                     // Check encoding of codec.
2128                     // The KEY_PCM_ENCODING key is advisory, so should not cause configuration
2129                     // failure.  The actual PCM encoding is returned from
2130                     // the input format (encoder) or output format (decoder).
2131                     final Integer actualEncoding = encodingOfAudioCodec(name, format, isEncoder);
2132                     if (actualEncoding != null) {
2133                         assertTrue(
2134                                 "returned audio encoding must be 16 bit or float for codec: "
2135                                 + name + " type: " + type + " encoding: " + actualEncoding,
2136                                 actualEncoding == AudioFormat.ENCODING_PCM_16BIT
2137                                 || actualEncoding == AudioFormat.ENCODING_PCM_FLOAT);
2138                         if (actualEncoding == AudioFormat.ENCODING_PCM_FLOAT) {
2139                             Log.d(TAG, "codec: " + name + " type: " + type + " supports float");
2140                         }
2141                     }
2142 
2143                     // As of Q, all codecs of type "audio/raw" must support float.
2144                     if (type.equals("audio/raw")) {
2145                         assertTrue(type + " must support float",
2146                                 actualEncoding != null &&
2147                                 actualEncoding.intValue() == AudioFormat.ENCODING_PCM_FLOAT);
2148                     }
2149                 }
2150             }
2151         }
2152     }
2153 
2154     /**
2155      * Returns the PCM encoding of an audio codec, or null if codec doesn't exist,
2156      * or not an audio codec, or PCM encoding key doesn't exist.
2157      */
encodingOfAudioCodec(String name, MediaFormat format, boolean encode)2158     private Integer encodingOfAudioCodec(String name, MediaFormat format, boolean encode)
2159             throws IOException {
2160         final int flagEncoder = encode ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
2161         final MediaCodec codec = MediaCodec.createByCodecName(name);
2162         Integer actualEncoding = null;
2163 
2164         try {
2165             codec.configure(format, null /* surface */, null /* crypto */, flagEncoder);
2166 
2167             // Check input/output format - this must exist.
2168             final MediaFormat actualFormat =
2169                     encode ? codec.getInputFormat() : codec.getOutputFormat();
2170             assertNotNull("cannot get format for " + name, actualFormat);
2171 
2172             // Check actual encoding - this may or may not exist
2173             try {
2174                 actualEncoding = actualFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);
2175             } catch (Exception e) {
2176                 ; // trying to get a non-existent key throws exception
2177             }
2178         } finally {
2179             codec.release();
2180         }
2181         return actualEncoding;
2182     }
2183 
2184     @ApiTest(apis = "android.media.AudioFormat#KEY_FLAC_COMPRESSION_LEVEL")
2185     @SmallTest
2186     @Test
testFlacIdentity()2187     public void testFlacIdentity() throws Exception {
2188         final int PCM_FRAMES = 1152 * 4; // FIXME: requires 4 flac frames to work with OMX codecs.
2189         final int SAMPLES = PCM_FRAMES * AUDIO_CHANNEL_COUNT;
2190         final int[] SAMPLE_RATES = {AUDIO_SAMPLE_RATE, 192000}; // ensure 192kHz supported.
2191 
2192         for (int sampleRate : SAMPLE_RATES) {
2193             final MediaFormat format = new MediaFormat();
2194             format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_FLAC);
2195             format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
2196             format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, AUDIO_CHANNEL_COUNT);
2197 
2198             Log.d(TAG, "Trying sample rate: " + sampleRate
2199                     + " channel count: " + AUDIO_CHANNEL_COUNT);
2200             // this key is only needed for encode, ignored for decode
2201             format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 5);
2202 
2203             for (int i = 0; i < 2; ++i) {
2204                 final boolean useFloat = (i == 1);
2205                 final StreamUtils.PcmAudioBufferStream audioStream =
2206                     new StreamUtils.PcmAudioBufferStream(SAMPLES, sampleRate, 1000 /* frequency */,
2207                     100 /* sweep */, useFloat);
2208 
2209                 if (useFloat) {
2210                     format.setInteger(
2211                         MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
2212                 }
2213 
2214                 final StreamUtils.MediaCodecStream rawToFlac = new StreamUtils.MediaCodecStream(
2215                     new StreamUtils.ByteBufferInputStream(audioStream), format, true /* encode */);
2216                 final StreamUtils.MediaCodecStream flacToRaw = new StreamUtils.MediaCodecStream(
2217                     rawToFlac, format, false /* encode */);
2218 
2219                 if (useFloat) { // ensure float precision supported at the sample rate.
2220                     assertTrue("No float FLAC encoder at " + sampleRate,
2221                             rawToFlac.mIsFloat);
2222                     assertTrue("No float FLAC decoder at " + sampleRate,
2223                             flacToRaw.mIsFloat);
2224                 }
2225 
2226                 // Note: the existence of signed zero (as well as NAN) may make byte
2227                 // comparisons invalid for floating point output. In our case, since the
2228                 // floats come through integer to float conversion, it does not matter.
2229                 assertEquals("Audio data not identical after compression",
2230                     audioStream.sizeInBytes(),
2231                     StreamUtils.compareStreams(new StreamUtils.ByteBufferInputStream(flacToRaw),
2232                         new StreamUtils.ByteBufferInputStream(
2233                         new StreamUtils.PcmAudioBufferStream(audioStream))));
2234             }
2235         }
2236     }
2237 
2238     @ApiTest(apis = "MediaCodec#release")
2239     @Test
testAsyncRelease()2240     public void testAsyncRelease() throws Exception {
2241         OutputSurface outputSurface = new OutputSurface(1, 1);
2242         MediaExtractor mediaExtractor = getMediaExtractorForMimeType(INPUT_RESOURCE, "video/");
2243         MediaFormat mediaFormat =
2244                 mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
2245         String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
2246         for (int i = 0; i < 100; ++i) {
2247             final MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
2248 
2249             try {
2250                 final ConditionVariable cv = new ConditionVariable();
2251                 Runnable first = null;
2252                 switch (i % 5) {
2253                     case 0:  // release
2254                         codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
2255                         codec.start();
2256                         first = () -> { cv.block(); codec.release(); };
2257                         break;
2258                     case 1:  // start
2259                         codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
2260                         first = () -> {
2261                             cv.block();
2262                             try {
2263                                 codec.start();
2264                             } catch (Exception e) {
2265                                 Log.i(TAG, "start failed", e);
2266                             }
2267                         };
2268                         break;
2269                     case 2:  // configure
2270                         first = () -> {
2271                             cv.block();
2272                             try {
2273                                 codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
2274                             } catch (Exception e) {
2275                                 Log.i(TAG, "configure failed", e);
2276                             }
2277                         };
2278                         break;
2279                     case 3:  // stop
2280                         codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
2281                         codec.start();
2282                         first = () -> {
2283                             cv.block();
2284                             try {
2285                                 codec.stop();
2286                             } catch (Exception e) {
2287                                 Log.i(TAG, "stop failed", e);
2288                             }
2289                         };
2290                         break;
2291                     case 4:  // flush
2292                         codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
2293                         codec.start();
2294                         codec.dequeueInputBuffer(0);
2295                         first = () -> {
2296                             cv.block();
2297                             try {
2298                                 codec.flush();
2299                             } catch (Exception e) {
2300                                 Log.i(TAG, "flush failed", e);
2301                             }
2302                         };
2303                         break;
2304                 }
2305 
2306                 Thread[] threads = new Thread[10];
2307                 threads[0] = new Thread(first);
2308                 for (int j = 1; j < threads.length; ++j) {
2309                     threads[j] = new Thread(() -> { cv.block(); codec.release(); });
2310                 }
2311                 for (Thread thread : threads) {
2312                     thread.start();
2313                 }
2314                 // Wait a little bit so that threads may reach block() call.
2315                 Thread.sleep(50);
2316                 cv.open();
2317                 for (Thread thread : threads) {
2318                     thread.join();
2319                 }
2320             } finally {
2321                 codec.release();
2322             }
2323         }
2324     }
2325 
2326     @ApiTest(apis = "MediaCodec#setAudioPresentation")
2327     @Test
testSetAudioPresentation()2328     public void testSetAudioPresentation() throws Exception {
2329         MediaFormat format = MediaFormat.createAudioFormat(
2330                 MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */);
2331         String mimeType = format.getString(MediaFormat.KEY_MIME);
2332         MediaCodec codec = createCodecByType(
2333                 format.getString(MediaFormat.KEY_MIME), false /* isEncoder */);
2334         assertNotNull(codec);
2335         assertThrows(NullPointerException.class, () -> {
2336             codec.setAudioPresentation(null);
2337         });
2338         codec.setAudioPresentation(
2339                 (new AudioPresentation.Builder(42 /* presentationId */)).build());
2340     }
2341 
2342     @ApiTest(apis = "android.media.MediaFormat#KEY_PREPEND_HEADER_TO_SYNC_FRAMES")
2343     @Test
testPrependHeadersToSyncFrames()2344     public void testPrependHeadersToSyncFrames() throws IOException {
2345         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
2346         for (MediaCodecInfo info : mcl.getCodecInfos()) {
2347             boolean isEncoder = info.isEncoder();
2348             for (String mime: info.getSupportedTypes()) {
2349                 CodecCapabilities caps = info.getCapabilitiesForType(mime);
2350                 boolean isVideo = (caps.getVideoCapabilities() != null);
2351                 boolean isAudio = (caps.getAudioCapabilities() != null);
2352 
2353                 MediaCodec codec = null;
2354                 MediaFormat format = null;
2355                 try {
2356                     codec = MediaCodec.createByCodecName(info.getName());
2357                     if (isVideo) {
2358                         VideoCapabilities vcaps = caps.getVideoCapabilities();
2359                         int minWidth = vcaps.getSupportedWidths().getLower();
2360                         int minHeight = vcaps.getSupportedHeightsFor(minWidth).getLower();
2361                         int minBitrate = vcaps.getBitrateRange().getLower();
2362                         int minFrameRate = Math.max(vcaps.getSupportedFrameRatesFor(
2363                                 minWidth, minHeight) .getLower().intValue(), 1);
2364                         format = MediaFormat.createVideoFormat(mime, minWidth, minHeight);
2365                         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
2366                         format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
2367                         format.setInteger(MediaFormat.KEY_FRAME_RATE, minFrameRate);
2368                         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
2369                     } else if(isAudio){
2370                         AudioCapabilities acaps = caps.getAudioCapabilities();
2371                         int minSampleRate = acaps.getSupportedSampleRateRanges()[0].getLower();
2372                         int minChannelCount = 1;
2373                         if (mIsAtLeastS) {
2374                             minChannelCount = acaps.getMinInputChannelCount();
2375                         }
2376                         int minBitrate = acaps.getBitrateRange().getLower();
2377                         format = MediaFormat.createAudioFormat(mime, minSampleRate, minChannelCount);
2378                         format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
2379                     }
2380 
2381                     if (isVideo || isAudio) {
2382                         format.setInteger(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES, 1);
2383 
2384                         codec.configure(format, null /* surface */, null /* crypto */,
2385                             isEncoder ? codec.CONFIGURE_FLAG_ENCODE : 0);
2386                     }
2387                     if (isVideo && isEncoder) {
2388                         Log.i(TAG, info.getName() + " supports KEY_PREPEND_HEADER_TO_SYNC_FRAMES");
2389                     } else {
2390                         Log.i(TAG, info.getName() + " is not a video encoder, so" +
2391                                 " KEY_PREPEND_HEADER_TO_SYNC_FRAMES is no-op, as expected");
2392                     }
2393                     // TODO: actually test encoders prepend the headers to sync frames.
2394                 } catch (IllegalArgumentException | CodecException e) {
2395                     if (isVideo && isEncoder) {
2396                         Log.i(TAG, info.getName() + " does not support" +
2397                                 " KEY_PREPEND_HEADER_TO_SYNC_FRAMES");
2398                     } else {
2399                         fail(info.getName() + " is not a video encoder," +
2400                                 " so it should not fail to configure.\n" + e.toString());
2401                     }
2402                 } finally {
2403                     if (codec != null) {
2404                         codec.release();
2405                     }
2406                 }
2407             }
2408         }
2409     }
2410 
2411     /**
2412      * Test if flushing early in the playback does not prevent client from getting the
2413      * latest configuration. Empirically, this happens most often when the
2414      * codec is flushed after the first buffer is queued, so this test walks
2415      * through the scenario.
2416      */
2417     @ApiTest(apis = "MediaCodec#flush")
2418     @Test
testFlushAfterFirstBuffer()2419     public void testFlushAfterFirstBuffer() throws Exception {
2420         if (MediaUtils.check(mIsAtLeastR, "test needs Android 11")) {
2421             for (int i = 0; i < 100; ++i) {
2422                 doFlushAfterFirstBuffer();
2423             }
2424         }
2425     }
2426 
doFlushAfterFirstBuffer()2427     private void doFlushAfterFirstBuffer() throws Exception {
2428         MediaExtractor extractor = null;
2429         MediaCodec codec = null;
2430 
2431         try {
2432             MediaFormat newFormat = null;
2433             extractor = getMediaExtractorForMimeType(
2434                     "noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a", "audio/");
2435             int trackIndex = extractor.getSampleTrackIndex();
2436             MediaFormat format = extractor.getTrackFormat(trackIndex);
2437             codec = createCodecByType(
2438                     format.getString(MediaFormat.KEY_MIME), false /* isEncoder */);
2439             codec.configure(format, null, null, 0);
2440             codec.start();
2441             int firstInputIndex = codec.dequeueInputBuffer(0);
2442             while (firstInputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
2443                 firstInputIndex = codec.dequeueInputBuffer(5000);
2444             }
2445             assertTrue(firstInputIndex >= 0);
2446             extractor.readSampleData(codec.getInputBuffer(firstInputIndex), 0);
2447             codec.queueInputBuffer(
2448                     firstInputIndex, 0, Math.toIntExact(extractor.getSampleSize()),
2449                     extractor.getSampleTime(), extractor.getSampleFlags());
2450             // Don't advance, so the first buffer will be read again after flush
2451             codec.flush();
2452             ByteBuffer csd = format.getByteBuffer("csd-0");
2453             MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
2454             // We don't need to decode many frames
2455             int numFrames = 10;
2456             boolean eos = false;
2457             while (!eos) {
2458                 if (numFrames > 0) {
2459                     int inputIndex = codec.dequeueInputBuffer(0);
2460                     if (inputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
2461                         inputIndex = codec.dequeueInputBuffer(5000);
2462                     }
2463                     if (inputIndex >= 0) {
2464                         ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex);
2465                         if (csd != null) {
2466                             inputBuffer.clear();
2467                             inputBuffer.put(csd);
2468                             codec.queueInputBuffer(
2469                                     inputIndex, 0, inputBuffer.position(), 0,
2470                                     MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
2471                             csd = null;
2472                         } else {
2473                             int size = extractor.readSampleData(inputBuffer, 0);
2474                             if (size <= 0) {
2475                                 break;
2476                             }
2477                             int flags = extractor.getSampleFlags();
2478                             --numFrames;
2479                             if (numFrames <= 0) {
2480                                 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
2481                             }
2482                             codec.queueInputBuffer(
2483                                     inputIndex, 0, size, extractor.getSampleTime(), flags);
2484                             extractor.advance();
2485                         }
2486                     }
2487                 }
2488 
2489                 int outputIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
2490                 if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
2491                     outputIndex = codec.dequeueOutputBuffer(bufferInfo, 5000);
2492                 }
2493                 if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
2494                     newFormat = codec.getOutputFormat();
2495                 } else if (outputIndex >= 0) {
2496                     if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
2497                         eos = true;
2498                     }
2499                     codec.releaseOutputBuffer(outputIndex, false);
2500                 }
2501             }
2502             assertNotNull(newFormat);
2503             assertEquals(48000, newFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
2504         } finally {
2505             if (extractor != null) {
2506                 extractor.release();
2507             }
2508             if (codec != null) {
2509                 codec.stop();
2510                 codec.release();
2511             }
2512         }
2513     }
2514 
2515     @ApiTest(apis = {"MediaCodec#getSupportedVendorParameters",
2516             "MediaCodec#getParameterDescriptor",
2517             "MediaCodec#subscribeToVendorParameters",
2518             "MediaCodec#unsubscribeFromVendorParameters"})
2519     @Test
testVendorParameters()2520     public void testVendorParameters() {
2521         if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) {
2522             return;
2523         }
2524         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
2525         for (MediaCodecInfo info : mcl.getCodecInfos()) {
2526             if (info.isAlias()) {
2527                 continue;
2528             }
2529             MediaCodec codec = null;
2530             if (!TestUtils.isTestableCodecInCurrentMode(info.getName())) {
2531                 Log.d(TAG, "skip testing codec " + info.getName() + " in current mode:"
2532                                 + TestUtils.currentTestModeName());
2533                 continue;
2534             }
2535             try {
2536                 codec = MediaCodec.createByCodecName(info.getName());
2537                 List<String> vendorParams = codec.getSupportedVendorParameters();
2538                 if (VERBOSE) {
2539                     Log.d(TAG, "vendor params supported by " + info.getName() + ": " +
2540                             vendorParams.toString());
2541                 }
2542                 for (String name : vendorParams) {
2543                     MediaCodec.ParameterDescriptor desc = codec.getParameterDescriptor(name);
2544                     assertNotNull(name + " is in the list of supported parameters, so the codec" +
2545                             " should be able to describe it.", desc);
2546                     assertEquals("name differs from the name in the descriptor",
2547                             name, desc.getName());
2548                     assertTrue("type in the descriptor cannot be TYPE_NULL",
2549                             MediaFormat.TYPE_NULL != desc.getType());
2550                     if (VERBOSE) {
2551                         Log.d(TAG, name + " is of type " + desc.getType());
2552                     }
2553                 }
2554                 codec.subscribeToVendorParameters(vendorParams);
2555 
2556                 // Build a MediaFormat that makes sense to the codec.
2557                 String type = info.getSupportedTypes()[0];
2558                 MediaFormat format = null;
2559                 CodecCapabilities caps = info.getCapabilitiesForType(type);
2560                 AudioCapabilities audioCaps = caps.getAudioCapabilities();
2561                 VideoCapabilities videoCaps = caps.getVideoCapabilities();
2562                 if (audioCaps != null) {
2563                     format = MediaFormat.createAudioFormat(
2564                             type,
2565                             audioCaps.getSupportedSampleRateRanges()[0].getLower(),
2566                             audioCaps.getMaxInputChannelCount());
2567                     if (info.isEncoder()) {
2568                         format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
2569                     }
2570                 } else if (videoCaps != null) {
2571                     int width = videoCaps.getSupportedWidths().getLower();
2572                     int height = videoCaps.getSupportedHeightsFor(width).getLower();
2573                     format = MediaFormat.createVideoFormat(type, width, height);
2574                     if (info.isEncoder()) {
2575                         EncoderCapabilities encCaps = caps.getEncoderCapabilities();
2576                         if (encCaps != null) {
2577                             int bitrateMode = -1;
2578                             List<Integer> candidates = Arrays.asList(
2579                                     EncoderCapabilities.BITRATE_MODE_VBR,
2580                                     EncoderCapabilities.BITRATE_MODE_CBR,
2581                                     EncoderCapabilities.BITRATE_MODE_CQ,
2582                                     EncoderCapabilities.BITRATE_MODE_CBR_FD);
2583                             for (int candidate : candidates) {
2584                                 if (encCaps.isBitrateModeSupported(candidate)) {
2585                                     bitrateMode = candidate;
2586                                     break;
2587                                 }
2588                             }
2589                             if (VERBOSE) {
2590                                 Log.d(TAG, "video encoder: bitrate mode = " + bitrateMode);
2591                             }
2592                             format.setInteger(MediaFormat.KEY_BITRATE_MODE, bitrateMode);
2593                             switch (bitrateMode) {
2594                             case EncoderCapabilities.BITRATE_MODE_VBR:
2595                             case EncoderCapabilities.BITRATE_MODE_CBR:
2596                             case EncoderCapabilities.BITRATE_MODE_CBR_FD:
2597                                 format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
2598                                 break;
2599                             case EncoderCapabilities.BITRATE_MODE_CQ:
2600                                 format.setInteger(
2601                                         MediaFormat.KEY_QUALITY,
2602                                         encCaps.getQualityRange().getLower());
2603                                 if (VERBOSE) {
2604                                     Log.d(TAG, "video encoder: quality = " +
2605                                             encCaps.getQualityRange().getLower());
2606                                 }
2607                                 break;
2608                             default:
2609                                 format.removeKey(MediaFormat.KEY_BITRATE_MODE);
2610                             }
2611                         }
2612                         format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
2613                         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
2614                         format.setInteger(
2615                                 MediaFormat.KEY_COLOR_FORMAT,
2616                                 CodecCapabilities.COLOR_FormatSurface);
2617                     }
2618                 } else {
2619                     Log.i(TAG, info.getName() + " is in neither audio nor video domain; skipped");
2620                     codec.release();
2621                     continue;
2622                 }
2623                 codec.configure(
2624                         format, null, null,
2625                         info.isEncoder() ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
2626                 Surface inputSurface = null;
2627                 if (videoCaps != null && info.isEncoder()) {
2628                     inputSurface = codec.createInputSurface();
2629                 }
2630                 codec.start();
2631                 codec.unsubscribeFromVendorParameters(vendorParams);
2632                 codec.stop();
2633             } catch (Exception e) {
2634                 throw new RuntimeException("codec name: " + info.getName(), e);
2635             } finally {
2636                 if (codec != null) {
2637                     codec.release();
2638                 }
2639             }
2640         }
2641     }
2642 }
2643