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