• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.codec.Flags.FLAG_CODEC_AVAILABILITY;
20 import static android.media.codec.Flags.codecAvailability;
21 import static android.media.codec.Flags.codecAvailabilitySupport;
22 import static android.mediav2.cts.AdaptivePlaybackTest.createInputList;
23 import static android.mediav2.cts.AdaptivePlaybackTest.getSupportedFiles;
24 import static android.mediav2.cts.CodecResourceUtils.CodecState;
25 import static android.mediav2.cts.CodecResourceUtils.LHS_RESOURCE_GE;
26 import static android.mediav2.cts.CodecResourceUtils.RHS_RESOURCE_GE;
27 import static android.mediav2.cts.CodecResourceUtils.compareResources;
28 import static android.mediav2.cts.CodecResourceUtils.computeConsumption;
29 import static android.mediav2.cts.CodecResourceUtils.getCurrentGlobalCodecResources;
30 import static android.mediav2.cts.CodecResourceUtils.validateGetCodecResources;
31 
32 import android.media.MediaCodec;
33 import android.media.MediaCodecInfo;
34 import android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint;
35 import android.media.MediaExtractor;
36 import android.media.MediaFormat;
37 import android.mediav2.common.cts.CodecAsyncHandler;
38 import android.mediav2.common.cts.CodecDecoderTestBase;
39 import android.mediav2.common.cts.CodecDynamicTestActivity;
40 import android.mediav2.common.cts.OutputManager;
41 import android.platform.test.annotations.RequiresFlagsEnabled;
42 import android.util.Log;
43 import android.util.Pair;
44 import android.util.Range;
45 import android.util.Size;
46 import android.view.Surface;
47 
48 import androidx.annotation.NonNull;
49 import androidx.test.ext.junit.rules.ActivityScenarioRule;
50 import androidx.test.filters.LargeTest;
51 
52 import com.android.compatibility.common.util.ApiTest;
53 import com.android.compatibility.common.util.VsrTest;
54 
55 import org.junit.After;
56 import org.junit.Assert;
57 import org.junit.Assume;
58 import org.junit.Before;
59 import org.junit.Ignore;
60 import org.junit.Rule;
61 import org.junit.Test;
62 import org.junit.runner.RunWith;
63 import org.junit.runners.Parameterized;
64 
65 import java.io.File;
66 import java.io.IOException;
67 import java.nio.ByteBuffer;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Collection;
71 import java.util.List;
72 import java.util.Locale;
73 import java.util.Scanner;
74 import java.util.function.BiFunction;
75 import java.util.function.Function;
76 
77 /**
78  * Helper class for running mediacodec in asynchronous mode with resource tracking enabled. All
79  * mediacodec callback events are registered in this object so that the client can take
80  * appropriate action as desired.
81  */
82 @RequiresFlagsEnabled(FLAG_CODEC_AVAILABILITY)
83 class CodecAsyncHandlerResource extends CodecAsyncHandler {
84     private boolean mResourceChangeCbReceived;
85     private int mResourceChangeCbCount;
86 
CodecAsyncHandlerResource()87     public CodecAsyncHandlerResource() {
88         super();
89         mResourceChangeCbReceived = false;
90         mResourceChangeCbCount = 0;
91     }
92 
93     @Override
resetContext()94     public void resetContext() {
95         super.resetContext();
96         mResourceChangeCbReceived = false;
97         mResourceChangeCbCount = 0;
98     }
99 
100     @Override
onRequiredResourcesChanged(@onNull MediaCodec codec)101     public void onRequiredResourcesChanged(@NonNull MediaCodec codec) {
102         mResourceChangeCbReceived = true;
103         mResourceChangeCbCount++;
104     }
105 
hasRequiredResourceChangeCbReceived()106     public boolean hasRequiredResourceChangeCbReceived() {
107         return mResourceChangeCbReceived;
108     }
109 
getResourceChangeCbCount()110     public int getResourceChangeCbCount() {
111         return mResourceChangeCbCount;
112     }
113 }
114 
115 /**
116  * This class comprises of tests that validate codec resource availability apis for video decoders
117  */
118 @RunWith(Parameterized.class)
119 public class VideoDecoderAvailabilityTest extends CodecDecoderTestBase {
120     private static final String LOG_TAG = VideoDecoderAvailabilityTest.class.getSimpleName();
121     private static final String MEDIA_DIR = WorkDir.getMediaDirString();
122     // Minimum threshold for resource consumption of a codec for a given performance point.
123     // The test expects the resource consumption to be at least this value.
124     static final int MIN_UTILIZATION_THRESHOLD = 60;
125     private static List<CodecResource> GLOBAL_AVBL_RESOURCES;
126 
127     private final String[] mSrcFiles;
128 
129     private CodecDynamicTestActivity mDynamicActivity;
130 
131     @Rule
132     public ActivityScenarioRule<CodecDynamicTestActivity> mActivityRule =
133             new ActivityScenarioRule<>(CodecDynamicTestActivity.class);
134 
135     @After
VideoDecoderAvailabilityTestTearDown()136     public void VideoDecoderAvailabilityTestTearDown() {
137         if (mDynamicActivity != null) {
138             mDynamicActivity.finish();
139             mDynamicActivity = null;
140         }
141     }
142 
VideoDecoderAvailabilityTest(String decoder, String mediaType, String[] srcFiles, String allTestParams)143     public VideoDecoderAvailabilityTest(String decoder, String mediaType, String[] srcFiles,
144             String allTestParams) {
145         super(decoder, mediaType, null, allTestParams);
146         mSrcFiles = new String[srcFiles.length];
147         for (int i = 0; i < srcFiles.length; i++) {
148             mSrcFiles[i] = MEDIA_DIR + srcFiles[i];
149         }
150     }
151 
152     @Parameterized.Parameters(name = "{index}_{0}_{1}")
input()153     public static Collection<Object[]> input() {
154         final boolean isEncoder = false;
155         final boolean needAudio = false;
156         final boolean needVideo = true;
157         // mediaType, testClip
158         final List<Object[]> exhaustiveArgsList = new ArrayList<>(Arrays.asList(
159                 new Object[][]{
160                         {MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
161                                 "bbb_800x640_768kbps_30fps_avc_2b.mp4",
162                                 "bbb_800x640_768kbps_30fps_avc_nob.mp4",
163                                 "bbb_1280x720_1mbps_30fps_avc_2b.mp4",
164                                 "bbb_640x360_512kbps_30fps_avc_nob.mp4",
165                                 "bbb_1280x720_1mbps_30fps_avc_nob.mp4",
166                                 "bbb_640x360_512kbps_30fps_avc_2b.mp4",
167                                 "bbb_1280x720_1mbps_30fps_avc_nob.mp4",
168                                 "bbb_640x360_512kbps_30fps_avc_nob.mp4",
169                                 "bbb_640x360_512kbps_30fps_avc_2b.mp4"}},
170                         {MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{
171                                 "bbb_800x640_768kbps_30fps_hevc_2b.mp4",
172                                 "bbb_800x640_768kbps_30fps_hevc_nob.mp4",
173                                 "bbb_1280x720_1mbps_30fps_hevc_2b.mp4",
174                                 "bbb_640x360_512kbps_30fps_hevc_nob.mp4",
175                                 "bbb_1280x720_1mbps_30fps_hevc_nob.mp4",
176                                 "bbb_640x360_512kbps_30fps_hevc_2b.mp4",
177                                 "bbb_1280x720_1mbps_30fps_hevc_nob.mp4",
178                                 "bbb_640x360_512kbps_30fps_hevc_nob.mp4",
179                                 "bbb_640x360_512kbps_30fps_hevc_2b.mp4"}},
180                         {MediaFormat.MIMETYPE_VIDEO_VP8, new String[]{
181                                 "bbb_800x640_768kbps_30fps_vp8.webm",
182                                 "bbb_1280x720_1mbps_30fps_vp8.webm",
183                                 "bbb_640x360_512kbps_30fps_vp8.webm"}},
184                         {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{
185                                 "bbb_800x640_768kbps_30fps_vp9.webm",
186                                 "bbb_1280x720_1mbps_30fps_vp9.webm",
187                                 "bbb_640x360_512kbps_30fps_vp9.webm"}},
188                         {MediaFormat.MIMETYPE_VIDEO_MPEG4, new String[]{
189                                 "bbb_128x96_64kbps_12fps_mpeg4.mp4",
190                                 "bbb_176x144_192kbps_15fps_mpeg4.mp4",
191                                 "bbb_128x96_64kbps_12fps_mpeg4.mp4"}},
192                         {MediaFormat.MIMETYPE_VIDEO_AV1, new String[]{
193                                 "bbb_800x640_768kbps_30fps_av1.webm",
194                                 "bbb_1280x720_1mbps_30fps_av1.webm",
195                                 "bbb_640x360_512kbps_30fps_av1.webm"}},
196                         {MediaFormat.MIMETYPE_VIDEO_MPEG2, new String[]{
197                                 "bbb_800x640_768kbps_30fps_mpeg2_2b.mp4",
198                                 "bbb_800x640_768kbps_30fps_mpeg2_nob.mp4",
199                                 "bbb_1280x720_1mbps_30fps_mpeg2_2b.mp4",
200                                 "bbb_640x360_512kbps_30fps_mpeg2_nob.mp4",
201                                 "bbb_1280x720_1mbps_30fps_mpeg2_nob.mp4",
202                                 "bbb_640x360_512kbps_30fps_mpeg2_2b.mp4",
203                                 "bbb_1280x720_1mbps_30fps_mpeg2_nob.mp4",
204                                 "bbb_640x360_512kbps_30fps_mpeg2_nob.mp4",
205                                 "bbb_640x360_512kbps_30fps_mpeg2_2b.mp4"}},
206                 }));
207         return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, false,
208                 ComponentClass.HARDWARE);
209     }
210 
211     @Before
prerequisite()212     public void prerequisite() {
213         Assume.assumeTrue("Skipping! Requires devices with board_first_sdk >= 202504",
214                 BOARD_FIRST_SDK_IS_AT_LEAST_202504);
215         Assume.assumeTrue("requires codec availability api support", codecAvailability());
216         Assume.assumeTrue("requires codec availability api implementation",
217                 codecAvailabilitySupport());
218         GLOBAL_AVBL_RESOURCES = getCurrentGlobalCodecResources();
219     }
220 
221     @Override
dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)222     protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
223         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
224             mSawOutputEOS = true;
225         }
226         if (ENABLE_LOGS) {
227             Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " +
228                     info.size + " timestamp: " + info.presentationTimeUs);
229         }
230         if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
231             mOutputBuff.saveOutPTS(info.presentationTimeUs);
232             mOutputCount++;
233         }
234         mCodec.releaseOutputBuffer(bufferIndex, mSurface != null);
235     }
236 
237     /**
238      * Briefly, this test verifies the functionality of media codec apis getRequiredResources()
239      * and onRequiredResourcesChanged() at various codec states.
240      * <p>
241      * getRequiredResources() is expected to return illegal state exception in uninitialized
242      * state and resources required for current codec configuration in executing state. The test
243      * tries this api at various codec states and expects,
244      * <ul>
245      *     <li>Illegal state exception or, </li>
246      *     <li>Resources required for current instance </li>
247      * </ul>
248      * The test verifies if the globally available resources at any state is in agreement with
249      * the codec operational consumption resources. In other words, at any given time, current
250      * global available resources + current instance codec resources equals global available
251      * resources at the start of the test.
252      * <p>
253      * In the executing state, the codec shall update the required resources status via
254      * callback onRequiredResourcesChanged(). This is also verified.
255      */
256     @LargeTest
257     @VsrTest(requirements = {"VSR-4.1-002"})
258     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
259     @RequiresFlagsEnabled(FLAG_CODEC_AVAILABILITY)
260     @ApiTest(apis = {"android.media.MediaCodec#getGloballyAvailableResources",
261             "android.media.MediaCodec#getRequiredResources",
262             "android.media.MediaCodec.Callback#onRequiredResourcesChanged"})
testSimpleDecode()263     public void testSimpleDecode() throws IOException, InterruptedException {
264         CodecAsyncHandlerResource asyncHandleResource = new CodecAsyncHandlerResource();
265         mAsyncHandle = asyncHandleResource;
266         mCodec = MediaCodec.createByCodecName(mCodecName);
267         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.UNINITIALIZED)),
268                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
269                         "getRequiredResources() succeeded in %s state \n", CodecState.UNINITIALIZED)
270                         + mTestEnv + mTestConfig);
271         MediaFormat format = setUpSource(mSrcFiles[0]);
272         List<MediaFormat> formats = new ArrayList<>();
273         formats.add(format);
274         Assume.assumeTrue("Codec: " + mCodecName + " doesn't support format: " + format,
275                 areFormatsSupported(mCodecName, mMediaType, formats));
276         mOutputBuff = new OutputManager();
277         configureCodec(format, true, true, false);
278         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.CONFIGURED)),
279                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
280                         "getRequiredResources() failed in %s state \n", CodecState.CONFIGURED)
281                         + mTestEnv + mTestConfig);
282         mCodec.start();
283         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.RUNNING)),
284                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
285                         "getRequiredResources() failed in %s state \n", CodecState.RUNNING)
286                         + mTestEnv + mTestConfig);
287         doWork(10);
288         queueEOS();
289         waitForAllOutputs();
290         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.EOS)),
291                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
292                         "getRequiredResources() failed in %s state \n", CodecState.EOS)
293                         + mTestEnv + mTestConfig);
294         mCodec.flush();
295         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.FLUSHED)),
296                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
297                         "getRequiredResources() failed in %s state \n", CodecState.FLUSHED)
298                         + mTestEnv + mTestConfig);
299         mCodec.start();
300         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
301         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.RUNNING)),
302                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
303                         "getRequiredResources() failed in %s state \n", CodecState.RUNNING)
304                         + mTestEnv + mTestConfig);
305         doWork(5);
306         queueEOS();
307         waitForAllOutputs();
308         Assert.assertTrue("did not receive callback onRequiredResourcesChanged() from"
309                         + " codec\n" + mTestEnv + mTestConfig,
310                 asyncHandleResource.hasRequiredResourceChangeCbReceived());
311         mCodec.stop();
312         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.STOPPED)),
313                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
314                         "getRequiredResources() succeeded in %s state \n", CodecState.STOPPED)
315                         + mTestEnv + mTestConfig);
316         mCodec.reset();
317         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.UNINITIALIZED)),
318                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
319                         "getRequiredResources() succeeded in %s state \n", CodecState.UNINITIALIZED)
320                         + mTestEnv + mTestConfig);
321         mCodec.release();
322         validateGetCodecResources(List.of(Pair.create(mCodec, CodecState.RELEASED)),
323                 GLOBAL_AVBL_RESOURCES, String.format(Locale.getDefault(),
324                         "getRequiredResources() succeeded in %s state \n", CodecState.RELEASED)
325                         + mTestEnv + mTestConfig);
326     }
327 
estimateVideoSizeFromPerformancePoint(PerformancePoint pp)328     static Size estimateVideoSizeFromPerformancePoint(PerformancePoint pp) {
329         final Size SUBQCIF = new Size(128, 96);
330         final Size QCIF = new Size(176, 144);
331         final Size SD144P = new Size(256, 144);
332         final Size CIFNTSC = new Size(352, 240);
333         final Size CIF = new Size(352, 288);
334         final Size QVGA = new Size(320, 240);
335         final Size SD240P = new Size(426, 240);
336         final Size SD360P = new Size(640, 360);
337         final Size VGA = new Size(640, 480);
338         final Size SDNTSC = new Size(720, 480);
339         final Size SDPAL = new Size(720, 576);
340         final Size WVGA = new Size(800, 480);
341         final Size SD480P = new Size(854, 480);
342         final Size HD = new Size(1280, 720);
343         final Size HDPAL = new Size(1440, 1080);
344         final Size FULLHD = new Size(1920, 1080);
345         final Size FULLHD_ALT = new Size(1920, 1088);
346         final Size UHD1440P = new Size(2560, 1440);
347         final Size UHD = new Size(3840, 2160);
348         final Size DC4K = new Size(4096, 2160);
349         final Size UHD8K = new Size(7680, 4320);
350         final Size[] STANDARD_RES =
351                 {SUBQCIF, QCIF, SD144P, CIFNTSC, CIF, QVGA, SD240P, SD360P, VGA, SDNTSC, SDPAL,
352                         WVGA, SD480P, HD, HDPAL, FULLHD, FULLHD_ALT, UHD1440P, UHD, DC4K, UHD8K};
353         Size maxSupportedSize = null;
354         long maxResolution = 0;
355         for (Size size : STANDARD_RES) {
356             if (pp.covers(new PerformancePoint(size.getWidth(), size.getHeight(),
357                     pp.getMaxFrameRate()))) {
358                 long resolution = (long) size.getWidth() * size.getHeight();
359                 if (resolution > maxResolution) {
360                     maxResolution = resolution;
361                     maxSupportedSize = size;
362                 }
363             }
364         }
365         if (maxSupportedSize != null) {
366             return maxSupportedSize;
367         }
368         // if look up is not successful, rely on string parsing to get the desired info
369         String info = pp.toString();
370         Scanner scanner = new Scanner(info);
371         scanner.useDelimiter("[\\(x@]");
372         scanner.next(); // skip "PerformancePoint(" part
373         int width = scanner.nextInt();
374         int height = scanner.nextInt();
375         return new Size(width, height);
376     }
377 
378     static class VideoConfig {
379         int mWidth;
380         int mHeight;
381         int mMaxFrameRate;
382 
VideoConfig(int width, int height, int maxFrameRate)383         VideoConfig(int width, int height, int maxFrameRate) {
384             mWidth = width;
385             mHeight = height;
386             mMaxFrameRate = maxFrameRate;
387         }
388     }
389 
getFormatsCoveringMaxFrameRate(String mediaType, int width, int height, int frameRate, int maxFrameRate)390     private List<MediaFormat> getFormatsCoveringMaxFrameRate(String mediaType, int width,
391             int height, int frameRate, int maxFrameRate) {
392         List<MediaFormat> formats = new ArrayList<>();
393         int frameRateOffset = 0;
394         do {
395             int currFrameRate = Math.min(frameRate, maxFrameRate - frameRateOffset);
396             MediaFormat format = MediaFormat.createVideoFormat(mediaType, width, height);
397             format.setInteger(MediaFormat.KEY_FRAME_RATE, currFrameRate);
398             format.setInteger(MediaFormat.KEY_PRIORITY, 0);
399             frameRateOffset += currFrameRate;
400             formats.add(format);
401         } while (frameRateOffset < maxFrameRate);
402         return formats;
403     }
404 
getCodecTestFormatList(String codecName, String mediaType, BiFunction<Size, Integer, List<T>> formatGenerator)405     public static <T> List<List<T>> getCodecTestFormatList(String codecName, String mediaType,
406             BiFunction<Size, Integer, List<T>> formatGenerator) {
407         MediaCodecInfo.CodecCapabilities caps = getCodecCapabilities(codecName, mediaType);
408         Assert.assertNotNull("received null capabilities for codec : " + codecName, caps);
409         MediaCodecInfo.VideoCapabilities vcaps = caps.getVideoCapabilities();
410         Assert.assertNotNull("received null video capabilities for codec : " + codecName, vcaps);
411         List<PerformancePoint> pps = vcaps.getSupportedPerformancePoints();
412         if (pps == null || pps.isEmpty()) {
413             Log.d(LOG_TAG, codecName + " did not advertise any performance points");
414             return null;
415         }
416         List<List<T>> testableFormats = new ArrayList<>();
417         for (PerformancePoint pp : pps) {
418             Size videoSize = estimateVideoSizeFromPerformancePoint(pp);
419             int maxFrameRate = pp.getMaxFrameRate();
420             testableFormats.add(formatGenerator.apply(videoSize, maxFrameRate));
421         }
422         return testableFormats;
423     }
424 
getCodecTestFormatListNoPerfPoints(String codecName, String mediaType, Function<VideoConfig, List<T>> formatGenerator)425     public static <T> List<List<T>> getCodecTestFormatListNoPerfPoints(String codecName,
426             String mediaType, Function<VideoConfig, List<T>> formatGenerator) {
427         MediaCodecInfo.CodecCapabilities caps = getCodecCapabilities(codecName, mediaType);
428         Assert.assertNotNull("received null capabilities for codec : " + codecName, caps);
429         MediaCodecInfo.VideoCapabilities vcaps = caps.getVideoCapabilities();
430         Assert.assertNotNull("received null video capabilities for codec : " + codecName, vcaps);
431         int width = vcaps.getSupportedWidths().getUpper();
432         int height = vcaps.getSupportedHeightsFor(width).getUpper();
433         Range<Double> frameRates = vcaps.getAchievableFrameRatesFor(width, height);
434         if (frameRates == null) {
435             Log.d(LOG_TAG, String.format(Locale.getDefault(),
436                     "%s did not advertise any achievable frame rates for %dx%d", codecName, width,
437                     height));
438             return null;
439         }
440         List<List<T>> testableFormats = new ArrayList<>();
441         int maxFrameRate = (int) Math.floor(frameRates.getUpper());
442         testableFormats.add(formatGenerator.apply(new VideoConfig(width, height, maxFrameRate)));
443         return testableFormats;
444     }
445 
getCodecTestFormatList(String codecName, String mediaType)446     private List<List<MediaFormat>> getCodecTestFormatList(String codecName, String mediaType) {
447         return getCodecTestFormatList(codecName, mediaType,
448                 (videoSize, maxFrameRate) -> getFormatsCoveringMaxFrameRate(mediaType,
449                         videoSize.getWidth(), videoSize.getHeight(), 30, maxFrameRate));
450     }
451 
getCodecTestFormatListNoPerfPoints(String codecName, String mediaType)452     private List<List<MediaFormat>> getCodecTestFormatListNoPerfPoints(String codecName,
453             String mediaType) {
454         return getCodecTestFormatListNoPerfPoints(codecName, mediaType,
455                 config -> getFormatsCoveringMaxFrameRate(mediaType, config.mWidth, config.mHeight,
456                         30, config.mMaxFrameRate));
457     }
458 
validateMaxInstances(String codecName, String mediaType)459     private void validateMaxInstances(String codecName, String mediaType) {
460         List<List<MediaFormat>> testableFormats =
461                 getCodecTestFormatList(codecName, mediaType);
462         if (testableFormats == null) {
463             testableFormats = getCodecTestFormatListNoPerfPoints(codecName, mediaType);
464         }
465         Assume.assumeNotNull("formats to configure codec unavailable", testableFormats);
466         List<MediaCodec> codecs = new ArrayList<>();
467         List<Pair<Integer, Surface>> surfaces = new ArrayList<>();
468         MediaCodec codec = null;
469         int numInstances;
470         List<CodecResource> lastGlobalResources;
471         List<CodecResource> currentGlobalResources;
472         MediaFormat lastFormat = null;
473         MediaFormat currentFormat = null;
474         List<CodecResource> lastGlobalResourceForFormat = null;
475         List<CodecResource> currentGlobalResourcesForFormat = null;
476         StringBuilder testLogs = new StringBuilder();
477         int maxInstances = getMaxCodecInstances(codecName, mediaType);
478         for (List<MediaFormat> formats : testableFormats) {
479             numInstances = 0;
480             lastGlobalResources = getCurrentGlobalCodecResources();
481             while (numInstances < maxInstances) {
482                 Pair<Integer, Surface> obj = null;
483                 try {
484                     obj = mDynamicActivity.getSurface();
485                     if (obj == null) {
486                         int index = mDynamicActivity.addSurfaceView();
487                         mDynamicActivity.waitTillSurfaceIsCreated(index);
488                         obj = mDynamicActivity.getSurface();
489                     }
490                     codec = MediaCodec.createByCodecName(codecName);
491                     MediaFormat configFormat =
492                             numInstances < formats.size() ? formats.get(numInstances) :
493                                     formats.get(0);
494                     codec.configure(configFormat, obj.second, null, 0);
495                     codec.start();
496                     codecs.add(codec);
497                     surfaces.add(obj);
498                     numInstances++;
499                     codec = null;
500                     obj = null;
501                     currentGlobalResources = getCurrentGlobalCodecResources();
502                     int result = compareResources(lastGlobalResources, currentGlobalResources,
503                             testLogs);
504                     Assert.assertEquals("creating new instance did not reduce resources"
505                                     + " available \n" + testLogs + mTestEnv + mTestConfig,
506                             LHS_RESOURCE_GE, result);
507                     lastGlobalResources = currentGlobalResources;
508                     if (numInstances == 1) {
509                         currentGlobalResourcesForFormat = currentGlobalResources;
510                         currentFormat = configFormat;
511                     }
512                 } catch (InterruptedException e) {
513                     Assert.fail("Got unexpected InterruptedException " + e.getMessage());
514                 } catch (IllegalArgumentException e) {
515                     Assert.fail("Got unexpected IllegalArgumentException " + e.getMessage());
516                 } catch (IOException e) {
517                     Assert.fail("Got unexpected IOException " + e.getMessage());
518                 } catch (MediaCodec.CodecException e) {
519                     // ERROR_INSUFFICIENT_RESOURCE is expected as the test keep creating codecs.
520                     // But other exception should be treated as failure.
521                     if (e.getErrorCode() == MediaCodec.CodecException.ERROR_INSUFFICIENT_RESOURCE) {
522                         Log.d(LOG_TAG, "Got CodecException with ERROR_INSUFFICIENT_RESOURCE.");
523                         break;
524                     } else {
525                         Assert.fail("Unexpected CodecException " + e.getDiagnosticInfo());
526                     }
527                 } finally {
528                     if (codec != null) {
529                         Log.d(LOG_TAG, "release codec");
530                         codec.release();
531                         codec = null;
532                     }
533                     if (obj != null) {
534                         mDynamicActivity.markSurface(obj.first, true);
535                     }
536                 }
537             }
538             for (int i = 0; i < codecs.size(); ++i) {
539                 Log.d(LOG_TAG, "release codec #" + i);
540                 lastGlobalResources = getCurrentGlobalCodecResources();
541                 codecs.get(i).stop();
542                 codecs.get(i).release();
543                 List<CodecResource> currGlobalResources = getCurrentGlobalCodecResources();
544                 int result = compareResources(lastGlobalResources, currGlobalResources, testLogs);
545                 Assert.assertEquals("releasing a codec instance did not increase resources"
546                                 + " available \n" + testLogs + mTestEnv + mTestConfig,
547                         RHS_RESOURCE_GE, result);
548             }
549             for (int i = 0; i < surfaces.size(); ++i) {
550                 Log.d(LOG_TAG, "mark surface usable #" + i);
551                 mDynamicActivity.markSurface(surfaces.get(i).first, true);
552             }
553             surfaces.clear();
554             codecs.clear();
555             if (lastGlobalResourceForFormat != null) {
556                 int result = compareResources(lastGlobalResourceForFormat,
557                         currentGlobalResourcesForFormat, testLogs);
558                 Assert.assertEquals("format : " + (lastFormat == null ? "empty" : lastFormat)
559                         + " is expected to consume more resources than format : " + currentFormat
560                         + testLogs + mTestEnv + mTestConfig, RHS_RESOURCE_GE, result);
561             }
562             lastGlobalResourceForFormat = currentGlobalResourcesForFormat;
563             lastFormat = currentFormat;
564         }
565     }
566 
567     /**
568      * For a given codec name and media type, the current test sequentially instantiates
569      * component instances until the codec exception ERROR_INSUFFICIENT_RESOURCE is raised. Prior
570      * to each instantiation, the test records the current global resources. After a successful
571      * instantiation, the test again records the global resources and compares the new value with
572      * the previous one. The expectation is that the current value MUST be less than or equal to
573      * the previous value.
574      * <p>
575      * During the teardown phase, the test releases components one by one and expects the current
576      * global resources to be greater than or equal to the last recorded reference value.
577      */
578     @LargeTest
579     @VsrTest(requirements = {"VSR-4.1-002"})
580     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
581     @RequiresFlagsEnabled(FLAG_CODEC_AVAILABILITY)
582     @ApiTest(apis = {"android.media.MediaCodec#getGloballyAvailableResources",
583             "android.media.MediaCodec#getRequiredResources",
584             "android.media.MediaCodec.Callback#onRequiredResourcesChanged"})
585     public void testConcurrentMaxInstances() {
586         mActivityRule.getScenario().onActivity(activity -> mDynamicActivity = activity);
587         validateMaxInstances(mCodecName, mMediaType);
588     }
589 
590     /**
591      * During adaptive playback, as the resolution changes, the resources required/consumed will
592      * be different. The test expects if the updated resource requirements are indicated to
593      * client via callbacks.
594      */
595     @LargeTest
596     @VsrTest(requirements = {"VSR-4.1-002"})
597     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
598     @Ignore("Skipped for 25Q2 release")
599     @RequiresFlagsEnabled(FLAG_CODEC_AVAILABILITY)
600     @ApiTest(apis = {"android.media.MediaCodec#getGloballyAvailableResources",
601             "android.media.MediaCodec#getRequiredResources",
602             "android.media.MediaCodec.Callback#onRequiredResourcesChanged"})
603     public void testAdaptivePlayback() throws IOException, InterruptedException {
604         boolean hasSupport = isFeatureSupported(mCodecName, mMediaType,
605                 MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback);
606         Assume.assumeTrue("codec: " + mCodecName + " does not support FEATURE_AdaptivePlayback",
607                 hasSupport);
608         List<String> resFiles = getSupportedFiles(mSrcFiles, mCodecName, mMediaType);
609         Assume.assumeTrue("none of the given test clips are supported by the codec: "
610                 + mCodecName, !resFiles.isEmpty());
611         mActivityRule.getScenario().onActivity(activity -> mDynamicActivity = activity);
612         Pair<Integer, Surface> obj = mDynamicActivity.getSurface();
613         if (obj == null) {
614             int index = mDynamicActivity.addSurfaceView();
615             mDynamicActivity.waitTillSurfaceIsCreated(index);
616             obj = mDynamicActivity.getSurface();
617             mSurface = obj.second;
618         }
619         CodecAsyncHandlerResource asyncHandleResource = new CodecAsyncHandlerResource();
620         mAsyncHandle = asyncHandleResource;
621         ArrayList<MediaFormat> formats = new ArrayList<>();
622         int totalSize = 0;
623         for (String resFile : resFiles) {
624             File file = new File(resFile);
625             totalSize += (int) file.length();
626         }
627         long ptsOffset = 0;
628         int buffOffset = 0;
629         ArrayList<MediaCodec.BufferInfo> list = new ArrayList<>();
630         ByteBuffer buffer = ByteBuffer.allocate(totalSize);
631         for (String file : resFiles) {
632             Pair<MediaFormat, Long> metadata =
633                     createInputList(file, mMediaType, buffer, list, buffOffset, ptsOffset);
634             formats.add(metadata.first);
635             ptsOffset = metadata.second + 1000000L;
636             buffOffset = (list.get(list.size() - 1).offset) + (list.get(list.size() - 1).size);
637         }
638         mOutputBuff = new OutputManager();
639         mCodec = MediaCodec.createByCodecName(mCodecName);
640         MediaFormat format = formats.get(0);
641         mOutputBuff.reset();
642         configureCodec(format, true, false, false);
643         mCodec.start();
644         doWork(buffer, list);
645         queueEOS();
646         waitForAllOutputs();
647         mCodec.stop();
648         mCodec.release();
649         if (obj != null) {
650             mDynamicActivity.markSurface(obj.first, true);
651         }
652         if (asyncHandleResource.getResourceChangeCbCount() < resFiles.size()) {
653             Assert.fail(String.format("number of resource change callbacks received is less than"
654                             + " number of files tried in apb test. exp >= %d, got %d \n",
655                     resFiles.size(),
656                     asyncHandleResource.getResourceChangeCbCount()) + mTestEnv + mTestConfig);
657         }
658     }
659 
660     /**
661      * Tests the resource consumption of a codec for various advertised performance points.
662      * This test iterates through the supported performance points of a given codec,
663      * configures the codec with a video format corresponding to each performance point,
664      * starts the codec, and measures the resource consumption. It checks if the consumption
665      * meets a minimum threshold.
666      */
667     @LargeTest
668     @VsrTest(requirements = {"VSR-4.1-002"})
669     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
670     @RequiresFlagsEnabled(FLAG_CODEC_AVAILABILITY)
671     @ApiTest(apis = {"android.media.MediaCodec#getGloballyAvailableResources",
672             "android.media.MediaCodec#getRequiredResources",
673             "android.media.MediaCodec.Callback#onRequiredResourcesChanged"})
674     public void testResourceConsumptionForPerfPoints() throws IOException, InterruptedException {
675         List<CodecResource> globalResources = getCurrentGlobalCodecResources();
676         MediaCodecInfo.CodecCapabilities caps = getCodecCapabilities(mCodecName, mMediaType);
677         Assert.assertNotNull("received null capabilities for codec : " + mCodecName, caps);
678         MediaCodecInfo.VideoCapabilities vcaps = caps.getVideoCapabilities();
679         Assert.assertNotNull("received null video capabilities for codec : " + mCodecName, vcaps);
680         List<PerformancePoint> pps = vcaps.getSupportedPerformancePoints();
681         Assume.assumeFalse(mCodecName + " did not advertise any performance points",
682                 pps == null || pps.isEmpty());
683         mActivityRule.getScenario().onActivity(activity -> mDynamicActivity = activity);
684         for (PerformancePoint pp : pps) {
685             Pair<Integer, Surface> obj = mDynamicActivity.getSurface();
686             if (obj == null) {
687                 int index = mDynamicActivity.addSurfaceView();
688                 mDynamicActivity.waitTillSurfaceIsCreated(index);
689                 obj = mDynamicActivity.getSurface();
690             }
691             MediaCodec codec = MediaCodec.createByCodecName(mCodecName);
692             Size videoSize = estimateVideoSizeFromPerformancePoint(pp);
693             MediaFormat format = MediaFormat.createVideoFormat(mMediaType, videoSize.getWidth(),
694                     videoSize.getHeight());
695             format.setInteger(MediaFormat.KEY_FRAME_RATE, pp.getMaxFrameRate());
696             format.setInteger(MediaFormat.KEY_PRIORITY, 0);
697             codec.configure(format, obj.second, null, 0);
698             codec.start();
699             List<CodecResource> usedResources = getCurrentGlobalCodecResources();
700             double consumption = computeConsumption(globalResources, usedResources);
701             codec.stop();
702             codec.release();
703             mDynamicActivity.markSurface(obj.first, true);
704             if (consumption < MIN_UTILIZATION_THRESHOLD) {
705                 Assert.fail("For performance point " + pp + " and codec : " + mCodecName
706                         + " max resources consumed is expected to be at least "
707                         + MIN_UTILIZATION_THRESHOLD + "% but got " + consumption + "%");
708             }
709         }
710     }
711 }
712