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