1 /*
2  * Copyright (C) 2018 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 package com.android.car.audio;
17 
18 import static android.media.AudioDeviceInfo.TYPE_BUS;
19 import static android.media.AudioFormat.ENCODING_DEFAULT;
20 import static android.media.AudioFormat.ENCODING_PCM_16BIT;
21 import static android.media.AudioFormat.ENCODING_PCM_24BIT_PACKED;
22 import static android.media.AudioFormat.ENCODING_PCM_32BIT;
23 import static android.media.AudioFormat.ENCODING_PCM_8BIT;
24 import static android.media.AudioFormat.ENCODING_PCM_FLOAT;
25 
26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
27 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
28 
29 import android.annotation.Nullable;
30 import android.car.builtin.media.AudioManagerHelper;
31 import android.car.builtin.media.AudioManagerHelper.AudioGainInfo;
32 import android.car.builtin.util.Slogf;
33 import android.media.AudioDeviceAttributes;
34 import android.media.AudioDeviceInfo;
35 import android.util.proto.ProtoOutputStream;
36 
37 import com.android.car.CarLog;
38 import com.android.car.audio.CarAudioDumpProto.CarAudioDeviceInfoProto;
39 import com.android.car.audio.hal.HalAudioDeviceInfo;
40 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
41 import com.android.car.internal.util.IndentingPrintWriter;
42 import com.android.internal.annotations.GuardedBy;
43 
44 import java.util.List;
45 import java.util.Objects;
46 
47 /**
48  * A helper class wraps {@link AudioDeviceAttributes}, and helps manage the details of the audio
49  * device: gains, format, sample rate, channel count
50  *
51  * Note to the reader. For whatever reason, it seems that AudioGain contains only configuration
52  * information (min/max/step, etc) while the AudioGainConfig class contains the
53  * actual currently active gain value(s).
54  */
55 /* package */ final class CarAudioDeviceInfo {
56 
57     public static final int DEFAULT_SAMPLE_RATE = 48000;
58     private static final int DEFAULT_NUM_CHANNELS = 1;
59     private static final int UNINITIALIZED_GAIN = -1;
60 
61     /*
62      * PCM 16 bit is supposed to be guaranteed for all devices
63      * per {@link ENCODING_PCM_16BIT}'s documentation.
64      */
65     private static final int DEFAULT_ENCODING_FORMAT = ENCODING_PCM_16BIT;
66     private final AudioManagerWrapper mAudioManager;
67 
68     private final Object mLock = new Object();
69     @GuardedBy("mLock")
70     private AudioDeviceAttributes mAudioDeviceAttributes;
71     @GuardedBy("mLock")
72     private int mDefaultGain;
73     @GuardedBy("mLock")
74     private int mMaxGain;
75     @GuardedBy("mLock")
76     private int mMinGain;
77     @GuardedBy("mLock")
78     private int mStepValue;
79     @GuardedBy("mLock")
80     private boolean mCanBeRoutedWithDynamicPolicyMixRule = true;
81     @GuardedBy("mLock")
82     private int mSampleRate;
83     @GuardedBy("mLock")
84     private int mEncodingFormat;
85     @GuardedBy("mLock")
86     private int mChannelCount;
87 
88     /**
89      * We need to store the current gain because it is not accessible from the current
90      * audio engine implementation. It would be nice if AudioPort#activeConfig() would return it,
91      * but in the current implementation, that function actually works only for mixer ports.
92      */
93     @GuardedBy("mLock")
94     private int mCurrentGain;
95     @GuardedBy("mLock")
96     private boolean mIsActive;
97 
CarAudioDeviceInfo(AudioManagerWrapper audioManager, AudioDeviceAttributes audioDeviceAttributes)98     CarAudioDeviceInfo(AudioManagerWrapper audioManager,
99             AudioDeviceAttributes audioDeviceAttributes) {
100         mAudioManager = audioManager;
101         mAudioDeviceAttributes = audioDeviceAttributes;
102         // Device specific information will be initialized once an actual audio device info is set
103         mSampleRate = DEFAULT_SAMPLE_RATE;
104         mEncodingFormat = DEFAULT_ENCODING_FORMAT;
105         mChannelCount = DEFAULT_NUM_CHANNELS;
106         mDefaultGain = UNINITIALIZED_GAIN;
107         mMaxGain = UNINITIALIZED_GAIN;
108         mMinGain = UNINITIALIZED_GAIN;
109         mStepValue = UNINITIALIZED_GAIN;
110 
111         mCurrentGain = UNINITIALIZED_GAIN; // Not initialized till explicitly set
112     }
113 
isActive()114     boolean isActive() {
115         synchronized (mLock) {
116             return isActiveLocked();
117         }
118     }
119 
120     /**
121      * Updates the volume group with new audio device information if the device info matches
122      *
123      * <p>Note only updates car audio devices that are dynamic (i.e. non bus devices)
124      *
125      * @param devices List of audio devices that will be use for update
126      * @return {@code true} if the device is updated, {@code false} otherwise.
127      */
audioDevicesAdded(List<AudioDeviceInfo> devices)128     boolean audioDevicesAdded(List<AudioDeviceInfo> devices) {
129         Objects.requireNonNull(devices, "Audio devices can not be null");
130         // Audio device type bus do not allow for devices to be swapped at run time.
131         synchronized (mLock) {
132             if (getTypeLocked() == TYPE_BUS || isActiveLocked()) {
133                 return false;
134             }
135 
136             for (int c = 0; c < devices.size(); c++) {
137                 if (getTypeLocked() != devices.get(c).getType()) {
138                     continue;
139                 }
140                 setAudioDeviceInfoLocked(devices.get(c));
141                 return true;
142             }
143         }
144 
145         return false;
146     }
147 
148     /**
149      * Updates the volume group with removed device information if the device info matches
150      *
151      * <p>Note only updates car audio devices that are dynamic (i.e. non bus devices)
152      *
153      * @param devices List of audio devices that will be use for update
154      * @return {@code true} if the device is removed, {@code false} otherwise.
155      */
audioDevicesRemoved(List<AudioDeviceInfo> devices)156     public boolean audioDevicesRemoved(List<AudioDeviceInfo> devices) {
157         Objects.requireNonNull(devices, "Audio devices can not be null");
158         // Audio device type bus do not allow for devices to be swapped at run time.
159         synchronized (mLock) {
160             if (getTypeLocked() == TYPE_BUS) {
161                 return false;
162             }
163 
164             for (int c = 0; c < devices.size(); c++) {
165                 if (getTypeLocked() != devices.get(c).getType()
166                         || !getAddressLocked().equals(devices.get(c).getAddress())) {
167                     continue;
168                 }
169                 setAudioDeviceInfoLocked(null);
170                 return true;
171             }
172         }
173         return false;
174     }
175 
176     /**
177      * Sets the audio device info
178      *
179      * <p>Given that the audio device information may not be available at the time of construction,
180      * the method must call to set the audio device info, so that the actual details of the device
181      * are known.
182      *
183      * <p>Setting the audio device info to {@code null} means the device is not active, such is
184      * the case for dynamic audio devices that should disappear on disconnection.
185      *
186      * @param info that will be use to obtain the device specific information
187      */
setAudioDeviceInfo(@ullable AudioDeviceInfo info)188     void setAudioDeviceInfo(@Nullable AudioDeviceInfo info) {
189         synchronized (mLock) {
190             setAudioDeviceInfoLocked(info);
191         }
192     }
193 
getAudioDevice()194     AudioDeviceAttributes getAudioDevice() {
195         synchronized (mLock) {
196             return mAudioDeviceAttributes;
197         }
198     }
199 
getAddress()200     String getAddress() {
201         synchronized (mLock) {
202             return getAddressLocked();
203         }
204     }
205 
getType()206     int getType() {
207         synchronized (mLock) {
208             return getTypeLocked();
209         }
210     }
211 
212     /**
213      * By default, considers all AudioDevice can be used to establish dynamic policy mixing rules.
214      * until validation state is performed.
215      * Once called, the device is marked definitively as "connot be routed with dynamic mixes".
216      */
resetCanBeRoutedWithDynamicPolicyMix()217     void resetCanBeRoutedWithDynamicPolicyMix() {
218         synchronized (mLock) {
219             mCanBeRoutedWithDynamicPolicyMixRule = false;
220         }
221     }
222 
canBeRoutedWithDynamicPolicyMix()223     boolean canBeRoutedWithDynamicPolicyMix() {
224         synchronized (mLock) {
225             return mCanBeRoutedWithDynamicPolicyMixRule;
226         }
227     }
228 
getDefaultGain()229     int getDefaultGain() {
230         synchronized (mLock) {
231             return mDefaultGain;
232         }
233     }
234 
getMaxGain()235     int getMaxGain() {
236         synchronized (mLock) {
237             return mMaxGain;
238         }
239     }
240 
getMinGain()241     int getMinGain() {
242         synchronized (mLock) {
243             return mMinGain;
244         }
245     }
246 
getSampleRate()247     int getSampleRate() {
248         synchronized (mLock) {
249             return mSampleRate;
250         }
251     }
252 
getEncodingFormat()253     int getEncodingFormat() {
254         synchronized (mLock) {
255             return mEncodingFormat;
256         }
257     }
258 
getChannelCount()259     int getChannelCount() {
260         synchronized (mLock) {
261             return mChannelCount;
262         }
263     }
264 
getStepValue()265     int getStepValue() {
266         synchronized (mLock) {
267             return mStepValue;
268         }
269     }
270 
271 
setCurrentGain(int gainInMillibels)272     void setCurrentGain(int gainInMillibels) {
273         int gain = gainInMillibels;
274         // Clamp the incoming value to our valid range.  Out of range values ARE legal input
275         synchronized (mLock) {
276             if (gain < mMinGain) {
277                 gain = mMinGain;
278             } else if (gain > mMaxGain) {
279                 gain = mMaxGain;
280             }
281         }
282 
283         if (mAudioManager.setAudioDeviceGain(getAddress(), gain, true)) {
284             // Since we can't query for the gain on a device port later,
285             // we have to remember what we asked for
286             synchronized (mLock) {
287                 mCurrentGain = gain;
288             }
289         } else {
290             Slogf.e(CarLog.TAG_AUDIO, "Failed to setAudioPortGain " + gain
291                     + " for output device " + getAddress());
292         }
293     }
294 
295     // Updates audio device info for dynamic gain stage configurations
updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo)296     void updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo) {
297         synchronized (mLock) {
298             mMinGain = halDeviceInfo.getGainMinValue();
299             mMaxGain = halDeviceInfo.getGainMaxValue();
300             mStepValue = halDeviceInfo.getGainStepValue();
301             mDefaultGain = halDeviceInfo.getGainDefaultValue();
302         }
303     }
304 
305     @GuardedBy("mLock")
getTypeLocked()306     private int getTypeLocked() {
307         return mAudioDeviceAttributes.getType();
308     }
309 
310     @GuardedBy("mLock")
setAudioDeviceInfoLocked(AudioDeviceInfo info)311     private void setAudioDeviceInfoLocked(AudioDeviceInfo info) {
312         if (info != null && info.getType() != getTypeLocked()) {
313             return;
314         }
315 
316         // BUS device type can not be unset
317         if ((getTypeLocked() == TYPE_BUS)
318                 && (info == null || !getAddressLocked().equals(info.getAddress()))) {
319             return;
320         }
321 
322         resetAudioDeviceInfoToDefaultLocked();
323 
324         if (info == null) {
325             return;
326         }
327 
328         setAudioDeviceInfoWithNewInfoLocked(info);
329     }
330 
331     @GuardedBy("mLock")
setAudioDeviceInfoWithNewInfoLocked(AudioDeviceInfo info)332     private void setAudioDeviceInfoWithNewInfoLocked(AudioDeviceInfo info) {
333         setAudioGainInfoIfNeededLocked(info);
334         mIsActive = true;
335     }
336 
337     @GuardedBy("mLock")
setAudioGainInfoIfNeededLocked(AudioDeviceInfo info)338     private void setAudioGainInfoIfNeededLocked(AudioDeviceInfo info) {
339         mAudioDeviceAttributes = new AudioDeviceAttributes(info);
340         // Only audio device bus supports audio gain management by car audio service
341         // Dynamic devices only support core audio volume management
342         if (info.getType() != TYPE_BUS) {
343             return;
344         }
345         AudioGainInfo audioGainInfo = AudioManagerHelper.getAudioGainInfo(info);
346         mDefaultGain = audioGainInfo.getDefaultGain();
347         mMaxGain = audioGainInfo.getMaxGain();
348         mMinGain = audioGainInfo.getMinGain();
349         mStepValue = audioGainInfo.getStepValue();
350         mChannelCount = getMaxChannels(info);
351         mSampleRate = getMaxSampleRate(info);
352         mEncodingFormat = getEncodingFormat(info);
353     }
354 
355     @GuardedBy("mLock")
resetAudioDeviceInfoToDefaultLocked()356     private void resetAudioDeviceInfoToDefaultLocked() {
357         int type = mAudioDeviceAttributes.getType();
358         mAudioDeviceAttributes = new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
359                 type, /* address= */ "");
360         mSampleRate = DEFAULT_SAMPLE_RATE;
361         mEncodingFormat = DEFAULT_ENCODING_FORMAT;
362         mChannelCount = DEFAULT_NUM_CHANNELS;
363         mDefaultGain = UNINITIALIZED_GAIN;
364         mMaxGain = UNINITIALIZED_GAIN;
365         mMinGain = UNINITIALIZED_GAIN;
366         mStepValue = UNINITIALIZED_GAIN;
367         mCurrentGain = UNINITIALIZED_GAIN;
368         mIsActive = false;
369     }
370 
371     @GuardedBy("mLock")
getAddressLocked()372     private String getAddressLocked() {
373         return mAudioDeviceAttributes.getAddress();
374     }
375 
376     @GuardedBy("mLock")
isActiveLocked()377     private boolean isActiveLocked() {
378         return mIsActive;
379     }
380 
getMaxSampleRate(AudioDeviceInfo info)381     private static int getMaxSampleRate(AudioDeviceInfo info) {
382         int[] sampleRates = info.getSampleRates();
383         if (sampleRates == null || sampleRates.length == 0) {
384             return DEFAULT_SAMPLE_RATE;
385         }
386         int sampleRate = sampleRates[0];
387         for (int i = 1; i < sampleRates.length; i++) {
388             if (sampleRates[i] > sampleRate) {
389                 sampleRate = sampleRates[i];
390             }
391         }
392         return sampleRate;
393     }
394 
getEncodingFormat(AudioDeviceInfo info)395     private static int getEncodingFormat(AudioDeviceInfo info) {
396         int[] formats = info.getEncodings();
397         // If the formats are not specified, then arbitrary encoding are supported
398         if (formats == null) {
399             return DEFAULT_ENCODING_FORMAT;
400         }
401 
402         for (int c = 0; c < formats.length; c++) {
403             // Audio policy mix limits linear PCMs
404             if (isEncodingLinearPcm(formats[c])) {
405                 return formats[c];
406             }
407         }
408 
409         return DEFAULT_ENCODING_FORMAT;
410     }
411 
isEncodingLinearPcm(int audioFormat)412     private static boolean isEncodingLinearPcm(int audioFormat) {
413         switch (audioFormat) {
414             case ENCODING_PCM_16BIT:
415             case ENCODING_PCM_8BIT:
416             case ENCODING_PCM_FLOAT:
417             case ENCODING_PCM_24BIT_PACKED:
418             case ENCODING_PCM_32BIT:
419             case ENCODING_DEFAULT:
420                 return true;
421             default:
422                 return false;
423         }
424     }
425 
426     /*
427      * If there are no profiles in the device this will return the {@link #DEFAULT_NUM_CHANNELS}
428      */
getMaxChannels(AudioDeviceInfo info)429     private static int getMaxChannels(AudioDeviceInfo info) {
430         int numChannels = 1;
431         int[] channelMasks = info.getChannelMasks();
432         if (channelMasks == null) {
433             return numChannels;
434         }
435         for (int channelMask : channelMasks) {
436             int currentNumChannels = Integer.bitCount(channelMask);
437             if (currentNumChannels > numChannels) {
438                 numChannels = currentNumChannels;
439             }
440         }
441         return numChannels;
442     }
443 
444     @Override
445     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
toString()446     public String toString() {
447         int currentGain;
448         synchronized (mLock) {
449             currentGain = mCurrentGain;
450         }
451         return "address: " + getAddress()
452                 + " sampleRate: " + getSampleRate()
453                 + " encodingFormat: " + getEncodingFormat()
454                 + " channelCount: " + getChannelCount()
455                 + " currentGain: " + currentGain
456                 + " maxGain: " + getMaxGain()
457                 + " minGain: " + getMinGain();
458     }
459 
460     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)461     void dump(IndentingPrintWriter writer) {
462         synchronized (mLock) {
463             writer.printf("CarAudioDeviceInfo Device(%s)\n", mAudioDeviceAttributes.getAddress());
464             writer.printf("CarAudioDeviceInfo Type(%s)\n", mAudioDeviceAttributes.getType());
465             writer.increaseIndent();
466             writer.printf("Is active (%b)\n", mIsActive);
467             writer.printf("Routing with Dynamic Mix enabled (%b)\n",
468                     mCanBeRoutedWithDynamicPolicyMixRule);
469             writer.printf("sample rate / encoding format / channel count: %d %d %d\n",
470                     getSampleRate(), getEncodingFormat(), getChannelCount());
471             writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n",
472                     mMinGain, mMaxGain, mDefaultGain, mCurrentGain);
473             writer.decreaseIndent();
474         }
475     }
476 
477     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(long fieldId, ProtoOutputStream proto)478     void dumpProto(long fieldId, ProtoOutputStream proto) {
479         long token = proto.start(fieldId);
480         synchronized (mLock) {
481             proto.write(CarAudioDeviceInfoProto.ADDRESS, mAudioDeviceAttributes.getAddress());
482             proto.write(CarAudioDeviceInfoProto.CAN_BE_ROUTED_WITH_DYNAMIC_POLICY_MIX_RULE,
483                     mCanBeRoutedWithDynamicPolicyMixRule);
484             proto.write(CarAudioDeviceInfoProto.SAMPLE_RATE, getSampleRate());
485             proto.write(CarAudioDeviceInfoProto.ENCODING_FORMAT, getEncodingFormat());
486             proto.write(CarAudioDeviceInfoProto.CHANNEL_COUNT, getChannelCount());
487 
488             long volumeGainToken = proto.start(CarAudioDeviceInfoProto.VOLUME_GAIN);
489             proto.write(CarAudioDumpProto.CarVolumeGain.MIN_GAIN_INDEX, mMinGain);
490             proto.write(CarAudioDumpProto.CarVolumeGain.MAX_GAIN_INDEX, mMaxGain);
491             proto.write(CarAudioDumpProto.CarVolumeGain.DEFAULT_GAIN_INDEX, mDefaultGain);
492             proto.write(CarAudioDumpProto.CarVolumeGain.CURRENT_GAIN_INDEX, mCurrentGain);
493             proto.write(CarAudioDeviceInfoProto.IS_ACTIVE , mIsActive);
494             proto.end(volumeGainToken);
495         }
496         proto.end(token);
497     }
498 }
499