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