1 /* 2 * Copyright (C) 2016 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.decoder.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertTrue; 22 import static org.junit.Assert.fail; 23 24 import android.app.Instrumentation; 25 import android.content.res.AssetFileDescriptor; 26 import android.content.res.Resources; 27 import android.media.MediaCodec; 28 import android.media.MediaExtractor; 29 import android.media.MediaFormat; 30 import android.media.decoder.cts.DecoderTest.AudioParameter; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.platform.test.annotations.AppModeFull; 34 import android.util.Log; 35 36 import androidx.test.InstrumentationRegistry; 37 38 import com.android.compatibility.common.util.ApiLevelUtil; 39 import com.android.compatibility.common.util.MediaUtils; 40 41 import org.junit.Before; 42 import org.junit.Test; 43 44 import java.io.IOException; 45 import java.nio.ByteBuffer; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.List; 49 50 @AppModeFull(reason = "DecoderTest is non-instant") 51 public class DecoderTestAacDrc { 52 private static final String TAG = "DecoderTestAacDrc"; 53 54 private static final boolean sIsAndroidRAndAbove = 55 ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R); 56 57 private Resources mResources; 58 59 @Before setUp()60 public void setUp() throws Exception { 61 final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); 62 assertNotNull(inst); 63 mResources = inst.getContext().getResources(); 64 } 65 66 /** 67 * Verify correct decoding of MPEG-4 AAC with output level normalization to -23dBFS. 68 */ 69 @Test testDecodeAacDrcLevelM4a()70 public void testDecodeAacDrcLevelM4a() throws Exception { 71 AudioParameter decParams = new AudioParameter(); 72 // full boost, full cut, target ref level: -23dBFS, heavy compression: no 73 DrcParams drcParams = new DrcParams(127, 127, 92, 0); 74 short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drclevel_mp4, 75 -1, null, drcParams, null /*decoderName: use default decoder*/); 76 DecoderTest decTester = new DecoderTest(); 77 decTester.checkEnergy(decSamples, decParams, 2, 0.70f); 78 } 79 80 /** 81 * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata. 82 * Fully apply light compression DRC (default settings). 83 */ 84 @Test testDecodeAacDrcFullM4a()85 public void testDecodeAacDrcFullM4a() throws Exception { 86 AudioParameter decParams = new AudioParameter(); 87 short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcfull_mp4, 88 -1, null, null, null /*decoderName: use default decoder*/); 89 DecoderTest decTester = new DecoderTest(); 90 decTester.checkEnergy(decSamples, decParams, 2, 0.80f); 91 } 92 93 /** 94 * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata. 95 * Apply only half of the light compression DRC and normalize to -20dBFS output level. 96 */ 97 @Test testDecodeAacDrcHalfM4a()98 public void testDecodeAacDrcHalfM4a() throws Exception { 99 AudioParameter decParams = new AudioParameter(); 100 // half boost, half cut, target ref level: -20dBFS, heavy compression: no 101 DrcParams drcParams = new DrcParams(63, 63, 80, 0); 102 short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_drchalf_mp4, 103 -1, null, drcParams, null /*decoderName: use default decoder*/); 104 DecoderTest decTester = new DecoderTest(); 105 decTester.checkEnergy(decSamples, decParams, 2, 0.80f); 106 } 107 108 /** 109 * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata. 110 * Disable light compression DRC to test if MediaFormat keys reach the decoder. 111 */ 112 @Test testDecodeAacDrcOffM4a()113 public void testDecodeAacDrcOffM4a() throws Exception { 114 AudioParameter decParams = new AudioParameter(); 115 // no boost, no cut, target ref level: -16dBFS, heavy compression: no 116 DrcParams drcParams = new DrcParams(0, 0, 64, 0); // normalize to -16dBFS 117 short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcoff_mp4, 118 -1, null, drcParams, null /*decoderName: use default decoder*/); 119 DecoderTest decTester = new DecoderTest(); 120 decTester.checkEnergy(decSamples, decParams, 2, 0.80f); 121 } 122 123 /** 124 * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata. 125 * Apply heavy compression gains and normalize to -16dBFS output level. 126 */ 127 @Test testDecodeAacDrcHeavyM4a()128 public void testDecodeAacDrcHeavyM4a() throws Exception { 129 AudioParameter decParams = new AudioParameter(); 130 // full boost, full cut, target ref level: -16dBFS, heavy compression: yes 131 DrcParams drcParams = new DrcParams(127, 127, 64, 1); 132 short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_drcheavy_mp4, 133 -1, null, drcParams, null /*decoderName: use default decoder*/); 134 DecoderTest decTester = new DecoderTest(); 135 decTester.checkEnergy(decSamples, decParams, 2, 0.80f); 136 } 137 138 /** 139 * Test signal limiting (without clipping) of MPEG-4 AAC decoder with the help of DRC metadata. 140 * Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input. 141 */ 142 @Test testDecodeAacDrcClipM4a()143 public void testDecodeAacDrcClipM4a() throws Exception { 144 AudioParameter decParams = new AudioParameter(); 145 short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcclip_mp4, 146 -1, null, null, null /*decoderName: use default decoder*/); 147 checkClipping(decSamples, decParams, 248.0f /* Hz */); 148 } 149 150 /** 151 * Test if there is decoder internal clipping of MPEG-4 AAC decoder. 152 * Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input. 153 */ 154 @Test testDecodeAacInternalClipM4a()155 public void testDecodeAacInternalClipM4a() throws Exception { 156 if (!MediaUtils.check(sIsAndroidRAndAbove, "Internal clipping fixed in Android R")) 157 return; 158 AudioParameter decParams = new AudioParameter(); 159 short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_internalclip_mp4, 160 -1, null, null, null /*decoderName: use default decoder*/); 161 checkClipping(decSamples, decParams, 248.0f /* Hz */); 162 } 163 164 /** 165 * Default decoder target level. 166 * The actual default value used by the decoder can differ between platforms, or even devices, 167 * but tests will measure energy relative to this value. 168 */ 169 public static final int DEFAULT_DECODER_TARGET_LEVEL = 64; // -16.0 dBFs 170 171 /** 172 * Test USAC decoder with different target loudness levels 173 */ 174 @Test testDecodeUsacLoudnessM4a()175 public void testDecodeUsacLoudnessM4a() throws Exception { 176 Log.v(TAG, "START testDecodeUsacLoudnessM4a"); 177 178 ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames(); 179 assertTrue("No AAC decoder found", aacDecoderNames.size() > 0); 180 181 for (String aacDecName : aacDecoderNames) { 182 // test default loudness 183 // decoderTargetLevel = 64 --> target output level = -16.0 dBFs 184 try { 185 checkUsacLoudness(DEFAULT_DECODER_TARGET_LEVEL, 1, 1.0f, aacDecName); 186 } catch (Exception e) { 187 Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " + 188 aacDecName); 189 throw new RuntimeException(e); 190 } 191 192 // test loudness boost 193 // decoderTargetLevel = 40 --> target output level = -10.0 dBFs 194 // normFactor = 1/(10^(-6/10)) = 3.98f 195 // where "-6" is the difference between the default level (-16), and -10 for this test 196 try { 197 checkUsacLoudness(40, 1, (float)(1.0f/Math.pow(10.0f, -6.0f/10.0f)), aacDecName); 198 } catch (Exception e) { 199 Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness boost failed for " + aacDecName); 200 throw new RuntimeException(e); 201 } 202 203 // test loudness attenuation 204 // decoderTargetLevel = 96 --> target output level = -24.0 dBFs 205 // normFactor = 1/(10^(8/10)) = 0.15f 206 // where 8 is the difference between the default level (-16), and -24 for this test 207 try { 208 checkUsacLoudness(96, 0, (float)(1.0f/Math.pow(10.0f, 8.0f/10.0f)), aacDecName); 209 } catch (Exception e) { 210 Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for " 211 + aacDecName); 212 throw new RuntimeException(e); 213 } 214 215 if (sIsAndroidRAndAbove) { 216 // test loudness normalization off 217 // decoderTargetLevel = -1 --> target output level = -19.0 dBFs (program loudness of 218 // waveform) 219 // normFactor = 1/(10^(3/10)) = 0.5f 220 // where 3 is the difference between the default level (-16), and -19 for this test 221 try { 222 checkUsacLoudness(-1, 0, (float) (1.0f / Math.pow(10.0f, 3.0f / 10.0f)), 223 aacDecName); 224 } catch (Exception e) { 225 Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for " 226 + aacDecName); 227 throw new RuntimeException(e); 228 } 229 } 230 } 231 } 232 233 /** 234 * Verify that the correct output loudness values are returned by the MPEG-4 AAC decoder 235 */ 236 @Test testDecodeAacDrcOutputLoudnessM4a()237 public void testDecodeAacDrcOutputLoudnessM4a() throws Exception { 238 Log.v(TAG, "START testDecodeAacDrcOutputLoudnessM4a"); 239 240 ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames(); 241 assertTrue("No AAC decoder found", aacDecoderNames.size() > 0); 242 243 for (String aacDecName : aacDecoderNames) { 244 // test drc output loudness 245 // testfile without loudness metadata and loudness normalization off 246 // -> expected value: -1 247 try { 248 checkAacDrcOutputLoudness( 249 R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, -1, -1, aacDecName); 250 } catch (Exception e) { 251 Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " + 252 aacDecName); 253 throw new RuntimeException(e); 254 } 255 // test drc output loudness 256 // testfile without loudness metadata and loudness normalization on 257 // -> expected value: -1 258 try { 259 checkAacDrcOutputLoudness( 260 R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, 70, -1, aacDecName); 261 } catch (Exception e) { 262 Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " + 263 aacDecName); 264 throw new RuntimeException(e); 265 } 266 // test drc output loudness 267 // testfile with MPEG-4 DRC loudness metadata and loudness normalization off 268 // -> expected value: loudness metadata in bitstream (-16*-4 = 64) 269 try { 270 checkAacDrcOutputLoudness( 271 R.raw.sine_2ch_48khz_aot2_drchalf_mp4, -1, 64, aacDecName); 272 } catch (Exception e) { 273 Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " + 274 aacDecName); 275 throw new RuntimeException(e); 276 } 277 // test drc output loudness 278 // testfile with MPEG-4 DRC loudness metadata and loudness normalization off 279 // -> expected value: loudness metadata in bitstream (-31*-4 = 124) 280 try { 281 checkAacDrcOutputLoudness( 282 R.raw.sine_2ch_48khz_aot5_drcclip_mp4, -1, 124, aacDecName); 283 } catch (Exception e) { 284 Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " + 285 aacDecName); 286 throw new RuntimeException(e); 287 } 288 // test drc output loudness 289 // testfile with MPEG-4 DRC loudness metadata and loudness normalization on 290 // -> expected value: target loudness value (85) 291 try { 292 checkAacDrcOutputLoudness( 293 R.raw.sine_2ch_48khz_aot5_drcclip_mp4, 85, 85, aacDecName); 294 } catch (Exception e) { 295 Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " + 296 aacDecName); 297 throw new RuntimeException(e); 298 } 299 } 300 } 301 302 /** 303 * Internal utilities 304 */ 305 306 /** 307 * The test routine performs a THD+N (Total Harmonic Distortion + Noise) analysis on a given 308 * audio signal (decSamples). The THD+N value is defined here as harmonic distortion (+ noise) 309 * RMS over full signal RMS. 310 * 311 * After the energy measurement of the unprocessed signal the routine creates and applies a 312 * notch filter at the given frequency (sineFrequency). Afterwards the signal energy is 313 * measured again. Then the THD+N value is calculated as the ratio of the filtered and the full 314 * signal energy. 315 * 316 * The test passes if the THD+N value is lower than -60 dB. Otherwise it fails. 317 * 318 * @param decSamples the decoded audio samples to be tested 319 * @param decParams the audio parameters of the given audio samples (decSamples) 320 * @param sineFrequency frequency of the test signal tone used for testing 321 * @throws RuntimeException 322 */ checkClipping(short[] decSamples, AudioParameter decParams, float sineFrequency)323 private void checkClipping(short[] decSamples, AudioParameter decParams, float sineFrequency) 324 throws RuntimeException 325 { 326 final double threshold_clipping = -60.0; // dB 327 final int numChannels = decParams.getNumChannels(); 328 final int startSample = 2 * 2048 * numChannels; // exclude signal on- & offset to 329 final int stopSample = decSamples.length - startSample; // ... measure only the stationary 330 // ... sine tone 331 // get full energy of signal (all channels) 332 double nrgFull = getEnergy(decSamples, startSample, stopSample); 333 334 // create notch filter to suppress sine-tone at 248 Hz 335 Biquad filter = new Biquad(sineFrequency, decParams.getSamplingRate()); 336 for (int channel = 0; channel < numChannels; channel++) { 337 // apply notch-filter on buffer for each channel to filter out the sine tone. 338 // only the harmonics (and noise) remain. */ 339 filter.apply(decSamples, channel, numChannels); 340 } 341 342 // get energy of harmonic distortion (signal without sine-tone) 343 double nrgHd = getEnergy(decSamples, startSample, stopSample); 344 345 // Total Harmonic Distortion + Noise, defined here as harmonic distortion (+ noise) RMS 346 // over full signal RMS, given in dB 347 double THDplusN = 10 * Math.log10(nrgHd / nrgFull); 348 assertTrue("signal has clipping samples", THDplusN <= threshold_clipping); 349 } 350 351 /** 352 * Measure the energy of a given signal over all channels within a given signal range. 353 * @param signal audio signal samples 354 * @param start start offset of the measuring range 355 * @param stop stop sample which is the last sample of the measuring range 356 * @return the signal energy in the given range 357 */ getEnergy(short[] signal, int start, int stop)358 private double getEnergy(short[] signal, int start, int stop) { 359 double nrg = 0.0; 360 for (int sample = start; sample < stop; sample++) { 361 double v = signal[sample]; 362 nrg += v * v; 363 } 364 return nrg; 365 } 366 367 // Notch filter implementation 368 private class Biquad { 369 // filter coefficients for biquad filter (2nd order IIR filter) 370 float[] a; 371 float[] b; 372 // filter states 373 float[] state_ff; 374 float[] state_fb; 375 376 protected float alpha = 0.95f; 377 Biquad(float f_notch, float f_s)378 public Biquad(float f_notch, float f_s) { 379 // Create filter coefficients of notch filter which suppresses a sine tone with f_notch 380 // Hz at sampling frequency f_s. Zeros placed at unit circle at f_notch, poles placed 381 // nearby the unit circle at f_notch. 382 state_ff = new float[2]; 383 state_fb = new float[2]; 384 state_ff[0] = state_ff[1] = state_fb[0] = state_fb[1] = 0.0f; 385 386 a = new float[3]; 387 b = new float[3]; 388 double omega = 2.0 * Math.PI * f_notch / f_s; 389 a[0] = b[0] = b[2] = 1.0f; 390 a[1] = -2.0f * alpha * (float)Math.cos(omega); 391 a[2] = alpha * alpha; 392 b[1] = -2.0f * (float)Math.cos(omega); 393 } 394 apply(short[] signal, int offset, int stride)395 public void apply(short[] signal, int offset, int stride) { 396 // reset states 397 state_ff[0] = state_ff[1] = 0.0f; 398 state_fb[0] = state_fb[1] = 0.0f; 399 // process 2nd order IIR filter in Direct Form I 400 float x_0, x_1, x_2, y_0, y_1, y_2; 401 x_2 = state_ff[0]; // x[n-2] 402 x_1 = state_ff[1]; // x[n-1] 403 y_2 = state_fb[0]; // y[n-2] 404 y_1 = state_fb[1]; // y[n-1] 405 for (int sample = offset; sample < signal.length; sample += stride) { 406 x_0 = signal[sample]; 407 y_0 = b[0] * x_0 + b[1] * x_1 + b[2] * x_2 408 - a[1] * y_1 - a[2] * y_2; 409 x_2 = x_1; 410 x_1 = x_0; 411 y_2 = y_1; 412 y_1 = y_0; 413 signal[sample] = (short)y_0; 414 } 415 state_ff[0] = x_2; // next x[n-2] 416 state_ff[1] = x_1; // next x[n-1] 417 state_fb[0] = y_2; // next y[n-2] 418 state_fb[1] = y_1; // next y[n-1] 419 } 420 } 421 422 /** 423 * USAC test DRC loudness 424 */ checkUsacLoudness(int decoderTargetLevel, int heavy, float normFactor, String decoderName)425 private void checkUsacLoudness(int decoderTargetLevel, int heavy, float normFactor, 426 String decoderName) throws Exception { 427 for (boolean runtimeChange : new boolean[] {false, true}) { 428 if (runtimeChange && !sIsAndroidRAndAbove) { 429 // changing decoder configuration after it has been initialized requires R and above 430 continue; 431 } 432 AudioParameter decParams = new AudioParameter(); 433 DrcParams drcParams_def = new DrcParams(127, 127, DEFAULT_DECODER_TARGET_LEVEL, 1); 434 DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, heavy); 435 436 short[] decSamples_def = decodeToMemory(decParams, 437 R.raw.noise_2ch_48khz_aot42_19_lufs_mp4, 438 -1, null, drcParams_def, decoderName); 439 short[] decSamples_test = decodeToMemory(decParams, 440 R.raw.noise_2ch_48khz_aot42_19_lufs_mp4, 441 -1, null, drcParams_test, decoderName, runtimeChange); 442 443 DecoderTestXheAac decTesterXheAac = new DecoderTestXheAac(); 444 float[] nrg_def = decTesterXheAac.checkEnergyUSAC(decSamples_def, decParams, 2, 1); 445 float[] nrg_test = decTesterXheAac.checkEnergyUSAC(decSamples_test, decParams, 2, 1); 446 447 float[] nrgThreshold = {2602510595620.0f, 2354652443657.0f}; 448 449 // Check default loudness behavior 450 if (nrg_def[0] > nrgThreshold[0] || nrg_def[0] < nrgThreshold[1]) { 451 throw new Exception("Default loudness behavior not as expected"); 452 } 453 454 float nrgRatio = nrg_def[0]/nrg_test[0]; 455 456 // Check for loudness boost/attenuation if decoderTargetLevel deviates from default value 457 // used in these tests (note that the default target level can change from platform 458 // to platform, or device to device) 459 if (decoderTargetLevel != -1) { 460 if ((decoderTargetLevel < DEFAULT_DECODER_TARGET_LEVEL) // boosted loudness 461 && (nrg_def[0] > nrg_test[0])) { 462 throw new Exception("Signal not attenuated"); 463 } 464 if ((decoderTargetLevel > DEFAULT_DECODER_TARGET_LEVEL) // attenuated loudness 465 && (nrg_def[0] < nrg_test[0])) { 466 throw new Exception("Signal not boosted"); 467 } 468 } 469 nrgRatio = nrgRatio * normFactor; 470 471 // Check whether loudness behavior is as expected 472 if (nrgRatio > 1.05f || nrgRatio < 0.95f ){ 473 throw new Exception("Loudness behavior not as expected"); 474 } 475 } 476 } 477 478 /** 479 * AAC test Output Loudness 480 */ checkAacDrcOutputLoudness(int testInput, int decoderTargetLevel, int expectedOutputLoudness, String decoderName)481 private void checkAacDrcOutputLoudness(int testInput, int decoderTargetLevel, int expectedOutputLoudness, String decoderName) throws Exception { 482 for (boolean runtimeChange : new boolean[] {false, true}) { 483 AudioParameter decParams = new AudioParameter(); 484 DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, 0, 6); 485 486 // Check drc loudness preference 487 short[] decSamples_test = decodeToMemory(decParams, testInput, -1, null, 488 drcParams_test, decoderName, runtimeChange, expectedOutputLoudness); 489 } 490 } 491 492 493 /** 494 * Class handling all MPEG-4 and MPEG-D Dynamic Range Control (DRC) parameter relevant 495 * for testing 496 */ 497 protected static class DrcParams { 498 int mBoost; // scaling of boosting gains 499 int mCut; // scaling of compressing gains 500 int mDecoderTargetLevel; // desired target output level (for normalization) 501 int mHeavy; // en-/disable heavy compression 502 int mEffectType; // MPEG-D DRC Effect Type 503 int mAlbumMode; // MPEG-D DRC Album Mode 504 DrcParams()505 public DrcParams() { 506 mBoost = 127; // no scaling 507 mCut = 127; // no scaling 508 mHeavy = 1; // enabled 509 } 510 DrcParams(int boost, int cut, int decoderTargetLevel, int heavy)511 public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy) { 512 mBoost = boost; 513 mCut = cut; 514 mDecoderTargetLevel = decoderTargetLevel; 515 mHeavy = heavy; 516 } 517 DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType)518 public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType) { 519 this(boost, cut, decoderTargetLevel, heavy); 520 mEffectType = effectType; 521 } 522 DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType, int albumMode)523 public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType, 524 int albumMode) { 525 this(boost, cut, decoderTargetLevel, heavy, effectType); 526 mAlbumMode = albumMode; 527 } 528 } 529 530 531 // TODO: code is the same as in DecoderTest, differences are: 532 // - addition of application of DRC parameters 533 // - no need/use of resetMode, configMode 534 // Split method so code can be shared 535 decodeToMemory(AudioParameter audioParams, int testinput, int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange, int expectedOutputLoudness)536 private short[] decodeToMemory(AudioParameter audioParams, int testinput, int eossample, 537 List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange, 538 int expectedOutputLoudness) 539 throws IOException 540 { 541 String localTag = TAG + "#decodeToMemory"; 542 short [] decoded = new short[0]; 543 int decodedIdx = 0; 544 545 AssetFileDescriptor testFd = mResources.openRawResourceFd(testinput); 546 547 MediaExtractor extractor; 548 MediaCodec codec; 549 ByteBuffer[] codecInputBuffers; 550 ByteBuffer[] codecOutputBuffers; 551 552 extractor = new MediaExtractor(); 553 extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(), 554 testFd.getLength()); 555 testFd.close(); 556 557 assertEquals("wrong number of tracks", 1, extractor.getTrackCount()); 558 MediaFormat format = extractor.getTrackFormat(0); 559 String mime = format.getString(MediaFormat.KEY_MIME); 560 assertTrue("not an audio file", mime.startsWith("audio/")); 561 562 MediaFormat configFormat = format; 563 if (decoderName == null) { 564 codec = MediaCodec.createDecoderByType(mime); 565 } else { 566 codec = MediaCodec.createByCodecName(decoderName); 567 } 568 569 // set DRC parameters 570 if (drcParams != null) { 571 configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost); 572 configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut); 573 if (!runtimeChange) { 574 if (drcParams.mDecoderTargetLevel != 0) { 575 configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 576 drcParams.mDecoderTargetLevel); 577 } 578 } 579 configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy); 580 } 581 Log.v(localTag, "configuring with " + configFormat); 582 codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */); 583 584 if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R 585 if(!runtimeChange) { 586 // check if MediaCodec gives back correct drc parameters 587 if (drcParams.mDecoderTargetLevel != 0) { 588 final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec, 589 MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL); 590 if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) { 591 fail("DRC Target Ref Level received from MediaCodec is not the level set"); 592 } 593 } 594 } 595 } 596 597 codec.start(); 598 codecInputBuffers = codec.getInputBuffers(); 599 codecOutputBuffers = codec.getOutputBuffers(); 600 601 if (drcParams != null) { 602 if (runtimeChange) { 603 if (drcParams.mDecoderTargetLevel != 0) { 604 Bundle b = new Bundle(); 605 b.putInt(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 606 drcParams.mDecoderTargetLevel); 607 codec.setParameters(b); 608 } 609 } 610 } 611 612 extractor.selectTrack(0); 613 614 // start decoding 615 final long kTimeOutUs = 5000; 616 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 617 boolean sawInputEOS = false; 618 boolean sawOutputEOS = false; 619 int noOutputCounter = 0; 620 int samplecounter = 0; 621 while (!sawOutputEOS && noOutputCounter < 50) { 622 noOutputCounter++; 623 if (!sawInputEOS) { 624 int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs); 625 626 if (inputBufIndex >= 0) { 627 ByteBuffer dstBuf = codecInputBuffers[inputBufIndex]; 628 629 int sampleSize = 630 extractor.readSampleData(dstBuf, 0 /* offset */); 631 632 long presentationTimeUs = 0; 633 634 if (sampleSize < 0 && eossample > 0) { 635 fail("test is broken: never reached eos sample"); 636 } 637 if (sampleSize < 0) { 638 Log.d(TAG, "saw input EOS."); 639 sawInputEOS = true; 640 sampleSize = 0; 641 } else { 642 if (samplecounter == eossample) { 643 sawInputEOS = true; 644 } 645 samplecounter++; 646 presentationTimeUs = extractor.getSampleTime(); 647 } 648 codec.queueInputBuffer( 649 inputBufIndex, 650 0 /* offset */, 651 sampleSize, 652 presentationTimeUs, 653 sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0); 654 655 if (!sawInputEOS) { 656 extractor.advance(); 657 } 658 } 659 } 660 661 int res = codec.dequeueOutputBuffer(info, kTimeOutUs); 662 663 if (res >= 0) { 664 //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs); 665 666 if (info.size > 0) { 667 noOutputCounter = 0; 668 if (timestamps != null) { 669 timestamps.add(info.presentationTimeUs); 670 } 671 } 672 673 int outputBufIndex = res; 674 ByteBuffer buf = codecOutputBuffers[outputBufIndex]; 675 676 if (decodedIdx + (info.size / 2) >= decoded.length) { 677 decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2)); 678 } 679 680 buf.position(info.offset); 681 for (int i = 0; i < info.size; i += 2) { 682 decoded[decodedIdx++] = buf.getShort(); 683 } 684 685 codec.releaseOutputBuffer(outputBufIndex, false /* render */); 686 687 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 688 Log.d(TAG, "saw output EOS."); 689 sawOutputEOS = true; 690 } 691 } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 692 codecOutputBuffers = codec.getOutputBuffers(); 693 694 Log.d(TAG, "output buffers have changed."); 695 } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 696 MediaFormat oformat = codec.getOutputFormat(); 697 audioParams.setNumChannels(oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT)); 698 audioParams.setSamplingRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE)); 699 Log.d(TAG, "output format has changed to " + oformat); 700 } else { 701 Log.d(TAG, "dequeueOutputBuffer returned " + res); 702 } 703 } 704 if (noOutputCounter >= 50) { 705 fail("decoder stopped outputing data"); 706 } 707 708 // check if MediaCodec gives back correct drc parameters (R and above) 709 if (drcParams != null && sIsAndroidRAndAbove) { 710 if (drcParams.mDecoderTargetLevel != 0) { 711 final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec, 712 MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL); 713 if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) { 714 fail("DRC Target Ref Level received from MediaCodec is not the level set"); 715 } 716 } 717 718 final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec, 719 MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR); 720 assertEquals("Attenuation factor received from MediaCodec differs from set:", 721 drcParams.mCut, cutFromCodec); 722 final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec, 723 MediaFormat.KEY_AAC_DRC_BOOST_FACTOR); 724 assertEquals("Boost factor received from MediaCodec differs from set:", 725 drcParams.mBoost, boostFromCodec); 726 } 727 728 // expectedOutputLoudness == -2 indicates that output loudness is not tested 729 if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) { 730 final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec, 731 MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS); 732 if (outputLoudnessFromCodec != expectedOutputLoudness) { 733 fail("Received decoder output loudness is not the expected value"); 734 } 735 } 736 737 codec.stop(); 738 codec.release(); 739 return decoded; 740 } 741 decodeToMemory(AudioParameter audioParams, int testinput, int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)742 private short[] decodeToMemory(AudioParameter audioParams, int testinput, 743 int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName) 744 throws IOException 745 { 746 final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps, 747 drcParams, decoderName, false, -2); 748 return decoded; 749 } 750 decodeToMemory(AudioParameter audioParams, int testinput, int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange)751 private short[] decodeToMemory(AudioParameter audioParams, int testinput, 752 int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName, 753 boolean runtimeChange) 754 throws IOException 755 { 756 final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps, 757 drcParams, decoderName, runtimeChange, -2); 758 return decoded; 759 } 760 761 } 762 763