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