1 /* 2 * Copyright (C) 2022 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.videocodec.cts; 18 19 import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR; 20 import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR; 21 import static android.mediav2.common.cts.CodecTestBase.ComponentClass.HARDWARE; 22 import static android.mediav2.common.cts.CodecTestBase.VNDK_IS_BEFORE_U; 23 import static android.videocodec.cts.VideoEncoderInput.getRawResource; 24 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assume.assumeTrue; 29 30 import android.media.MediaCodecInfo; 31 import android.media.MediaFormat; 32 import android.mediav2.common.cts.CompareStreams; 33 import android.mediav2.common.cts.EncoderConfigParams; 34 import android.mediav2.common.cts.RawResource; 35 36 import com.android.compatibility.common.util.ApiTest; 37 38 import org.junit.Before; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 import org.junit.runners.Parameterized; 42 43 import java.io.IOException; 44 import java.util.ArrayList; 45 import java.util.Collection; 46 import java.util.List; 47 48 /** 49 * 1. MinMaxResolutionsTest should query the ranges of supported width and height using 50 * MediaCodecInfo.VideoCapabilities, test the min resolution and the max resolution of the encoder. 51 * <p> Test Params: Input configuration = Resolution: min/max, frame rate: 30, bitrate: choose 52 * basing on resolution </p> 53 * 2. MinMaxBitrateTest should query the range of the supported bitrates, and test min/max of them. 54 * <p> Test Params: Input configuration = Resolution: choose basing on bitrate, frame rate: 30, 55 * bitrate: min/max </p> 56 * 3. MinMaxFrameRatesTest should query the range of the supported frame rates, and test min/max 57 * of them. 58 * <p> Test Params: Input configuration = Resolution: 720p, frame rate: min/max, bitrate: 5mbps 59 * </p> 60 * All tests should run for following combinations: 61 * <p>Bitrate mode = CBR/VBR, MaxBFrames = 0/1, Codec type = AVC/HEVC, Intra frame interval = 0/1 62 * second</p> 63 */ 64 @RunWith(Parameterized.class) 65 public class VideoEncoderMinMaxTest extends VideoEncoderValidationTestBase { 66 private static final float MIN_ACCEPTABLE_QUALITY = 20.0f; // psnr in dB 67 private static final int FRAME_LIMIT = 300; 68 private static final int TARGET_WIDTH = 1280; 69 private static final int TARGET_HEIGHT = 720; 70 private static final int TARGET_FRAME_RATE = 30; 71 private static final int TARGET_BIT_RATE = 5000000; 72 private static final List<Object[]> exhaustiveArgsList = new ArrayList<>(); 73 getVideoEncoderCfgParams(String mediaType, int bitRateMode, int maxBFrames, int intraInterval)74 private static EncoderConfigParams getVideoEncoderCfgParams(String mediaType, int bitRateMode, 75 int maxBFrames, int intraInterval) { 76 return new EncoderConfigParams.Builder(mediaType) 77 .setWidth(TARGET_WIDTH) 78 .setHeight(TARGET_HEIGHT) 79 .setBitRate(TARGET_BIT_RATE) 80 .setBitRateMode(bitRateMode) 81 .setMaxBFrames(maxBFrames) 82 .setKeyFrameInterval(intraInterval) 83 .setFrameRate(TARGET_FRAME_RATE) 84 .build(); 85 } 86 addParams()87 private static void addParams() { 88 final String[] mediaTypes = new String[]{MediaFormat.MIMETYPE_VIDEO_AVC, 89 MediaFormat.MIMETYPE_VIDEO_HEVC}; 90 final int[] maxBFramesPerSubGop = new int[]{0, 1}; 91 final int[] intraIntervals = new int[]{0, 1}; 92 final int[] bitRateModes = new int[]{BITRATE_MODE_CBR, BITRATE_MODE_VBR}; 93 for (String mediaType : mediaTypes) { 94 for (int maxBFrames : maxBFramesPerSubGop) { 95 for (int intraInterval : intraIntervals) { 96 for (int bitRateMode : bitRateModes) { 97 // mediaType, cfg 98 exhaustiveArgsList.add( 99 new Object[]{mediaType, getVideoEncoderCfgParams(mediaType, 100 bitRateMode, maxBFrames, intraInterval)}); 101 } 102 } 103 } 104 } 105 } 106 applyMinMaxRanges(MediaCodecInfo.VideoCapabilities caps, Object cfgObject)107 private static List<Object> applyMinMaxRanges(MediaCodecInfo.VideoCapabilities caps, 108 Object cfgObject) throws CloneNotSupportedException { 109 List<Object> cfgObjects = new ArrayList<>(); 110 EncoderConfigParams cfgParam = (EncoderConfigParams) cfgObject; 111 int minW = caps.getSupportedWidths().getLower(); 112 int minHForMinW = caps.getSupportedHeightsFor(minW).getLower(); 113 int maxHForMinW = caps.getSupportedHeightsFor(minW).getUpper(); 114 int minH = caps.getSupportedHeights().getLower(); 115 int minWForMinH = caps.getSupportedWidthsFor(minH).getLower(); 116 int maxWForMinH = caps.getSupportedWidthsFor(minH).getUpper(); 117 int maxW = caps.getSupportedWidths().getUpper(); 118 int minHForMaxW = caps.getSupportedHeightsFor(maxW).getLower(); 119 int maxHForMaxW = caps.getSupportedHeightsFor(maxW).getUpper(); 120 int maxH = caps.getSupportedHeights().getUpper(); 121 int minWForMaxH = caps.getSupportedWidthsFor(maxH).getLower(); 122 int maxWForMaxH = caps.getSupportedWidthsFor(maxH).getUpper(); 123 int minBitRate = caps.getBitrateRange().getLower(); 124 int maxBitRate = caps.getBitrateRange().getUpper(); 125 126 // min max res & bitrate tests 127 android.util.Range<Double> rates = caps.getSupportedFrameRatesFor(minW, minHForMinW); 128 int frameRate = rates.clamp((double) TARGET_FRAME_RATE).intValue(); 129 cfgObjects.add(cfgParam.getBuilder().setWidth(minW).setHeight(minHForMinW) 130 .setFrameRate(frameRate).setBitRate(minBitRate).build()); 131 rates = caps.getSupportedFrameRatesFor(maxW, maxHForMaxW); 132 frameRate = rates.clamp((double) TARGET_FRAME_RATE).intValue(); 133 cfgObjects.add(cfgParam.getBuilder().setWidth(maxW).setHeight(maxHForMaxW) 134 .setFrameRate(frameRate).setBitRate(maxBitRate).build()); 135 int bitrate; 136 if (minW != minWForMinH || minH != minHForMinW) { 137 rates = caps.getSupportedFrameRatesFor(minWForMinH, minH); 138 frameRate = rates.clamp((double) TARGET_FRAME_RATE).intValue(); 139 bitrate = caps.getBitrateRange().clamp((int) (maxBitRate / Math.sqrt( 140 (double) maxW * maxHForMaxW / minWForMinH / minH))); 141 cfgObjects.add(cfgParam.getBuilder().setWidth(minWForMinH).setHeight(minH) 142 .setFrameRate(frameRate).setBitRate(bitrate).build()); 143 } 144 if (maxW != maxWForMaxH || maxH != maxHForMaxW) { 145 rates = caps.getSupportedFrameRatesFor(maxWForMaxH, maxH); 146 frameRate = rates.clamp((double) TARGET_FRAME_RATE).intValue(); 147 bitrate = caps.getBitrateRange().clamp((int) (maxBitRate / Math.sqrt( 148 (double) maxW * maxHForMaxW / maxWForMaxH / maxH))); 149 cfgObjects.add(cfgParam.getBuilder().setWidth(maxWForMaxH).setHeight(maxH) 150 .setFrameRate(frameRate).setBitRate(bitrate).build()); 151 } 152 153 rates = caps.getSupportedFrameRatesFor(minW, maxHForMinW); 154 frameRate = rates.clamp((double) TARGET_FRAME_RATE).intValue(); 155 bitrate = caps.getBitrateRange().clamp((int) (maxBitRate / Math.sqrt( 156 (double) maxW * maxHForMaxW / minW / maxHForMinW))); 157 cfgObjects.add(cfgParam.getBuilder().setWidth(minW).setHeight(maxHForMinW) 158 .setFrameRate(frameRate).setBitRate(bitrate).build()); 159 160 rates = caps.getSupportedFrameRatesFor(maxWForMinH, minH); 161 frameRate = rates.clamp((double) TARGET_FRAME_RATE).intValue(); 162 bitrate = caps.getBitrateRange().clamp((int) (maxBitRate / Math.sqrt( 163 (double) maxW * maxHForMaxW / maxWForMinH / minH))); 164 cfgObjects.add(cfgParam.getBuilder().setWidth(maxWForMinH).setHeight(minH) 165 .setFrameRate(frameRate).setBitRate(bitrate).build()); 166 if (maxW != maxWForMinH || minH != minHForMaxW) { 167 rates = caps.getSupportedFrameRatesFor(maxW, minHForMaxW); 168 frameRate = rates.clamp((double) TARGET_FRAME_RATE).intValue(); 169 bitrate = caps.getBitrateRange().clamp((int) (maxBitRate / Math.sqrt( 170 (double) maxW * maxHForMaxW / maxW / minHForMaxW))); 171 cfgObjects.add(cfgParam.getBuilder().setWidth(maxW).setHeight(minHForMaxW) 172 .setFrameRate(frameRate).setBitRate(bitrate).build()); 173 } 174 if (minW != minWForMaxH || maxH != maxHForMinW) { 175 rates = caps.getSupportedFrameRatesFor(minWForMaxH, maxH); 176 frameRate = rates.clamp((double) TARGET_FRAME_RATE).intValue(); 177 bitrate = caps.getBitrateRange().clamp((int) (maxBitRate / Math.sqrt( 178 (double) maxW * maxHForMaxW / minWForMaxH / maxH))); 179 cfgObjects.add(cfgParam.getBuilder().setWidth(minWForMaxH).setHeight(maxH) 180 .setFrameRate(frameRate).setBitRate(bitrate).build()); 181 } 182 183 // min-max frame rate tests 184 try { 185 int minFps = caps.getSupportedFrameRatesFor(TARGET_WIDTH, TARGET_HEIGHT).getLower() 186 .intValue(); 187 cfgObjects.add(cfgParam.getBuilder().setFrameRate(minFps).build()); 188 } catch (IllegalArgumentException ignored) { 189 } 190 try { 191 int maxFps = caps.getSupportedFrameRatesFor(TARGET_WIDTH, TARGET_HEIGHT).getUpper() 192 .intValue(); 193 cfgObjects.add(cfgParam.getBuilder().setFrameRate(maxFps).build()); 194 } catch (IllegalArgumentException ignored) { 195 } 196 197 return cfgObjects; 198 } 199 getCodecInfo(String codecName, String mediaType)200 private static MediaCodecInfo getCodecInfo(String codecName, String mediaType) { 201 for (MediaCodecInfo info : MEDIA_CODEC_LIST_REGULAR.getCodecInfos()) { 202 if (info.getName().equals(codecName)) { 203 for (String type : info.getSupportedTypes()) { 204 if (mediaType.equals(type)) { 205 return info; 206 } 207 } 208 } 209 } 210 return null; 211 } 212 getMinMaxRangeCfgObjects(Object codecName, Object mediaType, Object cfgObject)213 private static List<Object> getMinMaxRangeCfgObjects(Object codecName, Object mediaType, 214 Object cfgObject) throws CloneNotSupportedException { 215 MediaCodecInfo info = getCodecInfo((String) codecName, (String) mediaType); 216 MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType((String) mediaType); 217 return applyMinMaxRanges(caps.getVideoCapabilities(), cfgObject); 218 } 219 updateParamList(Collection<Object[]> paramList)220 private static Collection<Object[]> updateParamList(Collection<Object[]> paramList) 221 throws CloneNotSupportedException { 222 Collection<Object[]> newParamList = new ArrayList<>(); 223 for (Object[] arg : paramList) { 224 List<Object> cfgObjects = getMinMaxRangeCfgObjects(arg[0], arg[1], arg[2]); 225 for (Object obj : cfgObjects) { 226 Object[] argUpdate = new Object[arg.length + 1]; 227 System.arraycopy(arg, 0, argUpdate, 0, arg.length); 228 argUpdate[2] = obj; 229 EncoderConfigParams cfgVar = (EncoderConfigParams) obj; 230 String label = String.format("%.2fmbps_%dx%d_%dfps_maxb-%d_%s_i-dist-%d", 231 cfgVar.mBitRate / 1000000., cfgVar.mWidth, cfgVar.mHeight, 232 cfgVar.mFrameRate, cfgVar.mMaxBFrames, 233 bitRateModeToString(cfgVar.mBitRateMode), (int) cfgVar.mKeyFrameInterval); 234 argUpdate[arg.length - 1] = label; 235 argUpdate[arg.length] = paramToString(argUpdate); 236 newParamList.add(argUpdate); 237 } 238 } 239 return newParamList; 240 } 241 242 @Parameterized.Parameters(name = "{index}_{0}_{1}_{3}") input()243 public static Collection<Object[]> input() throws CloneNotSupportedException { 244 addParams(); 245 return updateParamList(prepareParamList(exhaustiveArgsList, true, false, true, false, 246 HARDWARE)); 247 } 248 VideoEncoderMinMaxTest(String encoder, String mediaType, EncoderConfigParams cfgParams, @SuppressWarnings("unused") String testLabel, String allTestParams)249 public VideoEncoderMinMaxTest(String encoder, String mediaType, EncoderConfigParams cfgParams, 250 @SuppressWarnings("unused") String testLabel, String allTestParams) { 251 super(encoder, mediaType, cfgParams, allTestParams); 252 } 253 254 @Before setUp()255 public void setUp() { 256 mIsLoopBack = true; 257 } 258 259 @ApiTest(apis = {"VideoCapabilities#getSupportedWidths", 260 "VideoCapabilities#getSupportedHeightsFor", 261 "VideoCapabilities#getSupportedWidthsFor", 262 "VideoCapabilities#getSupportedHeights", 263 "VideoCapabilities#getSupportedFrameRatesFor", 264 "VideoCapabilities#getBitrateRange", 265 "android.media.MediaFormat#KEY_WIDTH", 266 "android.media.MediaFormat#KEY_HEIGHT", 267 "android.media.MediaFormat#KEY_BITRATE", 268 "android.media.MediaFormat#KEY_FRAME_RATE"}) 269 @Test testMinMaxSupport()270 public void testMinMaxSupport() throws IOException, InterruptedException { 271 if (VNDK_IS_BEFORE_U) { 272 assumeTrue("Frame rate > 240 fps are limited to VNDK U and above.", 273 mEncCfgParams[0].mFrameRate <= 240); 274 } 275 276 MediaFormat format = mEncCfgParams[0].getFormat(); 277 MediaCodecInfo info = getCodecInfo(mCodecName, mMediaType); 278 assumeTrue(mCodecName + " does not support bitrate mode : " + bitRateModeToString( 279 mEncCfgParams[0].mBitRateMode), 280 info.getCapabilitiesForType(mMediaType).getEncoderCapabilities() 281 .isBitrateModeSupported(mEncCfgParams[0].mBitRateMode)); 282 ArrayList<MediaFormat> formats = new ArrayList<>(); 283 formats.add(format); 284 assertTrue("Encoder: " + mCodecName + " doesn't support format: " + format, 285 areFormatsSupported(mCodecName, mMediaType, formats)); 286 RawResource res = getRawResource(mEncCfgParams[0]); 287 assertNotNull("no raw resource found for testing config : " + mEncCfgParams[0] + mTestConfig 288 + mTestEnv + DIAGNOSTICS, res); 289 encodeToMemory(mCodecName, mEncCfgParams[0], res, FRAME_LIMIT, false, true); 290 CompareStreams cs = null; 291 StringBuilder msg = new StringBuilder(); 292 boolean isOk = true; 293 try { 294 cs = new CompareStreams(res, mMediaType, mMuxedOutputFile, true, mIsLoopBack); 295 final double[] minPSNR = cs.getMinimumPSNR(); 296 for (int i = 0; i < minPSNR.length; i++) { 297 if (minPSNR[i] < MIN_ACCEPTABLE_QUALITY) { 298 msg.append(String.format("For %d plane, minPSNR is less than tolerance" 299 + " threshold, Got %f, Threshold %f", i, minPSNR[i], 300 MIN_ACCEPTABLE_QUALITY)); 301 isOk = false; 302 break; 303 } 304 } 305 } finally { 306 if (cs != null) cs.cleanUp(); 307 } 308 assertEquals("encoder did not encode the requested number of frames \n" 309 + mTestConfig + mTestEnv, FRAME_LIMIT, mOutputCount); 310 assertTrue("Encountered frames with PSNR less than configured threshold " 311 + MIN_ACCEPTABLE_QUALITY + "dB \n" + msg + mTestConfig + mTestEnv, isOk); 312 } 313 } 314