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