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 static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import android.media.MediaCodec;
26 import android.media.MediaFormat;
27 import android.mediav2.common.cts.CodecEncoderTestBase;
28 import android.mediav2.common.cts.EncoderConfigParams;
29 import android.mediav2.common.cts.OutputManager;
30 import android.os.Bundle;
31 import android.util.Log;
32 
33 import androidx.test.filters.LargeTest;
34 import androidx.test.filters.SmallTest;
35 
36 import com.android.compatibility.common.util.ApiTest;
37 import com.android.compatibility.common.util.CddTest;
38 
39 import org.junit.Assume;
40 import org.junit.Before;
41 import org.junit.Ignore;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.junit.runners.Parameterized;
45 
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collection;
50 import java.util.List;
51 
52 /**
53  * Test mediacodec api, encoders and their interactions in bytebuffer mode.
54  * <p>
55  * The test feeds raw input data (audio/video) to the component and receives compressed bitstream
56  * from the component.
57  * <p>
58  * At the end of encoding process, the test enforces following checks :-
59  * <ul>
60  *     <li> For audio components, the test expects the output timestamps to be strictly
61  *     increasing.</li>
62  *     <li>For video components the test expects the output frame count to be identical to input
63  *     frame count and the output timestamp list to be identical to input timestamp list.</li>
64  *     <li>As encoders are expected to give consistent output for a given input and configuration
65  *     parameters, the test checks for consistency across runs. For now, this attribute is not
66  *     strictly enforced in this test.</li>
67  * </ul>
68  * <p>
69  * The test does not validate the integrity of the encoder output. That is done by
70  * CodecEncoderValidationTest. This test checks only the framework <-> plugin <-> encoder
71  * interactions.
72  * <p>
73  * The test runs mediacodec in synchronous and asynchronous mode.
74  */
75 @RunWith(Parameterized.class)
76 public class CodecEncoderTest extends CodecEncoderTestBase {
77     private static final String LOG_TAG = CodecEncoderTest.class.getSimpleName();
78     private static final ArrayList<String> ABR_MEDIATYPE_LIST = new ArrayList<>();
79 
80     private boolean mGotCSD;
81     private int mNumSyncFramesReceived;
82     private final ArrayList<Integer> mSyncFramesPos = new ArrayList<>();
83 
84     static {
85         System.loadLibrary("ctsmediav2codecenc_jni");
86 
87         ABR_MEDIATYPE_LIST.add(MediaFormat.MIMETYPE_VIDEO_AVC);
88         ABR_MEDIATYPE_LIST.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
89         ABR_MEDIATYPE_LIST.add(MediaFormat.MIMETYPE_VIDEO_VP8);
90         ABR_MEDIATYPE_LIST.add(MediaFormat.MIMETYPE_VIDEO_VP9);
91         ABR_MEDIATYPE_LIST.add(MediaFormat.MIMETYPE_VIDEO_AV1);
92     }
93 
CodecEncoderTest(String encoder, String mediaType, EncoderConfigParams[] cfgParams, String allTestParams)94     public CodecEncoderTest(String encoder, String mediaType, EncoderConfigParams[] cfgParams,
95             String allTestParams) {
96         super(encoder, mediaType, cfgParams, allTestParams);
97     }
98 
99     @Override
resetContext(boolean isAsync, boolean signalEOSWithLastFrame)100     protected void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
101         super.resetContext(isAsync, signalEOSWithLastFrame);
102         mGotCSD = false;
103         mNumSyncFramesReceived = 0;
104         mSyncFramesPos.clear();
105     }
106 
107     @Override
dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)108     protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
109         if (info.size > 0 && ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0)) {
110             mGotCSD = true;
111         }
112         if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
113             mNumSyncFramesReceived += 1;
114             mSyncFramesPos.add(mOutputCount);
115         }
116         super.dequeueOutput(bufferIndex, info);
117     }
118 
forceSyncFrame()119     private void forceSyncFrame() {
120         final Bundle syncFrame = new Bundle();
121         syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
122         if (ENABLE_LOGS) {
123             Log.v(LOG_TAG, "requesting key frame");
124         }
125         mCodec.setParameters(syncFrame);
126     }
127 
updateBitrate(int bitrate)128     private void updateBitrate(int bitrate) {
129         final Bundle bitrateUpdate = new Bundle();
130         bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
131         if (ENABLE_LOGS) {
132             Log.v(LOG_TAG, "requesting bitrate to be changed to " + bitrate);
133         }
134         mCodec.setParameters(bitrateUpdate);
135     }
136 
getVideoEncoderCfgParam(String mediaType, int width, int height, int bitRate, int maxBFrames)137     private static EncoderConfigParams getVideoEncoderCfgParam(String mediaType, int width,
138             int height, int bitRate, int maxBFrames) {
139         return new EncoderConfigParams.Builder(mediaType).setWidth(width).setHeight(height)
140                 .setMaxBFrames(maxBFrames).setBitRate(bitRate).build();
141     }
142 
getAudioEncoderCfgParam(String mediaType, int sampleRate, int channelCount, int qualityPreset)143     private static EncoderConfigParams getAudioEncoderCfgParam(String mediaType, int sampleRate,
144             int channelCount, int qualityPreset) {
145         EncoderConfigParams.Builder foreman =
146                 new EncoderConfigParams.Builder(mediaType).setSampleRate(sampleRate)
147                         .setChannelCount(channelCount);
148         if (mediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
149             foreman = foreman.setCompressionLevel(qualityPreset);
150         } else {
151             foreman = foreman.setBitRate(qualityPreset);
152         }
153         return foreman.build();
154     }
155 
getAacCfgParams()156     private static EncoderConfigParams[] getAacCfgParams() {
157         EncoderConfigParams[] params = new EncoderConfigParams[2];
158         params[0] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_AAC, 8000, 1, 128000);
159         params[1] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 2, 128000);
160         return params;
161     }
162 
getOpusCfgParams()163     private static EncoderConfigParams[] getOpusCfgParams() {
164         EncoderConfigParams[] params = new EncoderConfigParams[2];
165         params[0] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_OPUS, 16000, 1, 64000);
166         params[1] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_OPUS, 16000, 1, 128000);
167         return params;
168     }
169 
getAmrnbCfgParams()170     private static EncoderConfigParams[] getAmrnbCfgParams() {
171         EncoderConfigParams[] params = new EncoderConfigParams[2];
172         params[0] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_AMR_NB, 8000, 1, 4750);
173         params[1] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_AMR_NB, 8000, 1, 12200);
174         return params;
175     }
176 
getAmrwbCfgParams()177     private static EncoderConfigParams[] getAmrwbCfgParams() {
178         EncoderConfigParams[] params = new EncoderConfigParams[2];
179         params[0] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_AMR_WB, 16000, 1, 6600);
180         params[1] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_AMR_WB, 16000, 1, 23850);
181         return params;
182     }
183 
getFlacCfgParams()184     private static EncoderConfigParams[] getFlacCfgParams() {
185         EncoderConfigParams[] params = new EncoderConfigParams[2];
186         params[0] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_FLAC, 8000, 1, 6);
187         params[1] = getAudioEncoderCfgParam(MediaFormat.MIMETYPE_AUDIO_FLAC, 48000, 2, 5);
188         return params;
189     }
190 
getH263CfgParams()191     private static EncoderConfigParams[] getH263CfgParams() {
192         EncoderConfigParams[] params = new EncoderConfigParams[2];
193         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_H263, 176, 144, 32000, 0);
194         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_H263, 176, 144, 64000, 0);
195         return params;
196     }
197 
getMpeg4CfgParams()198     private static EncoderConfigParams[] getMpeg4CfgParams() {
199         EncoderConfigParams[] params = new EncoderConfigParams[2];
200         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_MPEG4, 176, 144, 32000, 0);
201         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_MPEG4, 176, 144, 64000, 0);
202         return params;
203     }
204 
getAvcCfgParams()205     private static EncoderConfigParams[] getAvcCfgParams() {
206         EncoderConfigParams[] params = new EncoderConfigParams[2];
207         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_AVC, 176, 144, 512000, 0);
208         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_AVC, 352, 288, 512000, 0);
209         return params;
210     }
211 
getHevcCfgParams()212     private static EncoderConfigParams[] getHevcCfgParams() {
213         EncoderConfigParams[] params = new EncoderConfigParams[2];
214         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_HEVC, 176, 144, 512000, 0);
215         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_HEVC, 352, 288, 512000, 0);
216         return params;
217     }
218 
getAvcCfgParamsWithBFrames()219     private static EncoderConfigParams[] getAvcCfgParamsWithBFrames() {
220         EncoderConfigParams[] params = new EncoderConfigParams[2];
221         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_AVC, 320, 240, 512000, 2);
222         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 360, 768000, 2);
223         return params;
224     }
225 
getHevcCfgParamsWithBFrames()226     private static EncoderConfigParams[] getHevcCfgParamsWithBFrames() {
227         EncoderConfigParams[] params = new EncoderConfigParams[2];
228         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_HEVC, 320, 240, 384000, 2);
229         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_HEVC, 480, 360, 512000, 2);
230         return params;
231     }
232 
getVp8CfgParams()233     private static EncoderConfigParams[] getVp8CfgParams() {
234         EncoderConfigParams[] params = new EncoderConfigParams[2];
235         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_VP8, 176, 144, 512000, 0);
236         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_VP8, 352, 288, 512000, 0);
237         return params;
238     }
239 
getVp9CfgParams()240     private static EncoderConfigParams[] getVp9CfgParams() {
241         EncoderConfigParams[] params = new EncoderConfigParams[2];
242         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_VP9, 176, 144, 512000, 0);
243         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_VP9, 352, 288, 512000, 0);
244         return params;
245     }
246 
getAv1CfgParams()247     private static EncoderConfigParams[] getAv1CfgParams() {
248         EncoderConfigParams[] params = new EncoderConfigParams[2];
249         params[0] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_AV1, 176, 144, 512000, 0);
250         params[1] = getVideoEncoderCfgParam(MediaFormat.MIMETYPE_VIDEO_AV1, 352, 288, 512000, 0);
251         return params;
252     }
253 
254     @Parameterized.Parameters(name = "{index}_{0}_{1}")
input()255     public static Collection<Object[]> input() {
256         final boolean isEncoder = true;
257         final boolean needAudio = true;
258         final boolean needVideo = true;
259         final List<Object[]> exhaustiveArgsList = new ArrayList<>(Arrays.asList(new Object[][]{
260                 // mediaType, cfg params
261                 {MediaFormat.MIMETYPE_AUDIO_AAC, getAacCfgParams()},
262                 {MediaFormat.MIMETYPE_AUDIO_OPUS, getOpusCfgParams()},
263                 {MediaFormat.MIMETYPE_AUDIO_AMR_NB, getAmrnbCfgParams()},
264                 {MediaFormat.MIMETYPE_AUDIO_AMR_WB, getAmrwbCfgParams()},
265                 {MediaFormat.MIMETYPE_AUDIO_FLAC, getFlacCfgParams()},
266                 {MediaFormat.MIMETYPE_VIDEO_H263, getH263CfgParams()},
267                 {MediaFormat.MIMETYPE_VIDEO_MPEG4, getMpeg4CfgParams()},
268                 {MediaFormat.MIMETYPE_VIDEO_AVC, getAvcCfgParams()},
269                 {MediaFormat.MIMETYPE_VIDEO_AVC, getAvcCfgParamsWithBFrames()},
270                 {MediaFormat.MIMETYPE_VIDEO_HEVC, getHevcCfgParams()},
271                 {MediaFormat.MIMETYPE_VIDEO_HEVC, getHevcCfgParamsWithBFrames()},
272                 {MediaFormat.MIMETYPE_VIDEO_VP8, getVp8CfgParams()},
273                 {MediaFormat.MIMETYPE_VIDEO_VP9, getVp9CfgParams()},
274                 {MediaFormat.MIMETYPE_VIDEO_AV1, getAv1CfgParams()},
275         }));
276         return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, true);
277     }
278 
279     @Before
setUp()280     public void setUp() throws IOException {
281         mActiveEncCfg = mEncCfgParams[0];
282         mActiveRawRes = EncoderInput.getRawResource(mActiveEncCfg);
283         assertNotNull("no raw resource found for testing config : " + mActiveEncCfg + mTestConfig
284                 + mTestEnv, mActiveRawRes);
285     }
286 
validateCSD()287     private void validateCSD() {
288         boolean requireCSD = false;
289         if (mMediaType.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
290             if (!IS_AT_LEAST_V) {
291                 assertFalse("components that support mediaType: " + mMediaType
292                         + " must not generate CodecPrivateData before Android V\n"
293                         + mTestConfig + mTestEnv, mGotCSD);
294             } else if (BOARD_FIRST_SDK_IS_AT_LEAST_202404) {
295                 // For devices launching with Android V, CSD is mandated for VP9 encoders
296                 requireCSD = true;
297             } else {
298                 // For devices upgrading to Android V, CSD is not mandated for VP9 encoders
299                 requireCSD = false;
300             }
301         } else if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC)
302                 || mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_OPUS)
303                 || mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)
304                 || mMediaType.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)
305                 || mMediaType.equals(MediaFormat.MIMETYPE_VIDEO_AVC)
306                 || mMediaType.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
307             requireCSD = true;
308         }
309 
310         if (requireCSD) {
311             assertTrue("components that support mediaType: " + mMediaType
312                     + " must generate CodecPrivateData \n" + mTestConfig + mTestEnv, mGotCSD);
313         }
314     }
315 
316     /**
317      * Checks if the component under test can encode the test file correctly. The encoding
318      * happens in synchronous, asynchronous mode, eos flag signalled with last raw frame and
319      * eos flag signalled separately after sending all raw frames. It expects consistent
320      * output in all these runs. That is, the ByteBuffer info and output timestamp list has to be
321      * same in all the runs. Further for audio, the output timestamp has to be strictly
322      * increasing. For video the output timestamp list has to be same as input timestamp list. As
323      * encoders are expected to give consistent output for a given input and configuration
324      * parameters, the test checks for consistency across runs. Although the test collects the
325      * output in a byte buffer, no analysis is done that checks the integrity of the bitstream.
326      */
327     @CddTest(requirements = {"2.2.2", "2.3.2", "2.5.2", "5.1.1", "5.2/C-1-1", "5.2.4/C-1-3"})
328     @ApiTest(apis = {"android.media.MediaCodecInfo.CodecCapabilities#COLOR_FormatYUV420Flexible",
329             "android.media.AudioFormat#ENCODING_PCM_16BIT"})
330     @LargeTest
331     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSimpleEncode()332     public void testSimpleEncode() throws IOException, InterruptedException {
333         boolean[] boolStates = {true, false};
334         setUpSource(mActiveRawRes.mFileName);
335         OutputManager ref = new OutputManager();
336         OutputManager test = new OutputManager(ref.getSharedErrorLogs());
337         {
338             mCodec = MediaCodec.createByCodecName(mCodecName);
339             assertEquals("codec name act/got: " + mCodec.getName() + '/' + mCodecName,
340                     mCodec.getName(), mCodecName);
341             assertTrue("error! codec canonical name is null or empty",
342                     mCodec.getCanonicalName() != null && !mCodec.getCanonicalName().isEmpty());
343             mSaveToMem = false; /* TODO(b/149027258) */
344             MediaFormat format = mActiveEncCfg.getFormat();
345             {
346                 int loopCounter = 0;
347                 for (boolean eosType : boolStates) {
348                     for (boolean isAsync : boolStates) {
349                         mOutputBuff = loopCounter == 0 ? ref : test;
350                         mOutputBuff.reset();
351                         mInfoList.clear();
352                         validateMetrics(mCodecName);
353                         configureCodec(format, isAsync, eosType, true);
354                         mCodec.start();
355                         doWork(Integer.MAX_VALUE);
356                         queueEOS();
357                         waitForAllOutputs();
358                         validateMetrics(mCodecName, format);
359                         validateCSD();
360                         /* TODO(b/147348711) */
361                         if (false) mCodec.stop();
362                         else mCodec.reset();
363                         if (loopCounter != 0 && !ref.equals(test)) {
364                             fail("Encoder output is not consistent across runs \n" + mTestConfig
365                                     + mTestEnv + test.getErrMsg());
366                         }
367                         loopCounter++;
368                     }
369                 }
370             }
371             mCodec.release();
372         }
373     }
374 
nativeTestSimpleEncode(String encoder, String file, String mediaType, String cfgParams, String separator, StringBuilder retMsg)375     private native boolean nativeTestSimpleEncode(String encoder, String file, String mediaType,
376             String cfgParams, String separator, StringBuilder retMsg);
377 
378     /**
379      * Test is similar to {@link #testSimpleEncode()} but uses ndk api
380      */
381     @CddTest(requirements = {"2.2.2", "2.3.2", "2.5.2", "5.1.1", "5.1.7/C-1-3"})
382     @ApiTest(apis = {"android.media.MediaCodecInfo.CodecCapabilities#COLOR_FormatYUV420SemiPlanar",
383             "android.media.MediaCodecInfo.CodecCapabilities#COLOR_FormatYUV420Planar",
384             "android.media.AudioFormat#ENCODING_PCM_16BIT"})
385     @LargeTest
386     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSimpleEncodeNative()387     public void testSimpleEncodeNative() throws IOException, CloneNotSupportedException {
388         MediaFormat format = mActiveEncCfg.getFormat();
389         if (mIsVideo) {
390             int colorFormat = findByteBufferColorFormat(mCodecName, mMediaType);
391             assertTrue("no valid color formats received \n" + mTestConfig + mTestEnv,
392                     colorFormat != -1);
393             format = mActiveEncCfg.getBuilder().setColorFormat(colorFormat).build().getFormat();
394         }
395         boolean isPass = nativeTestSimpleEncode(mCodecName, mActiveRawRes.mFileName, mMediaType,
396                 EncoderConfigParams.serializeMediaFormat(format),
397                 EncoderConfigParams.TOKEN_SEPARATOR, mTestConfig);
398         assertTrue(mTestConfig.toString(), isPass);
399     }
400 
401     /**
402      * Checks component and framework behaviour on parameter (resolution, samplerate/channel
403      * count, ...) change. The reconfiguring of media codec component happens at various points.
404      * <ul>
405      *     <li>After initial configuration (stopped state).</li>
406      *     <li>In running state, before queueing any input.</li>
407      *     <li>In running state, after queueing n frames.</li>
408      *     <li>In eos state.</li>
409      * </ul>
410      * In eos state,
411      * <ul>
412      *     <li>reconfigure with same clip.</li>
413      *     <li>reconfigure with different clip (different resolution).</li>
414      * </ul>
415      * <p>
416      * In all situations (pre-reconfigure or post-reconfigure), the test expects the output
417      * timestamps to be strictly increasing. The reconfigure call makes the output received
418      * non-deterministic even for a given input. Hence, besides timestamp checks, no additional
419      * validation is done for outputs received before reconfigure. Post reconfigure, the encode
420      * begins from a sync frame. So the test expects consistent output and this needs to be
421      * identical to the reference.
422      * <p>
423      * The test runs mediacodec in synchronous and asynchronous mode.
424      * <p>
425      * During reconfiguration, the mode of operation is toggled. That is, if first configure
426      * operates the codec in sync mode, then next configure operates the codec in async mode and
427      * so on.
428      */
429     @Ignore("TODO(b/148523403)")
430     @ApiTest(apis = {"android.media.MediaCodec#configure"})
431     @LargeTest
432     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testReconfigure()433     public void testReconfigure() throws IOException, InterruptedException {
434 
435         boolean[] boolStates = {true, false};
436         {
437             boolean saveToMem = false; /* TODO(b/149027258) */
438             OutputManager configRef = null;
439             OutputManager configTest = null;
440             if (mEncCfgParams.length > 1) {
441                 encodeToMemory(mCodecName, mEncCfgParams[1], mActiveRawRes, Integer.MAX_VALUE,
442                         saveToMem, mMuxOutput);
443                 configRef = mOutputBuff;
444                 configTest = new OutputManager(configRef.getSharedErrorLogs());
445             }
446             encodeToMemory(mCodecName, mEncCfgParams[0], mActiveRawRes, Integer.MAX_VALUE,
447                     saveToMem, mMuxOutput);
448             OutputManager ref = mOutputBuff;
449             OutputManager test = new OutputManager(ref.getSharedErrorLogs());
450             MediaFormat format = mEncCfgParams[0].getFormat();
451             mCodec = MediaCodec.createByCodecName(mCodecName);
452             for (boolean isAsync : boolStates) {
453                 mOutputBuff = test;
454                 configureCodec(format, isAsync, true, true);
455 
456                 /* test reconfigure in stopped state */
457                 reConfigureCodec(format, !isAsync, false, true);
458                 mCodec.start();
459 
460                 /* test reconfigure in running state before queuing input */
461                 reConfigureCodec(format, !isAsync, false, true);
462                 mCodec.start();
463                 doWork(23);
464 
465                 if (mOutputCount != 0) validateMetrics(mCodecName, format);
466 
467                 /* test reconfigure codec in running state */
468                 reConfigureCodec(format, isAsync, true, true);
469                 mCodec.start();
470                 mSaveToMem = saveToMem;
471                 test.reset();
472                 doWork(Integer.MAX_VALUE);
473                 queueEOS();
474                 waitForAllOutputs();
475                 /* TODO(b/147348711) */
476                 if (false) mCodec.stop();
477                 else mCodec.reset();
478                 if (!ref.equals(test)) {
479                     fail("Encoder output is not consistent across runs \n" + mTestConfig
480                             + mTestEnv + test.getErrMsg());
481                 }
482 
483                 /* test reconfigure codec at eos state */
484                 reConfigureCodec(format, !isAsync, false, true);
485                 mCodec.start();
486                 test.reset();
487                 doWork(Integer.MAX_VALUE);
488                 queueEOS();
489                 waitForAllOutputs();
490                 /* TODO(b/147348711) */
491                 if (false) mCodec.stop();
492                 else mCodec.reset();
493                 if (!ref.equals(test)) {
494                     fail("Encoder output is not consistent across runs \n" + mTestConfig
495                             + mTestEnv + test.getErrMsg());
496                 }
497 
498                 /* test reconfigure codec for new format */
499                 if (mEncCfgParams.length > 1) {
500                     mOutputBuff = configTest;
501                     reConfigureCodec(mEncCfgParams[1].getFormat(), isAsync, false, true);
502                     mCodec.start();
503                     configTest.reset();
504                     doWork(Integer.MAX_VALUE);
505                     queueEOS();
506                     waitForAllOutputs();
507                     /* TODO(b/147348711) */
508                     if (false) mCodec.stop();
509                     else mCodec.reset();
510                     if (!configRef.equals(configTest)) {
511                         fail("Encoder output is not consistent across runs \n" + mTestConfig
512                                 + mTestEnv + configTest.getErrMsg());
513                     }
514                 }
515                 mSaveToMem = false;
516             }
517             mCodec.release();
518         }
519     }
520 
nativeTestReconfigure(String encoder, String file, String mediaType, String cfgParams, String cfgReconfigParams, String separator, StringBuilder retMsg)521     private native boolean nativeTestReconfigure(String encoder, String file, String mediaType,
522             String cfgParams, String cfgReconfigParams, String separator, StringBuilder retMsg);
523 
524     /**
525      * Test is similar to {@link #testReconfigure()} but uses ndk api
526      */
527     @Ignore("TODO(b/147348711, b/149981033)")
528     @ApiTest(apis = {"android.media.MediaCodec#configure"})
529     @LargeTest
530     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testReconfigureNative()531     public void testReconfigureNative() throws IOException, CloneNotSupportedException {
532         MediaFormat format = mEncCfgParams[0].getFormat();
533         MediaFormat reconfigFormat = mEncCfgParams.length > 1 ? mEncCfgParams[1].getFormat() : null;
534         if (mIsVideo) {
535             int colorFormat = findByteBufferColorFormat(mCodecName, mMediaType);
536             assertTrue("no valid color formats received \n" + mTestConfig + mTestEnv,
537                     colorFormat != -1);
538             format = mEncCfgParams[0].getBuilder().setColorFormat(colorFormat).build().getFormat();
539             if (mEncCfgParams.length > 1) {
540                 reconfigFormat = mEncCfgParams[1].getBuilder().setColorFormat(colorFormat).build()
541                         .getFormat();
542             }
543         }
544         boolean isPass = nativeTestReconfigure(mCodecName, mActiveRawRes.mFileName, mMediaType,
545                 EncoderConfigParams.serializeMediaFormat(format), reconfigFormat == null ? null :
546                         EncoderConfigParams.serializeMediaFormat(reconfigFormat),
547                 EncoderConfigParams.TOKEN_SEPARATOR, mTestConfig);
548         assertTrue(mTestConfig.toString(), isPass);
549     }
550 
551     /**
552      * Test encoder for EOS only input. As BUFFER_FLAG_END_OF_STREAM is queued with an input buffer
553      * of size 0, during dequeue the test expects to receive BUFFER_FLAG_END_OF_STREAM with an
554      * output buffer of size 0. No input is given, so no output shall be received.
555      */
556     @ApiTest(apis = "android.media.MediaCodec#BUFFER_FLAG_END_OF_STREAM")
557     @SmallTest
558     @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
testOnlyEos()559     public void testOnlyEos() throws IOException, InterruptedException {
560         boolean[] boolStates = {true, false};
561         OutputManager ref = new OutputManager();
562         OutputManager test = new OutputManager(ref.getSharedErrorLogs());
563         {
564             mCodec = MediaCodec.createByCodecName(mCodecName);
565             mSaveToMem = false; /* TODO(b/149027258) */
566             int loopCounter = 0;
567             MediaFormat format = mActiveEncCfg.getFormat();
568             for (boolean isAsync : boolStates) {
569                 configureCodec(format, isAsync, false, true);
570                 mOutputBuff = loopCounter == 0 ? ref : test;
571                 mOutputBuff.reset();
572                 mInfoList.clear();
573                 mCodec.start();
574                 queueEOS();
575                 waitForAllOutputs();
576                 /* TODO(b/147348711) */
577                 if (false) mCodec.stop();
578                 else mCodec.reset();
579                 if (loopCounter != 0 && !ref.equals(test)) {
580                     fail("Encoder output is not consistent across runs \n" + mTestConfig
581                             + mTestEnv + test.getErrMsg());
582                 }
583                 loopCounter++;
584             }
585             mCodec.release();
586         }
587     }
588 
nativeTestOnlyEos(String encoder, String mediaType, String cfgParams, String separator, StringBuilder retMsg)589     private native boolean nativeTestOnlyEos(String encoder, String mediaType, String cfgParams,
590             String separator, StringBuilder retMsg);
591 
592     /**
593      * Test is similar to {@link #testOnlyEos()} but uses ndk api
594      */
595     @ApiTest(apis = "android.media.MediaCodec#BUFFER_FLAG_END_OF_STREAM")
596     @SmallTest
597     @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
testOnlyEosNative()598     public void testOnlyEosNative() throws IOException, CloneNotSupportedException {
599         MediaFormat format = mActiveEncCfg.getFormat();
600         if (mIsVideo) {
601             int colorFormat = findByteBufferColorFormat(mCodecName, mMediaType);
602             assertTrue("no valid color formats received \n" + mTestConfig + mTestEnv,
603                     colorFormat != -1);
604             format = mActiveEncCfg.getBuilder().setColorFormat(colorFormat).build().getFormat();
605         }
606         boolean isPass = nativeTestOnlyEos(mCodecName, mMediaType,
607                 EncoderConfigParams.serializeMediaFormat(format),
608                 EncoderConfigParams.TOKEN_SEPARATOR, mTestConfig);
609         assertTrue(mTestConfig.toString(), isPass);
610     }
611 
612     /**
613      * Test video encoders for feature "request-sync". Video encoders are expected to give a sync
614      * frame upon request. The test requests encoder to provide key frame every 'n' seconds.  The
615      * test feeds encoder input for 'm' seconds. At the end, it expects to receive m/n key frames
616      * at least. Also it checks if the key frame received is not too far from the point of request.
617      */
618     @ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_REQUEST_SYNC_FRAME")
619     @LargeTest
620     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSetForceSyncFrame()621     public void testSetForceSyncFrame()
622             throws IOException, InterruptedException, CloneNotSupportedException {
623         Assume.assumeTrue("Test is applicable only for video encoders", mIsVideo);
624         EncoderConfigParams currCfg = mActiveEncCfg.getBuilder().setKeyFrameInterval(500.f).build();
625         MediaFormat format = currCfg.getFormat();
626         // Maximum allowed key frame interval variation from the target value.
627         final int maxKeyframeIntervalVariation = 3;
628         final int keyFrameInterval = 2; // force key frame every 2 seconds.
629         final int keyFramePos = currCfg.mFrameRate * keyFrameInterval;
630         final int numKeyFrameRequests = 7;
631 
632         setUpSource(mActiveRawRes.mFileName);
633         mOutputBuff = new OutputManager();
634         boolean[] boolStates = {true, false};
635         {
636             mCodec = MediaCodec.createByCodecName(mCodecName);
637             for (boolean isAsync : boolStates) {
638                 mOutputBuff.reset();
639                 mInfoList.clear();
640                 configureCodec(format, isAsync, false, true);
641                 mCodec.start();
642                 for (int i = 0; i < numKeyFrameRequests; i++) {
643                     doWork(keyFramePos);
644                     if (mSawInputEOS) {
645                         fail(String.format("Unable to encode %d frames as the input resource "
646                                 + "contains only %d frames \n", keyFramePos, mInputCount));
647                     }
648                     forceSyncFrame();
649                     mInputBufferReadOffset = 0;
650                 }
651                 queueEOS();
652                 waitForAllOutputs();
653                 /* TODO(b/147348711) */
654                 if (false) mCodec.stop();
655                 else mCodec.reset();
656                 String msg = String.format("Received only %d key frames for %d key frame "
657                         + "requests \n", mNumSyncFramesReceived, numKeyFrameRequests);
658                 assertTrue(msg + mTestConfig + mTestEnv,
659                         mNumSyncFramesReceived >= numKeyFrameRequests);
660                 for (int i = 0, expPos = 0, index = 0; i < numKeyFrameRequests; i++) {
661                     int j = index;
662                     for (; j < mSyncFramesPos.size(); j++) {
663                         // Check key frame intervals:
664                         // key frame position should not be greater than target value + 3
665                         // key frame position should not be less than target value - 3
666                         if (Math.abs(expPos - mSyncFramesPos.get(j)) <=
667                                 maxKeyframeIntervalVariation) {
668                             index = j;
669                             break;
670                         }
671                     }
672                     if (j == mSyncFramesPos.size()) {
673                         Log.w(LOG_TAG, "requested key frame at frame index " + expPos +
674                                 " none found near by");
675                     }
676                     expPos += keyFramePos;
677                 }
678             }
679             mCodec.release();
680         }
681     }
682 
nativeTestSetForceSyncFrame(String encoder, String file, String mediaType, String cfgParams, String separator, StringBuilder retMsg)683     private native boolean nativeTestSetForceSyncFrame(String encoder, String file,
684             String mediaType, String cfgParams, String separator, StringBuilder retMsg);
685 
686     /**
687      * Test is similar to {@link #testSetForceSyncFrame()} but uses ndk api
688      */
689     @ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_REQUEST_SYNC_FRAME")
690     @LargeTest
691     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSetForceSyncFrameNative()692     public void testSetForceSyncFrameNative() throws IOException, CloneNotSupportedException {
693         Assume.assumeTrue("Test is applicable only for encoders", mIsVideo);
694 
695         int colorFormat = findByteBufferColorFormat(mCodecName, mMediaType);
696         assertTrue("no valid color formats received \n" + mTestConfig + mTestEnv,
697                 colorFormat != -1);
698         MediaFormat format =
699                 mActiveEncCfg.getBuilder().setColorFormat(colorFormat).setKeyFrameInterval(500.f)
700                         .build().getFormat();
701         boolean isPass = nativeTestSetForceSyncFrame(mCodecName, mActiveRawRes.mFileName,
702                 mMediaType, EncoderConfigParams.serializeMediaFormat(format),
703                 EncoderConfigParams.TOKEN_SEPARATOR, mTestConfig);
704         assertTrue(mTestConfig.toString(), isPass);
705     }
706 
707     /**
708      * Test video encoders for feature adaptive bitrate. Video encoders are expected to honor
709      * bitrate changes upon request. The test requests encoder to encode at new bitrate every 'n'
710      * seconds.  The test feeds encoder input for 'm' seconds. At the end, it expects the output
711      * file size to be around {sum of (n * Bi) for i in the range [0, (m/n)]} and Bi is the
712      * bitrate chosen for the interval 'n' seconds
713      */
714     @CddTest(requirements = "5.2/C-2-1")
715     @ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_VIDEO_BITRATE")
716     @LargeTest
717     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testAdaptiveBitRate()718     public void testAdaptiveBitRate() throws IOException, InterruptedException {
719         Assume.assumeTrue("Skipping AdaptiveBitrate test for " + mMediaType,
720                 ABR_MEDIATYPE_LIST.contains(mMediaType));
721         MediaFormat format = mActiveEncCfg.getFormat();
722         final int adaptiveBrInterval = 3; // change br every 3 seconds.
723         final int adaptiveBrDurFrm = mActiveEncCfg.mFrameRate * adaptiveBrInterval;
724         final int brChangeRequests = 7;
725         // TODO(b/251265293) Reduce the allowed deviation after improving the test conditions
726         final float maxBitrateDeviation = 60.0f; // allowed bitrate deviation in %
727 
728         boolean[] boolStates = {true, false};
729         setUpSource(mActiveRawRes.mFileName);
730         mOutputBuff = new OutputManager();
731         mSaveToMem = true;
732         {
733             mCodec = MediaCodec.createByCodecName(mCodecName);
734             for (boolean isAsync : boolStates) {
735                 mOutputBuff.reset();
736                 mInfoList.clear();
737                 configureCodec(format, isAsync, false, true);
738                 mCodec.start();
739                 int expOutSize = 0;
740                 int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
741                 for (int i = 0; i < brChangeRequests; i++) {
742                     doWork(adaptiveBrDurFrm);
743                     if (mSawInputEOS) {
744                         fail(String.format("Unable to encode %d frames as the input resource "
745                                 + "contains only %d frames \n", adaptiveBrDurFrm, mInputCount));
746                     }
747                     expOutSize += adaptiveBrInterval * bitrate;
748                     if ((i & 1) == 1) bitrate *= 2;
749                     else bitrate /= 2;
750                     updateBitrate(bitrate);
751                     mInputBufferReadOffset = 0;
752                 }
753                 queueEOS();
754                 waitForAllOutputs();
755                 /* TODO(b/147348711) */
756                 if (false) mCodec.stop();
757                 else mCodec.reset();
758                 /* TODO: validate output br with sliding window constraints Sec 5.2 cdd */
759                 int outSize = mOutputBuff.getOutStreamSize() * 8;
760                 float brDev = Math.abs(expOutSize - outSize) * 100.0f / expOutSize;
761                 if (brDev > maxBitrateDeviation) {
762                     fail("Relative Bitrate error is too large " + brDev + "\n" + mTestConfig
763                             + mTestEnv);
764                 }
765             }
766             mCodec.release();
767         }
768     }
769 
nativeTestAdaptiveBitRate(String encoder, String file, String mediaType, String cfgParams, String separator, StringBuilder retMsg)770     private native boolean nativeTestAdaptiveBitRate(String encoder, String file, String mediaType,
771             String cfgParams, String separator, StringBuilder retMsg);
772 
773     /**
774      * Test is similar to {@link #testAdaptiveBitRate()} but uses ndk api
775      */
776     @CddTest(requirements = "5.2/C-2-1")
777     @ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_VIDEO_BITRATE")
778     @LargeTest
779     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testAdaptiveBitRateNative()780     public void testAdaptiveBitRateNative() throws IOException, CloneNotSupportedException {
781         Assume.assumeTrue("Skipping Native AdaptiveBitrate test for " + mMediaType,
782                 ABR_MEDIATYPE_LIST.contains(mMediaType));
783         int colorFormat = findByteBufferColorFormat(mCodecName, mMediaType);
784         assertTrue("no valid color formats received \n" + mTestConfig + mTestEnv,
785                 colorFormat != -1);
786         MediaFormat format =
787                 mActiveEncCfg.getBuilder().setColorFormat(colorFormat).build().getFormat();
788         boolean isPass = nativeTestAdaptiveBitRate(mCodecName, mActiveRawRes.mFileName, mMediaType,
789                 EncoderConfigParams.serializeMediaFormat(format),
790                 EncoderConfigParams.TOKEN_SEPARATOR, mTestConfig);
791         assertTrue(mTestConfig.toString(), isPass);
792     }
793 }
794