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
setContext(Context context)66     public void setContext(Context context) {
67         super.setContext(context);
68     }
69 
testAMRNBEncoders()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 
testAMRWBEncoders()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 
testAACEncoders()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 
testEncoderWithFormats( String mime, List<MediaFormat> formatList)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 
queueInputBuffer( MediaCodec codec, ByteBuffer[] inputBuffers, int index, InputStream istream, int mode, long timeUs, Random random)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 
dequeueOutputBuffer( MediaCodec codec, ByteBuffer[] outputBuffers, int index, MediaCodec.BufferInfo info)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 
EncoderRun(String componentName, MediaFormat format)243         EncoderRun(String componentName, MediaFormat format) {
244             mComponentName = componentName;
245             mFormat = format;
246         }
247         @Override
run()248         public void run() {
249             testEncoder(mComponentName, mFormat);
250         }
251     }
252 
testEncoder(String componentName, MediaFormat format)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 
testEncoder(String componentName, MediaFormat format, long startSeed, int resid, int mode)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