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