Adds a CTS test verifying audio encoder functionality. (AAC, AMR-NB, AMR-WB)

Change-Id: I53d75f875f26921cf11e99a966cd65da4de22807
diff --git a/tests/tests/media/src/android/media/cts/EncoderTest.java b/tests/tests/media/src/android/media/cts/EncoderTest.java
new file mode 100644
index 0000000..e9d0b5f
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/EncoderTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2012 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.cts;
+
+import com.android.cts.media.R;
+
+import android.content.Context;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+public class EncoderTest extends AndroidTestCase {
+    private static final String TAG = "EncoderTest";
+    private static final boolean VERBOSE = false;
+
+    private static final int kNumInputBytes = 256 * 1024;
+    private static final long kTimeoutUs = 10000;
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+    }
+
+    public void testAMRNBEncoders() {
+        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
+
+        final int kBitRates[] =
+            { 4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200 };
+
+        for (int j = 0; j < kBitRates.length; ++j) {
+            MediaFormat format  = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, "audio/3gpp");
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
+            formats.push(format);
+        }
+
+        testEncoderWithFormats("audio/3gpp", formats);
+    }
+
+    public void testAMRWBEncoders() {
+        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
+
+        final int kBitRates[] =
+            { 6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850 };
+
+        for (int j = 0; j < kBitRates.length; ++j) {
+            MediaFormat format  = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, "audio/amr-wb");
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
+            formats.push(format);
+        }
+
+        testEncoderWithFormats("audio/amr-wb", formats);
+    }
+
+    public void testAACEncoders() {
+        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
+
+        final int kAACProfiles[] = {
+            2 /* OMX_AUDIO_AACObjectLC */,
+            5 /* OMX_AUDIO_AACObjectHE */,
+            39 /* OMX_AUDIO_AACObjectELD */
+        };
+
+        final int kSampleRates[] = { 8000, 11025, 22050, 44100, 48000 };
+        final int kBitRates[] = { 64000, 128000 };
+
+        for (int k = 0; k < kAACProfiles.length; ++k) {
+            for (int i = 0; i < kSampleRates.length; ++i) {
+                if (kAACProfiles[k] == 5 && kSampleRates[i] < 22050) {
+                    // Is this right? HE does not support sample rates < 22050Hz?
+                    continue;
+                }
+                for (int j = 0; j < kBitRates.length; ++j) {
+                    for (int ch = 1; ch <= 2; ++ch) {
+                        MediaFormat format  = new MediaFormat();
+                        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
+
+                        format.setInteger(
+                                MediaFormat.KEY_AAC_PROFILE, kAACProfiles[k]);
+
+                        format.setInteger(
+                                MediaFormat.KEY_SAMPLE_RATE, kSampleRates[i]);
+
+                        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, ch);
+                        format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
+                        formats.push(format);
+                    }
+                }
+            }
+        }
+
+        testEncoderWithFormats("audio/mp4a-latm", formats);
+    }
+
+    private void testEncoderWithFormats(
+            String mime, List<MediaFormat> formats) {
+        List<String> componentNames = getEncoderNamesForType(mime);
+
+        for (String componentName : componentNames) {
+            Log.d(TAG, "testing component '" + componentName + "'");
+            for (MediaFormat format : formats) {
+                Log.d(TAG, "  testing format '" + format + "'");
+                assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+                testEncoder(componentName, format);
+            }
+        }
+    }
+
+    private List<String> getEncoderNamesForType(String mime) {
+        LinkedList<String> names = new LinkedList<String>();
+
+        int n = MediaCodecList.getCodecCount();
+        for (int i = 0; i < n; ++i) {
+            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+
+            if (!info.isEncoder()) {
+                continue;
+            }
+
+            if (!info.getName().startsWith("OMX.")) {
+                // Unfortunately for legacy reasons, "AACEncoder", a
+                // non OMX component had to be in this list for the video
+                // editor code to work... but it cannot actually be instantiated
+                // using MediaCodec.
+                Log.d(TAG, "skipping '" + info.getName() + "'.");
+                continue;
+            }
+
+            String[] supportedTypes = info.getSupportedTypes();
+
+            for (int j = 0; j < supportedTypes.length; ++j) {
+                if (supportedTypes[j].equalsIgnoreCase(mime)) {
+                    names.push(info.getName());
+                    break;
+                }
+            }
+        }
+
+        return names;
+    }
+
+    private int queueInputBuffer(
+            MediaCodec codec, ByteBuffer[] inputBuffers, int index) {
+        ByteBuffer buffer = inputBuffers[index];
+        buffer.clear();
+
+        int size = buffer.limit();
+
+        byte[] zeroes = new byte[size];
+        buffer.put(zeroes);
+
+        codec.queueInputBuffer(index, 0 /* offset */, size, 0 /* timeUs */, 0);
+
+        return size;
+    }
+
+    private void dequeueOutputBuffer(
+            MediaCodec codec, ByteBuffer[] outputBuffers,
+            int index, MediaCodec.BufferInfo info) {
+        codec.releaseOutputBuffer(index, false /* render */);
+    }
+
+    private void testEncoder(String componentName, MediaFormat format) {
+        MediaCodec codec = MediaCodec.createByCodecName(componentName);
+
+        try {
+            codec.configure(
+                    format,
+                    null /* surface */,
+                    null /* crypto */,
+                    MediaCodec.CONFIGURE_FLAG_ENCODE);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "codec '" + componentName + "' failed configuration.");
+
+            assertTrue("codec '" + componentName + "' failed configuration.", false);
+        }
+
+        codec.start();
+        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
+
+        int numBytesSubmitted = 0;
+        boolean doneSubmittingInput = false;
+        int numBytesDequeued = 0;
+
+        while (true) {
+            int index;
+
+            if (!doneSubmittingInput) {
+                index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
+
+                if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (numBytesSubmitted >= kNumInputBytes) {
+                        codec.queueInputBuffer(
+                                index,
+                                0 /* offset */,
+                                0 /* size */,
+                                0 /* timeUs */,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+
+                        if (VERBOSE) {
+                            Log.d(TAG, "queued input EOS.");
+                        }
+
+                        doneSubmittingInput = true;
+                    } else {
+                        int size = queueInputBuffer(
+                                codec, codecInputBuffers, index);
+
+                        numBytesSubmitted += size;
+
+                        if (VERBOSE) {
+                            Log.d(TAG, "queued " + size + " bytes of input data.");
+                        }
+                    }
+                }
+            }
+
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
+
+            if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
+            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+            } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+            } else {
+                dequeueOutputBuffer(codec, codecOutputBuffers, index, info);
+
+                numBytesDequeued += info.size;
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    if (VERBOSE) {
+                        Log.d(TAG, "dequeued output EOS.");
+                    }
+                    break;
+                }
+
+                if (VERBOSE) {
+                    Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
+                }
+            }
+        }
+
+        if (VERBOSE) {
+            Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
+                    + "dequeued " + numBytesDequeued + " bytes.");
+        }
+
+        int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+        int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+        int inBitrate = sampleRate * channelCount * 16;  // bit/sec
+        int outBitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+
+        float desiredRatio = (float)outBitrate / (float)inBitrate;
+        float actualRatio = (float)numBytesDequeued / (float)numBytesSubmitted;
+
+        if (actualRatio < 0.9 * desiredRatio || actualRatio > 1.1 * desiredRatio) {
+            Log.w(TAG, "desiredRatio = " + desiredRatio
+                    + ", actualRatio = " + actualRatio);
+        }
+
+        codec.release();
+        codec = null;
+    }
+}
+