enc stat CTS for average QP, only check QP vs Bitrate
This test invokes video encoder only and check whether
lower average QP gives higher bitrate video.
Bug: 205853668
Test: CTS
Change-Id: I6566dc930019a0261eb6ffe477c4a0b22f6e94c8
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java
index c7570e8..1dd7651 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java
@@ -175,7 +175,8 @@
targetBitrate,
true);
ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
- ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params, codecConfigs);
+ VideoEncodeOutput videoEncodeOutput = encode(params, codecConfigs);
+ ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
if (bufInfo == null) {
continue;
}
@@ -236,7 +237,8 @@
BITRATE,
syncEncoding);
ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
- ArrayList<MediaCodec.BufferInfo> bufInfos = encodeAsync(params, codecConfigs);
+ VideoEncodeOutput videoEncodeOutput = encodeAsync(params, codecConfigs);
+ ArrayList<MediaCodec.BufferInfo> bufInfos = videoEncodeOutput.bufferInfo;
if (bufInfos == null) {
Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
return;
@@ -263,7 +265,8 @@
BITRATE,
syncEncoding);
codecConfigs.clear();
- bufInfos = encode(params, codecConfigs);
+ videoEncodeOutput = encode(params, codecConfigs);
+ bufInfos = videoEncodeOutput.bufferInfo;
if (bufInfos == null) {
Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
return;
@@ -312,7 +315,8 @@
params.syncFrameInterval = encodeSeconds * FPS;
params.syncForceFrameInterval = FPS;
params.useNdk = useNdk;
- ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+ VideoEncodeOutput videoEncodeOutput = encode(params);
+ ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
if (bufInfo == null) {
Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
return;
@@ -376,7 +380,8 @@
}
params.useNdk = useNdk;
- ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+ VideoEncodeOutput videoEncodeOutput = encode(params);
+ ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
if (bufInfo == null) {
Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
return;
@@ -453,9 +458,11 @@
try {
ArrayList<MediaCodec.BufferInfo> bufInfo;
if (codecConfigs.isEmpty()) {
- bufInfo = encode(params, codecConfigs);
+ VideoEncodeOutput videoEncodeOutput = encode(params, codecConfigs);
+ bufInfo = videoEncodeOutput.bufferInfo;
} else {
- bufInfo = encode(params);
+ VideoEncodeOutput videoEncodeOutput = encode(params);
+ bufInfo = videoEncodeOutput.bufferInfo;
}
VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
bitrate[0] = statistics.mAverageBitrate;
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTestBase.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTestBase.java
index 3aefbab..cbe3966 100644
--- a/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTestBase.java
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTestBase.java
@@ -247,6 +247,33 @@
boolean runInLooperThread;
// Flag if use NdkMediaCodec
boolean useNdk;
+ // Encoding Statistics Level
+ // 0: None, 1: Average block QP and picture type of a frame
+ public int encodingStatisticsLevel;
+ }
+
+ /**
+ * Encoding Statistics for a whole sequence
+ */
+ protected class EncodingStatisticsInfo {
+ public float averageSeqQp = 0; // Average qp of a whole sequence,
+ // i.e. average of 'per-frame average block QP'
+ public int encodedFrames = 0; // # of encoded frames,
+ // i.e. # of average_block_qp is reported
+ }
+
+ /**
+ * Encoding Statistics for a whole sequence
+ */
+ protected class VideoEncodeOutput{
+ public ArrayList<MediaCodec.BufferInfo> bufferInfo;
+ public EncodingStatisticsInfo encStat;
+
+ VideoEncodeOutput(ArrayList<MediaCodec.BufferInfo> bufferInfo,
+ EncodingStatisticsInfo encStat) {
+ bufferInfo = bufferInfo;
+ encStat = encStat;
+ }
}
private String getCodecSuffix(String codecMimeType) {
@@ -795,19 +822,22 @@
private InputStream mYuvStream;
private int mInputFrameIndex;
+ private final EncodingStatisticsInfo mEncStatInfo;
MediaEncoderAsyncHelper(
EncoderOutputStreamParameters streamParams,
CodecProperties properties,
ArrayList<MediaCodec.BufferInfo> bufferInfos,
IvfWriter ivf,
- ArrayList<ByteBuffer> codecConfigs)
+ ArrayList<ByteBuffer> codecConfigs,
+ EncodingStatisticsInfo encStatInfo)
throws Exception {
mStreamParams = streamParams;
mProperties = properties;
mBufferInfos = bufferInfos;
mIvf = ivf;
mCodecConfigs = codecConfigs;
+ mEncStatInfo = encStatInfo;
int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
mSrcFrame = new byte[srcFrameSize];
@@ -884,6 +914,11 @@
}
return false;
}
+
+ public void saveAvgQp(int avg_qp) {
+ mEncStatInfo.averageSeqQp += (float) avg_qp;
+ ++mEncStatInfo.encodedFrames; // Note: Duplicated info to mOutputFrameIndex
+ }
}
/**
@@ -1012,6 +1047,13 @@
out.flags = info.flags;
out.outputGenerated = true;
+ MediaFormat format = codec.getOutputFormat(index);
+ if (format.containsKey(MediaFormat.KEY_VIDEO_QP_AVERAGE)) {
+ int avgQp = format.getInteger(MediaFormat.KEY_VIDEO_QP_AVERAGE);
+ // Copy per-frame avgQp to sequence level buffer
+ mHelper.saveAvgQp(avgQp);
+ }
+
if (mHelper.saveOutputFrame(out)) {
// output EOS
signalCompletion();
@@ -1347,7 +1389,7 @@
/**
* @see #encode(EncoderOutputStreamParameters, ArrayList<ByteBuffer>)
*/
- protected ArrayList<MediaCodec.BufferInfo> encode(
+ protected VideoEncodeOutput encode(
EncoderOutputStreamParameters streamParams) throws Exception {
return encode(streamParams, new ArrayList<ByteBuffer>());
}
@@ -1367,13 +1409,16 @@
*
* @param streamParams Structure with encoder parameters
* @param codecConfigs List to be filled with codec config buffers
- * @return Returns array of encoded frames information for each frame.
+ * @return Returns VideoEncodeOutput, which consists of
+ * array of encoded frames information for each frame and Encoding
+ * Statistics Information.
*/
- protected ArrayList<MediaCodec.BufferInfo> encode(
+ protected VideoEncodeOutput encode(
EncoderOutputStreamParameters streamParams,
ArrayList<ByteBuffer> codecConfigs) throws Exception {
ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+ EncodingStatisticsInfo encStatInfo = new EncodingStatisticsInfo();
Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
streamParams.frameHeight);
int bitrate = streamParams.bitrateSet[0];
@@ -1404,6 +1449,11 @@
int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
streamParams.frameRate;
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+ if (streamParams.encodingStatisticsLevel !=
+ MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_NONE) {
+ format.setInteger(MediaFormat.KEY_VIDEO_ENCODING_STATISTICS_LEVEL,
+ streamParams.encodingStatisticsLevel);
+ }
// Create encoder
Log.d(TAG, "Creating encoder " + properties.codecName +
@@ -1529,7 +1579,7 @@
ivf.close();
yuvStream.close();
- return bufferInfos;
+ return new VideoEncodeOutput(bufferInfos, encStatInfo);
}
/**
@@ -1546,9 +1596,11 @@
*
* @param streamParams Structure with encoder parameters
* @param codecConfigs List to be filled with codec config buffers
- * @return Returns array of encoded frames information for each frame.
+ * @return Returns VideoEncodeOutput, which consists of
+ * array of encoded frames information for each frame and Encoding
+ * Statistics Information.
*/
- protected ArrayList<MediaCodec.BufferInfo> encodeAsync(
+ protected VideoEncodeOutput encodeAsync(
EncoderOutputStreamParameters streamParams,
ArrayList<ByteBuffer> codecConfigs) throws Exception {
if (!streamParams.runInLooperThread) {
@@ -1556,6 +1608,7 @@
}
ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+ EncodingStatisticsInfo encStatInfo = new EncodingStatisticsInfo();
Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
streamParams.frameHeight);
int bitrate = streamParams.bitrateSet[0];
@@ -1584,7 +1637,11 @@
int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
streamParams.frameRate;
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-
+ if (streamParams.encodingStatisticsLevel !=
+ MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_NONE) {
+ format.setInteger(MediaFormat.KEY_VIDEO_ENCODING_STATISTICS_LEVEL,
+ MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_1);
+ }
// Create encoder
Log.d(TAG, "Creating encoder " + properties.codecName +
". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
@@ -1598,7 +1655,7 @@
MediaEncoderAsync codec = new MediaEncoderAsync();
MediaEncoderAsyncHelper helper = new MediaEncoderAsyncHelper(
- streamParams, properties, bufferInfos, ivf, codecConfigs);
+ streamParams, properties, bufferInfos, ivf, codecConfigs, encStatInfo);
codec.setAsyncHelper(helper);
codec.createCodec(0, properties.codecName, format,
@@ -1608,7 +1665,7 @@
codec.deleteCodec();
ivf.close();
- return bufferInfos;
+ return new VideoEncodeOutput(bufferInfos, encStatInfo);
}
/**
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java
new file mode 100644
index 0000000..10a18dd
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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.media.codec.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.cts.MediaCodecWrapper;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Verification test for video encoding statistics.
+ *
+ * Check whether a higher bitrate gives a lower average QP reported from encoder
+ *
+ */
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(Parameterized.class)
+public class VideoEncodingStatisticsTest extends VideoCodecTestBase {
+
+ private static final String ENCODED_IVF_BASE = "football";
+ private static final String INPUT_YUV = null;
+ private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
+ ENCODED_IVF_BASE + "_out.yuv";
+
+ // YUV stream properties.
+ private static final int WIDTH = 320;
+ private static final int HEIGHT = 240;
+ private static final int FPS = 30;
+ // Default encoding bitrate.
+ private static final int BITRATE = 400000;
+ // List of bitrates used in quality and basic bitrate tests.
+ private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
+
+ private static final String CODEC_PREFIX_KEY = "codec-prefix";
+ private static final String mCodecPrefix;
+
+ @Parameterized.Parameter(0)
+ public String mCodecName;
+
+ @Parameterized.Parameter(1)
+ public String mCodecMimeType;
+
+ @Parameterized.Parameter(2)
+ public int mBitRateMode;
+
+ static {
+ android.os.Bundle args = InstrumentationRegistry.getArguments();
+ mCodecPrefix = args.getString(CODEC_PREFIX_KEY);
+ }
+
+ static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
+ final List<Object[]> argsList = new ArrayList<>();
+ int argLength = exhaustiveArgsList.get(0).length;
+ for (Object[] arg : exhaustiveArgsList) {
+ String[] encodersForMime = MediaUtils.getEncoderNamesForMime((String) arg[0]);
+ for (String encoder : encodersForMime) {
+ if (mCodecPrefix != null && !encoder.startsWith(mCodecPrefix)) {
+ continue;
+ }
+ Object[] testArgs = new Object[argLength + 1];
+ testArgs[0] = encoder;
+ System.arraycopy(arg, 0, testArgs, 1, argLength);
+ argsList.add(testArgs);
+ }
+ }
+ return argsList;
+ }
+
+ @Parameterized.Parameters(name = "{index}({0}:{1}:{2})")
+ public static Collection<Object[]> input() {
+ final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+ {AVC_MIME, VIDEO_ControlRateConstant},
+ {AVC_MIME, VIDEO_ControlRateVariable},
+ {HEVC_MIME, VIDEO_ControlRateConstant},
+ {HEVC_MIME, VIDEO_ControlRateVariable},
+ });
+ return prepareParamList(exhaustiveArgsList);
+ }
+
+ private static CodecCapabilities getCodecCapabilities(
+ String encoderName, String mime, boolean isEncoder) {
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+ if (isEncoder != codecInfo.isEncoder()) {
+ continue;
+ }
+ if (encoderName.equals(codecInfo.getName())) {
+ return codecInfo.getCapabilitiesForType(mime);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check whethera a higher bitrate gives a lower average QP
+ *
+ * Video streams with higher bitrate should have lower average qp.
+ */
+ private void testEncStatRateAvgQp(String codecName, String codecMimeType, int bitRateMode)
+ throws Exception {
+ int encodeSeconds = 9; // Encoding sequence duration in seconds for each bitrate.
+ float[] avgSeqQp = new float[TEST_BITRATES_SET.length];
+ boolean[] completed = new boolean[TEST_BITRATES_SET.length];
+ boolean skipped = true;
+ ArrayList<MediaCodec.BufferInfo> bufInfos;
+
+ CodecCapabilities caps = getCodecCapabilities(codecName, codecMimeType, true);
+ Assume.assumeTrue(codecName + " does not support FEATURE_EncodingStatistics",
+ caps.isFeatureSupported(CodecCapabilities.FEATURE_EncodingStatistics));
+
+ for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
+ EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+ INPUT_YUV,
+ ENCODED_IVF_BASE,
+ codecName,
+ codecMimeType,
+ encodeSeconds,
+ WIDTH,
+ HEIGHT,
+ FPS,
+ bitRateMode,
+ TEST_BITRATES_SET[i],
+ true);
+ // Enable encoding statistics at VIDEO_ENCODING_STATISTICS_LEVEL_1
+ params.encodingStatisticsLevel = MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_1;
+ ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+ VideoEncodeOutput videoEncodeOutput = encode(params, codecConfigs);
+ bufInfos = videoEncodeOutput.bufferInfo;
+ if (bufInfos == null) {
+ // parameters not supported, try other bitrates
+ completed[i] = false;
+ continue;
+ }
+ completed[i] = true;
+ skipped = false;
+ if (videoEncodeOutput.encStat.encodedFrames > 0) {
+ avgSeqQp[i] = (float) videoEncodeOutput.encStat.averageSeqQp
+ / videoEncodeOutput.encStat.encodedFrames;
+ }
+ }
+
+ if (skipped) {
+ Log.i(TAG, "SKIPPING testEncodingStatisticsAvgQp(): no bitrates supported");
+ return;
+ }
+
+ // First do a validity check - higher bitrates should results in lower QP.
+ for (int i = 1; i < TEST_BITRATES_SET.length; i++) {
+ if (!completed[i]) {
+ continue;
+ }
+ for (int j = 0; j < i; j++) {
+ if (!completed[j]) {
+ continue;
+ }
+ double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
+ double differenceAvgQp = avgSeqQp[i] - avgSeqQp[j];
+ if (differenceBitrate * differenceAvgQp > 0) {
+ throw new RuntimeException("Target bitrates: " +
+ TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
+ ". Average QP: "
+ + avgSeqQp[j] + ", " + avgSeqQp[i]);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testEncodingStatisticsAvgQp() throws Exception {
+ testEncStatRateAvgQp(mCodecName, mCodecMimeType, mBitRateMode);
+ }
+}
+