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