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();
+            }
+        }
+    }
 }