• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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