1 /* 2 * Copyright (C) 2023 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.videocodec.cts.VideoEncoderValidationTestBase.BIRTHDAY_FULLHD_LANDSCAPE; 23 import static android.videocodec.cts.VideoEncoderValidationTestBase.DIAGNOSTICS; 24 import static android.videocodec.cts.VideoEncoderValidationTestBase.logAllFilesInCacheDir; 25 26 import static org.junit.Assert.assertEquals; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 import static org.junit.Assume.assumeNotNull; 30 import static org.junit.Assume.assumeTrue; 31 32 import android.media.MediaFormat; 33 import android.mediav2.common.cts.CodecEncoderTestBase; 34 import android.mediav2.common.cts.CodecTestBase; 35 import android.mediav2.common.cts.CompareStreams; 36 import android.mediav2.common.cts.DecodeStreamToYuv; 37 import android.mediav2.common.cts.EncoderConfigParams; 38 import android.mediav2.common.cts.RawResource; 39 import android.util.Log; 40 41 import com.android.compatibility.common.util.ApiTest; 42 43 import org.junit.After; 44 import org.junit.AfterClass; 45 import org.junit.Assume; 46 import org.junit.Before; 47 import org.junit.BeforeClass; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.junit.runners.Parameterized; 51 52 import java.io.File; 53 import java.io.IOException; 54 import java.util.ArrayList; 55 import java.util.Collection; 56 import java.util.List; 57 58 /** 59 * This test is to ensure no quality regression is seen from avc to hevc. Also no quality 60 * regression is seen from '0' b frames to '1' b frame 61 * <p></p> 62 * Global Encode Config: 63 * <p>Input resolution = 1080p30fps</p> 64 * <p>Bitrate mode = VBR/CBR</p> 65 * <p>Codec type = AVC/HEVC</p> 66 * <p>IFrameInterval = 1 seconds</p> 67 * <p></p> 68 */ 69 @RunWith(Parameterized.class) 70 public class VideoEncoderQualityRegressionTest { 71 private static final String LOG_TAG = VideoEncoderQualityRegressionTest.class.getSimpleName(); 72 private static final String[] MEDIA_TYPES = 73 {MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_VIDEO_HEVC}; 74 private static final int WIDTH = 1920; 75 private static final int HEIGHT = 1080; 76 private static final int[] BIT_RATES = {2000000, 4000000, 6000000, 8000000, 10000000, 12000000}; 77 private static final int[] BIT_RATE_MODES = {BITRATE_MODE_CBR, BITRATE_MODE_VBR}; 78 private static final int[] B_FRAMES = {0, 1}; 79 private static final int FRAME_RATE = 30; 80 private static final int KEY_FRAME_INTERVAL = 1; 81 private static final int FRAME_LIMIT = 300; 82 private static final List<Object[]> exhaustiveArgsList = new ArrayList<>(); 83 private static final VideoEncoderValidationTestBase.CompressedResource RES = 84 BIRTHDAY_FULLHD_LANDSCAPE; 85 private static RawResource sActiveRawRes = null; 86 87 private final int mBitRateMode; 88 private final ArrayList<String> mTmpFiles = new ArrayList<>(); 89 90 static { 91 System.loadLibrary("ctsvideoqualityutils_jni"); 92 } 93 94 @Parameterized.Parameters(name = "{index}_{1}") input()95 public static Collection<Object[]> input() { 96 for (int bitRateMode : BIT_RATE_MODES) { 97 exhaustiveArgsList.add(new Object[]{bitRateMode, 98 CodecEncoderTestBase.bitRateModeToString(bitRateMode)}); 99 } 100 return exhaustiveArgsList; 101 } 102 VideoEncoderQualityRegressionTest(int bitRateMode, @SuppressWarnings("unused") String testLabel)103 public VideoEncoderQualityRegressionTest(int bitRateMode, 104 @SuppressWarnings("unused") String testLabel) { 105 mBitRateMode = bitRateMode; 106 } 107 108 @BeforeClass decodeResourceToYuv()109 public static void decodeResourceToYuv() throws IOException, InterruptedException { 110 logAllFilesInCacheDir(true); 111 try { 112 DecodeStreamToYuv yuv = new DecodeStreamToYuv(RES.mMediaType, RES.mResFile, 113 FRAME_LIMIT, LOG_TAG); 114 sActiveRawRes = yuv.getDecodedYuv(); 115 } catch (Exception e) { 116 DIAGNOSTICS.append(String.format("\nWhile decoding the resource : %s," 117 + " encountered exception : %s was thrown", RES, e)); 118 logAllFilesInCacheDir(false); 119 } 120 } 121 122 @AfterClass cleanUpResources()123 public static void cleanUpResources() { 124 new File(sActiveRawRes.mFileName).delete(); 125 sActiveRawRes = null; 126 } 127 128 @Before setUp()129 public void setUp() { 130 assumeNotNull("no raw resource found for testing : ", sActiveRawRes); 131 } 132 133 @After tearDown()134 public void tearDown() { 135 for (String tmpFile : mTmpFiles) { 136 File tmp = new File(tmpFile); 137 if (tmp.exists()) assertTrue("unable to delete file " + tmpFile, tmp.delete()); 138 } 139 mTmpFiles.clear(); 140 } 141 getVideoEncoderCfgParams(String mediaType, int bitRate, int bitRateMode, int maxBFrames)142 private static EncoderConfigParams getVideoEncoderCfgParams(String mediaType, int bitRate, 143 int bitRateMode, int maxBFrames) { 144 return new EncoderConfigParams.Builder(mediaType) 145 .setWidth(VideoEncoderQualityRegressionTest.WIDTH) 146 .setHeight(VideoEncoderQualityRegressionTest.HEIGHT) 147 .setBitRate(bitRate) 148 .setBitRateMode(bitRateMode) 149 .setKeyFrameInterval(KEY_FRAME_INTERVAL) 150 .setFrameRate(FRAME_RATE) 151 .setMaxBFrames(maxBFrames) 152 .build(); 153 } 154 nativeGetBDRate(double[] qualitiesA, double[] ratesA, double[] qualitiesB, double[] ratesB, boolean selBdSnr, StringBuilder retMsg)155 private native double nativeGetBDRate(double[] qualitiesA, double[] ratesA, double[] qualitiesB, 156 double[] ratesB, boolean selBdSnr, StringBuilder retMsg); 157 getQualityRegressionForCfgs(List<EncoderConfigParams[]> cfgsUnion, String[] encoderNames, double minGain)158 void getQualityRegressionForCfgs(List<EncoderConfigParams[]> cfgsUnion, String[] encoderNames, 159 double minGain) throws IOException, InterruptedException { 160 double[][] psnrs = new double[cfgsUnion.size()][cfgsUnion.get(0).length]; 161 double[][] rates = new double[cfgsUnion.size()][cfgsUnion.get(0).length]; 162 for (int i = 0; i < cfgsUnion.size(); i++) { 163 EncoderConfigParams[] cfgs = cfgsUnion.get(i); 164 String mediaType = cfgs[0].mMediaType; 165 VideoEncoderValidationTestBase vevtb = new VideoEncoderValidationTestBase(null, 166 mediaType, null, null, ""); 167 vevtb.setLoopBack(true); 168 for (int j = 0; j < cfgs.length; j++) { 169 vevtb.encodeToMemory(encoderNames[i], cfgs[j], sActiveRawRes, FRAME_LIMIT, true, 170 true); 171 mTmpFiles.add(vevtb.getMuxedOutputFilePath()); 172 assertEquals("encoder did not encode the requested number of frames \n", 173 FRAME_LIMIT, vevtb.getOutputCount()); 174 int outSize = vevtb.getOutputManager().getOutStreamSize(); 175 double achievedBitRate = ((double) outSize * 8 * FRAME_RATE) / (1000 * FRAME_LIMIT); 176 CompareStreams cs = null; 177 try { 178 cs = new CompareStreams(sActiveRawRes, mediaType, 179 vevtb.getMuxedOutputFilePath(), true, true); 180 final double[] globalPSNR = cs.getGlobalPSNR(); 181 double weightedPSNR = (6 * globalPSNR[0] + globalPSNR[1] + globalPSNR[2]) / 8; 182 psnrs[i][j] = weightedPSNR; 183 rates[i][j] = achievedBitRate; 184 } finally { 185 if (cs != null) cs.cleanUp(); 186 } 187 vevtb.deleteMuxedFile(); 188 } 189 } 190 StringBuilder retMsg = new StringBuilder(); 191 double bdRate = nativeGetBDRate(psnrs[0], rates[0], psnrs[1], rates[1], false, retMsg); 192 if (retMsg.length() != 0) fail(retMsg.toString()); 193 for (int i = 0; i < psnrs.length; i++) { 194 retMsg.append(String.format("\nBitrate GlbPsnr Set %d\n", i)); 195 for (int j = 0; j < psnrs[i].length; j++) { 196 retMsg.append(String.format("{%f, %f},\n", rates[i][j], psnrs[i][j])); 197 } 198 } 199 retMsg.append(String.format("bd rate %f not < %f", bdRate, minGain)); 200 Log.d(LOG_TAG, retMsg.toString()); 201 // assuming set B encoding is superior to set A, 202 assumeTrue(retMsg.toString(), bdRate < minGain); 203 } 204 205 @ApiTest(apis = {"android.media.MediaFormat#KEY_BITRATE", 206 "android.media.MediaFormat#KEY_BITRATE_MODE"}) 207 @Test 208 public void testQualityRegressionOverCodecs() throws IOException, InterruptedException { 209 String[] encoderNames = new String[MEDIA_TYPES.length]; 210 List<EncoderConfigParams[]> cfgsUnion = new ArrayList<>(); 211 for (int i = 0; i < MEDIA_TYPES.length; i++) { 212 EncoderConfigParams[] cfgsOfMediaType = new EncoderConfigParams[BIT_RATES.length]; 213 cfgsUnion.add(cfgsOfMediaType); 214 ArrayList<MediaFormat> fmts = new ArrayList<>(); 215 for (int j = 0; j < cfgsOfMediaType.length; j++) { 216 cfgsOfMediaType[j] = getVideoEncoderCfgParams(MEDIA_TYPES[i], BIT_RATES[j], 217 mBitRateMode, 0); 218 fmts.add(cfgsOfMediaType[j].getFormat()); 219 } 220 ArrayList<String> encoders = CodecTestBase.selectCodecs(MEDIA_TYPES[i], fmts, null, 221 true, HARDWARE); 222 Assume.assumeTrue("no encoders present on device that support encoding fmts: " + fmts, 223 encoders.size() > 0); 224 encoderNames[i] = encoders.get(0); 225 } 226 getQualityRegressionForCfgs(cfgsUnion, encoderNames, 0); 227 } 228 229 void testQualityRegressionOverBFrames(String mediaType) 230 throws IOException, InterruptedException { 231 String[] encoderNames = new String[B_FRAMES.length]; 232 List<EncoderConfigParams[]> cfgsUnion = new ArrayList<>(); 233 for (int i = 0; i < B_FRAMES.length; i++) { 234 EncoderConfigParams[] cfgs = new EncoderConfigParams[BIT_RATES.length]; 235 cfgsUnion.add(cfgs); 236 ArrayList<MediaFormat> fmts = new ArrayList<>(); 237 for (int j = 0; j < cfgs.length; j++) { 238 cfgs[j] = getVideoEncoderCfgParams(mediaType, BIT_RATES[j], mBitRateMode, 239 B_FRAMES[i]); 240 fmts.add(cfgs[j].getFormat()); 241 } 242 ArrayList<String> encoders = CodecTestBase.selectCodecs(mediaType, fmts, null, true, 243 HARDWARE); 244 Assume.assumeTrue("no encoders present on device that support encoding fmts: " + fmts, 245 encoders.size() > 0); 246 encoderNames[i] = encoders.get(0); 247 } 248 getQualityRegressionForCfgs(cfgsUnion, encoderNames, 0.000001d); 249 } 250 251 @ApiTest(apis = {"android.media.MediaFormat#KEY_BITRATE", 252 "android.media.MediaFormat#KEY_BITRATE_MODE", 253 "android.media.MediaFormat#KEY_MAX_B_FRAMES"}) 254 @Test 255 public void testQualityRegressionOverBFramesAvc() throws IOException, InterruptedException { 256 testQualityRegressionOverBFrames(MediaFormat.MIMETYPE_VIDEO_AVC); 257 } 258 259 @ApiTest(apis = {"android.media.MediaFormat#KEY_BITRATE", 260 "android.media.MediaFormat#KEY_BITRATE_MODE", 261 "android.media.MediaFormat#KEY_MAX_B_FRAMES"}) 262 @Test 263 public void testQualityRegressionOverBFramesHevc() throws IOException, InterruptedException { 264 testQualityRegressionOverBFrames(MediaFormat.MIMETYPE_VIDEO_HEVC); 265 } 266 } 267