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