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