blob: f59a45079ad274550e27e67e7312cbd598a8ad1b [file] [log] [blame]
/*
* 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);
}
}