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