• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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