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