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