blob: ddee6126973b9d06ef0f32c8cf8733691a77e277 [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.mediav2.common.cts.CodecTestBase.ComponentClass.HARDWARE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.media.MediaFormat;
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.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;
/**
* This test is to verify whether the rate control process of the tested encoder generates a
* bitstream whose size matches the frame rate set by the media format key of KEY_FRAME_RATE.
* <p></p>
* Test Params:
* <p>Input resolution = 1920x1080</p>
* <p>Number of frames = 300</p>
* <p>Target bitrate = 5 Mbps</p>
* <p>MaxBFrames = 0/1</p>
* <p>FPS = 15/30</p>
* <p>IFrameInterval = 1 second</p>
* <p></p>
* Let's denote the encoded file size for fps=15 as A, and the encoded file size for fps=30
* as B. It is expected that 0.8 <= A/(2*B) <= 1.2.
*/
@RunWith(Parameterized.class)
public class VideoEncoderFrameRateTest extends VideoEncoderValidationTestBase {
private static final int KEY_FRAME_INTERVAL = 1;
private static final int FRAME_LIMIT = 300;
private static final int BASE_FRAME_RATE = 30;
private static final int BIT_RATE = 5000000;
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) {
return new EncoderConfigParams.Builder(mediaType)
.setBitRate(BIT_RATE)
.setKeyFrameInterval(KEY_FRAME_INTERVAL)
.setFrameRate(BASE_FRAME_RATE)
.setWidth(width)
.setHeight(height)
.setMaxBFrames(maxBFrames)
.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};
for (String mediaType : mediaTypes) {
for (int maxBFrames : maxBFramesPerSubGop) {
// mediaType, cfg, resource file, test label
String label = String.format("%dkbps_%dx%d_maxb-%d", BIT_RATE / 1000, width,
height, maxBFrames);
exhaustiveArgsList.add(new Object[]{mediaType, getVideoEncoderCfgParams(mediaType,
width, height, maxBFrames), res, label});
}
}
}
@Parameterized.Parameters(name = "{index}({0}_{1}_{4})")
public static Collection<Object[]> input() {
addParams(1920, 1080, BIRTHDAY_FULLHD_LANDSCAPE);
addParams(1080, 1920, SELFIEGROUP_FULLHD_PORTRAIT);
return prepareParamList(exhaustiveArgsList, true, false, true, false, HARDWARE);
}
public VideoEncoderFrameRateTest(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_FRAME_RATE"})
@Test
public void testEncoderFrameRateSupport() throws IOException, InterruptedException,
CloneNotSupportedException {
float[] scaleFactors = new float[]{1.0f, 0.5f};
float refFactor = -1.0f;
int refSize = -1;
boolean testSkipped = false;
RawResource res = RES_YUV_MAP.getOrDefault(mCRes.uniqueLabel(), null);
assertNotNull("no raw resource found for testing config : " + mEncCfgParams[0] + mTestConfig
+ mTestEnv, res);
for (float scaleFactor : scaleFactors) {
EncoderConfigParams cfg = mEncCfgParams[0].getBuilder()
.setFrameRate((int) (BASE_FRAME_RATE * scaleFactor)).build();
MediaFormat format = cfg.getFormat();
ArrayList<MediaFormat> formats = new ArrayList<>();
formats.add(format);
if (!areFormatsSupported(mCodecName, mMediaType, formats)) {
continue;
}
encodeToMemory(mCodecName, cfg, res, FRAME_LIMIT, true, false);
assertEquals("encoder did not encode the requested number of frames \n" + mTestConfig
+ mTestEnv, FRAME_LIMIT, mOutputCount);
if (refFactor == -1.0) {
refFactor = scaleFactor;
refSize = mOutputBuff.getOutStreamSize();
} else {
testSkipped = true;
float scaledStreamSize = (scaleFactor / refFactor) * mOutputBuff.getOutStreamSize();
float sizeRatio = scaledStreamSize / refSize;
String msg = String.format(
"Output bitstream size does not scale as per configured frame rate. \n"
+ "For %d frame rate, encoder generates %d bytes of data. \n"
+ "for %d frame rate, encoder generates %d bytes of data. \n",
(int) (BASE_FRAME_RATE * refFactor), refSize,
(int) (BASE_FRAME_RATE * scaleFactor), mOutputBuff.getOutStreamSize());
assertTrue(msg + mTestConfig + mTestEnv, sizeRatio >= 0.8 && sizeRatio <= 1.2);
}
}
assumeTrue("did not find at least 2 formats that are supported by the component",
testSkipped);
}
}