1 /*
2  * Copyright (C) 2019 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.mediav2.cts;
18 
19 import android.media.MediaCodec;
20 import android.media.MediaCodecInfo;
21 import android.media.MediaFormat;
22 import android.os.Bundle;
23 import android.util.Log;
24 
25 import androidx.test.filters.LargeTest;
26 import androidx.test.filters.SmallTest;
27 
28 import org.junit.Assume;
29 import org.junit.Ignore;
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 import org.junit.runners.Parameterized;
33 
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Set;
41 
42 import static org.junit.Assert.assertFalse;
43 import static org.junit.Assert.assertTrue;
44 import static org.junit.Assert.fail;
45 
46 /**
47  * Validate encode functionality of listed encoder components
48  *
49  * The test aims to test all encoders advertised in MediaCodecList. Hence we are not using
50  * MediaCodecList#findEncoderForFormat to create codec. Further, it can so happen that the
51  * test clip chosen is not supported by component (codecCapabilities.isFormatSupported()
52  * fails), then it is better to remove the format but not skip testing the component. The idea
53  * of these tests are not to cover CDD requirements but to test components and their plugins
54  */
55 @RunWith(Parameterized.class)
56 public class CodecEncoderTest extends CodecEncoderTestBase {
57     private static final String LOG_TAG = CodecEncoderTest.class.getSimpleName();
58     private final int[] mBitrates;
59     private final int[] mEncParamList1;
60     private final int[] mEncParamList2;
61     private ArrayList<MediaFormat> mFormats;
62     private int mNumSyncFramesReceived;
63     private ArrayList<Integer> mSyncFramesPos;
64 
CodecEncoderTest(String mime, int[] bitrates, int[] encoderInfo1, int[] encoderInfo2)65     public CodecEncoderTest(String mime, int[] bitrates, int[] encoderInfo1, int[] encoderInfo2) {
66         super(mime);
67         mBitrates = bitrates;
68         mEncParamList1 = encoderInfo1;
69         mEncParamList2 = encoderInfo2;
70         mFormats = new ArrayList<>();
71         mSyncFramesPos = new ArrayList<>();
72     }
73 
74     @Override
resetContext(boolean isAsync, boolean signalEOSWithLastFrame)75     void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
76         super.resetContext(isAsync, signalEOSWithLastFrame);
77         mNumSyncFramesReceived = 0;
78         mSyncFramesPos.clear();
79     }
80 
81     @Override
flushCodec()82     void flushCodec() {
83         super.flushCodec();
84         mNumSyncFramesReceived = 0;
85         mSyncFramesPos.clear();
86     }
87 
dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)88     void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
89         if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
90             mNumSyncFramesReceived += 1;
91             mSyncFramesPos.add(mOutputCount);
92         }
93         super.dequeueOutput(bufferIndex, info);
94     }
95 
encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format)96     private void encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format)
97             throws IOException, InterruptedException {
98         /* TODO(b/149027258) */
99         if (true) mSaveToMem = false;
100         else mSaveToMem = true;
101         mOutputBuff = new OutputManager();
102         mCodec = MediaCodec.createByCodecName(encoder);
103         setUpSource(file);
104         configureCodec(format, false, true, true);
105         if (mIsAudio) {
106             mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
107             mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
108         } else {
109             mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
110             mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
111         }
112         mCodec.start();
113         doWork(frameLimit);
114         queueEOS();
115         waitForAllOutputs();
116         mCodec.stop();
117         mCodec.release();
118         mSaveToMem = false;
119     }
120 
121     /**
122      * Selects encoder input color format in byte buffer mode. As of now ndk tests support only
123      * 420p, 420sp. COLOR_FormatYUV420Flexible although can represent any form of yuv, it doesn't
124      * work in ndk due to lack of AMediaCodec_GetInputImage()
125      */
findByteBufferColorFormat(String encoder, String mime)126     private static int findByteBufferColorFormat(String encoder, String mime) throws IOException {
127         MediaCodec codec = MediaCodec.createByCodecName(encoder);
128         MediaCodecInfo.CodecCapabilities cap = codec.getCodecInfo().getCapabilitiesForType(mime);
129         int colorFormat = -1;
130         for (int c : cap.colorFormats) {
131             if (c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar ||
132                     c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) {
133                 Log.v(LOG_TAG, "selecting color format: " + c);
134                 colorFormat = c;
135                 break;
136             }
137         }
138         codec.release();
139         return colorFormat;
140     }
141 
forceSyncFrame()142     private void forceSyncFrame() {
143         final Bundle syncFrame = new Bundle();
144         syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
145         if (ENABLE_LOGS) {
146             Log.v(LOG_TAG, "requesting key frame");
147         }
148         mCodec.setParameters(syncFrame);
149     }
150 
updateBitrate(int bitrate)151     private void updateBitrate(int bitrate) {
152         final Bundle bitrateUpdate = new Bundle();
153         bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
154         if (ENABLE_LOGS) {
155             Log.v(LOG_TAG, "requesting bitrate to be changed to " + bitrate);
156         }
157         mCodec.setParameters(bitrateUpdate);
158     }
159 
160     @Parameterized.Parameters(name = "{index}({0})")
input()161     public static Collection<Object[]> input() {
162         Set<String> list = new HashSet<>();
163         if (hasMicrophone()) {
164             // sec 5.1.1
165             // TODO(b/154423550)
166             // list.add(MediaFormat.MIMETYPE_AUDIO_RAW);
167             list.add(MediaFormat.MIMETYPE_AUDIO_FLAC);
168             list.add(MediaFormat.MIMETYPE_AUDIO_OPUS);
169         }
170         if (isHandheld() || isTv() || isAutomotive()) {
171             // sec 2.2.2, 2.3.2, 2.5.2
172             list.add(MediaFormat.MIMETYPE_AUDIO_AAC);
173             list.add(MediaFormat.MIMETYPE_VIDEO_AVC);
174             list.add(MediaFormat.MIMETYPE_VIDEO_VP8);
175         }
176         if (isHandheld()) {
177             // sec 2.2.2
178             list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
179             list.add(MediaFormat.MIMETYPE_AUDIO_AMR_WB);
180         }
181         ArrayList<String> cddRequiredMimeList = new ArrayList<>(list);
182         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
183                 // Audio - CodecMime, arrays of bit-rates, sample rates, channel counts
184                 {MediaFormat.MIMETYPE_AUDIO_AAC, new int[]{64000, 128000}, new int[]{8000, 11025,
185                         22050, 44100, 48000}, new int[]{1, 2}},
186                 {MediaFormat.MIMETYPE_AUDIO_OPUS, new int[]{6600, 8850, 12650, 14250, 15850,
187                         18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1}},
188                 {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new int[]{4750, 5150, 5900, 6700, 7400, 7950,
189                         10200, 12200}, new int[]{8000}, new int[]{1}},
190                 {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new int[]{6600, 8850, 12650, 14250, 15850,
191                         18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1}},
192                 {MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{64000, 192000}, new int[]{8000, 48000
193                         , 96000, 192000}, new int[]{1, 2}},
194 
195                 // Video - CodecMime, arrays of bit-rates, height, width
196                 {MediaFormat.MIMETYPE_VIDEO_H263, new int[]{32000, 64000}, new int[]{176},
197                         new int[]{144}},
198                 {MediaFormat.MIMETYPE_VIDEO_MPEG4, new int[]{32000, 64000}, new int[]{176},
199                         new int[]{144}},
200                 {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{256000, 512000}, new int[]{176, 352,
201                         352, 480}, new int[]{144, 240, 288, 360}},
202                 {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{256000, 512000}, new int[]{176, 352,
203                         352, 480}, new int[]{144, 240, 288, 360}},
204                 {MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{256000, 512000}, new int[]{176, 352,
205                         352, 480}, new int[]{144, 240, 288, 360}},
206                 {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{256000, 512000}, new int[]{176, 352,
207                         352, 480}, new int[]{144, 240, 288, 360}},
208                 {MediaFormat.MIMETYPE_VIDEO_AV1, new int[]{256000, 512000}, new int[]{176, 352,
209                         352, 480}, new int[]{144, 240, 288, 360}},
210         });
211         return prepareParamList(cddRequiredMimeList, exhaustiveArgsList, true);
212     }
213 
setUpParams(int limit)214     private void setUpParams(int limit) {
215         int count = 0;
216         for (int bitrate : mBitrates) {
217             if (mIsAudio) {
218                 for (int rate : mEncParamList1) {
219                     for (int channels : mEncParamList2) {
220                         MediaFormat format = new MediaFormat();
221                         format.setString(MediaFormat.KEY_MIME, mMime);
222                         format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
223                         format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate);
224                         format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channels);
225                         mFormats.add(format);
226                         count++;
227                         if (count >= limit) return;
228                     }
229                 }
230             } else {
231                 assertTrue("Wrong number of height, width parameters",
232                         mEncParamList1.length == mEncParamList2.length);
233                 for (int i = 0; i < mEncParamList1.length; i++) {
234                     MediaFormat format = new MediaFormat();
235                     format.setString(MediaFormat.KEY_MIME, mMime);
236                     format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
237                     format.setInteger(MediaFormat.KEY_WIDTH, mEncParamList1[i]);
238                     format.setInteger(MediaFormat.KEY_HEIGHT, mEncParamList2[i]);
239                     format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
240                     format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, mMaxBFrames);
241                     format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
242                     format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
243                             MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
244                     mFormats.add(format);
245                     count++;
246                     if (count >= limit) return;
247                 }
248             }
249         }
250     }
251 
252     /**
253      * Tests encoder for combinations:
254      * 1. Codec Sync Mode, Signal Eos with Last frame
255      * 2. Codec Sync Mode, Signal Eos Separately
256      * 3. Codec Async Mode, Signal Eos with Last frame
257      * 4. Codec Async Mode, Signal Eos Separately
258      * In all these scenarios, Timestamp ordering is verified. The output has to be
259      * consistent (not flaky) in all runs.
260      */
261     @LargeTest
262     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSimpleEncode()263     public void testSimpleEncode() throws IOException, InterruptedException {
264         setUpParams(Integer.MAX_VALUE);
265         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
266         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
267         boolean[] boolStates = {true, false};
268         setUpSource(mInputFile);
269         OutputManager ref = new OutputManager();
270         OutputManager test = new OutputManager();
271         for (String encoder : listOfEncoders) {
272             mCodec = MediaCodec.createByCodecName(encoder);
273             assertTrue("codec name act/got: " + mCodec.getName() + '/' + encoder,
274                     mCodec.getName().equals(encoder));
275             assertTrue("error! codec canonical name is null",
276                     mCodec.getCanonicalName() != null && !mCodec.getCanonicalName().isEmpty());
277             /* TODO(b/149027258) */
278             if (true) mSaveToMem = false;
279             else mSaveToMem = true;
280             for (MediaFormat format : mFormats) {
281                 int loopCounter = 0;
282                 if (mIsAudio) {
283                     mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
284                     mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
285                 } else {
286                     mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
287                     mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
288                 }
289                 for (boolean eosType : boolStates) {
290                     for (boolean isAsync : boolStates) {
291                         String log = String.format(
292                                 "format: %s \n codec: %s, file: %s, mode: %s, eos type: %s:: ",
293                                 format, encoder, mInputFile, (isAsync ? "async" : "sync"),
294                                 (eosType ? "eos with last frame" : "eos separate"));
295                         mOutputBuff = loopCounter == 0 ? ref : test;
296                         mOutputBuff.reset();
297                         validateMetrics(encoder);
298                         configureCodec(format, isAsync, eosType, true);
299                         mCodec.start();
300                         doWork(Integer.MAX_VALUE);
301                         queueEOS();
302                         waitForAllOutputs();
303                         validateMetrics(encoder, format);
304                         /* TODO(b/147348711) */
305                         if (false) mCodec.stop();
306                         else mCodec.reset();
307                         assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
308                         assertTrue(log + "no input sent", 0 != mInputCount);
309                         assertTrue(log + "output received", 0 != mOutputCount);
310                         if (!mIsAudio) {
311                             assertTrue(
312                                     log + "input count != output count, act/exp: " + mOutputCount +
313                                             " / " + mInputCount, mInputCount == mOutputCount);
314                         }
315                         if (loopCounter != 0) {
316                             assertTrue(log + "encoder output is flaky", ref.equals(test));
317                         } else {
318                             if (mIsAudio) {
319                                 assertTrue(log + " pts is not strictly increasing",
320                                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
321                             } else {
322                                 assertTrue(
323                                         log + " input pts list and output pts list are not identical",
324                                         ref.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
325                             }
326                         }
327                         loopCounter++;
328                     }
329                 }
330             }
331             mCodec.release();
332         }
333     }
334 
nativeTestSimpleEncode(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat)335     private native boolean nativeTestSimpleEncode(String encoder, String file, String mime,
336             int[] list0, int[] list1, int[] list2, int colorFormat);
337 
338     @LargeTest
339     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSimpleEncodeNative()340     public void testSimpleEncodeNative() throws IOException {
341         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
342         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
343         int colorFormat = -1;
344         for (String encoder : listOfEncoders) {
345             if (!mIsAudio) {
346                 colorFormat = findByteBufferColorFormat(encoder, mMime);
347                 assertTrue("no valid color formats received", colorFormat != -1);
348             }
349             assertTrue(nativeTestSimpleEncode(encoder, mInpPrefix + mInputFile, mMime, mBitrates,
350                     mEncParamList1, mEncParamList2, colorFormat));
351         }
352     }
353 
354     /**
355      * Tests flush when codec is in sync and async mode. In these scenarios, Timestamp
356      * ordering is verified. The output has to be consistent (not flaky) in all runs
357      */
358     @Ignore("TODO(b/147576107, b/148652492, b/148651699)")
359     @LargeTest
360     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testFlush()361     public void testFlush() throws IOException, InterruptedException {
362         setUpParams(1);
363         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
364         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
365         setUpSource(mInputFile);
366         boolean[] boolStates = {true, false};
367         mOutputBuff = new OutputManager();
368         for (String encoder : listOfEncoders) {
369             MediaFormat inpFormat = mFormats.get(0);
370             if (mIsAudio) {
371                 mSampleRate = inpFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
372                 mChannels = inpFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
373             } else {
374                 mWidth = inpFormat.getInteger(MediaFormat.KEY_WIDTH);
375                 mHeight = inpFormat.getInteger(MediaFormat.KEY_HEIGHT);
376             }
377             mCodec = MediaCodec.createByCodecName(encoder);
378             for (boolean isAsync : boolStates) {
379                 String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
380                         mInputFile, (isAsync ? "async" : "sync"));
381                 configureCodec(inpFormat, isAsync, true, true);
382                 mCodec.start();
383 
384                 /* test flush in running state before queuing input */
385                 flushCodec();
386                 mOutputBuff.reset();
387                 if (mIsCodecInAsyncMode) mCodec.start();
388                 doWork(23);
389                 assertTrue(log + " pts is not strictly increasing",
390                         mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts));
391                 boolean checkMetrics = (mOutputCount != 0);
392 
393                 /* test flush in running state */
394                 flushCodec();
395                 mOutputBuff.reset();
396                 if (mIsCodecInAsyncMode) mCodec.start();
397                 if (checkMetrics) validateMetrics(encoder, inpFormat);
398                 doWork(Integer.MAX_VALUE);
399                 queueEOS();
400                 waitForAllOutputs();
401                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
402                 assertTrue(log + "no input sent", 0 != mInputCount);
403                 assertTrue(log + "output received", 0 != mOutputCount);
404                 if (!mIsAudio) {
405                     assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
406                             " / " + mInputCount, mInputCount == mOutputCount);
407                     assertTrue(
408                             log + " input pts list and output pts list are not identical",
409                             mOutputBuff.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
410                 } else {
411                     assertTrue(log + " pts is not strictly increasing",
412                             mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts));
413                 }
414 
415                 /* test flush in eos state */
416                 flushCodec();
417                 mOutputBuff.reset();
418                 if (mIsCodecInAsyncMode) mCodec.start();
419                 doWork(Integer.MAX_VALUE);
420                 queueEOS();
421                 waitForAllOutputs();
422                 /* TODO(b/147348711) */
423                 if (false) mCodec.stop();
424                 else mCodec.reset();
425                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
426                 assertTrue(log + "no input sent", 0 != mInputCount);
427                 assertTrue(log + "output received", 0 != mOutputCount);
428                 if (!mIsAudio) {
429                     assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
430                             " / " + mInputCount, mInputCount == mOutputCount);
431                     assertTrue(
432                             log + " input pts list and output pts list are not identical",
433                             mOutputBuff.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
434                 } else {
435                     assertTrue(log + " pts is not strictly increasing",
436                             mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts));
437                 }
438             }
439             mCodec.release();
440         }
441     }
442 
nativeTestFlush(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat)443     private native boolean nativeTestFlush(String encoder, String file, String mime,
444             int[] list0, int[] list1, int[] list2, int colorFormat);
445 
446     @Ignore("TODO(b/147576107, b/148652492, b/148651699)")
447     @LargeTest
448     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testFlushNative()449     public void testFlushNative() throws IOException {
450         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
451         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
452         int colorFormat = -1;
453         for (String encoder : listOfEncoders) {
454             if (!mIsAudio) {
455                 colorFormat = findByteBufferColorFormat(encoder, mMime);
456                 assertTrue("no valid color formats received", colorFormat != -1);
457             }
458             assertTrue(nativeTestFlush(encoder, mInpPrefix + mInputFile, mMime, mBitrates,
459                     mEncParamList1, mEncParamList2, colorFormat));
460         }
461     }
462 
463     /**
464      * Tests reconfigure when codec is in sync and async mode. In these
465      * scenarios, Timestamp ordering is verified. The output has to be consistent (not flaky)
466      * in all runs
467      */
468     @Ignore("TODO(b/148523403)")
469     @LargeTest
470     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testReconfigure()471     public void testReconfigure() throws IOException, InterruptedException {
472         setUpParams(2);
473         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
474         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
475         setUpSource(mInputFile);
476         boolean[] boolStates = {true, false};
477         OutputManager test = new OutputManager();
478         for (String encoder : listOfEncoders) {
479             OutputManager configRef = null;
480             if (mFormats.size() > 1) {
481                 MediaFormat format = mFormats.get(1);
482                 encodeToMemory(mInputFile, encoder, Integer.MAX_VALUE, format);
483                 configRef = mOutputBuff;
484                 if (mIsAudio) {
485                     assertTrue("config reference output pts is not strictly increasing",
486                             configRef.isPtsStrictlyIncreasing(mPrevOutputPts));
487                 } else {
488                     assertTrue("input pts list and reconfig ref output pts list are not identical",
489                             configRef.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
490                 }
491             }
492             MediaFormat format = mFormats.get(0);
493             encodeToMemory(mInputFile, encoder, Integer.MAX_VALUE, format);
494             OutputManager ref = mOutputBuff;
495             if (mIsAudio) {
496                 assertTrue("reference output pts is not strictly increasing",
497                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
498             } else {
499                 assertTrue("input pts list and ref output pts list are not identical",
500                         ref.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
501             }
502             mOutputBuff = test;
503             mCodec = MediaCodec.createByCodecName(encoder);
504             for (boolean isAsync : boolStates) {
505                 String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
506                         mInputFile, (isAsync ? "async" : "sync"));
507                 configureCodec(format, isAsync, true, true);
508 
509                 /* test reconfigure in stopped state */
510                 reConfigureCodec(format, !isAsync, false, true);
511                 mCodec.start();
512 
513                 /* test reconfigure in running state before queuing input */
514                 reConfigureCodec(format, !isAsync, false, true);
515                 mCodec.start();
516                 doWork(23);
517 
518                 if (mOutputCount != 0) validateMetrics(encoder, format);
519 
520                 /* test reconfigure codec in running state */
521                 reConfigureCodec(format, isAsync, true, true);
522                 mCodec.start();
523                 /* TODO(b/149027258) */
524                 if (true) mSaveToMem = false;
525                 else mSaveToMem = true;
526                 test.reset();
527                 doWork(Integer.MAX_VALUE);
528                 queueEOS();
529                 waitForAllOutputs();
530                 /* TODO(b/147348711) */
531                 if (false) mCodec.stop();
532                 else mCodec.reset();
533                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
534                 assertTrue(log + "no input sent", 0 != mInputCount);
535                 assertTrue(log + "output received", 0 != mOutputCount);
536                 if (!mIsAudio) {
537                     assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
538                             " / " + mInputCount, mInputCount == mOutputCount);
539                 }
540                 assertTrue(log + "encoder output is flaky", ref.equals(test));
541 
542                 /* test reconfigure codec at eos state */
543                 reConfigureCodec(format, !isAsync, false, true);
544                 mCodec.start();
545                 test.reset();
546                 doWork(Integer.MAX_VALUE);
547                 queueEOS();
548                 waitForAllOutputs();
549                 /* TODO(b/147348711) */
550                 if (false) mCodec.stop();
551                 else mCodec.reset();
552                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
553                 assertTrue(log + "no input sent", 0 != mInputCount);
554                 assertTrue(log + "output received", 0 != mOutputCount);
555                 if (!mIsAudio) {
556                     assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
557                             " / " + mInputCount, mInputCount == mOutputCount);
558                 }
559                 assertTrue(log + "encoder output is flaky", ref.equals(test));
560 
561                 /* test reconfigure codec for new format */
562                 if (mFormats.size() > 1) {
563                     reConfigureCodec(mFormats.get(1), isAsync, false, true);
564                     mCodec.start();
565                     test.reset();
566                     doWork(Integer.MAX_VALUE);
567                     queueEOS();
568                     waitForAllOutputs();
569                     /* TODO(b/147348711) */
570                     if (false) mCodec.stop();
571                     else mCodec.reset();
572                     assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
573                     assertTrue(log + "no input sent", 0 != mInputCount);
574                     assertTrue(log + "output received", 0 != mOutputCount);
575                     if (!mIsAudio) {
576                         assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
577                                 " / " + mInputCount, mInputCount == mOutputCount);
578                     }
579                     assertTrue(log + "encoder output is flaky", configRef.equals(test));
580                 }
581                 mSaveToMem = false;
582             }
583             mCodec.release();
584         }
585     }
586 
nativeTestReconfigure(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat)587     private native boolean nativeTestReconfigure(String encoder, String file, String mime,
588             int[] list0, int[] list1, int[] list2, int colorFormat);
589 
590     @Ignore("TODO(b/147348711, b/149981033)")
591     @LargeTest
592     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testReconfigureNative()593     public void testReconfigureNative() throws IOException {
594         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
595         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
596         int colorFormat = -1;
597         for (String encoder : listOfEncoders) {
598             if (!mIsAudio) {
599                 colorFormat = findByteBufferColorFormat(encoder, mMime);
600                 assertTrue("no valid color formats received", colorFormat != -1);
601             }
602             assertTrue(nativeTestReconfigure(encoder, mInpPrefix + mInputFile, mMime, mBitrates,
603                     mEncParamList1, mEncParamList2, colorFormat));
604         }
605     }
606 
607     /**
608      * Tests encoder for only EOS frame
609      */
610     @SmallTest
611     @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
testOnlyEos()612     public void testOnlyEos() throws IOException, InterruptedException {
613         setUpParams(1);
614         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
615         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
616         boolean[] boolStates = {true, false};
617         OutputManager ref = new OutputManager();
618         OutputManager test = new OutputManager();
619         for (String encoder : listOfEncoders) {
620             mCodec = MediaCodec.createByCodecName(encoder);
621             /* TODO(b/149027258) */
622             if (true) mSaveToMem = false;
623             else mSaveToMem = true;
624             int loopCounter = 0;
625             for (boolean isAsync : boolStates) {
626                 String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
627                         mInputFile, (isAsync ? "async" : "sync"));
628                 configureCodec(mFormats.get(0), isAsync, false, true);
629                 mOutputBuff = loopCounter == 0 ? ref : test;
630                 mOutputBuff.reset();
631                 mCodec.start();
632                 queueEOS();
633                 waitForAllOutputs();
634                 /* TODO(b/147348711) */
635                 if (false) mCodec.stop();
636                 else mCodec.reset();
637                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
638                 if (loopCounter != 0) {
639                     assertTrue(log + "encoder output is flaky", ref.equals(test));
640                 } else {
641                     if (mIsAudio) {
642                         assertTrue(log + " pts is not strictly increasing",
643                                 ref.isPtsStrictlyIncreasing(mPrevOutputPts));
644                     } else {
645                         assertTrue(
646                                 log + " input pts list and output pts list are not identical",
647                                 ref.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
648                     }
649                 }
650                 loopCounter++;
651             }
652             mCodec.release();
653         }
654     }
655 
nativeTestOnlyEos(String encoder, String mime, int[] list0, int[] list1, int[] list2, int colorFormat)656     private native boolean nativeTestOnlyEos(String encoder, String mime, int[] list0, int[] list1,
657             int[] list2, int colorFormat);
658 
659     @SmallTest
660     @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
testOnlyEosNative()661     public void testOnlyEosNative() throws IOException {
662         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
663         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
664         int colorFormat = -1;
665         for (String encoder : listOfEncoders) {
666             if (!mIsAudio) {
667                 colorFormat = findByteBufferColorFormat(encoder, mMime);
668                 assertTrue("no valid color formats received", colorFormat != -1);
669             }
670             assertTrue(nativeTestOnlyEos(encoder, mMime, mBitrates, mEncParamList1, mEncParamList2,
671                     colorFormat));
672         }
673     }
674 
675     /**
676      * Test set parameters : force key frame
677      */
678     @Ignore("TODO(b/151302863)")
679     @LargeTest
680     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSetForceSyncFrame()681     public void testSetForceSyncFrame() throws IOException, InterruptedException {
682         Assume.assumeTrue(!mIsAudio);
683         // Maximum allowed key frame interval variation from the target value.
684         final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
685         setUpParams(1);
686         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
687         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
688         boolean[] boolStates = {true, false};
689         setUpSource(mInputFile);
690         MediaFormat format = mFormats.get(0);
691         format.removeKey(MediaFormat.KEY_I_FRAME_INTERVAL);
692         format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 500.f);
693         mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
694         mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
695         final int KEY_FRAME_INTERVAL = 2; // force key frame every 2 seconds.
696         final int KEY_FRAME_POS = mFrameRate * KEY_FRAME_INTERVAL;
697         final int NUM_KEY_FRAME_REQUESTS = 7;
698         mOutputBuff = new OutputManager();
699         for (String encoder : listOfEncoders) {
700             mCodec = MediaCodec.createByCodecName(encoder);
701             for (boolean isAsync : boolStates) {
702                 String log = String.format(
703                         "format: %s \n codec: %s, file: %s, mode: %s:: ", format, encoder,
704                         mInputFile, (isAsync ? "async" : "sync"));
705                 mOutputBuff.reset();
706                 configureCodec(format, isAsync, false, true);
707                 mCodec.start();
708                 for (int i = 0; i < NUM_KEY_FRAME_REQUESTS; i++) {
709                     doWork(KEY_FRAME_POS);
710                     assertTrue(!mSawInputEOS);
711                     forceSyncFrame();
712                     mNumBytesSubmitted = 0;
713                 }
714                 queueEOS();
715                 waitForAllOutputs();
716                 /* TODO(b/147348711) */
717                 if (false) mCodec.stop();
718                 else mCodec.reset();
719                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
720                 assertTrue(log + "no input sent", 0 != mInputCount);
721                 assertTrue(log + "output received", 0 != mOutputCount);
722                 assertTrue(log + "input count != output count, act/exp: " + mOutputCount + " / " +
723                         mInputCount, mInputCount == mOutputCount);
724                 assertTrue(log + " input pts list and output pts list are not identical",
725                         mOutputBuff.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
726                 assertTrue(log + "sync frames exp/act: " + NUM_KEY_FRAME_REQUESTS + " / " +
727                         mNumSyncFramesReceived, mNumSyncFramesReceived >= NUM_KEY_FRAME_REQUESTS);
728                 for (int i = 0, expPos = 0, index = 0; i < NUM_KEY_FRAME_REQUESTS; i++) {
729                     int j = index;
730                     for (; j < mSyncFramesPos.size(); j++) {
731                         // Check key frame intervals:
732                         // key frame position should not be greater than target value + 3
733                         // key frame position should not be less than target value - 3
734                         if (Math.abs(expPos - mSyncFramesPos.get(j)) <=
735                                 MAX_KEYFRAME_INTERVAL_VARIATION) {
736                             index = j;
737                             break;
738                         }
739                     }
740                     if (j == mSyncFramesPos.size()) {
741                         Log.w(LOG_TAG, "requested key frame at frame index " + expPos +
742                                 " none found near by");
743                     }
744                     expPos += KEY_FRAME_POS;
745                 }
746             }
747             mCodec.release();
748         }
749     }
750 
nativeTestSetForceSyncFrame(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat)751     private native boolean nativeTestSetForceSyncFrame(String encoder, String file, String mime,
752             int[] list0, int[] list1, int[] list2, int colorFormat);
753 
754     @Ignore("TODO(b/) = test sometimes timesout")
755     @LargeTest
756     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSetForceSyncFrameNative()757     public void testSetForceSyncFrameNative() throws IOException {
758         Assume.assumeTrue(!mIsAudio);
759         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
760         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
761         int colorFormat = -1;
762         for (String encoder : listOfEncoders) {
763             if (!mIsAudio) {
764                 colorFormat = findByteBufferColorFormat(encoder, mMime);
765                 assertTrue("no valid color formats received", colorFormat != -1);
766             }
767             assertTrue(nativeTestSetForceSyncFrame(encoder, mInpPrefix + mInputFile, mMime,
768                     mBitrates, mEncParamList1, mEncParamList2, colorFormat));
769         }
770     }
771 
772     /**
773      * Test set parameters : change bitrate dynamically
774      */
775     @Ignore("TODO(b/151302863)")
776     @LargeTest
777     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testAdaptiveBitRate()778     public void testAdaptiveBitRate() throws IOException, InterruptedException {
779         Assume.assumeTrue(!mIsAudio);
780         setUpParams(1);
781         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
782         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
783         boolean[] boolStates = {true, false};
784         setUpSource(mInputFile);
785         MediaFormat format = mFormats.get(0);
786         mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
787         mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
788         final int ADAPTIVE_BR_INTERVAL = 3; // change br every 3 seconds.
789         final int ADAPTIVE_BR_DUR_FRM = mFrameRate * ADAPTIVE_BR_INTERVAL;
790         final int BR_CHANGE_REQUESTS = 7;
791         mOutputBuff = new OutputManager();
792         mSaveToMem = true;
793         for (String encoder : listOfEncoders) {
794             /* TODO(b/147574800) */
795             if (encoder.equals("c2.android.hevc.encoder")) continue;
796             mCodec = MediaCodec.createByCodecName(encoder);
797             format.removeKey(MediaFormat.KEY_BITRATE_MODE);
798             MediaCodecInfo.EncoderCapabilities cap =
799                     mCodec.getCodecInfo().getCapabilitiesForType(mMime).getEncoderCapabilities();
800             if (cap.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR)) {
801                 format.setInteger(MediaFormat.KEY_BITRATE_MODE,
802                         MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
803             } else {
804                 format.setInteger(MediaFormat.KEY_BITRATE_MODE,
805                         MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
806             }
807             for (boolean isAsync : boolStates) {
808                 String log = String.format(
809                         "format: %s \n codec: %s, file: %s, mode: %s:: ", format, encoder,
810                         mInputFile, (isAsync ? "async" : "sync"));
811                 mOutputBuff.reset();
812                 configureCodec(format, isAsync, false, true);
813                 mCodec.start();
814                 int expOutSize = 0;
815                 int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
816                 for (int i = 0; i < BR_CHANGE_REQUESTS; i++) {
817                     doWork(ADAPTIVE_BR_DUR_FRM);
818                     assertTrue(!mSawInputEOS);
819                     expOutSize += ADAPTIVE_BR_INTERVAL * bitrate;
820                     if ((i & 1) == 1) bitrate *= 2;
821                     else bitrate /= 2;
822                     updateBitrate(bitrate);
823                     mNumBytesSubmitted = 0;
824                 }
825                 queueEOS();
826                 waitForAllOutputs();
827                 /* TODO(b/147348711) */
828                 if (false) mCodec.stop();
829                 else mCodec.reset();
830                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
831                 assertTrue(log + "no input sent", 0 != mInputCount);
832                 assertTrue(log + "output received", 0 != mOutputCount);
833                 assertTrue(log + "input count != output count, act/exp: " + mOutputCount + " / " +
834                         mInputCount, mInputCount == mOutputCount);
835                 assertTrue(log + " input pts list and output pts list are not identical",
836                         mOutputBuff.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
837                 /* TODO: validate output br with sliding window constraints Sec 5.2 cdd */
838                 int outSize = mOutputBuff.getOutStreamSize() * 8;
839                 float brDev = Math.abs(expOutSize - outSize) * 100.0f / expOutSize;
840                 if (ENABLE_LOGS) {
841                     Log.d(LOG_TAG, log + "relative br error is " + brDev + '%');
842                 }
843                 if (brDev > 50) {
844                     fail(log + "relative br error is too large " + brDev + '%');
845                 }
846             }
847             mCodec.release();
848         }
849     }
850 
nativeTestAdaptiveBitRate(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat)851     private native boolean nativeTestAdaptiveBitRate(String encoder, String file, String mime,
852             int[] list0, int[] list1, int[] list2, int colorFormat);
853 
854     @Ignore("TODO(b/) = test sometimes timesout")
855     @LargeTest
856     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testAdaptiveBitRateNative()857     public void testAdaptiveBitRateNative() throws IOException {
858         Assume.assumeTrue(!mIsAudio);
859         ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
860         assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
861         int colorFormat = -1;
862         for (String encoder : listOfEncoders) {
863             /* TODO(b/147574800) */
864             if (encoder.equals("c2.android.hevc.encoder")) continue;
865             if (!mIsAudio) {
866                 colorFormat = findByteBufferColorFormat(encoder, mMime);
867                 assertTrue("no valid color formats received", colorFormat != -1);
868             }
869             assertTrue(nativeTestAdaptiveBitRate(encoder, mInpPrefix + mInputFile, mMime, mBitrates,
870                     mEncParamList1, mEncParamList2, colorFormat));
871         }
872     }
873 }
874