1 /* 2 * Copyright (C) 2022 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 com.android.server.audio; 18 19 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT; 20 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; 21 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID; 22 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH; 23 import static android.media.AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID; 24 import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_4; 25 import static android.media.LoudnessCodecInfo.CodecMetadataType.CODEC_METADATA_TYPE_MPEG_D; 26 import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE; 27 import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; 28 import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; 29 import static android.media.audio.Flags.automaticBtDeviceType; 30 31 import android.annotation.IntDef; 32 import android.annotation.NonNull; 33 import android.media.AudioAttributes; 34 import android.media.AudioDeviceAttributes; 35 import android.media.AudioDeviceInfo; 36 import android.media.AudioManager.AudioDeviceCategory; 37 import android.media.AudioPlaybackConfiguration; 38 import android.media.AudioSystem; 39 import android.media.ILoudnessCodecUpdatesDispatcher; 40 import android.media.LoudnessCodecInfo; 41 import android.media.permission.ClearCallingIdentityContext; 42 import android.media.permission.SafeCloseable; 43 import android.os.Binder; 44 import android.os.PersistableBundle; 45 import android.os.RemoteCallbackList; 46 import android.os.RemoteException; 47 import android.os.SystemProperties; 48 import android.util.Log; 49 import android.util.SparseIntArray; 50 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.server.audio.AudioServiceEvents.LoudnessEvent; 54 import com.android.server.utils.EventLogger; 55 56 import java.io.PrintWriter; 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.util.ArrayList; 60 import java.util.HashMap; 61 import java.util.HashSet; 62 import java.util.List; 63 import java.util.Map; 64 import java.util.Objects; 65 import java.util.Optional; 66 import java.util.Set; 67 import java.util.stream.Collectors; 68 69 /** 70 * Class to handle the updates in loudness parameters and responsible to generate parameters that 71 * can be set directly on a MediaCodec. 72 */ 73 public class LoudnessCodecHelper { 74 private static final String TAG = "AS.LoudnessCodecHelper"; 75 76 private static final boolean DEBUG = false; 77 78 /** 79 * Property containing a string to set for a custom built in speaker SPL range as defined by 80 * CTA2075. The options that can be set are: 81 * - "small": for max SPL with test signal < 75 dB, 82 * - "medium": for max SPL with test signal between 70 and 90 dB, 83 * - "large": for max SPL with test signal > 85 dB. 84 */ 85 private static final String SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE = 86 "audio.loudness.builtin-speaker-spl-range-size"; 87 88 @VisibleForTesting 89 static final int SPL_RANGE_UNKNOWN = 0; 90 @VisibleForTesting 91 static final int SPL_RANGE_SMALL = 1; 92 @VisibleForTesting 93 static final int SPL_RANGE_MEDIUM = 2; 94 @VisibleForTesting 95 static final int SPL_RANGE_LARGE = 3; 96 97 /** The possible transducer SPL ranges as defined in CTA2075 */ 98 @IntDef({ 99 SPL_RANGE_UNKNOWN, 100 SPL_RANGE_SMALL, 101 SPL_RANGE_MEDIUM, 102 SPL_RANGE_LARGE 103 }) 104 @Retention(RetentionPolicy.SOURCE) 105 public @interface DeviceSplRange { 106 } 107 108 private static final class LoudnessRemoteCallbackList extends 109 RemoteCallbackList<ILoudnessCodecUpdatesDispatcher> { 110 private final LoudnessCodecHelper mLoudnessCodecHelper; 111 LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper)112 LoudnessRemoteCallbackList(LoudnessCodecHelper loudnessCodecHelper) { 113 mLoudnessCodecHelper = loudnessCodecHelper; 114 } 115 116 @Override onCallbackDied(ILoudnessCodecUpdatesDispatcher callback, Object cookie)117 public void onCallbackDied(ILoudnessCodecUpdatesDispatcher callback, Object cookie) { 118 Integer pid = null; 119 if (cookie instanceof Integer) { 120 pid = (Integer) cookie; 121 } 122 if (pid != null) { 123 if (DEBUG) { 124 Log.d(TAG, "Client with pid " + pid + " died, removing from receiving updates"); 125 } 126 sLogger.enqueue(LoudnessEvent.getClientDied(pid)); 127 mLoudnessCodecHelper.onClientPidDied(pid); 128 } 129 super.onCallbackDied(callback, cookie); 130 } 131 } 132 133 private static final EventLogger sLogger = new EventLogger( 134 AudioService.LOG_NB_EVENTS_LOUDNESS_CODEC, "Loudness updates"); 135 136 private final LoudnessRemoteCallbackList mLoudnessUpdateDispatchers = 137 new LoudnessRemoteCallbackList(this); 138 139 private final Object mLock = new Object(); 140 141 /** Contains for each started track id the known started piids. */ 142 @GuardedBy("mLock") 143 private final HashMap<LoudnessTrackId, Set<Integer>> mStartedConfigPiids = 144 new HashMap<>(); 145 146 /** Contains for each LoudnessTrackId a set of started coudec infos. */ 147 @GuardedBy("mLock") 148 private final HashMap<LoudnessTrackId, Set<LoudnessCodecInfo>> mStartedConfigInfo = 149 new HashMap<>(); 150 151 /** Contains the current device id assignment for each piid. */ 152 @GuardedBy("mLock") 153 private final SparseIntArray mPiidToDeviceIdCache = new SparseIntArray(); 154 155 /** Maps each piid to the owner process of the player. */ 156 @GuardedBy("mLock") 157 private final SparseIntArray mPiidToPidCache = new SparseIntArray(); 158 159 private final AudioService mAudioService; 160 161 /** Contains the properties necessary to compute the codec loudness related parameters. */ 162 @VisibleForTesting 163 static final class LoudnessCodecInputProperties { 164 private final int mMetadataType; 165 166 private final boolean mIsDownmixing; 167 168 @DeviceSplRange 169 private final int mDeviceSplRange; 170 171 static final class Builder { 172 private int mMetadataType; 173 174 private boolean mIsDownmixing; 175 176 @DeviceSplRange 177 private int mDeviceSplRange; 178 setMetadataType(int metadataType)179 Builder setMetadataType(int metadataType) { 180 mMetadataType = metadataType; 181 return this; 182 } 183 setIsDownmixing(boolean isDownmixing)184 Builder setIsDownmixing(boolean isDownmixing) { 185 mIsDownmixing = isDownmixing; 186 return this; 187 } 188 setDeviceSplRange(@eviceSplRange int deviceSplRange)189 Builder setDeviceSplRange(@DeviceSplRange int deviceSplRange) { 190 mDeviceSplRange = deviceSplRange; 191 return this; 192 } 193 build()194 LoudnessCodecInputProperties build() { 195 return new LoudnessCodecInputProperties(mMetadataType, 196 mIsDownmixing, mDeviceSplRange); 197 } 198 } 199 LoudnessCodecInputProperties(int metadataType, boolean isDownmixing, @DeviceSplRange int deviceSplRange)200 private LoudnessCodecInputProperties(int metadataType, 201 boolean isDownmixing, 202 @DeviceSplRange int deviceSplRange) { 203 mMetadataType = metadataType; 204 mIsDownmixing = isDownmixing; 205 mDeviceSplRange = deviceSplRange; 206 } 207 208 @Override equals(Object obj)209 public boolean equals(Object obj) { 210 if (this == obj) { 211 return true; 212 } 213 if (obj == null) { 214 return false; 215 } 216 // type check and cast 217 if (getClass() != obj.getClass()) { 218 return false; 219 } 220 final LoudnessCodecInputProperties lcip = (LoudnessCodecInputProperties) obj; 221 return mMetadataType == lcip.mMetadataType 222 && mIsDownmixing == lcip.mIsDownmixing 223 && mDeviceSplRange == lcip.mDeviceSplRange; 224 } 225 226 @Override hashCode()227 public int hashCode() { 228 return Objects.hash(mMetadataType, mIsDownmixing, mDeviceSplRange); 229 } 230 231 @Override toString()232 public String toString() { 233 return "Loudness properties:" 234 + " device SPL range: " + splRangeToString(mDeviceSplRange) 235 + " down-mixing: " + mIsDownmixing 236 + " metadata type: " + mMetadataType; 237 } 238 createLoudnessParameters()239 PersistableBundle createLoudnessParameters() { 240 PersistableBundle loudnessParams = new PersistableBundle(); 241 242 switch (mDeviceSplRange) { 243 case SPL_RANGE_LARGE: 244 // corresponds to -31dB attenuation 245 loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 124); 246 if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) { 247 loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, 0); 248 } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) { 249 // general compression 250 loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6); 251 } 252 break; 253 case SPL_RANGE_MEDIUM: 254 // corresponds to -24dB attenuation 255 loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 96); 256 if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) { 257 loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, mIsDownmixing ? 1 : 0); 258 } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) { 259 // general compression 260 loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6); 261 } 262 break; 263 case SPL_RANGE_SMALL: 264 // corresponds to -16dB attenuation 265 loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 64); 266 if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) { 267 loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, 1); 268 } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) { 269 // limited playback range compression 270 loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 3); 271 } 272 break; 273 default: 274 // corresponds to -24dB attenuation 275 loudnessParams.putInt(KEY_AAC_DRC_TARGET_REFERENCE_LEVEL, 96); 276 if (mMetadataType == CODEC_METADATA_TYPE_MPEG_4) { 277 loudnessParams.putInt(KEY_AAC_DRC_HEAVY_COMPRESSION, mIsDownmixing ? 1 : 0); 278 } else if (mMetadataType == CODEC_METADATA_TYPE_MPEG_D) { 279 // general compression 280 loudnessParams.putInt(KEY_AAC_DRC_EFFECT_TYPE, 6); 281 } 282 break; 283 } 284 285 return loudnessParams; 286 } 287 } 288 289 /** 290 * Contains the properties necessary to identify the tracks that are receiving annotated 291 * loudness data. 292 **/ 293 @VisibleForTesting 294 static final class LoudnessTrackId { 295 private final int mSessionId; 296 297 private final int mPid; 298 LoudnessTrackId(int sessionId, int pid)299 private LoudnessTrackId(int sessionId, int pid) { 300 mSessionId = sessionId; 301 mPid = pid; 302 } 303 304 @Override equals(Object obj)305 public boolean equals(Object obj) { 306 if (this == obj) { 307 return true; 308 } 309 if (obj == null) { 310 return false; 311 } 312 // type check and cast 313 if (getClass() != obj.getClass()) { 314 return false; 315 } 316 final LoudnessTrackId lti = (LoudnessTrackId) obj; 317 return mSessionId == lti.mSessionId && mPid == lti.mPid; 318 } 319 320 @Override hashCode()321 public int hashCode() { 322 return Objects.hash(mSessionId, mPid); 323 } 324 325 @Override toString()326 public String toString() { 327 return "Loudness track id:" 328 + " session ID: " + mSessionId 329 + " pid: " + mPid; 330 } 331 } 332 333 @GuardedBy("mLock") 334 private final HashMap<LoudnessCodecInputProperties, PersistableBundle> mCachedProperties = 335 new HashMap<>(); 336 LoudnessCodecHelper(@onNull AudioService audioService)337 LoudnessCodecHelper(@NonNull AudioService audioService) { 338 mAudioService = Objects.requireNonNull(audioService); 339 } 340 registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher)341 void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) { 342 mLoudnessUpdateDispatchers.register(dispatcher, Binder.getCallingPid()); 343 } 344 unregisterLoudnessCodecUpdatesDispatcher( ILoudnessCodecUpdatesDispatcher dispatcher)345 void unregisterLoudnessCodecUpdatesDispatcher( 346 ILoudnessCodecUpdatesDispatcher dispatcher) { 347 mLoudnessUpdateDispatchers.unregister(dispatcher); 348 } 349 startLoudnessCodecUpdates(int sessionId)350 void startLoudnessCodecUpdates(int sessionId) { 351 int pid = Binder.getCallingPid(); 352 if (DEBUG) { 353 Log.d(TAG, 354 "startLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid); 355 } 356 357 final LoudnessTrackId newConfig = new LoudnessTrackId(sessionId, pid); 358 HashSet<Integer> newPiids; 359 synchronized (mLock) { 360 if (mStartedConfigInfo.containsKey(newConfig)) { 361 Log.w(TAG, "Already started loudness updates for config: " + newConfig); 362 return; 363 } 364 365 mStartedConfigInfo.put(newConfig, new HashSet<>()); 366 newPiids = new HashSet<>(); 367 mStartedConfigPiids.put(newConfig, newPiids); 368 } 369 370 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 371 mAudioService.getActivePlaybackConfigurations().stream().filter( 372 conf -> conf.getSessionId() == sessionId 373 && conf.getClientPid() == pid).forEach(apc -> { 374 int piid = apc.getPlayerInterfaceId(); 375 synchronized (mLock) { 376 newPiids.add(piid); 377 mPiidToPidCache.put(piid, pid); 378 sLogger.enqueue(LoudnessEvent.getStartPiid(piid, pid)); 379 } 380 }); 381 } 382 } 383 stopLoudnessCodecUpdates(int sessionId)384 void stopLoudnessCodecUpdates(int sessionId) { 385 int pid = Binder.getCallingPid(); 386 if (DEBUG) { 387 Log.d(TAG, 388 "stopLoudnessCodecUpdates: sessionId " + sessionId + " pid " + pid); 389 } 390 391 final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid); 392 synchronized (mLock) { 393 if (!mStartedConfigInfo.containsKey(config)) { 394 Log.w(TAG, "Loudness updates are already stopped config: " + config); 395 return; 396 } 397 398 final Set<Integer> startedPiidSet = mStartedConfigPiids.get(config); 399 if (startedPiidSet == null) { 400 Log.e(TAG, "Loudness updates are already stopped config: " + config); 401 return; 402 } 403 for (Integer piid : startedPiidSet) { 404 sLogger.enqueue(LoudnessEvent.getStopPiid(piid, mPiidToPidCache.get(piid, -1))); 405 mPiidToDeviceIdCache.delete(piid); 406 mPiidToPidCache.delete(piid); 407 } 408 mStartedConfigPiids.remove(config); 409 mStartedConfigInfo.remove(config); 410 } 411 } 412 addLoudnessCodecInfo(int sessionId, int mediaCodecHash, LoudnessCodecInfo info)413 void addLoudnessCodecInfo(int sessionId, int mediaCodecHash, 414 LoudnessCodecInfo info) { 415 int pid = Binder.getCallingPid(); 416 if (DEBUG) { 417 Log.d(TAG, "addLoudnessCodecInfo: sessionId " + sessionId 418 + " mcHash " + mediaCodecHash + " info " + info + " pid " + pid); 419 } 420 421 final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid); 422 Set<LoudnessCodecInfo> infoSet; 423 Set<Integer> piids; 424 synchronized (mLock) { 425 if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey( 426 config)) { 427 Log.w(TAG, "Cannot add new loudness info for stopped config " + config); 428 return; 429 } 430 431 piids = mStartedConfigPiids.get(config); 432 infoSet = mStartedConfigInfo.get(config); 433 infoSet.add(info); 434 } 435 436 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 437 final PersistableBundle updateBundle = new PersistableBundle(); 438 Optional<AudioPlaybackConfiguration> apc = 439 mAudioService.getActivePlaybackConfigurations().stream().filter( 440 conf -> conf.getSessionId() == sessionId 441 && conf.getClientPid() == pid).findFirst(); 442 if (apc.isEmpty()) { 443 if (DEBUG) { 444 Log.d(TAG, 445 "No APCs found when adding loudness codec info. Using AudioAttributes" 446 + " routing for initial update"); 447 } 448 updateBundle.putPersistableBundle(Integer.toString(mediaCodecHash), 449 getLoudnessParams(info)); 450 } else { 451 final AudioDeviceInfo deviceInfo = apc.get().getAudioDeviceInfo(); 452 if (deviceInfo != null) { 453 synchronized (mLock) { 454 // found a piid that matches the configuration 455 piids.add(apc.get().getPlayerInterfaceId()); 456 457 updateBundle.putPersistableBundle( 458 Integer.toString(mediaCodecHash), 459 getCodecBundle_l(deviceInfo.getInternalType(), 460 deviceInfo.getAddress(), info)); 461 } 462 } 463 } 464 if (!updateBundle.isDefinitelyEmpty()) { 465 dispatchNewLoudnessParameters(sessionId, updateBundle); 466 } 467 } 468 } 469 removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo)470 void removeLoudnessCodecInfo(int sessionId, LoudnessCodecInfo codecInfo) { 471 if (DEBUG) { 472 Log.d(TAG, "removeLoudnessCodecInfo: session ID" + sessionId + " info " + codecInfo); 473 } 474 475 int pid = Binder.getCallingPid(); 476 final LoudnessTrackId config = new LoudnessTrackId(sessionId, pid); 477 synchronized (mLock) { 478 if (!mStartedConfigInfo.containsKey(config) || !mStartedConfigPiids.containsKey( 479 config)) { 480 Log.w(TAG, "Cannot remove loudness info for stopped config " + config); 481 return; 482 } 483 final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config); 484 if (!codecInfos.remove(codecInfo)) { 485 Log.w(TAG, "Could not find to remove codecInfo " + codecInfo); 486 } 487 } 488 } 489 getLoudnessParams(LoudnessCodecInfo codecInfo)490 PersistableBundle getLoudnessParams(LoudnessCodecInfo codecInfo) { 491 if (DEBUG) { 492 Log.d(TAG, "getLoudnessParams: codecInfo " + codecInfo); 493 } 494 final ArrayList<AudioDeviceAttributes> devicesForAttributes = 495 mAudioService.getDevicesForAttributesInt(new AudioAttributes.Builder() 496 .setUsage(AudioAttributes.USAGE_MEDIA) 497 .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) 498 .build(), /*forVolume=*/false); 499 if (!devicesForAttributes.isEmpty()) { 500 final AudioDeviceAttributes audioDeviceAttribute = devicesForAttributes.get(0); 501 synchronized (mLock) { 502 return getCodecBundle_l(audioDeviceAttribute.getInternalType(), 503 audioDeviceAttribute.getAddress(), codecInfo); 504 } 505 } 506 507 // return empty Bundle 508 return new PersistableBundle(); 509 } 510 511 /** Method to be called whenever there is a changed in the active playback configurations. */ updateCodecParameters(List<AudioPlaybackConfiguration> configs)512 void updateCodecParameters(List<AudioPlaybackConfiguration> configs) { 513 if (DEBUG) { 514 Log.d(TAG, "updateCodecParameters: configs " + configs); 515 } 516 517 List<AudioPlaybackConfiguration> updateApcList = new ArrayList<>(); 518 synchronized (mLock) { 519 for (final AudioPlaybackConfiguration apc : configs) { 520 int piid = apc.getPlayerInterfaceId(); 521 int cachedDeviceId = mPiidToDeviceIdCache.get(piid, PLAYER_DEVICEID_INVALID); 522 AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo(); 523 if (deviceInfo == null) { 524 if (DEBUG) { 525 Log.d(TAG, "No device info for piid: " + piid); 526 } 527 if (cachedDeviceId != PLAYER_DEVICEID_INVALID) { 528 mPiidToDeviceIdCache.delete(piid); 529 if (DEBUG) { 530 Log.d(TAG, "Remove cached device id for piid: " + piid); 531 } 532 } 533 continue; 534 } 535 if (cachedDeviceId == deviceInfo.getId()) { 536 // deviceId did not change 537 if (DEBUG) { 538 Log.d(TAG, "DeviceId " + cachedDeviceId + " for piid: " + piid 539 + " did not change"); 540 } 541 continue; 542 } 543 mPiidToDeviceIdCache.put(piid, deviceInfo.getId()); 544 final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(), 545 apc.getClientPid()); 546 if (mStartedConfigInfo.containsKey(config) && mStartedConfigPiids.containsKey( 547 config)) { 548 if (DEBUG) { 549 Log.d(TAG, "Updating config: " + config + " with APC " + apc); 550 } 551 updateApcList.add(apc); 552 // update the started piid set 553 mStartedConfigPiids.get(config).add(piid); 554 } 555 } 556 } 557 558 updateApcList.forEach(this::updateCodecParametersForConfiguration); 559 } 560 561 /** Updates and dispatches the new loudness parameters for all its registered codecs. */ dump(PrintWriter pw)562 void dump(PrintWriter pw) { 563 // Registered clients 564 pw.println("\nRegistered clients:\n"); 565 synchronized (mLock) { 566 for (Map.Entry<LoudnessTrackId, Set<Integer>> entry : mStartedConfigPiids.entrySet()) { 567 for (Integer piid : entry.getValue()) { 568 int pid = mPiidToPidCache.get(piid, -1); 569 final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get( 570 entry.getKey()); 571 if (codecInfos != null) { 572 pw.println( 573 String.format("Player piid %d pid %d active codec types %s\n", piid, 574 pid, codecInfos.stream().map(Object::toString).collect( 575 Collectors.joining(", ")))); 576 } 577 } 578 } 579 pw.println(); 580 } 581 582 sLogger.dump(pw); 583 pw.println(); 584 } 585 onClientPidDied(int pid)586 private void onClientPidDied(int pid) { 587 synchronized (mLock) { 588 for (int i = 0; i < mPiidToPidCache.size(); ++i) { 589 int piid = mPiidToPidCache.keyAt(i); 590 if (mPiidToPidCache.get(piid) == pid) { 591 if (DEBUG) { 592 Log.d(TAG, "Removing piid " + piid); 593 } 594 mPiidToDeviceIdCache.delete(piid); 595 } 596 } 597 598 mStartedConfigPiids.entrySet().removeIf(entry -> entry.getKey().mPid == pid); 599 mStartedConfigInfo.entrySet().removeIf(entry -> entry.getKey().mPid == pid); 600 } 601 } 602 603 /** 604 * Updates and dispatches the new loudness parameters for the {@code codecInfos} set. 605 * 606 * @param apc the player configuration for which the loudness parameters are updated. 607 */ updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc)608 private void updateCodecParametersForConfiguration(AudioPlaybackConfiguration apc) { 609 if (DEBUG) { 610 Log.d(TAG, "updateCodecParametersForConfiguration apc:" + apc); 611 } 612 613 final PersistableBundle allBundles = new PersistableBundle(); 614 615 synchronized (mLock) { 616 final LoudnessTrackId config = new LoudnessTrackId(apc.getSessionId(), 617 apc.getClientPid()); 618 final Set<LoudnessCodecInfo> codecInfos = mStartedConfigInfo.get(config); 619 final AudioDeviceInfo deviceInfo = apc.getAudioDeviceInfo(); 620 621 if (codecInfos != null && deviceInfo != null) { 622 for (LoudnessCodecInfo info : codecInfos) { 623 if (info != null) { 624 allBundles.putPersistableBundle(Integer.toString(info.hashCode()), 625 getCodecBundle_l(deviceInfo.getInternalType(), 626 deviceInfo.getAddress(), info)); 627 } 628 } 629 } 630 } 631 632 if (!allBundles.isDefinitelyEmpty()) { 633 dispatchNewLoudnessParameters(apc.getSessionId(), allBundles); 634 } 635 } 636 dispatchNewLoudnessParameters(int sessionId, PersistableBundle bundle)637 private void dispatchNewLoudnessParameters(int sessionId, 638 PersistableBundle bundle) { 639 if (DEBUG) { 640 Log.d(TAG, 641 "dispatchNewLoudnessParameters: sessionId " + sessionId + " bundle: " + bundle); 642 } 643 final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast(); 644 for (int i = 0; i < nbDispatchers; ++i) { 645 try { 646 mLoudnessUpdateDispatchers.getBroadcastItem(i) 647 .dispatchLoudnessCodecParameterChange(sessionId, bundle); 648 } catch (RemoteException e) { 649 Log.e(TAG, "Error dispatching for sessionId " + sessionId + " bundle: " + bundle, 650 e); 651 } 652 } 653 mLoudnessUpdateDispatchers.finishBroadcast(); 654 } 655 656 @GuardedBy("mLock") getCodecBundle_l(int internalDeviceType, String address, LoudnessCodecInfo codecInfo)657 private PersistableBundle getCodecBundle_l(int internalDeviceType, 658 String address, 659 LoudnessCodecInfo codecInfo) { 660 LoudnessCodecInputProperties.Builder builder = new LoudnessCodecInputProperties.Builder(); 661 LoudnessCodecInputProperties prop = builder.setDeviceSplRange( 662 getDeviceSplRange(internalDeviceType, address)) 663 .setIsDownmixing(codecInfo.isDownmixing) 664 .setMetadataType(codecInfo.metadataType) 665 .build(); 666 667 if (mCachedProperties.containsKey(prop)) { 668 return mCachedProperties.get(prop); 669 } 670 final PersistableBundle codecBundle = prop.createLoudnessParameters(); 671 mCachedProperties.put(prop, codecBundle); 672 return codecBundle; 673 } 674 675 @DeviceSplRange getDeviceSplRange(int internalDeviceType, String address)676 private int getDeviceSplRange(int internalDeviceType, String address) { 677 @AudioDeviceCategory int deviceCategory; 678 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 679 if (automaticBtDeviceType()) { 680 deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(address); 681 } else { 682 deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy( 683 address, AudioSystem.isBluetoothLeDevice(internalDeviceType)); 684 } 685 } 686 if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) { 687 final String splRange = SystemProperties.get( 688 SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE, "unknown"); 689 if (!splRange.equals("unknown")) { 690 return stringToSplRange(splRange); 691 } 692 693 @DeviceSplRange int result = SPL_RANGE_SMALL; // default for phone/tablet/watch 694 if (mAudioService.isPlatformAutomotive() || mAudioService.isPlatformTelevision()) { 695 result = SPL_RANGE_MEDIUM; 696 } 697 698 return result; 699 } else if (internalDeviceType == AudioSystem.DEVICE_OUT_USB_HEADSET 700 || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE 701 || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET 702 || (AudioSystem.isBluetoothDevice(internalDeviceType) 703 && deviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES)) { 704 return SPL_RANGE_LARGE; 705 } else if (AudioSystem.isBluetoothDevice(internalDeviceType)) { 706 if (deviceCategory == AUDIO_DEVICE_CATEGORY_CARKIT) { 707 return SPL_RANGE_MEDIUM; 708 } else if (deviceCategory == AUDIO_DEVICE_CATEGORY_WATCH) { 709 return SPL_RANGE_SMALL; 710 } else if (deviceCategory == AUDIO_DEVICE_CATEGORY_HEARING_AID) { 711 return SPL_RANGE_SMALL; 712 } 713 } 714 715 return SPL_RANGE_UNKNOWN; 716 } 717 splRangeToString(@eviceSplRange int splRange)718 private static String splRangeToString(@DeviceSplRange int splRange) { 719 switch (splRange) { 720 case SPL_RANGE_LARGE: 721 return "large"; 722 case SPL_RANGE_MEDIUM: 723 return "medium"; 724 case SPL_RANGE_SMALL: 725 return "small"; 726 default: 727 return "unknown"; 728 } 729 } 730 731 @DeviceSplRange stringToSplRange(String splRange)732 private static int stringToSplRange(String splRange) { 733 switch (splRange) { 734 case "large": 735 return SPL_RANGE_LARGE; 736 case "medium": 737 return SPL_RANGE_MEDIUM; 738 case "small": 739 return SPL_RANGE_SMALL; 740 default: 741 return SPL_RANGE_UNKNOWN; 742 } 743 } 744 } 745