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 android.media.AudioDeviceInfo.TYPE_BUS; 19 import static android.media.AudioFormat.ENCODING_DEFAULT; 20 import static android.media.AudioFormat.ENCODING_PCM_16BIT; 21 import static android.media.AudioFormat.ENCODING_PCM_24BIT_PACKED; 22 import static android.media.AudioFormat.ENCODING_PCM_32BIT; 23 import static android.media.AudioFormat.ENCODING_PCM_8BIT; 24 import static android.media.AudioFormat.ENCODING_PCM_FLOAT; 25 26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 27 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 28 29 import android.annotation.Nullable; 30 import android.car.builtin.media.AudioManagerHelper; 31 import android.car.builtin.media.AudioManagerHelper.AudioGainInfo; 32 import android.car.builtin.util.Slogf; 33 import android.media.AudioDeviceAttributes; 34 import android.media.AudioDeviceInfo; 35 import android.util.proto.ProtoOutputStream; 36 37 import com.android.car.CarLog; 38 import com.android.car.audio.CarAudioDumpProto.CarAudioDeviceInfoProto; 39 import com.android.car.audio.hal.HalAudioDeviceInfo; 40 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 41 import com.android.car.internal.util.IndentingPrintWriter; 42 import com.android.internal.annotations.GuardedBy; 43 44 import java.util.List; 45 import java.util.Objects; 46 47 /** 48 * A helper class wraps {@link AudioDeviceAttributes}, and helps manage the details of the audio 49 * device: gains, format, sample rate, channel count 50 * 51 * Note to the reader. For whatever reason, it seems that AudioGain contains only configuration 52 * information (min/max/step, etc) while the AudioGainConfig class contains the 53 * actual currently active gain value(s). 54 */ 55 /* package */ final class CarAudioDeviceInfo { 56 57 public static final int DEFAULT_SAMPLE_RATE = 48000; 58 private static final int DEFAULT_NUM_CHANNELS = 1; 59 private static final int UNINITIALIZED_GAIN = -1; 60 61 /* 62 * PCM 16 bit is supposed to be guaranteed for all devices 63 * per {@link ENCODING_PCM_16BIT}'s documentation. 64 */ 65 private static final int DEFAULT_ENCODING_FORMAT = ENCODING_PCM_16BIT; 66 private final AudioManagerWrapper mAudioManager; 67 68 private final Object mLock = new Object(); 69 @GuardedBy("mLock") 70 private AudioDeviceAttributes mAudioDeviceAttributes; 71 @GuardedBy("mLock") 72 private int mDefaultGain; 73 @GuardedBy("mLock") 74 private int mMaxGain; 75 @GuardedBy("mLock") 76 private int mMinGain; 77 @GuardedBy("mLock") 78 private int mStepValue; 79 @GuardedBy("mLock") 80 private boolean mCanBeRoutedWithDynamicPolicyMixRule = true; 81 @GuardedBy("mLock") 82 private int mSampleRate; 83 @GuardedBy("mLock") 84 private int mEncodingFormat; 85 @GuardedBy("mLock") 86 private int mChannelCount; 87 88 /** 89 * We need to store the current gain because it is not accessible from the current 90 * audio engine implementation. It would be nice if AudioPort#activeConfig() would return it, 91 * but in the current implementation, that function actually works only for mixer ports. 92 */ 93 @GuardedBy("mLock") 94 private int mCurrentGain; 95 @GuardedBy("mLock") 96 private boolean mIsActive; 97 CarAudioDeviceInfo(AudioManagerWrapper audioManager, AudioDeviceAttributes audioDeviceAttributes)98 CarAudioDeviceInfo(AudioManagerWrapper audioManager, 99 AudioDeviceAttributes audioDeviceAttributes) { 100 mAudioManager = audioManager; 101 mAudioDeviceAttributes = audioDeviceAttributes; 102 // Device specific information will be initialized once an actual audio device info is set 103 mSampleRate = DEFAULT_SAMPLE_RATE; 104 mEncodingFormat = DEFAULT_ENCODING_FORMAT; 105 mChannelCount = DEFAULT_NUM_CHANNELS; 106 mDefaultGain = UNINITIALIZED_GAIN; 107 mMaxGain = UNINITIALIZED_GAIN; 108 mMinGain = UNINITIALIZED_GAIN; 109 mStepValue = UNINITIALIZED_GAIN; 110 111 mCurrentGain = UNINITIALIZED_GAIN; // Not initialized till explicitly set 112 } 113 isActive()114 boolean isActive() { 115 synchronized (mLock) { 116 return isActiveLocked(); 117 } 118 } 119 120 /** 121 * Updates the volume group with new audio device information if the device info matches 122 * 123 * <p>Note only updates car audio devices that are dynamic (i.e. non bus devices) 124 * 125 * @param devices List of audio devices that will be use for update 126 * @return {@code true} if the device is updated, {@code false} otherwise. 127 */ audioDevicesAdded(List<AudioDeviceInfo> devices)128 boolean audioDevicesAdded(List<AudioDeviceInfo> devices) { 129 Objects.requireNonNull(devices, "Audio devices can not be null"); 130 // Audio device type bus do not allow for devices to be swapped at run time. 131 synchronized (mLock) { 132 if (getTypeLocked() == TYPE_BUS || isActiveLocked()) { 133 return false; 134 } 135 136 for (int c = 0; c < devices.size(); c++) { 137 if (getTypeLocked() != devices.get(c).getType()) { 138 continue; 139 } 140 setAudioDeviceInfoLocked(devices.get(c)); 141 return true; 142 } 143 } 144 145 return false; 146 } 147 148 /** 149 * Updates the volume group with removed device information if the device info matches 150 * 151 * <p>Note only updates car audio devices that are dynamic (i.e. non bus devices) 152 * 153 * @param devices List of audio devices that will be use for update 154 * @return {@code true} if the device is removed, {@code false} otherwise. 155 */ audioDevicesRemoved(List<AudioDeviceInfo> devices)156 public boolean audioDevicesRemoved(List<AudioDeviceInfo> devices) { 157 Objects.requireNonNull(devices, "Audio devices can not be null"); 158 // Audio device type bus do not allow for devices to be swapped at run time. 159 synchronized (mLock) { 160 if (getTypeLocked() == TYPE_BUS) { 161 return false; 162 } 163 164 for (int c = 0; c < devices.size(); c++) { 165 if (getTypeLocked() != devices.get(c).getType() 166 || !getAddressLocked().equals(devices.get(c).getAddress())) { 167 continue; 168 } 169 setAudioDeviceInfoLocked(null); 170 return true; 171 } 172 } 173 return false; 174 } 175 176 /** 177 * Sets the audio device info 178 * 179 * <p>Given that the audio device information may not be available at the time of construction, 180 * the method must call to set the audio device info, so that the actual details of the device 181 * are known. 182 * 183 * <p>Setting the audio device info to {@code null} means the device is not active, such is 184 * the case for dynamic audio devices that should disappear on disconnection. 185 * 186 * @param info that will be use to obtain the device specific information 187 */ setAudioDeviceInfo(@ullable AudioDeviceInfo info)188 void setAudioDeviceInfo(@Nullable AudioDeviceInfo info) { 189 synchronized (mLock) { 190 setAudioDeviceInfoLocked(info); 191 } 192 } 193 getAudioDevice()194 AudioDeviceAttributes getAudioDevice() { 195 synchronized (mLock) { 196 return mAudioDeviceAttributes; 197 } 198 } 199 getAddress()200 String getAddress() { 201 synchronized (mLock) { 202 return getAddressLocked(); 203 } 204 } 205 getType()206 int getType() { 207 synchronized (mLock) { 208 return getTypeLocked(); 209 } 210 } 211 212 /** 213 * By default, considers all AudioDevice can be used to establish dynamic policy mixing rules. 214 * until validation state is performed. 215 * Once called, the device is marked definitively as "connot be routed with dynamic mixes". 216 */ resetCanBeRoutedWithDynamicPolicyMix()217 void resetCanBeRoutedWithDynamicPolicyMix() { 218 synchronized (mLock) { 219 mCanBeRoutedWithDynamicPolicyMixRule = false; 220 } 221 } 222 canBeRoutedWithDynamicPolicyMix()223 boolean canBeRoutedWithDynamicPolicyMix() { 224 synchronized (mLock) { 225 return mCanBeRoutedWithDynamicPolicyMixRule; 226 } 227 } 228 getDefaultGain()229 int getDefaultGain() { 230 synchronized (mLock) { 231 return mDefaultGain; 232 } 233 } 234 getMaxGain()235 int getMaxGain() { 236 synchronized (mLock) { 237 return mMaxGain; 238 } 239 } 240 getMinGain()241 int getMinGain() { 242 synchronized (mLock) { 243 return mMinGain; 244 } 245 } 246 getSampleRate()247 int getSampleRate() { 248 synchronized (mLock) { 249 return mSampleRate; 250 } 251 } 252 getEncodingFormat()253 int getEncodingFormat() { 254 synchronized (mLock) { 255 return mEncodingFormat; 256 } 257 } 258 getChannelCount()259 int getChannelCount() { 260 synchronized (mLock) { 261 return mChannelCount; 262 } 263 } 264 getStepValue()265 int getStepValue() { 266 synchronized (mLock) { 267 return mStepValue; 268 } 269 } 270 271 setCurrentGain(int gainInMillibels)272 void setCurrentGain(int gainInMillibels) { 273 int gain = gainInMillibels; 274 // Clamp the incoming value to our valid range. Out of range values ARE legal input 275 synchronized (mLock) { 276 if (gain < mMinGain) { 277 gain = mMinGain; 278 } else if (gain > mMaxGain) { 279 gain = mMaxGain; 280 } 281 } 282 283 if (mAudioManager.setAudioDeviceGain(getAddress(), gain, true)) { 284 // Since we can't query for the gain on a device port later, 285 // we have to remember what we asked for 286 synchronized (mLock) { 287 mCurrentGain = gain; 288 } 289 } else { 290 Slogf.e(CarLog.TAG_AUDIO, "Failed to setAudioPortGain " + gain 291 + " for output device " + getAddress()); 292 } 293 } 294 295 // Updates audio device info for dynamic gain stage configurations updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo)296 void updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo) { 297 synchronized (mLock) { 298 mMinGain = halDeviceInfo.getGainMinValue(); 299 mMaxGain = halDeviceInfo.getGainMaxValue(); 300 mStepValue = halDeviceInfo.getGainStepValue(); 301 mDefaultGain = halDeviceInfo.getGainDefaultValue(); 302 } 303 } 304 305 @GuardedBy("mLock") getTypeLocked()306 private int getTypeLocked() { 307 return mAudioDeviceAttributes.getType(); 308 } 309 310 @GuardedBy("mLock") setAudioDeviceInfoLocked(AudioDeviceInfo info)311 private void setAudioDeviceInfoLocked(AudioDeviceInfo info) { 312 if (info != null && info.getType() != getTypeLocked()) { 313 return; 314 } 315 316 // BUS device type can not be unset 317 if ((getTypeLocked() == TYPE_BUS) 318 && (info == null || !getAddressLocked().equals(info.getAddress()))) { 319 return; 320 } 321 322 resetAudioDeviceInfoToDefaultLocked(); 323 324 if (info == null) { 325 return; 326 } 327 328 setAudioDeviceInfoWithNewInfoLocked(info); 329 } 330 331 @GuardedBy("mLock") setAudioDeviceInfoWithNewInfoLocked(AudioDeviceInfo info)332 private void setAudioDeviceInfoWithNewInfoLocked(AudioDeviceInfo info) { 333 setAudioGainInfoIfNeededLocked(info); 334 mIsActive = true; 335 } 336 337 @GuardedBy("mLock") setAudioGainInfoIfNeededLocked(AudioDeviceInfo info)338 private void setAudioGainInfoIfNeededLocked(AudioDeviceInfo info) { 339 mAudioDeviceAttributes = new AudioDeviceAttributes(info); 340 // Only audio device bus supports audio gain management by car audio service 341 // Dynamic devices only support core audio volume management 342 if (info.getType() != TYPE_BUS) { 343 return; 344 } 345 AudioGainInfo audioGainInfo = AudioManagerHelper.getAudioGainInfo(info); 346 mDefaultGain = audioGainInfo.getDefaultGain(); 347 mMaxGain = audioGainInfo.getMaxGain(); 348 mMinGain = audioGainInfo.getMinGain(); 349 mStepValue = audioGainInfo.getStepValue(); 350 mChannelCount = getMaxChannels(info); 351 mSampleRate = getMaxSampleRate(info); 352 mEncodingFormat = getEncodingFormat(info); 353 } 354 355 @GuardedBy("mLock") resetAudioDeviceInfoToDefaultLocked()356 private void resetAudioDeviceInfoToDefaultLocked() { 357 int type = mAudioDeviceAttributes.getType(); 358 mAudioDeviceAttributes = new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, 359 type, /* address= */ ""); 360 mSampleRate = DEFAULT_SAMPLE_RATE; 361 mEncodingFormat = DEFAULT_ENCODING_FORMAT; 362 mChannelCount = DEFAULT_NUM_CHANNELS; 363 mDefaultGain = UNINITIALIZED_GAIN; 364 mMaxGain = UNINITIALIZED_GAIN; 365 mMinGain = UNINITIALIZED_GAIN; 366 mStepValue = UNINITIALIZED_GAIN; 367 mCurrentGain = UNINITIALIZED_GAIN; 368 mIsActive = false; 369 } 370 371 @GuardedBy("mLock") getAddressLocked()372 private String getAddressLocked() { 373 return mAudioDeviceAttributes.getAddress(); 374 } 375 376 @GuardedBy("mLock") isActiveLocked()377 private boolean isActiveLocked() { 378 return mIsActive; 379 } 380 getMaxSampleRate(AudioDeviceInfo info)381 private static int getMaxSampleRate(AudioDeviceInfo info) { 382 int[] sampleRates = info.getSampleRates(); 383 if (sampleRates == null || sampleRates.length == 0) { 384 return DEFAULT_SAMPLE_RATE; 385 } 386 int sampleRate = sampleRates[0]; 387 for (int i = 1; i < sampleRates.length; i++) { 388 if (sampleRates[i] > sampleRate) { 389 sampleRate = sampleRates[i]; 390 } 391 } 392 return sampleRate; 393 } 394 getEncodingFormat(AudioDeviceInfo info)395 private static int getEncodingFormat(AudioDeviceInfo info) { 396 int[] formats = info.getEncodings(); 397 // If the formats are not specified, then arbitrary encoding are supported 398 if (formats == null) { 399 return DEFAULT_ENCODING_FORMAT; 400 } 401 402 for (int c = 0; c < formats.length; c++) { 403 // Audio policy mix limits linear PCMs 404 if (isEncodingLinearPcm(formats[c])) { 405 return formats[c]; 406 } 407 } 408 409 return DEFAULT_ENCODING_FORMAT; 410 } 411 isEncodingLinearPcm(int audioFormat)412 private static boolean isEncodingLinearPcm(int audioFormat) { 413 switch (audioFormat) { 414 case ENCODING_PCM_16BIT: 415 case ENCODING_PCM_8BIT: 416 case ENCODING_PCM_FLOAT: 417 case ENCODING_PCM_24BIT_PACKED: 418 case ENCODING_PCM_32BIT: 419 case ENCODING_DEFAULT: 420 return true; 421 default: 422 return false; 423 } 424 } 425 426 /* 427 * If there are no profiles in the device this will return the {@link #DEFAULT_NUM_CHANNELS} 428 */ getMaxChannels(AudioDeviceInfo info)429 private static int getMaxChannels(AudioDeviceInfo info) { 430 int numChannels = 1; 431 int[] channelMasks = info.getChannelMasks(); 432 if (channelMasks == null) { 433 return numChannels; 434 } 435 for (int channelMask : channelMasks) { 436 int currentNumChannels = Integer.bitCount(channelMask); 437 if (currentNumChannels > numChannels) { 438 numChannels = currentNumChannels; 439 } 440 } 441 return numChannels; 442 } 443 444 @Override 445 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) toString()446 public String toString() { 447 int currentGain; 448 synchronized (mLock) { 449 currentGain = mCurrentGain; 450 } 451 return "address: " + getAddress() 452 + " sampleRate: " + getSampleRate() 453 + " encodingFormat: " + getEncodingFormat() 454 + " channelCount: " + getChannelCount() 455 + " currentGain: " + currentGain 456 + " maxGain: " + getMaxGain() 457 + " minGain: " + getMinGain(); 458 } 459 460 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)461 void dump(IndentingPrintWriter writer) { 462 synchronized (mLock) { 463 writer.printf("CarAudioDeviceInfo Device(%s)\n", mAudioDeviceAttributes.getAddress()); 464 writer.printf("CarAudioDeviceInfo Type(%s)\n", mAudioDeviceAttributes.getType()); 465 writer.increaseIndent(); 466 writer.printf("Is active (%b)\n", mIsActive); 467 writer.printf("Routing with Dynamic Mix enabled (%b)\n", 468 mCanBeRoutedWithDynamicPolicyMixRule); 469 writer.printf("sample rate / encoding format / channel count: %d %d %d\n", 470 getSampleRate(), getEncodingFormat(), getChannelCount()); 471 writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", 472 mMinGain, mMaxGain, mDefaultGain, mCurrentGain); 473 writer.decreaseIndent(); 474 } 475 } 476 477 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(long fieldId, ProtoOutputStream proto)478 void dumpProto(long fieldId, ProtoOutputStream proto) { 479 long token = proto.start(fieldId); 480 synchronized (mLock) { 481 proto.write(CarAudioDeviceInfoProto.ADDRESS, mAudioDeviceAttributes.getAddress()); 482 proto.write(CarAudioDeviceInfoProto.CAN_BE_ROUTED_WITH_DYNAMIC_POLICY_MIX_RULE, 483 mCanBeRoutedWithDynamicPolicyMixRule); 484 proto.write(CarAudioDeviceInfoProto.SAMPLE_RATE, getSampleRate()); 485 proto.write(CarAudioDeviceInfoProto.ENCODING_FORMAT, getEncodingFormat()); 486 proto.write(CarAudioDeviceInfoProto.CHANNEL_COUNT, getChannelCount()); 487 488 long volumeGainToken = proto.start(CarAudioDeviceInfoProto.VOLUME_GAIN); 489 proto.write(CarAudioDumpProto.CarVolumeGain.MIN_GAIN_INDEX, mMinGain); 490 proto.write(CarAudioDumpProto.CarVolumeGain.MAX_GAIN_INDEX, mMaxGain); 491 proto.write(CarAudioDumpProto.CarVolumeGain.DEFAULT_GAIN_INDEX, mDefaultGain); 492 proto.write(CarAudioDumpProto.CarVolumeGain.CURRENT_GAIN_INDEX, mCurrentGain); 493 proto.write(CarAudioDeviceInfoProto.IS_ACTIVE , mIsActive); 494 proto.end(volumeGainToken); 495 } 496 proto.end(token); 497 } 498 } 499