1 /*
2  * Copyright (C) 2024 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 android.media.MediaCodecInfo.CodecCapabilities.FEATURE_MultipleFrames;
20 import static android.mediav2.common.cts.CodecTestBase.SupportClass.CODEC_OPTIONAL;
21 import static android.mediav2.cts.CodecDecoderMultiAccessUnitTest.RECONFIG_FILE_MEDIA_TYPE_MAP;
22 import static android.mediav2.cts.CodecDecoderMultiAccessUnitTest.exhaustiveArgsList;
23 
24 import static org.junit.Assert.fail;
25 
26 import android.media.MediaCodec;
27 import android.media.MediaExtractor;
28 import android.media.MediaFormat;
29 import android.mediav2.common.cts.CodecDecoderBlockModelMultiAccessUnitTestBase;
30 import android.mediav2.common.cts.CodecDecoderBlockModelTestBase;
31 import android.mediav2.common.cts.CodecDecoderTestBase;
32 import android.mediav2.common.cts.OutputManager;
33 import android.os.Build;
34 import android.platform.test.annotations.AppModeFull;
35 import android.platform.test.annotations.RequiresFlagsEnabled;
36 
37 import androidx.test.filters.LargeTest;
38 import androidx.test.filters.SdkSuppress;
39 
40 import com.android.compatibility.common.util.ApiTest;
41 import com.android.media.codec.flags.Flags;
42 
43 import org.junit.Before;
44 import org.junit.Ignore;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 import org.junit.runners.Parameterized;
48 
49 import java.io.IOException;
50 import java.util.ArrayList;
51 import java.util.Collection;
52 
53 /**
54  * Tests audio decoders support for feature MultipleFrames in block model mode.
55  * <p>
56  * MultipleFrames feature is optional and is not required to support by all components. If a
57  * component supports this feature, then multiple access units are grouped together (demarcated
58  * with access unit offsets and timestamps) are sent as input to the component. The components
59  * processes the input sent and returns output in a large enough buffer (demarcated with access
60  * unit offsets and timestamps). The number of access units that can be grouped is dependent on
61  * format keys, KEY_MAX_INPUT_SIZE, KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE.
62  * <p>
63  * The test runs the component in MultipleFrames block model mode and normal mode and expects same
64  * output for a given input.
65  **/
66 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
67 @AppModeFull(reason = "Instant apps cannot access the SD card")
68 @RequiresFlagsEnabled(Flags.FLAG_LARGE_AUDIO_FRAME)
69 @RunWith(Parameterized.class)
70 public class CodecDecoderBlockModelMultiAccessUnitTest
71         extends CodecDecoderBlockModelMultiAccessUnitTestBase {
72     private static final String LOG_TAG =
73             CodecDecoderBlockModelMultiAccessUnitTest.class.getSimpleName();
74     private static final String MEDIA_DIR = WorkDir.getMediaDirString();
75     private static final int[][] OUT_SIZE_IN_MS = {
76             {1000, 250},  // max out size, threshold batch out size
77             {1000, 100},
78             {500, 20},
79             {100, 100},
80             {40, 100}
81     };
82 
83     private final String mReconfigFile;
84 
85     @Parameterized.Parameters(name = "{index}_{0}_{1}")
input()86     public static Collection<Object[]> input() {
87         return prepareParamList(exhaustiveArgsList, false, true, false, true, ComponentClass.ALL,
88                 new String[]{FEATURE_MultipleFrames});
89     }
90 
CodecDecoderBlockModelMultiAccessUnitTest(String decoder, String mediaType, String testFile, String allTestParams)91     public CodecDecoderBlockModelMultiAccessUnitTest(String decoder, String mediaType,
92             String testFile, String allTestParams) {
93         super(decoder, mediaType, MEDIA_DIR + testFile, allTestParams);
94         mReconfigFile = MEDIA_DIR + RECONFIG_FILE_MEDIA_TYPE_MAP.get(mediaType);
95     }
96 
97     @Before
setUp()98     public void setUp() throws IOException {
99         MediaFormat format = setUpSource(mTestFile);
100         mExtractor.release();
101         ArrayList<MediaFormat> formatList = new ArrayList<>();
102         formatList.add(format);
103         checkFormatSupport(mCodecName, mMediaType, false, formatList, null, CODEC_OPTIONAL);
104     }
105 
106     /**
107      * Verifies if the component under test can decode the test file correctly in multiple frame
108      * block model mode. The decoding happens in asynchronous mode with eos flag signalled with
109      * last compressed frame. The test verifies if the component / framework output is consistent
110      * with single access unit normal mode and single access unit block model mode.
111      * <p>
112      * Check description of class {@link CodecDecoderBlockModelMultiAccessUnitTest}
113      */
114     @ApiTest(apis = {"android.media.MediaFormat#KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE",
115             "android.media.MediaFormat#KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE",
116             "android.media.MediaCodec.Callback#onOutputBuffersAvailable",
117             "android.media.MediaCodec#CONFIGURE_FLAG_USE_BLOCK_MODEL"})
118     @LargeTest
119     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testSimpleDecode()120     public void testSimpleDecode() throws IOException, InterruptedException {
121         CodecDecoderTestBase cdtb = new CodecDecoderTestBase(mCodecName, mMediaType, null,
122                 mAllTestParams);
123         cdtb.decodeToMemory(mTestFile, mCodecName, 0, MediaExtractor.SEEK_TO_CLOSEST_SYNC,
124                 Integer.MAX_VALUE);
125         OutputManager ref = cdtb.getOutputManager();
126 
127         CodecDecoderBlockModelTestBase cdbmtb = new CodecDecoderBlockModelTestBase(
128                 mCodecName, mMediaType, null, mAllTestParams);
129         OutputManager test = new OutputManager(ref.getSharedErrorLogs());
130         cdbmtb.decodeToMemory(mTestFile, mCodecName, test, 0,
131                 MediaExtractor.SEEK_TO_CLOSEST_SYNC, Integer.MAX_VALUE);
132         if (!ref.equals(test)) {
133             fail("Output in block model mode is not same as output in normal mode. \n"
134                     + mTestConfig + mTestEnv + test.getErrMsg());
135         }
136 
137         boolean[] boolStates = {true, false};
138         OutputManager testA = new OutputManager(ref.getSharedErrorLogs());
139         OutputManager testB = new OutputManager(ref.getSharedErrorLogs());
140         mSaveToMem = true;
141         MediaFormat format = setUpSource(mTestFile);
142         int maxSampleSize = getMaxSampleSizeForMediaType(mTestFile, mMediaType);
143         mCodec = MediaCodec.createByCodecName(mCodecName);
144         for (int[] outSizeInMs : OUT_SIZE_IN_MS) {
145             configureKeysForLargeAudioBlockModelFrameMode(format, maxSampleSize, outSizeInMs[0],
146                     outSizeInMs[1]);
147             for (boolean signalEosWithLastFrame : boolStates) {
148                 mOutputBuff = signalEosWithLastFrame ? testA : testB;
149                 mOutputBuff.reset();
150                 configureCodec(format, true, signalEosWithLastFrame, false);
151                 mMaxInputLimitMs = outSizeInMs[0];
152                 mCodec.start();
153                 mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
154                 doWork(Integer.MAX_VALUE);
155                 queueEOS();
156                 waitForAllOutputs();
157                 mCodec.reset();
158                 if (!ref.equalsByteOutput(mOutputBuff)) {
159                     fail("Output of decoder component when fed with multiple access units in single"
160                             + " enqueue call differs from output received when each access unit is"
161                             + "fed separately. \n" + mTestConfig + mTestEnv
162                             + mOutputBuff.getErrMsg());
163                 }
164             }
165             if (!testA.equals(testB)) {
166                 fail("Output of decoder component is not consistent across runs. \n" + mTestConfig
167                         + mTestEnv + testB.getErrMsg());
168             }
169         }
170         mCodec.release();
171         mExtractor.release();
172     }
173 
174     /**
175      * Verifies component and framework behaviour to flush API when the codec is operating in
176      * multiple frame block model mode. The test verifies if the component / framework output
177      * is consistent with single access unit normal mode.
178      * <p>
179      * While the component is decoding the test clip, mediacodec flush() is called. The flush API
180      * is called at various points :-
181      * <ul>
182      *     <li>In running state, after queueing n frames.</li>
183      *     <li>In eos state.</li>
184      * </ul>
185      * <p>
186      * In all situations (pre-flush or post-flush), the test expects the output timestamps to be
187      * strictly increasing. The flush call makes the output received non-deterministic even for a
188      * given input. Hence, besides timestamp checks, no additional validation is done for outputs
189      * received before flush. Post flush, the decode begins from a sync frame. So the test
190      * expects consistent output and this needs to be identical to the reference
191      * (single access unit mode)
192      * <p>
193      */
194     @ApiTest(apis = {"android.media.MediaFormat#KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE",
195             "android.media.MediaFormat#KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE",
196             "android.media.MediaCodec.Callback#onOutputBuffersAvailable",
197             "android.media.MediaCodec#flush"})
198     @LargeTest
199     @Ignore("TODO(b/147576107)")
200     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testFlush()201     public void testFlush() throws IOException, InterruptedException {
202         MediaFormat format = setUpSource(mTestFile);
203         final long pts = 250000;
204         mExtractor.release();
205         OutputManager ref = null, test;
206         if (isMediaTypeOutputUnAffectedBySeek(mMediaType)) {
207             CodecDecoderTestBase cdtb = new CodecDecoderTestBase(mCodecName, mMediaType, null,
208                     mAllTestParams);
209             cdtb.decodeToMemory(mTestFile, mCodecName, pts, MediaExtractor.SEEK_TO_CLOSEST_SYNC,
210                     Integer.MAX_VALUE);
211             ref = cdtb.getOutputManager();
212             test = new OutputManager(ref.getSharedErrorLogs());
213         } else {
214             test = new OutputManager();
215         }
216 
217         mOutputBuff = test;
218         setUpSource(mTestFile);
219         int maxSampleSize = getMaxSampleSizeForMediaType(mTestFile, mMediaType);
220         configureKeysForLargeAudioBlockModelFrameMode(format, maxSampleSize, OUT_SIZE_IN_MS[0][0],
221                 OUT_SIZE_IN_MS[0][1]);
222         mMaxInputLimitMs = OUT_SIZE_IN_MS[0][0];
223         mCodec = MediaCodec.createByCodecName(mCodecName);
224         test.reset();
225         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
226         configureCodec(format, true, true, false);
227 
228         mCodec.start();
229         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
230         test.reset();
231         doWork(23);
232         if (!test.isPtsStrictlyIncreasing(mPrevOutputPts)) {
233             fail("Output timestamps are not strictly increasing \n" + mTestConfig + mTestEnv
234                     + test.getErrMsg());
235         }
236 
237         /* test flush in running state */
238         flushCodec();
239         mCodec.start();
240         mSaveToMem = true;
241         test.reset();
242         mExtractor.seekTo(pts, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
243         doWork(Integer.MAX_VALUE);
244         queueEOS();
245         waitForAllOutputs();
246         if (ref != null && !ref.equalsByteOutput(test)) {
247             fail("Decoder output is not consistent across runs \n" + mTestConfig + mTestEnv
248                     + test.getErrMsg());
249         }
250 
251         /* test flush in eos state */
252         flushCodec();
253         mCodec.start();
254         test.reset();
255         mExtractor.seekTo(pts, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
256         doWork(Integer.MAX_VALUE);
257         queueEOS();
258         waitForAllOutputs();
259         mCodec.stop();
260         if (ref != null && !ref.equalsByteOutput(test)) {
261             fail("Decoder output is not consistent across runs \n" + mTestConfig + mTestEnv
262                     + test.getErrMsg());
263         }
264 
265         mSaveToMem = false;
266         mCodec.release();
267         mExtractor.release();
268     }
269 
270     /**
271      * Verifies component and framework behaviour for format change in multiple frame block model
272      * mode. The format change is not seamless (AdaptivePlayback) but done via reconfigure.
273      * <p>
274      * The reconfiguring of media codec component happens at various points :-
275      * <ul>
276      *     <li>After initial configuration (stopped state).</li>
277      *     <li>In running state, before queueing any input.</li>
278      *     <li>In running state, after queuing n frames.</li>
279      *     <li>In eos state.</li>
280      * </ul>
281      * In eos state,
282      * <ul>
283      *     <li>reconfigure with same clip.</li>
284      *     <li>reconfigure with different clip (different resolution).</li>
285      * </ul>
286      * <p>
287      * In all situations (pre-reconfigure or post-reconfigure), the test expects the output
288      * timestamps to be strictly increasing. The reconfigure call makes the output received
289      * non-deterministic even for a given input. Hence, besides timestamp checks, no additional
290      * validation is done for outputs received before reconfigure. Post reconfigure, the decode
291      * begins from a sync frame. So the test expects consistent output and this needs to be
292      * identical to the reference (single access unit mode).
293      * <p>
294      */
295     @ApiTest(apis = {"android.media.MediaFormat#KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE",
296             "android.media.MediaFormat#KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE",
297             "android.media.MediaCodec.Callback#onOutputBuffersAvailable",
298             "android.media.MediaCodec#configure"})
299     @LargeTest
300     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
testReconfigure()301     public void testReconfigure() throws IOException, InterruptedException {
302         MediaFormat format = setUpSource(mTestFile);
303         mExtractor.release();
304         MediaFormat newFormat = setUpSource(mReconfigFile);
305         mExtractor.release();
306         ArrayList<MediaFormat> formatList = new ArrayList<>();
307         formatList.add(newFormat);
308         checkFormatSupport(mCodecName, mMediaType, false, formatList, null, CODEC_OPTIONAL);
309 
310         CodecDecoderTestBase cdtbA = new CodecDecoderTestBase(mCodecName, mMediaType, null,
311                 mAllTestParams);
312         cdtbA.decodeToMemory(mTestFile, mCodecName, 0, MediaExtractor.SEEK_TO_CLOSEST_SYNC,
313                 Integer.MAX_VALUE);
314         OutputManager ref = cdtbA.getOutputManager();
315         OutputManager test = new OutputManager(ref.getSharedErrorLogs());
316 
317         CodecDecoderTestBase cdtbB = new CodecDecoderTestBase(mCodecName, mMediaType, null,
318                 mAllTestParams);
319         cdtbB.decodeToMemory(mReconfigFile, mCodecName, 0, MediaExtractor.SEEK_TO_CLOSEST_SYNC,
320                 Integer.MAX_VALUE);
321         OutputManager configRef = cdtbB.getOutputManager();
322         OutputManager configTest = new OutputManager(configRef.getSharedErrorLogs());
323 
324         int maxSampleSize = getMaxSampleSizeForMediaType(mTestFile, mMediaType);
325         configureKeysForLargeAudioBlockModelFrameMode(format, maxSampleSize, OUT_SIZE_IN_MS[0][0],
326                 OUT_SIZE_IN_MS[0][1]);
327         mMaxInputLimitMs = OUT_SIZE_IN_MS[0][0];
328         mCodec = MediaCodec.createByCodecName(mCodecName);
329         mOutputBuff = test;
330         setUpSource(mTestFile);
331         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
332         configureCodec(format, true, true, false);
333 
334         /* test reconfigure in stopped state */
335         reConfigureCodec(format, true, false, false);
336         mCodec.start();
337 
338         /* test reconfigure in running state before queuing input */
339         reConfigureCodec(format, true, false, false);
340         mCodec.start();
341         doWork(23);
342 
343         /* test reconfigure codec in running state */
344         reConfigureCodec(format, true, true, false);
345         mCodec.start();
346         mSaveToMem = true;
347         test.reset();
348         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
349         doWork(Integer.MAX_VALUE);
350         queueEOS();
351         waitForAllOutputs();
352         mCodec.stop();
353         if (!ref.equalsByteOutput(test)) {
354             fail("Decoder output is not consistent across runs \n" + mTestConfig + mTestEnv
355                     + test.getErrMsg());
356         }
357 
358         /* test reconfigure codec at eos state */
359         reConfigureCodec(format, true, false, false);
360         mCodec.start();
361         test.reset();
362         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
363         doWork(Integer.MAX_VALUE);
364         queueEOS();
365         waitForAllOutputs();
366         mCodec.stop();
367         if (!ref.equalsByteOutput(test)) {
368             fail("Decoder output is not consistent across runs \n" + mTestConfig + mTestEnv
369                     + test.getErrMsg());
370         }
371         mExtractor.release();
372 
373         /* test reconfigure codec for new file */
374         maxSampleSize = getMaxSampleSizeForMediaType(mReconfigFile, mMediaType);
375         configureKeysForLargeAudioBlockModelFrameMode(newFormat, maxSampleSize,
376                 OUT_SIZE_IN_MS[0][0], OUT_SIZE_IN_MS[0][1]);
377         mOutputBuff = configTest;
378         setUpSource(mReconfigFile);
379         reConfigureCodec(newFormat, true, false, false);
380         mCodec.start();
381         configTest.reset();
382         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
383         doWork(Integer.MAX_VALUE);
384         queueEOS();
385         waitForAllOutputs();
386         mCodec.stop();
387         if (!configRef.equalsByteOutput(configTest)) {
388             fail("Decoder output is not consistent across runs \n" + mTestConfig + mTestEnv
389                     + configTest.getErrMsg());
390         }
391         mSaveToMem = false;
392         mExtractor.release();
393         mCodec.release();
394     }
395 }
396