blob: a2897761bc6a9e4be83bc7eb347909ace5296b4f [file] [log] [blame]
/*
* 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);
}
}