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