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