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