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