| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.car.audio; |
| |
| import static android.media.AudioFormat.ENCODING_DEFAULT; |
| import static android.media.AudioFormat.ENCODING_PCM_16BIT; |
| import static android.media.AudioFormat.ENCODING_PCM_24BIT_PACKED; |
| import static android.media.AudioFormat.ENCODING_PCM_32BIT; |
| import static android.media.AudioFormat.ENCODING_PCM_8BIT; |
| import static android.media.AudioFormat.ENCODING_PCM_FLOAT; |
| |
| import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; |
| import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; |
| |
| import android.car.builtin.media.AudioManagerHelper; |
| import android.car.builtin.media.AudioManagerHelper.AudioGainInfo; |
| import android.car.builtin.util.Slogf; |
| import android.media.AudioDeviceAttributes; |
| import android.media.AudioDeviceInfo; |
| import android.media.AudioManager; |
| import android.util.proto.ProtoOutputStream; |
| |
| import com.android.car.CarLog; |
| import com.android.car.audio.CarAudioDumpProto.CarAudioDeviceInfoProto; |
| import com.android.car.audio.hal.HalAudioDeviceInfo; |
| import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; |
| import com.android.car.internal.util.IndentingPrintWriter; |
| import com.android.internal.annotations.GuardedBy; |
| |
| /** |
| * A helper class wraps {@link AudioDeviceAttributes}, and helps manage the details of the audio |
| * device: gains, format, sample rate, channel count |
| * |
| * Note to the reader. For whatever reason, it seems that AudioGain contains only configuration |
| * information (min/max/step, etc) while the AudioGainConfig class contains the |
| * actual currently active gain value(s). |
| */ |
| /* package */ final class CarAudioDeviceInfo { |
| |
| public static final int DEFAULT_SAMPLE_RATE = 48000; |
| private static final int DEFAULT_NUM_CHANNELS = 1; |
| private static final int UNINITIALIZED_GAIN = -1; |
| |
| /* |
| * PCM 16 bit is supposed to be guaranteed for all devices |
| * per {@link ENCODING_PCM_16BIT}'s documentation. |
| */ |
| private static final int DEFAULT_ENCODING_FORMAT = ENCODING_PCM_16BIT; |
| private final AudioDeviceAttributes mAudioDeviceAttributes; |
| private final AudioManager mAudioManager; |
| |
| private final Object mLock = new Object(); |
| @GuardedBy("mLock") |
| private int mDefaultGain; |
| @GuardedBy("mLock") |
| private int mMaxGain; |
| @GuardedBy("mLock") |
| private int mMinGain; |
| @GuardedBy("mLock") |
| private int mStepValue; |
| @GuardedBy("mLock") |
| private boolean mCanBeRoutedWithDynamicPolicyMixRule = true; |
| @GuardedBy("mLock") |
| private int mSampleRate; |
| @GuardedBy("mLock") |
| private int mEncodingFormat; |
| @GuardedBy("mLock") |
| private int mChannelCount; |
| |
| /** |
| * We need to store the current gain because it is not accessible from the current |
| * audio engine implementation. It would be nice if AudioPort#activeConfig() would return it, |
| * but in the current implementation, that function actually works only for mixer ports. |
| */ |
| private int mCurrentGain; |
| |
| CarAudioDeviceInfo(AudioManager audioManager, AudioDeviceAttributes audioDeviceAttributes) { |
| mAudioManager = audioManager; |
| mAudioDeviceAttributes = audioDeviceAttributes; |
| // Device specific information will be initialized once an actual audio device info is set |
| mSampleRate = DEFAULT_SAMPLE_RATE; |
| mEncodingFormat = DEFAULT_ENCODING_FORMAT; |
| mChannelCount = DEFAULT_NUM_CHANNELS; |
| mDefaultGain = UNINITIALIZED_GAIN; |
| mMaxGain = UNINITIALIZED_GAIN; |
| mMinGain = UNINITIALIZED_GAIN; |
| mStepValue = UNINITIALIZED_GAIN; |
| |
| mCurrentGain = UNINITIALIZED_GAIN; // Not initialized till explicitly set |
| } |
| |
| /** |
| * Sets the audio device info |
| * |
| * <p>Given that the audio device information may not be available at the time of construction, |
| * the method must call to set the audio device info, so that the actual details of the device |
| * are known. |
| * |
| * @param info that will be use to obtain the device specific information |
| */ |
| void setAudioDeviceInfo(AudioDeviceInfo info) { |
| AudioGainInfo audioGainInfo = AudioManagerHelper.getAudioGainInfo(info); |
| synchronized (mLock) { |
| mDefaultGain = audioGainInfo.getDefaultGain(); |
| mMaxGain = audioGainInfo.getMaxGain(); |
| mMinGain = audioGainInfo.getMinGain(); |
| mStepValue = audioGainInfo.getStepValue(); |
| mChannelCount = getMaxChannels(info); |
| mSampleRate = getMaxSampleRate(info); |
| mEncodingFormat = getEncodingFormat(info); |
| } |
| |
| } |
| |
| AudioDeviceAttributes getAudioDevice() { |
| return mAudioDeviceAttributes; |
| } |
| |
| String getAddress() { |
| return mAudioDeviceAttributes.getAddress(); |
| } |
| |
| /** |
| * By default, considers all AudioDevice can be used to establish dynamic policy mixing rules. |
| * until validation state is performed. |
| * Once called, the device is marked definitively as "connot be routed with dynamic mixes". |
| */ |
| void resetCanBeRoutedWithDynamicPolicyMix() { |
| synchronized (mLock) { |
| mCanBeRoutedWithDynamicPolicyMixRule = false; |
| } |
| } |
| |
| boolean canBeRoutedWithDynamicPolicyMix() { |
| synchronized (mLock) { |
| return mCanBeRoutedWithDynamicPolicyMixRule; |
| } |
| } |
| |
| int getDefaultGain() { |
| synchronized (mLock) { |
| return mDefaultGain; |
| } |
| } |
| |
| int getMaxGain() { |
| synchronized (mLock) { |
| return mMaxGain; |
| } |
| } |
| |
| int getMinGain() { |
| synchronized (mLock) { |
| return mMinGain; |
| } |
| } |
| |
| int getSampleRate() { |
| synchronized (mLock) { |
| return mSampleRate; |
| } |
| } |
| |
| int getEncodingFormat() { |
| synchronized (mLock) { |
| return mEncodingFormat; |
| } |
| } |
| |
| int getChannelCount() { |
| synchronized (mLock) { |
| return mChannelCount; |
| } |
| } |
| |
| int getStepValue() { |
| synchronized (mLock) { |
| return mStepValue; |
| } |
| } |
| |
| |
| void setCurrentGain(int gainInMillibels) { |
| int gain = gainInMillibels; |
| // Clamp the incoming value to our valid range. Out of range values ARE legal input |
| synchronized (mLock) { |
| if (gain < mMinGain) { |
| gain = mMinGain; |
| } else if (gain > mMaxGain) { |
| gain = mMaxGain; |
| } |
| } |
| |
| if (AudioManagerHelper.setAudioDeviceGain(mAudioManager, |
| getAddress(), gain, true)) { |
| // Since we can't query for the gain on a device port later, |
| // we have to remember what we asked for |
| mCurrentGain = gain; |
| } else { |
| Slogf.e(CarLog.TAG_AUDIO, "Failed to setAudioPortGain " + gain |
| + " for output device " + getAddress()); |
| } |
| } |
| |
| // Updates audio device info for dynamic gain stage configurations |
| void updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo) { |
| synchronized (mLock) { |
| mMinGain = halDeviceInfo.getGainMinValue(); |
| mMaxGain = halDeviceInfo.getGainMaxValue(); |
| mStepValue = halDeviceInfo.getGainStepValue(); |
| mDefaultGain = halDeviceInfo.getGainDefaultValue(); |
| } |
| } |
| |
| private static int getMaxSampleRate(AudioDeviceInfo info) { |
| int[] sampleRates = info.getSampleRates(); |
| if (sampleRates == null || sampleRates.length == 0) { |
| return DEFAULT_SAMPLE_RATE; |
| } |
| int sampleRate = sampleRates[0]; |
| for (int i = 1; i < sampleRates.length; i++) { |
| if (sampleRates[i] > sampleRate) { |
| sampleRate = sampleRates[i]; |
| } |
| } |
| return sampleRate; |
| } |
| |
| private static int getEncodingFormat(AudioDeviceInfo info) { |
| int[] formats = info.getEncodings(); |
| // If the formats are not specified, then arbitrary encoding are supported |
| if (formats == null) { |
| return DEFAULT_ENCODING_FORMAT; |
| } |
| |
| for (int c = 0; c < formats.length; c++) { |
| // Audio policy mix limits linear PCMs |
| if (isEncodingLinearPcm(formats[c])) { |
| return formats[c]; |
| } |
| } |
| |
| return DEFAULT_ENCODING_FORMAT; |
| } |
| |
| private static boolean isEncodingLinearPcm(int audioFormat) { |
| switch (audioFormat) { |
| case ENCODING_PCM_16BIT: |
| case ENCODING_PCM_8BIT: |
| case ENCODING_PCM_FLOAT: |
| case ENCODING_PCM_24BIT_PACKED: |
| case ENCODING_PCM_32BIT: |
| case ENCODING_DEFAULT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /* |
| * If there are no profiles in the device this will return the {@link #DEFAULT_NUM_CHANNELS} |
| */ |
| private static int getMaxChannels(AudioDeviceInfo info) { |
| int numChannels = 1; |
| int[] channelMasks = info.getChannelMasks(); |
| if (channelMasks == null) { |
| return numChannels; |
| } |
| for (int channelMask : channelMasks) { |
| int currentNumChannels = Integer.bitCount(channelMask); |
| if (currentNumChannels > numChannels) { |
| numChannels = currentNumChannels; |
| } |
| } |
| return numChannels; |
| } |
| |
| @Override |
| @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) |
| public String toString() { |
| return "address: " + mAudioDeviceAttributes.getAddress() |
| + " sampleRate: " + getSampleRate() |
| + " encodingFormat: " + getEncodingFormat() |
| + " channelCount: " + getChannelCount() |
| + " currentGain: " + mCurrentGain |
| + " maxGain: " + getMaxGain() |
| + " minGain: " + getMinGain(); |
| } |
| |
| @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) |
| void dump(IndentingPrintWriter writer) { |
| synchronized (mLock) { |
| writer.printf("CarAudioDeviceInfo Device(%s)\n", mAudioDeviceAttributes.getAddress()); |
| writer.increaseIndent(); |
| writer.printf("Routing with Dynamic Mix enabled (%b)\n", |
| mCanBeRoutedWithDynamicPolicyMixRule); |
| writer.printf("sample rate / encoding format / channel count: %d %d %d\n", |
| getSampleRate(), getEncodingFormat(), getChannelCount()); |
| writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", |
| mMinGain, mMaxGain, mDefaultGain, mCurrentGain); |
| writer.decreaseIndent(); |
| } |
| } |
| |
| @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) |
| void dumpProto(long fieldId, ProtoOutputStream proto) { |
| long token = proto.start(fieldId); |
| synchronized (mLock) { |
| proto.write(CarAudioDeviceInfoProto.ADDRESS, mAudioDeviceAttributes.getAddress()); |
| proto.write(CarAudioDeviceInfoProto.CAN_BE_ROUTED_WITH_DYNAMIC_POLICY_MIX_RULE, |
| mCanBeRoutedWithDynamicPolicyMixRule); |
| proto.write(CarAudioDeviceInfoProto.SAMPLE_RATE, getSampleRate()); |
| proto.write(CarAudioDeviceInfoProto.ENCODING_FORMAT, getEncodingFormat()); |
| proto.write(CarAudioDeviceInfoProto.CHANNEL_COUNT, getChannelCount()); |
| |
| long volumeGainToken = proto.start(CarAudioDeviceInfoProto.VOLUME_GAIN); |
| proto.write(CarAudioDumpProto.CarVolumeGain.MIN_GAIN_INDEX, mMinGain); |
| proto.write(CarAudioDumpProto.CarVolumeGain.MAX_GAIN_INDEX, mMaxGain); |
| proto.write(CarAudioDumpProto.CarVolumeGain.DEFAULT_GAIN_INDEX, mDefaultGain); |
| proto.write(CarAudioDumpProto.CarVolumeGain.CURRENT_GAIN_INDEX, mCurrentGain); |
| proto.end(volumeGainToken); |
| } |
| proto.end(token); |
| } |
| } |