| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.videocodec.cts; |
| |
| import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR; |
| import static android.media.MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR; |
| import static android.mediav2.common.cts.CodecTestBase.ComponentClass.HARDWARE; |
| import static android.mediav2.common.cts.CodecTestBase.MEDIA_CODEC_LIST_REGULAR; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import android.media.MediaCodecInfo; |
| import android.media.MediaFormat; |
| import android.mediav2.common.cts.CompareStreams; |
| import android.mediav2.common.cts.EncoderConfigParams; |
| import android.mediav2.common.cts.RawResource; |
| |
| import com.android.compatibility.common.util.ApiTest; |
| |
| import org.junit.AfterClass; |
| import org.junit.Assume; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * 1. MinMaxResolutionsTest should query the ranges of supported width and height using |
| * MediaCodecInfo.VideoCapabilities, test the min resolution and the max resolution of the encoder. |
| * <p></p> |
| * Test Params: |
| * <p>Input resolution = min/max</p> |
| * <p>Number of frames = 30</p> |
| * <p>FrameRate = 30</p> |
| * <p>Target bitrate = 10 Mbps</p> |
| * <p>Bitrate mode = VBR</p> |
| * <p>MaxBFrames = 0/1</p> |
| * <p>Codec type = AVC/HEVC</p> |
| * <p>IFrameInterval = 0/1 second</p> |
| * <p></p> |
| * |
| * 2. MinMaxBitrateTest should query the range of the supported bitrates, and test min/max of them |
| * <p></p> |
| * Test Params: |
| * <p>Input resolution = 720p30fps</p> |
| * <p>Number of frames = 300</p> |
| * <p>FrameRate = 30</p> |
| * <p>Target bitrate = min/max</p> |
| * <p>Bitrate mode = CBR/VBR</p> |
| * <p>MaxBFrames = 0/1</p> |
| * <p>Codec type = AVC/HEVC</p> |
| * <p>IFrameInterval = 0/1 second</p> |
| * <p></p> |
| * |
| * 3. MinMaxFrameRatesTest should query the range of the supported frame rates, and test min/max |
| * of them. |
| * Test Params: |
| * <p>Input resolution = 720p</p> |
| * <p>Number of frames = 300</p> |
| * <p>FrameRate = min/max</p> |
| * <p>Target bitrate = 5Mbps</p> |
| * <p>Bitrate mode = CBR/VBR</p> |
| * <p>MaxBFrames = 0/1</p> |
| * <p>Codec type = AVC/HEVC</p> |
| * <p>IFrameInterval = 0/1 second</p> |
| * <p></p> |
| */ |
| @RunWith(Parameterized.class) |
| public class VideoEncoderMinMaxTest extends VideoEncoderValidationTestBase { |
| private static final float MIN_ACCEPTABLE_QUALITY = 20.0f; // psnr in dB |
| private static final int FRAME_LIMIT = 300; |
| private static final int FRAME_RATE = 30; |
| private static final int BIT_RATE = 10000000; |
| private static final List<Object[]> exhaustiveArgsList = new ArrayList<>(); |
| private static final HashMap<String, RawResource> RES_YUV_MAP = new HashMap<>(); |
| |
| @BeforeClass |
| public static void decodeResourcesToYuv() { |
| ArrayList<CompressedResource> resources = new ArrayList<>(); |
| for (Object[] arg : exhaustiveArgsList) { |
| resources.add((CompressedResource) arg[2]); |
| } |
| decodeStreamsToYuv(resources, RES_YUV_MAP); |
| } |
| |
| @AfterClass |
| public static void cleanUpResources() { |
| for (RawResource res : RES_YUV_MAP.values()) { |
| new File(res.mFileName).delete(); |
| } |
| } |
| |
| private static EncoderConfigParams getVideoEncoderCfgParams(String mediaType, int width, |
| int height, int maxBFrames, int intraInterval) { |
| return new EncoderConfigParams.Builder(mediaType) |
| .setWidth(width) |
| .setHeight(height) |
| .setBitRate(BIT_RATE) |
| .setMaxBFrames(maxBFrames) |
| .setKeyFrameInterval(intraInterval) |
| .setFrameRate(FRAME_RATE) |
| .build(); |
| } |
| |
| private static void addParams(int width, int height, CompressedResource res) { |
| final String[] mediaTypes = new String[]{MediaFormat.MIMETYPE_VIDEO_AVC, |
| MediaFormat.MIMETYPE_VIDEO_HEVC}; |
| final int[] maxBFramesPerSubGop = new int[]{0, 1}; |
| final int[] intraIntervals = new int[]{0, 1}; |
| for (String mediaType : mediaTypes) { |
| for (int maxBFrames : maxBFramesPerSubGop) { |
| for (int intraInterval : intraIntervals) { |
| // mediaType, cfg, resource file |
| exhaustiveArgsList.add(new Object[]{mediaType, |
| getVideoEncoderCfgParams(mediaType, width, height, maxBFrames, |
| intraInterval), res}); |
| } |
| } |
| } |
| } |
| |
| private static List<Object> applyMinMaxRanges(MediaCodecInfo.CodecCapabilities caps, |
| Object cfgObject) throws CloneNotSupportedException { |
| int minWidth = caps.getVideoCapabilities().getSupportedWidths().getLower(); |
| int maxWidth = caps.getVideoCapabilities().getSupportedWidths().getUpper(); |
| int minHeight = caps.getVideoCapabilities().getSupportedHeights().getLower(); |
| int maxHeight = caps.getVideoCapabilities().getSupportedHeights().getUpper(); |
| |
| int minBitRate = caps.getVideoCapabilities().getBitrateRange().getLower(); |
| int maxBitRate = caps.getVideoCapabilities().getBitrateRange().getUpper(); |
| |
| int minFrameRate = caps.getVideoCapabilities().getSupportedFrameRates().getLower(); |
| int maxFrameRate = caps.getVideoCapabilities().getSupportedFrameRates().getUpper(); |
| |
| List<Object> cfgObjects = new ArrayList<>(); |
| EncoderConfigParams cfgParam = (EncoderConfigParams) cfgObject; |
| |
| final int[] bitRateModes = new int[]{BITRATE_MODE_CBR, BITRATE_MODE_VBR}; |
| for (int bitRateMode : bitRateModes) { |
| cfgObjects.add((Object) cfgParam.getBuilder() |
| .setWidth(minWidth) |
| .setHeight(minHeight) |
| .setBitRate(minBitRate) |
| .setBitRateMode(bitRateMode) |
| .build()); |
| |
| cfgObjects.add((Object) cfgParam.getBuilder() |
| .setWidth(maxWidth) |
| .setHeight(maxHeight) |
| .setBitRate(maxBitRate) |
| .setBitRateMode(bitRateMode) |
| .build()); |
| |
| cfgObjects.add((Object) cfgParam.getBuilder() |
| .setFrameRate(minFrameRate) |
| .setBitRate(5000000) |
| .setBitRateMode(bitRateMode) |
| .build()); |
| |
| cfgObjects.add((Object) cfgParam.getBuilder() |
| .setFrameRate(maxFrameRate) |
| .setBitRate(5000000) |
| .setBitRateMode(bitRateMode) |
| .build()); |
| } |
| |
| cfgObjects.add((Object) cfgParam.getBuilder() |
| .setWidth(minWidth) |
| .setHeight(maxHeight) |
| .setBitRateMode(BITRATE_MODE_VBR) |
| .build()); |
| |
| cfgObjects.add((Object) cfgParam.getBuilder() |
| .setWidth(maxWidth) |
| .setHeight(minHeight) |
| .setBitRateMode(BITRATE_MODE_VBR) |
| .build()); |
| |
| return cfgObjects; |
| } |
| |
| private static List<Object> getMinMaxRangeCfgObjects(Object codecName, Object mediaType, |
| Object cfgObject) throws CloneNotSupportedException { |
| for (MediaCodecInfo codecInfo : MEDIA_CODEC_LIST_REGULAR.getCodecInfos()) { |
| for (String type : codecInfo.getSupportedTypes()) { |
| if (codecName.equals(codecInfo.getName()) && mediaType.equals(type)) { |
| MediaCodecInfo.CodecCapabilities caps = |
| codecInfo.getCapabilitiesForType(type); |
| return applyMinMaxRanges(caps, cfgObject); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static Collection<Object[]> updateParamList(Collection<Object[]> paramList) |
| throws CloneNotSupportedException { |
| Collection<Object[]> newParamList = new ArrayList<>(); |
| for (Object[] arg : paramList) { |
| List<Object> cfgObjects = getMinMaxRangeCfgObjects(arg[0], arg[1], arg[2]); |
| for (Object obj : cfgObjects) { |
| Object[] argUpdate = new Object[arg.length + 1]; |
| System.arraycopy(arg, 0, argUpdate, 0, arg.length); |
| argUpdate[2] = obj; |
| EncoderConfigParams cfgVar = (EncoderConfigParams) obj; |
| String label = String.format("%dkbps_%dx%d_%dfps_maxb-%d_%s_i-dist-%d", |
| cfgVar.mBitRate / 1000, cfgVar.mWidth, cfgVar.mHeight, cfgVar.mFrameRate, |
| cfgVar.mMaxBFrames, bitRateModeToString(cfgVar.mBitRateMode), |
| (int) cfgVar.mKeyFrameInterval); |
| argUpdate[arg.length - 1] = label; |
| argUpdate[arg.length] = paramToString(argUpdate); |
| newParamList.add(argUpdate); |
| } |
| } |
| return newParamList; |
| } |
| |
| @Parameterized.Parameters(name = "{index}({0}_{1}_{4})") |
| public static Collection<Object[]> input() throws CloneNotSupportedException { |
| addParams(1280, 720, BIRTHDAY_FULLHD_LANDSCAPE); |
| return updateParamList(prepareParamList(exhaustiveArgsList, true, false, true, false, |
| HARDWARE)); |
| } |
| |
| public VideoEncoderMinMaxTest(String encoder, String mediaType, EncoderConfigParams cfgParams, |
| CompressedResource res, @SuppressWarnings("unused") String testLabel, |
| String allTestParams) { |
| super(encoder, mediaType, cfgParams, res, allTestParams); |
| } |
| |
| @Before |
| public void setUp() { |
| mIsLoopBack = true; |
| } |
| |
| @ApiTest(apis = {"android.media.MediaFormat#KEY_WIDTH", |
| "android.media.MediaFormat#KEY_HEIGHT", |
| "android.media.MediaFormat#KEY_BITRATE", |
| "android.media.MediaFormat#KEY_FRAME_RATE"}) |
| @Test |
| public void testMinMaxSupport() throws IOException, InterruptedException { |
| MediaFormat format = mEncCfgParams[0].getFormat(); |
| ArrayList<MediaFormat> formats = new ArrayList<>(); |
| formats.add(format); |
| Assume.assumeTrue("Encoder: " + mCodecName + " doesn't support format: " + format, |
| areFormatsSupported(mCodecName, mMime, formats)); |
| RawResource res = RES_YUV_MAP.getOrDefault(mCRes.uniqueLabel(), null); |
| assertNotNull("no raw resource found for testing config : " + mEncCfgParams[0] |
| + mTestConfig + mTestEnv, res); |
| encodeToMemory(mCodecName, mEncCfgParams[0], res, FRAME_LIMIT, true, true); |
| CompareStreams cs = null; |
| StringBuilder msg = new StringBuilder(); |
| boolean isOk = true; |
| try { |
| cs = new CompareStreams(res, mMime, mMuxedOutputFile, true, mIsLoopBack); |
| final double[] minPSNR = cs.getMinimumPSNR(); |
| for (int i = 0; i < minPSNR.length; i++) { |
| if (minPSNR[i] < MIN_ACCEPTABLE_QUALITY) { |
| msg.append(String.format("For %d plane, minPSNR is less than tolerance" |
| + " threshold, Got %f, Threshold %f", i, minPSNR[i], |
| MIN_ACCEPTABLE_QUALITY)); |
| isOk = false; |
| break; |
| } |
| } |
| } finally { |
| if (cs != null) cs.cleanUp(); |
| } |
| new File(mMuxedOutputFile).delete(); |
| assertEquals("encoder did not encode the requested number of frames \n" |
| + mTestConfig + mTestEnv, FRAME_LIMIT, mOutputCount); |
| assertTrue("Encountered frames with PSNR less than configured threshold " |
| + MIN_ACCEPTABLE_QUALITY + "dB \n" + msg + mTestConfig + mTestEnv, isOk); |
| } |
| } |