• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.audio.cts;
18 
19 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
20 import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
21 import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
22 import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertThrows;
28 import static org.junit.Assert.assertTrue;
29 
30 import android.content.Context;
31 import android.content.res.AssetFileDescriptor;
32 import android.media.AudioAttributes;
33 import android.media.AudioFormat;
34 import android.media.AudioManager;
35 import android.media.AudioTrack;
36 import android.media.LoudnessCodecController;
37 import android.media.MediaCodec;
38 import android.media.MediaExtractor;
39 import android.media.MediaFormat;
40 import android.os.Bundle;
41 import android.platform.test.annotations.RequiresFlagsEnabled;
42 import android.platform.test.flag.junit.CheckFlagsRule;
43 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
44 import android.util.Log;
45 
46 import androidx.annotation.NonNull;
47 import androidx.test.ext.junit.runners.AndroidJUnit4;
48 import androidx.test.platform.app.InstrumentationRegistry;
49 
50 import com.android.compatibility.common.util.NonMainlineTest;
51 
52 import org.junit.After;
53 import org.junit.Before;
54 import org.junit.Rule;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 
58 import java.time.Duration;
59 import java.util.concurrent.Executors;
60 import java.util.concurrent.atomic.AtomicInteger;
61 import java.util.concurrent.atomic.AtomicReference;
62 
63 @NonMainlineTest
64 @RunWith(AndroidJUnit4.class)
65 public class LoudnessCodecControllerTest {
66     private static final String TAG = "LoudnessCodecControllerTest";
67 
68     private static final String TEST_MEDIA_AUDIO_CODEC_PREFIX = "audio/";
69     private static final int TEST_AUDIO_TRACK_SAMPLERATE = 48000;
70     private static final int TEST_AUDIO_TRACK_CHANNELS = 2;
71 
72     private static final Duration TEST_LOUDNESS_CALLBACK_TIMEOUT = Duration.ofMillis(200);
73 
74     @Rule
75     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
76 
77     private LoudnessCodecController mLcc;
78 
79     private int mSessionId;
80 
81     private AudioTrack mAt;
82 
83     private final AtomicInteger mCodecUpdateCallNumber = new AtomicInteger(0);
84 
85     private final AtomicReference<Bundle> mLastCodecUpdate = new AtomicReference<>();
86 
87     private final class MyLoudnessCodecUpdateListener
88             implements LoudnessCodecController.OnLoudnessCodecUpdateListener {
89         @Override
90         @NonNull
onLoudnessCodecUpdate(@onNull MediaCodec mediaCodec, @NonNull Bundle codecValues)91         public Bundle onLoudnessCodecUpdate(@NonNull MediaCodec mediaCodec,
92                 @NonNull Bundle codecValues) {
93             mCodecUpdateCallNumber.incrementAndGet();
94             mLastCodecUpdate.set(codecValues);
95             return codecValues;
96         }
97     }
98 
99     @Before
setUp()100     public void setUp() {
101         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
102         final AudioManager audioManager = (AudioManager) context.getSystemService(
103                 AudioManager.class);
104         mSessionId = 0;
105         if (audioManager != null) {
106             mSessionId = audioManager.generateAudioSessionId();
107         }
108         mLcc = LoudnessCodecController.create(mSessionId,
109                 Executors.newSingleThreadExecutor(), new MyLoudnessCodecUpdateListener());
110     }
111 
112     @After
tearDown()113     public void tearDown() throws Exception {
114         if (mAt != null) {
115             mAt.release();
116             mAt = null;
117         }
118         mLcc.close();
119     }
120 
121     @Test
122     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
createNewLoudnessCodecController_notNull()123     public void createNewLoudnessCodecController_notNull() {
124         assertNotNull("LoudnessCodecController must not be null", mLcc);
125 
126         mLcc = LoudnessCodecController.create(mSessionId);
127         assertNotNull("LoudnessCodecController must not be null", mLcc);
128     }
129 
130     @Test
131     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
addUnconfiguredMediaCodec_returnsFalse()132     public void addUnconfiguredMediaCodec_returnsFalse() throws Exception {
133         final MediaCodec mediaCodec = createMediaCodec(/*configure*/false);
134         try {
135             assertFalse(mLcc.addMediaCodec(mediaCodec));
136         } finally {
137             mediaCodec.release();
138         }
139     }
140 
141     @Test
142     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
addMediaCodec_withAudioTrack_sendsUpdate()143     public void addMediaCodec_withAudioTrack_sendsUpdate() throws Exception {
144         mAt = createAndStartAudioTrack();
145 
146         final MediaCodec mediaCodec = createMediaCodec(/*configure*/true);
147         try {
148             mLcc.addMediaCodec(mediaCodec);
149             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
150 
151             assertEquals(1, mCodecUpdateCallNumber.get());
152         } finally {
153             mediaCodec.release();
154         }
155     }
156 
157     @Test
158     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
addMediaCodecs_triggersUpdate()159     public void addMediaCodecs_triggersUpdate() throws Exception {
160         final MediaCodec mediaCodec1 = createMediaCodec(/*configure*/true);
161         final MediaCodec mediaCodec2 = createMediaCodec(/*configure*/true);
162         final MediaCodec mediaCodec3 = createMediaCodec(/*configure*/true);
163 
164         try {
165             mLcc.addMediaCodec(mediaCodec1);
166             mLcc.addMediaCodec(mediaCodec2);
167             mLcc.addMediaCodec(mediaCodec3);
168             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
169 
170             assertEquals(3, mCodecUpdateCallNumber.get());
171         } finally {
172             mediaCodec1.release();
173             mediaCodec2.release();
174             mediaCodec3.release();
175         }
176     }
177 
178     @Test
179     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
getLoudnessCodecParams_returnsCurrentParameters()180     public void getLoudnessCodecParams_returnsCurrentParameters() throws Exception {
181         final MediaCodec mediaCodec = createMediaCodec(/*configure*/true);
182         try {
183             mLcc.addMediaCodec(mediaCodec);
184             assertFalse(mLcc.getLoudnessCodecParams(mediaCodec).isDefinitelyEmpty());
185         } finally {
186             mediaCodec.release();
187         }
188     }
189 
190     @Test
191     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
getLoudnessCodecParamsForWrongMediaCodec_throwsIAE()192     public void getLoudnessCodecParamsForWrongMediaCodec_throwsIAE() throws Exception {
193         final MediaCodec mediaCodec1 = createMediaCodec(/*configure*/true);
194         final MediaCodec mediaCodec2 = createMediaCodec(/*configure*/true);
195         try {
196             mLcc.addMediaCodec(mediaCodec1);
197             assertThrows(IllegalArgumentException.class,
198                     () -> mLcc.getLoudnessCodecParams(mediaCodec2));
199         } finally {
200             mediaCodec1.release();
201             mediaCodec2.release();
202         }
203     }
204 
205     @Test
206     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
multipleLcc_withRegisteredCodecs_triggerUpdate()207     public void multipleLcc_withRegisteredCodecs_triggerUpdate() throws Exception {
208         try (LoudnessCodecController lcc2 = LoudnessCodecController.create(mSessionId,
209                 Executors.newSingleThreadExecutor(), new MyLoudnessCodecUpdateListener())) {
210 
211             final MediaCodec mediaCodec1 = createMediaCodec(/*configure*/true);
212             final MediaCodec mediaCodec2 = createMediaCodec(/*configure*/true);
213             final MediaCodec mediaCodec3 = createMediaCodec(/*configure*/true);
214             try {
215                 mLcc.addMediaCodec(mediaCodec1);
216                 mLcc.addMediaCodec(mediaCodec2);
217                 lcc2.addMediaCodec(mediaCodec3);
218                 Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
219 
220                 assertEquals(3, mCodecUpdateCallNumber.get());
221             } finally {
222                 mediaCodec1.release();
223                 mediaCodec2.release();
224                 mediaCodec3.release();
225             }
226         }
227     }
228 
229     @Test
230     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
removeUnregisteredCodec_throwsIAE()231     public void removeUnregisteredCodec_throwsIAE() throws Exception {
232         final MediaCodec mediaCodec1 = createMediaCodec(/*configure*/true);
233         final MediaCodec mediaCodec2 = createMediaCodec(/*configure*/true);
234 
235         try {
236             mLcc.addMediaCodec(mediaCodec1);
237             assertThrows(IllegalArgumentException.class, () -> mLcc.removeMediaCodec(mediaCodec2));
238         } finally {
239             mediaCodec1.release();
240             mediaCodec2.release();
241         }
242     }
243 
244     @Test
245     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
addMediaCodec_afterRelease_noUpdate()246     public void addMediaCodec_afterRelease_noUpdate() throws Exception {
247         final MediaCodec mediaCodec = createMediaCodec(/*configure*/true);
248         try {
249             mLcc.close();
250             mLcc.addMediaCodec(mediaCodec);
251             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
252 
253             assertEquals(0, mCodecUpdateCallNumber.get());
254         } finally {
255             mediaCodec.release();
256         }
257     }
258 
259     @Test
260     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
audioTrackStart_afterAddMediaCodec_checkUpdateNumber()261     public void audioTrackStart_afterAddMediaCodec_checkUpdateNumber() throws Exception {
262         final MediaCodec mediaCodec1 = createMediaCodec(/*configure*/true);
263         final MediaCodec mediaCodec2 = createMediaCodec(/*configure*/true);
264 
265         try {
266             mLcc.addMediaCodec(mediaCodec1);
267             mLcc.addMediaCodec(mediaCodec2);
268             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
269 
270             // Device id for AudioAttributes should not change to send more updates
271             // after creating the AudioTrack
272             mAt = createAndStartAudioTrack();
273             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
274 
275             assertEquals(2, mCodecUpdateCallNumber.get());
276         } finally {
277             mediaCodec1.release();
278             mediaCodec2.release();
279         }
280     }
281 
282     @Test
283     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
audioTrackStart_beforeAddMediaCodec_checkUpdateNumber()284     public void audioTrackStart_beforeAddMediaCodec_checkUpdateNumber() throws Exception {
285         final MediaCodec mediaCodec1 = createMediaCodec(/*configure*/true);
286         final MediaCodec mediaCodec2 = createMediaCodec(/*configure*/true);
287 
288         try {
289             mAt = createAndStartAudioTrack();
290             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
291 
292             mLcc.addMediaCodec(mediaCodec1);
293             mLcc.addMediaCodec(mediaCodec2);
294             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
295 
296             assertEquals(2, mCodecUpdateCallNumber.get());
297         } finally {
298             mediaCodec1.release();
299             mediaCodec2.release();
300         }
301     }
302 
303     @Test
304     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
addMediaCodec_afterRelease_noSecondUpdate()305     public void addMediaCodec_afterRelease_noSecondUpdate() throws Exception {
306         final MediaCodec mediaCodec1 = createMediaCodec(/*configure*/true);
307         final MediaCodec mediaCodec2 = createMediaCodec(/*configure*/true);
308 
309         try {
310             mLcc.addMediaCodec(mediaCodec1);
311             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
312 
313             mLcc.close();
314             mLcc.addMediaCodec(mediaCodec2);
315             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
316 
317             assertEquals(1, mCodecUpdateCallNumber.get());
318         } finally {
319             mediaCodec1.release();
320             mediaCodec2.release();
321         }
322     }
323 
324     @Test
325     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
mediaCodecUpdate_checkParameters()326     public void mediaCodecUpdate_checkParameters() throws Exception {
327         final MediaCodec mediaCodec = createMediaCodec(/*configure*/true);
328 
329         try {
330             mLcc.addMediaCodec(mediaCodec);
331             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
332 
333             assertEquals(1, mCodecUpdateCallNumber.get());
334             Bundle lastUpdate = mLastCodecUpdate.get();
335             assertNotNull(lastUpdate);
336             assertTrue(lastUpdate.getInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL) != 0);
337             assertTrue(lastUpdate.getInt(KEY_AAC_DRC_EFFECT_TYPE) != 0);
338         } finally {
339             mediaCodec.release();
340         }
341     }
342 
343     @Test
344     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
mediaCodecUpdate_checkParametersOnCodec()345     public void mediaCodecUpdate_checkParametersOnCodec() throws Exception {
346         mLcc = LoudnessCodecController.create(mSessionId);
347         final MediaCodec mediaCodec = createMediaCodec(/*configure*/true);
348 
349         try {
350             mLcc.addMediaCodec(mediaCodec);
351             Thread.sleep(TEST_LOUDNESS_CALLBACK_TIMEOUT.toMillis());
352 
353             MediaFormat format = mediaCodec.getOutputFormat();
354             assertNotNull(format);
355             assertTrue(format.getInteger(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL) != 0);
356             assertTrue(format.getInteger(KEY_AAC_DRC_EFFECT_TYPE) != 0);
357         } finally {
358             mediaCodec.release();
359         }
360     }
361 
362 
createAndStartAudioTrack()363     private AudioTrack createAndStartAudioTrack() {
364         final int bufferSizeInBytes =
365                 TEST_AUDIO_TRACK_SAMPLERATE * TEST_AUDIO_TRACK_CHANNELS * Short.BYTES;
366 
367         final AudioTrack track = new AudioTrack.Builder()
368                 .setAudioAttributes(new AudioAttributes.Builder()
369                         .setUsage(AudioAttributes.USAGE_MEDIA)
370                         .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
371                         .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE)
372                         .build())
373                 .setSessionId(mSessionId)
374                 .setBufferSizeInBytes(bufferSizeInBytes)
375                 .setAudioFormat(new AudioFormat.Builder()
376                         .setChannelMask(TEST_AUDIO_TRACK_CHANNELS)
377                         .setSampleRate(TEST_AUDIO_TRACK_SAMPLERATE)
378                         .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
379                         .build())
380                 .build();
381 
382         // enqueue silence to have a device assigned
383         short[] data = new short[bufferSizeInBytes / Short.BYTES];
384         track.write(data, 0, data.length, AudioTrack.WRITE_NON_BLOCKING);
385         track.play();
386 
387         return track;
388     }
389 
390     /** Creates a decoder for xHE-AAC content. */
createMediaCodec(boolean configure)391     private MediaCodec createMediaCodec(boolean configure) throws Exception {
392         AssetFileDescriptor testFd = InstrumentationRegistry.getInstrumentation().getContext()
393                 .getResources()
394                 .openRawResourceFd(R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4);
395 
396         MediaExtractor extractor;
397         extractor = new MediaExtractor();
398         try {
399             extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
400                     testFd.getLength());
401 
402             assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
403             MediaFormat format = extractor.getTrackFormat(0);
404             String mime = format.getString(MediaFormat.KEY_MIME);
405             assertTrue("not an audio file", mime.startsWith(TEST_MEDIA_AUDIO_CODEC_PREFIX));
406             final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mime);
407 
408             if (configure) {
409                 Log.v(TAG, "configuring with " + format);
410                 mediaCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
411             }
412             return mediaCodec;
413         } finally {
414             extractor.release();
415             testFd.close();
416         }
417     }
418 
419 }
420