Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media.cts;
     18 
     19 import android.media.cts.R;
     20 
     21 import android.content.Context;
     22 import android.media.MediaCodec;
     23 import android.media.MediaCodecInfo;
     24 import android.media.MediaCodecList;
     25 import android.media.MediaFormat;
     26 import android.media.MediaMuxer;
     27 import android.test.AndroidTestCase;
     28 import android.util.Log;
     29 
     30 import com.android.compatibility.common.util.MediaUtils;
     31 
     32 import java.io.File;
     33 import java.io.InputStream;
     34 import java.nio.BufferOverflowException;
     35 import java.nio.ByteBuffer;
     36 import java.util.Arrays;
     37 import java.util.LinkedList;
     38 import java.util.List;
     39 import java.util.Random;
     40 import java.util.concurrent.ExecutorService;
     41 import java.util.concurrent.Executors;
     42 import java.util.concurrent.TimeUnit;
     43 
     44 public class EncoderTest extends AndroidTestCase {
     45     private static final String TAG = "EncoderTest";
     46     private static final boolean VERBOSE = false;
     47 
     48     private static final int kNumInputBytes = 512 * 1024;
     49     private static final long kTimeoutUs = 100;
     50 
     51     // not all combinations are valid
     52     private static final int MODE_SILENT = 0;
     53     private static final int MODE_RANDOM = 1;
     54     private static final int MODE_RESOURCE = 2;
     55     private static final int MODE_QUIET = 4;
     56     private static final int MODE_SILENTLEAD = 8;
     57 
     58     /*
     59      * Set this to true to save the encoding results to /data/local/tmp
     60      * You will need to make /data/local/tmp writeable, run "setenforce 0",
     61      * and remove files left from a previous run.
     62      */
     63     private static boolean sSaveResults = false;
     64 
     65     @Override
     66     public void setContext(Context context) {
     67         super.setContext(context);
     68     }
     69 
     70     public void testAMRNBEncoders() {
     71         LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
     72 
     73         final int kBitRates[] =
     74             { 4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200 };
     75 
     76         for (int j = 0; j < kBitRates.length; ++j) {
     77             MediaFormat format  = new MediaFormat();
     78             format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
     79             format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
     80             format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
     81             format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
     82             formats.push(format);
     83         }
     84 
     85         testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_NB, formats);
     86     }
     87 
     88     public void testAMRWBEncoders() {
     89         LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
     90 
     91         final int kBitRates[] =
     92             { 6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850 };
     93 
     94         for (int j = 0; j < kBitRates.length; ++j) {
     95             MediaFormat format  = new MediaFormat();
     96             format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_WB);
     97             format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
     98             format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
     99             format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
    100             formats.push(format);
    101         }
    102 
    103         testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_WB, formats);
    104     }
    105 
    106     public void testAACEncoders() {
    107         LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
    108 
    109         final int kAACProfiles[] = {
    110             2 /* OMX_AUDIO_AACObjectLC */,
    111             5 /* OMX_AUDIO_AACObjectHE */,
    112             39 /* OMX_AUDIO_AACObjectELD */
    113         };
    114 
    115         final int kSampleRates[] = { 8000, 11025, 22050, 44100, 48000 };
    116         final int kBitRates[] = { 64000, 128000 };
    117 
    118         for (int k = 0; k < kAACProfiles.length; ++k) {
    119             for (int i = 0; i < kSampleRates.length; ++i) {
    120                 if (kAACProfiles[k] == 5 && kSampleRates[i] < 22050) {
    121                     // Is this right? HE does not support sample rates < 22050Hz?
    122                     continue;
    123                 }
    124                 for (int j = 0; j < kBitRates.length; ++j) {
    125                     for (int ch = 1; ch <= 2; ++ch) {
    126                         MediaFormat format  = new MediaFormat();
    127                         format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
    128 
    129                         format.setInteger(
    130                                 MediaFormat.KEY_AAC_PROFILE, kAACProfiles[k]);
    131 
    132                         format.setInteger(
    133                                 MediaFormat.KEY_SAMPLE_RATE, kSampleRates[i]);
    134 
    135                         format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, ch);
    136                         format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
    137                         formats.push(format);
    138                     }
    139                 }
    140             }
    141         }
    142 
    143         testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AAC, formats);
    144     }
    145 
    146     private void testEncoderWithFormats(
    147             String mime, List<MediaFormat> formatList) {
    148         MediaFormat[] formats = formatList.toArray(new MediaFormat[formatList.size()]);
    149         String[] componentNames = MediaUtils.getEncoderNames(formats);
    150         if (componentNames.length == 0) {
    151             MediaUtils.skipTest("no encoders found for " + Arrays.toString(formats));
    152             return;
    153         }
    154         ExecutorService pool = Executors.newFixedThreadPool(3);
    155 
    156         for (String componentName : componentNames) {
    157             for (MediaFormat format : formats) {
    158                 assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
    159                 pool.execute(new EncoderRun(componentName, format));
    160             }
    161         }
    162         try {
    163             pool.shutdown();
    164             assertTrue("timed out waiting for encoder threads",
    165                     pool.awaitTermination(10, TimeUnit.MINUTES));
    166         } catch (InterruptedException e) {
    167             fail("interrupted while waiting for encoder threads");
    168         }
    169     }
    170 
    171     // See bug 25843966
    172     private long[] mBadSeeds = {
    173             101833462733980l, // fail @ 23680 in all-random mode
    174             273262699095706l, // fail @ 58880 in all-random mode
    175             137295510492957l, // fail @ 35840 in zero-lead mode
    176             57821391502855l,  // fail @ 32000 in zero-lead mode
    177     };
    178 
    179     private int queueInputBuffer(
    180             MediaCodec codec, ByteBuffer[] inputBuffers, int index,
    181             InputStream istream, int mode, long timeUs, Random random) {
    182         ByteBuffer buffer = inputBuffers[index];
    183         buffer.rewind();
    184         int size = buffer.limit();
    185 
    186         if ((mode & MODE_RESOURCE) != 0 && istream != null) {
    187             while (buffer.hasRemaining()) {
    188                 try {
    189                     int next = istream.read();
    190                     if (next < 0) {
    191                         break;
    192                     }
    193                     buffer.put((byte) next);
    194                 } catch (Exception ex) {
    195                     Log.i(TAG, "caught exception writing: " + ex);
    196                     break;
    197                 }
    198             }
    199         } else if ((mode & MODE_RANDOM) != 0) {
    200             if ((mode & MODE_SILENTLEAD) != 0) {
    201                 buffer.putInt(0);
    202                 buffer.putInt(0);
    203                 buffer.putInt(0);
    204                 buffer.putInt(0);
    205             }
    206             while (true) {
    207                 try {
    208                     int next = random.nextInt();
    209                     buffer.putInt(random.nextInt());
    210                 } catch (BufferOverflowException ex) {
    211                     break;
    212                 }
    213             }
    214         } else {
    215             byte[] zeroes = new byte[size];
    216             buffer.put(zeroes);
    217         }
    218 
    219         if ((mode & MODE_QUIET) != 0) {
    220             int n = buffer.limit();
    221             for (int i = 0; i < n; i += 2) {
    222                 short s = buffer.getShort(i);
    223                 s /= 8;
    224                 buffer.putShort(i, s);
    225             }
    226         }
    227 
    228         codec.queueInputBuffer(index, 0 /* offset */, size, timeUs, 0 /* flags */);
    229 
    230         return size;
    231     }
    232 
    233     private void dequeueOutputBuffer(
    234             MediaCodec codec, ByteBuffer[] outputBuffers,
    235             int index, MediaCodec.BufferInfo info) {
    236         codec.releaseOutputBuffer(index, false /* render */);
    237     }
    238 
    239     class EncoderRun implements Runnable {
    240         String mComponentName;
    241         MediaFormat mFormat;
    242 
    243         EncoderRun(String componentName, MediaFormat format) {
    244             mComponentName = componentName;
    245             mFormat = format;
    246         }
    247         @Override
    248         public void run() {
    249             testEncoder(mComponentName, mFormat);
    250         }
    251     }
    252 
    253     private void testEncoder(String componentName, MediaFormat format) {
    254         Log.i(TAG, "testEncoder " + componentName + "/" + format);
    255         // test with all zeroes/silence
    256         testEncoder(componentName, format, 0, -1, MODE_SILENT);
    257 
    258         // test with pcm input file
    259         testEncoder(componentName, format, 0, R.raw.okgoogle123_good, MODE_RESOURCE);
    260         testEncoder(componentName, format, 0, R.raw.okgoogle123_good, MODE_RESOURCE | MODE_QUIET);
    261         testEncoder(componentName, format, 0, R.raw.tones, MODE_RESOURCE);
    262         testEncoder(componentName, format, 0, R.raw.tones, MODE_RESOURCE | MODE_QUIET);
    263 
    264         // test with random data, with and without a few leading zeroes
    265         for (int i = 0; i < mBadSeeds.length; i++) {
    266             testEncoder(componentName, format, mBadSeeds[i], -1, MODE_RANDOM);
    267             testEncoder(componentName, format, mBadSeeds[i], -1, MODE_RANDOM | MODE_SILENTLEAD);
    268         }
    269     }
    270 
    271     private void testEncoder(String componentName, MediaFormat format,
    272             long startSeed, int resid, int mode) {
    273 
    274         Log.i(TAG, "testEncoder " + componentName + "/" + mode + "/" + format);
    275         int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
    276         int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
    277         int inBitrate = sampleRate * channelCount * 16;  // bit/sec
    278         int outBitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
    279 
    280         MediaMuxer muxer = null;
    281         int muxidx = -1;
    282         if (sSaveResults) {
    283             try {
    284                 String outFile = "/data/local/tmp/transcoded-" + componentName +
    285                         "-" + sampleRate + "Hz-" + channelCount + "ch-" + outBitrate +
    286                         "bps-" + mode + "-" + resid + "-" + startSeed + "-" +
    287                         (android.os.Process.is64Bit() ? "64bit" : "32bit") + ".mp4";
    288                 new File("outFile").delete();
    289                 muxer = new MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
    290                 // The track can't be added until we have the codec specific data
    291             } catch (Exception e) {
    292                 Log.i(TAG, "couldn't create muxer: " + e);
    293             }
    294         }
    295 
    296         InputStream istream = null;
    297         if ((mode & MODE_RESOURCE) != 0) {
    298             istream = mContext.getResources().openRawResource(resid);
    299         }
    300 
    301         Random random = new Random(startSeed);
    302         MediaCodec codec;
    303         try {
    304             codec = MediaCodec.createByCodecName(componentName);
    305         } catch (Exception e) {
    306             fail("codec '" + componentName + "' failed construction.");
    307             return; /* does not get here, but avoids warning */
    308         }
    309         try {
    310             codec.configure(
    311                     format,
    312                     null /* surface */,
    313                     null /* crypto */,
    314                     MediaCodec.CONFIGURE_FLAG_ENCODE);
    315         } catch (IllegalStateException e) {
    316             fail("codec '" + componentName + "' failed configuration.");
    317         }
    318 
    319         codec.start();
    320         ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
    321         ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
    322 
    323         int numBytesSubmitted = 0;
    324         boolean doneSubmittingInput = false;
    325         int numBytesDequeued = 0;
    326 
    327         while (true) {
    328             int index;
    329 
    330             if (!doneSubmittingInput) {
    331                 index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
    332 
    333                 if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
    334                     long timeUs =
    335                             (long)numBytesSubmitted * 1000000 / (2 * channelCount * sampleRate);
    336                     if (numBytesSubmitted >= kNumInputBytes) {
    337                         codec.queueInputBuffer(
    338                                 index,
    339                                 0 /* offset */,
    340                                 0 /* size */,
    341                                 timeUs,
    342                                 MediaCodec.BUFFER_FLAG_END_OF_STREAM);
    343 
    344                         if (VERBOSE) {
    345                             Log.d(TAG, "queued input EOS.");
    346                         }
    347 
    348                         doneSubmittingInput = true;
    349                     } else {
    350                         int size = queueInputBuffer(
    351                                 codec, codecInputBuffers, index, istream, mode, timeUs, random);
    352 
    353                         numBytesSubmitted += size;
    354 
    355                         if (VERBOSE) {
    356                             Log.d(TAG, "queued " + size + " bytes of input data.");
    357                         }
    358                     }
    359                 }
    360             }
    361 
    362             MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    363             index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
    364 
    365             if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
    366             } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    367             } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
    368                 codecOutputBuffers = codec.getOutputBuffers();
    369             } else {
    370                 if (muxer != null) {
    371                     ByteBuffer buffer = codec.getOutputBuffer(index);
    372                     if (muxidx < 0) {
    373                         MediaFormat trackFormat = codec.getOutputFormat();
    374                         muxidx = muxer.addTrack(trackFormat);
    375                         muxer.start();
    376                     }
    377                     muxer.writeSampleData(muxidx, buffer, info);
    378                 }
    379 
    380                 dequeueOutputBuffer(codec, codecOutputBuffers, index, info);
    381 
    382                 numBytesDequeued += info.size;
    383 
    384                 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
    385                     if (VERBOSE) {
    386                         Log.d(TAG, "dequeued output EOS.");
    387                     }
    388                     break;
    389                 }
    390 
    391                 if (VERBOSE) {
    392                     Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
    393                 }
    394             }
    395         }
    396 
    397         if (VERBOSE) {
    398             Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
    399                     + "dequeued " + numBytesDequeued + " bytes.");
    400         }
    401 
    402         float desiredRatio = (float)outBitrate / (float)inBitrate;
    403         float actualRatio = (float)numBytesDequeued / (float)numBytesSubmitted;
    404 
    405         if (actualRatio < 0.9 * desiredRatio || actualRatio > 1.1 * desiredRatio) {
    406             Log.w(TAG, "desiredRatio = " + desiredRatio
    407                     + ", actualRatio = " + actualRatio);
    408         }
    409 
    410         codec.release();
    411         codec = null;
    412         if (muxer != null) {
    413             muxer.stop();
    414             muxer.release();
    415             muxer = null;
    416         }
    417     }
    418 }
    419 
    420