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 android.media.AudioDeviceInfo;
19 import android.media.AudioDevicePort;
20 import android.media.AudioFormat;
21 import android.media.AudioGain;
22 import android.media.AudioGainConfig;
23 import android.media.AudioManager;
24 import android.media.AudioPort;
25 import android.util.Log;
26 
27 import com.android.car.CarLog;
28 import com.android.internal.util.Preconditions;
29 
30 import java.io.PrintWriter;
31 import java.util.Objects;
32 
33 /**
34  * A helper class wraps {@link AudioDeviceInfo}, and helps get/set the gain on a specific port
35  * in terms of millibels.
36  * Note to the reader. For whatever reason, it seems that AudioGain contains only configuration
37  * information (min/max/step, etc) while the AudioGainConfig class contains the
38  * actual currently active gain value(s).
39  */
40 /* package */ class CarAudioDeviceInfo {
41 
42     private final AudioDeviceInfo mAudioDeviceInfo;
43     private final int mSampleRate;
44     private final int mEncodingFormat;
45     private final int mChannelCount;
46     private final int mDefaultGain;
47     private final int mMaxGain;
48     private final int mMinGain;
49     private final int mStepValue;
50 
51     /**
52      * We need to store the current gain because it is not accessible from the current
53      * audio engine implementation. It would be nice if AudioPort#activeConfig() would return it,
54      * but in the current implementation, that function actually works only for mixer ports.
55      */
56     private int mCurrentGain;
57 
CarAudioDeviceInfo(AudioDeviceInfo audioDeviceInfo)58     CarAudioDeviceInfo(AudioDeviceInfo audioDeviceInfo) {
59         mAudioDeviceInfo = audioDeviceInfo;
60         mSampleRate = getMaxSampleRate(audioDeviceInfo);
61         mEncodingFormat = getEncodingFormat(audioDeviceInfo);
62         mChannelCount = getMaxChannels(audioDeviceInfo);
63         final AudioGain audioGain = Objects.requireNonNull(
64                 getAudioGain(), "No audio gain on device port " + audioDeviceInfo);
65         mDefaultGain = audioGain.defaultValue();
66         mMaxGain = audioGain.maxValue();
67         mMinGain = audioGain.minValue();
68         mStepValue = audioGain.stepValue();
69 
70         mCurrentGain = -1; // Not initialized till explicitly set
71     }
72 
getAudioDeviceInfo()73     AudioDeviceInfo getAudioDeviceInfo() {
74         return mAudioDeviceInfo;
75     }
76 
getAudioDevicePort()77     AudioDevicePort getAudioDevicePort() {
78         return mAudioDeviceInfo.getPort();
79     }
80 
getAddress()81     String getAddress() {
82         return mAudioDeviceInfo.getAddress();
83     }
84 
getDefaultGain()85     int getDefaultGain() {
86         return mDefaultGain;
87     }
88 
getMaxGain()89     int getMaxGain() {
90         return mMaxGain;
91     }
92 
getMinGain()93     int getMinGain() {
94         return mMinGain;
95     }
96 
getSampleRate()97     int getSampleRate() {
98         return mSampleRate;
99     }
100 
getEncodingFormat()101     int getEncodingFormat() {
102         return mEncodingFormat;
103     }
104 
getChannelCount()105     int getChannelCount() {
106         return mChannelCount;
107     }
108 
getStepValue()109     int getStepValue() {
110         return mStepValue;
111     }
112 
113     // Input is in millibels
setCurrentGain(int gainInMillibels)114     void setCurrentGain(int gainInMillibels) {
115         // Clamp the incoming value to our valid range.  Out of range values ARE legal input
116         if (gainInMillibels < mMinGain) {
117             gainInMillibels = mMinGain;
118         } else if (gainInMillibels > mMaxGain) {
119             gainInMillibels = mMaxGain;
120         }
121 
122         // Push the new gain value down to our underlying port which will cause it to show up
123         // at the HAL.
124         AudioGain audioGain = getAudioGain();
125         if (audioGain == null) {
126             Log.e(CarLog.TAG_AUDIO, "getAudioGain() returned null.");
127             return;
128         }
129 
130         // size of gain values is 1 in MODE_JOINT
131         AudioGainConfig audioGainConfig = audioGain.buildConfig(
132                 AudioGain.MODE_JOINT,
133                 audioGain.channelMask(),
134                 new int[] { gainInMillibels },
135                 0);
136         if (audioGainConfig == null) {
137             Log.e(CarLog.TAG_AUDIO, "Failed to construct AudioGainConfig");
138             return;
139         }
140 
141         int r = AudioManager.setAudioPortGain(getAudioDevicePort(), audioGainConfig);
142         if (r == AudioManager.SUCCESS) {
143             // Since we can't query for the gain on a device port later,
144             // we have to remember what we asked for
145             mCurrentGain = gainInMillibels;
146         } else {
147             Log.e(CarLog.TAG_AUDIO, "Failed to setAudioPortGain: " + r);
148         }
149     }
150 
getMaxSampleRate(AudioDeviceInfo info)151     private int getMaxSampleRate(AudioDeviceInfo info) {
152         int[] sampleRates = info.getSampleRates();
153         if (sampleRates == null || sampleRates.length == 0) {
154             return 48000;
155         }
156         int sampleRate = sampleRates[0];
157         for (int i = 1; i < sampleRates.length; i++) {
158             if (sampleRates[i] > sampleRate) {
159                 sampleRate = sampleRates[i];
160             }
161         }
162         return sampleRate;
163     }
164 
165     /** Always returns {@link AudioFormat#ENCODING_PCM_16BIT} as for now */
getEncodingFormat(AudioDeviceInfo info)166     private int getEncodingFormat(AudioDeviceInfo info) {
167         return AudioFormat.ENCODING_PCM_16BIT;
168     }
169 
170     /**
171      * Gets the maximum channel count for a given {@link AudioDeviceInfo}
172      *
173      * @param info {@link AudioDeviceInfo} instance to get maximum channel count for
174      * @return Maximum channel count for a given {@link AudioDeviceInfo},
175      * 1 (mono) if there is no channel masks configured
176      */
getMaxChannels(AudioDeviceInfo info)177     private int getMaxChannels(AudioDeviceInfo info) {
178         int numChannels = 1;
179         int[] channelMasks = info.getChannelMasks();
180         if (channelMasks == null) {
181             return numChannels;
182         }
183         for (int channelMask : channelMasks) {
184             int currentNumChannels = Integer.bitCount(channelMask);
185             if (currentNumChannels > numChannels) {
186                 numChannels = currentNumChannels;
187             }
188         }
189         return numChannels;
190     }
191 
192     /**
193      * @return {@link AudioGain} with {@link AudioGain#MODE_JOINT} on a given {@link AudioPort}.
194      * This is useful for inspecting the configuration data associated with this gain controller
195      * (min/max/step/default).
196      */
getAudioGain()197     AudioGain getAudioGain() {
198         final AudioDevicePort audioPort = getAudioDevicePort();
199         if (audioPort != null && audioPort.gains().length > 0) {
200             for (AudioGain audioGain : audioPort.gains()) {
201                 if ((audioGain.mode() & AudioGain.MODE_JOINT) != 0) {
202                     return checkAudioGainConfiguration(audioGain);
203                 }
204             }
205         }
206         return null;
207     }
208 
209     /**
210      * Constraints applied to gain configuration, see also audio_policy_configuration.xml
211      */
checkAudioGainConfiguration(AudioGain audioGain)212     private AudioGain checkAudioGainConfiguration(AudioGain audioGain) {
213         Preconditions.checkArgument(audioGain.maxValue() >= audioGain.minValue());
214         Preconditions.checkArgument((audioGain.defaultValue() >= audioGain.minValue())
215                 && (audioGain.defaultValue() <= audioGain.maxValue()));
216         Preconditions.checkArgument(
217                 ((audioGain.maxValue() - audioGain.minValue()) % audioGain.stepValue()) == 0);
218         Preconditions.checkArgument(
219                 ((audioGain.defaultValue() - audioGain.minValue()) % audioGain.stepValue()) == 0);
220         return audioGain;
221     }
222 
223     @Override
toString()224     public String toString() {
225         return "address: " + mAudioDeviceInfo.getAddress()
226                 + " sampleRate: " + getSampleRate()
227                 + " encodingFormat: " + getEncodingFormat()
228                 + " channelCount: " + getChannelCount()
229                 + " currentGain: " + mCurrentGain
230                 + " maxGain: " + mMaxGain
231                 + " minGain: " + mMinGain;
232     }
233 
dump(String indent, PrintWriter writer)234     void dump(String indent, PrintWriter writer) {
235         writer.printf("%sCarAudioDeviceInfo Device(%s)\n ",
236                 indent, mAudioDeviceInfo.getAddress());
237         writer.printf("%s\tsample rate / encoding format / channel count: %d %d %d\n",
238                 indent, getSampleRate(), getEncodingFormat(), getChannelCount());
239         writer.printf("%s\tGain values (min / max / default/ current): %d %d %d %d\n",
240                 indent, mMinGain, mMaxGain, mDefaultGain, mCurrentGain);
241     }
242 }
243