xref: /aosp_15_r20/cts/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestAacDrc.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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