Test VP8 behavior with VIDEO_BITRATE
During the encode process, request several different bitrates. Compare
the sizes of the resulting frames to ensure that the bitrate does
change.
Bug: 8422347
Change-Id: If4969ea8bf03b336bc3bb4333887b64781520cb9
diff --git a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
index 326c959..14e4055 100644
--- a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
@@ -86,6 +86,19 @@
}
/**
+ * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
+ *
+ * Run the sample multiple times. Request periodic changes to the
+ * bitrate and ensure the encoder responds.
+ */
+ public void testVariableBitrate() throws Exception {
+ encodeVariableBitrate(R.raw.video_176x144_yv12,
+ 176, // width
+ 144, // height
+ 30); // framerate
+ }
+
+ /**
* A basic check if an encoded stream is decodable.
*
* The most basic confirmation we can get about a frame
@@ -204,9 +217,6 @@
*/
private void encode(String outputFilename, int rawInputFd,
int frameWidth, int frameHeight, int frameRate) throws Exception {
- int frameSize = frameWidth * frameHeight * 3 / 2;
-
-
// Create a media format signifying desired output
MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
@@ -243,6 +253,10 @@
if (!sawInputEOS) {
int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
if (inputBufIndex >= 0) {
+ // YUV420 has 3 planes. Y is full size. U and V are each half size (1/4 the
+ // pixels).
+ int frameSize = frameWidth * frameHeight * 3 / 2;
+
byte[] frame = new byte[frameSize];
int bytesRead = rawStream.read(frame);
@@ -330,9 +344,6 @@
*/
private void encodeSyncFrame(int rawInputFd, int frameWidth,
int frameHeight, int frameRate) throws Exception {
- int frameSize = frameWidth * frameHeight * 3 / 2;
-
-
// Create a media format signifying desired output
MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
format.setInteger(MediaFormat.KEY_BIT_RATE, 100000);
@@ -368,6 +379,8 @@
if (!sawInputEOS) {
int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
if (inputBufIndex >= 0) {
+ int frameSize = frameWidth * frameHeight * 3 / 2;
+
byte[] frame = new byte[frameSize];
int bytesRead = rawStream.read(frame);
@@ -431,4 +444,166 @@
}
}
}
+
+
+ /**
+ * Adjust bitrate
+ *
+ * MediaCodec will raise an IllegalStateException
+ * whenever vp8 encoder fails to encode a frame.
+ *
+ * Encode the file three times: once at the initial bitrate, once at an
+ * increased bitrate, and once at a decreased bitrate. Record the frame
+ * sizes that are returned and verify a strict ordering.
+ *
+ * Color format of input file should be YUV420, and frameWidth,
+ * frameHeight should be supplied correctly as raw input file doesn't
+ * include any header data.
+ *
+ * @param rawInputFd File descriptor for the raw input file (YUV420)
+ * @param frameWidth Frame width of input file
+ * @param frameHeight Frame height of input file
+ * @param frameRate Frame rate of input file in frames per second
+ */
+ private void encodeVariableBitrate(int rawInputFd, int frameWidth,
+ int frameHeight, int frameRate) throws Exception {
+ // Create a media format signifying desired output
+ MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, frameWidth, frameHeight);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, 75000);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ CodecCapabilities.COLOR_FormatYUV420Planar);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+
+ Log.d(TAG, "Creating encoder");
+ MediaCodec encoder;
+ encoder = MediaCodec.createByCodecName(VPX_ENCODER_NAME);
+ encoder.configure(format,
+ null, // surface
+ null, // crypto
+ MediaCodec.CONFIGURE_FLAG_ENCODE);
+ encoder.start();
+
+ mInputBuffers = encoder.getInputBuffers();
+ mOutputBuffers = encoder.getOutputBuffers();
+
+ InputStream rawStream = null;
+
+ int iteration = 0;
+ int[] bits = new int[100];
+
+ try {
+ rawStream = mResources.openRawResource(rawInputFd);
+ /* Doc says this is not the default:
+ * http://developer.android.com/reference/java/io/InputStream.html#markSupported()
+ * but it returns true so using .reset() instead of close/open
+ */
+ if (rawStream.markSupported()) Log.d(TAG, "Stream marking supported");
+ rawStream.mark(1000000);
+
+ // encode loop
+ long presentationTimeUs = 0;
+ int inputFrameIndex = 0;
+ int outputFrameIndex = 0;
+ boolean sawInputEOS = false;
+ boolean sawOutputEOS = false;
+
+ while (!sawOutputEOS) {
+ if (!sawInputEOS) {
+ int inputBufIndex = encoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+ if (inputBufIndex >= 0) {
+ int frameSize = frameWidth * frameHeight * 3 / 2;
+
+ byte[] frame = new byte[frameSize];
+ int bytesRead = rawStream.read(frame);
+
+ if (bytesRead == -1) {
+ if (iteration < 2) {
+ rawStream.reset();
+ Bundle bitrate = new Bundle();
+ if (iteration == 0) {
+ bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 150000);
+ Log.d(TAG, "Setting bitrate to 150000");
+ } else {
+ bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 25000);
+ Log.d(TAG, "Setting bitrate to 25000");
+ }
+ encoder.setParameters(bitrate);
+
+ iteration++;
+ continue;
+ } else {
+ sawInputEOS = true;
+ bytesRead = 0;
+ }
+ }
+
+ mInputBuffers[inputBufIndex].clear();
+ mInputBuffers[inputBufIndex].put(frame);
+ mInputBuffers[inputBufIndex].rewind();
+
+ presentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
+ encoder.queueInputBuffer(
+ inputBufIndex,
+ 0, // offset
+ bytesRead, // size
+ presentationTimeUs,
+ sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+ inputFrameIndex++;
+ }
+ }
+
+ int result = encoder.dequeueOutputBuffer(mBufferInfo, DEFAULT_TIMEOUT_US);
+ if (result >= 0) {
+
+ bits[outputFrameIndex] = mBufferInfo.size;
+
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ sawOutputEOS = true;
+ }
+
+ encoder.releaseOutputBuffer(result,
+ false); // render
+
+ outputFrameIndex++;
+
+ } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ mOutputBuffers = encoder.getOutputBuffers();
+ }
+ }
+
+ // 29 frames per run
+ int i;
+ int sum = 0;
+ int frames = 29;
+ for(i = 0; i < frames; i++)
+ sum += bits[i];
+ int midBitrateAvg = sum / frames;
+
+ sum = 0;
+ for(; i < frames * 2; i++)
+ sum += bits[i];
+ int highBitrateAvg = sum / frames;
+
+ sum = 0;
+ for(; i < frames * 3; i++)
+ sum += bits[i];
+ int lowBitrateAvg = sum / frames;
+
+ // For the given bitrates we expect mid ~= 350, high ~= 575 and low ~= 150
+ // bytes per frame
+ if ((midBitrateAvg + 100) > highBitrateAvg)
+ throw new RuntimeException("Bitrate did not increase when requesting higher bitrate");
+ if ((lowBitrateAvg + 100) > midBitrateAvg)
+ throw new RuntimeException("Bitrate did not decrease when requesting lower bitrate");
+
+
+ encoder.stop();
+ encoder.release();
+ } finally {
+ if (rawStream != null) {
+ rawStream.close();
+ }
+ }
+ }
}