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