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
setContext(Context context)68     public void setContext(Context context) {
69         super.setContext(context);
70     }
71 
testAMRNBEncoders()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 
testAMRWBEncoders()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 
testOpusEncoders()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 
testAACEncoders()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 
testEncoderWithFormats( String mime, List<MediaFormat> formatList)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 
queueInputBuffer( MediaCodec codec, ByteBuffer[] inputBuffers, int index, InputStream istream, int mode, long timeUs, Random random)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 
dequeueOutputBuffer( MediaCodec codec, ByteBuffer[] outputBuffers, int index, MediaCodec.BufferInfo info)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 
EncoderRun(String componentName, MediaFormat format)273         EncoderRun(String componentName, MediaFormat format) {
274             mComponentName = componentName;
275             mFormat = format;
276         }
277         @Override
run()278         public void run() {
279             testEncoder(mComponentName, mFormat);
280         }
281     }
282 
testEncoder(String componentName, MediaFormat format)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 
testEncoder(String componentName, MediaFormat format, long startSeed, int resid, int mode)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