blob: 4a2648bd7f267e657d31e1b103bb9610d352eefd [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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.mediav2.common.cts.EncoderConfigParams;
import android.mediav2.common.cts.RawResource;
import android.os.Bundle;
import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.CddTest;
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;
/**
* This test is added to extend the testing coverage of dynamically changing bitrates during an
* encoding session. This test verifies whether the rate control process of the encoder increases
* bits or decreases bits as instructed.
* <p></p>
* Encode Configs:
* <p>Input resolution = 1080p30fps/720p30fps</p>
* <p>Bitrate mode = VBR/CBR</p>
* <p>Codec type = AVC/HEVC</p>
* <p>IFrameInterval = 1 seconds</p>
* <p></p>
* <ul>
* <li>Change the bitrate every segment of 3 seconds (3 GOPs) from 5->8->12->8->5 if the
* input resolution is 1080p30fps.</li>
* <li>Change the bitrate every segment of 3 seconds (3 GOPs) from 2->3->5->3->2 if the
* input resolution is 720p30fps.</li>
* </ul>
* Save the encoded size per segment as A->B->C->D->E.
* <p></p>
* The test expects B/A>=1.15, C/B>=1.15, C/D>=1.15 and D/E>=1.15.
*/
@RunWith(Parameterized.class)
public class VideoEncoderAdaptiveBitRateTest extends VideoEncoderValidationTestBase {
private static final List<Object[]> exhaustiveArgsList = new ArrayList<>();
private static final HashMap<String, RawResource> RES_YUV_MAP = new HashMap<>();
private static final int SEGMENT_DURATION = 3;
private static final int[] SEGMENT_BITRATES_FULLHD =
new int[]{5000000, 8000000, 12000000, 8000000, 5000000};
private static final int[] SEGMENT_BITRATES_HD =
new int[]{2000000, 3000000, 5000000, 3000000, 2000000};
private static final int KEY_FRAME_INTERVAL = 1;
private final int[] mSegmentBitRates;
private final float[] mSegmentSizes;
private int mOutputSizeTillLastSegment = 0;
private static EncoderConfigParams getVideoEncoderCfgParams(String mediaType, int width,
int height, int bitRate, int bitRateMode) {
return new EncoderConfigParams.Builder(mediaType)
.setBitRate(bitRate)
.setKeyFrameInterval(KEY_FRAME_INTERVAL)
.setWidth(width)
.setHeight(height)
.setBitRateMode(bitRateMode)
.build();
}
private static void addParams(int width, int height, int[] segmentBitRates,
CompressedResource res) {
final String[] mediaTypes = new String[]{MediaFormat.MIMETYPE_VIDEO_AVC,
MediaFormat.MIMETYPE_VIDEO_HEVC};
final int[] bitRateModes = new int[]{BITRATE_MODE_CBR, BITRATE_MODE_VBR};
for (String mediaType : mediaTypes) {
for (int bitRateMode : bitRateModes) {
// mediaType, cfg, segment bitrates, res, test label
String label = String.format("%dx%d_%s", width, height,
bitRateModeToString(bitRateMode));
exhaustiveArgsList.add(new Object[]{mediaType, getVideoEncoderCfgParams(mediaType,
width, height, segmentBitRates[0], bitRateMode), segmentBitRates, res,
label});
}
}
}
@Parameterized.Parameters(name = "{index}({0}_{1}_{5})")
public static Collection<Object[]> input() {
addParams(1920, 1080, SEGMENT_BITRATES_FULLHD, BIRTHDAY_FULLHD_LANDSCAPE);
addParams(1080, 1920, SEGMENT_BITRATES_FULLHD, SELFIEGROUP_FULLHD_PORTRAIT);
addParams(1280, 720, SEGMENT_BITRATES_HD, BIRTHDAY_FULLHD_LANDSCAPE);
return prepareParamList(exhaustiveArgsList, true, false, true, false, HARDWARE);
}
@BeforeClass
public static void decodeResourcesToYuv() {
ArrayList<CompressedResource> resources = new ArrayList<>();
for (Object[] arg : exhaustiveArgsList) {
resources.add((CompressedResource) arg[3]);
}
decodeStreamsToYuv(resources, RES_YUV_MAP);
}
@AfterClass
public static void cleanUpResources() {
for (RawResource res : RES_YUV_MAP.values()) {
new File(res.mFileName).delete();
}
}
public VideoEncoderAdaptiveBitRateTest(String encoder, String mediaType,
EncoderConfigParams cfg, int[] segmentBitRates, CompressedResource res,
@SuppressWarnings("unused") String testLabel, String allTestParams) {
super(encoder, mediaType, cfg, res, allTestParams);
mSegmentBitRates = segmentBitRates;
mSegmentSizes = new float[segmentBitRates.length];
}
@Before
public void setUp() {
mIsLoopBack = true;
}
private void updateBitrate(int bitrate) {
final Bundle bitrateUpdate = new Bundle();
bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
mCodec.setParameters(bitrateUpdate);
}
protected void enqueueInput(int bufferIndex) {
super.enqueueInput(bufferIndex);
int segStartFrameIdx = SEGMENT_DURATION * mActiveEncCfg.mFrameRate;
int segIdx = mInputCount / segStartFrameIdx;
if ((mInputCount % segStartFrameIdx == 0) && (segIdx < mSegmentBitRates.length)) {
updateBitrate(mSegmentBitRates[segIdx]);
}
}
protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
super.dequeueOutput(bufferIndex, info);
int segEndFrameIdx = SEGMENT_DURATION * mActiveEncCfg.mFrameRate;
if (mOutputCount > 0 && mOutputCount % segEndFrameIdx == 0) {
int segIdx = mOutputCount / segEndFrameIdx - 1;
mSegmentSizes[segIdx] = mOutputBuff.getOutStreamSize() - mOutputSizeTillLastSegment;
mOutputSizeTillLastSegment = mOutputBuff.getOutStreamSize();
}
}
void passFailCriteria(int segA, int segB) {
String msg = String.format("For segment %d, configured bitrate is %d, received segment size"
+ " is %f \n For segment %d, configured bitrate is %d, received segment"
+ " size is %f \n Segment Relative Ratio %f \n",
segA, mSegmentBitRates[segA], mSegmentSizes[segA], segB, mSegmentBitRates[segB],
mSegmentSizes[segB], mSegmentSizes[segB] / mSegmentSizes[segA]);
assertTrue(msg + mTestConfig + mTestEnv, mSegmentSizes[segB] / mSegmentSizes[segA] >= 1.15);
}
@CddTest(requirements = "5.2/C.2.1")
@ApiTest(apis = "android.media.MediaCodec#PARAMETER_KEY_VIDEO_BITRATE")
@Test
public void testAdaptiveBitRate() throws IOException, InterruptedException,
CloneNotSupportedException {
int maxBitRate = 0;
for (int bitrate : mSegmentBitRates) {
maxBitRate = Math.max(bitrate, maxBitRate);
}
EncoderConfigParams cfg = mEncCfgParams[0].getBuilder().setBitRate(maxBitRate).build();
MediaFormat format = cfg.getFormat();
ArrayList<MediaFormat> formats = new ArrayList<>();
formats.add(format);
Assume.assumeTrue("Encoder: " + mCodecName + " doesn't support format: " + format,
areFormatsSupported(mCodecName, mMediaType, formats));
RawResource res = RES_YUV_MAP.getOrDefault(mCRes.uniqueLabel(), null);
assertNotNull("no raw resource found for testing config : " + mEncCfgParams[0] + mTestConfig
+ mTestEnv, res);
int limit = mSegmentBitRates.length * SEGMENT_DURATION * mEncCfgParams[0].mFrameRate;
encodeToMemory(mCodecName, mEncCfgParams[0], res, limit, true, false);
assertEquals("encoder did not encode the requested number of frames \n" + mTestConfig
+ mTestEnv, mOutputCount, limit);
passFailCriteria(0, 1);
passFailCriteria(1, 2);
passFailCriteria(3, 2);
passFailCriteria(4, 3);
}
}