Implement volume control on default audio HAL

Implemented volume control based on setting audio port config.

Bug: 336370745
Test: atest VtsHalAudioCoreTargetTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:122597a5e96c873239540a53523caf67fe806d90)
Merged-In: Ia590974e61aa3a1c3f70afdb54ce87c85e9a1b3c
Change-Id: Ia590974e61aa3a1c3f70afdb54ce87c85e9a1b3c
This commit is contained in:
Weilin Xu
2024-10-02 17:16:42 +00:00
committed by Android Build Cherrypicker Worker
parent ad6288c8bc
commit a33bb5eaf5
10 changed files with 160 additions and 1 deletions

View File

@@ -47,6 +47,7 @@ using aidl::android::media::audio::common::AudioDevice;
using aidl::android::media::audio::common::AudioDeviceType;
using aidl::android::media::audio::common::AudioFormatDescription;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioGainConfig;
using aidl::android::media::audio::common::AudioInputFlags;
using aidl::android::media::audio::common::AudioIoFlags;
using aidl::android::media::audio::common::AudioMMapPolicy;
@@ -1200,7 +1201,9 @@ ndk::ScopedAStatus Module::setAudioPortConfigImpl(
}
if (in_requested.gain.has_value()) {
// Let's pretend that gain can always be applied.
if (!setAudioPortConfigGain(*portIt, in_requested.gain.value())) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
out_suggested->gain = in_requested.gain.value();
}
@@ -1242,6 +1245,52 @@ ndk::ScopedAStatus Module::setAudioPortConfigImpl(
return ndk::ScopedAStatus::ok();
}
bool Module::setAudioPortConfigGain(const AudioPort& port, const AudioGainConfig& gainRequested) {
auto& ports = getConfig().ports;
if (gainRequested.index < 0 || gainRequested.index >= (int)port.gains.size()) {
LOG(ERROR) << __func__ << ": gains for port " << port.id << " is undefined";
return false;
}
int stepValue = port.gains[gainRequested.index].stepValue;
if (stepValue == 0) {
LOG(ERROR) << __func__ << ": port gain step value is 0";
return false;
}
int minValue = port.gains[gainRequested.index].minValue;
int maxValue = port.gains[gainRequested.index].maxValue;
if (gainRequested.values[0] > maxValue || gainRequested.values[0] < minValue) {
LOG(ERROR) << __func__ << ": gain value " << gainRequested.values[0]
<< " out of range of min and max gain config";
return false;
}
int gainIndex = (gainRequested.values[0] - minValue) / stepValue;
int totalSteps = (maxValue - minValue) / stepValue;
if (totalSteps == 0) {
LOG(ERROR) << __func__ << ": difference between port gain min value " << minValue
<< " and max value " << maxValue << " is less than step value " << stepValue;
return false;
}
// Root-power quantities are used in curve:
// 10^((minMb / 100 + (maxMb / 100 - minMb / 100) * gainIndex / totalSteps) / (10 * 2))
// where 100 is the conversion from mB to dB, 10 comes from the log 10 conversion from power
// ratios, and 2 means are the square of amplitude.
float gain =
pow(10, (minValue + (maxValue - minValue) * (gainIndex / (float)totalSteps)) / 2000);
if (gain < 0) {
LOG(ERROR) << __func__ << ": gain " << gain << " is less than 0";
return false;
}
for (const auto& route : getConfig().routes) {
if (route.sinkPortId != port.id) {
continue;
}
for (const auto sourcePortId : route.sourcePortIds) {
mStreams.setGain(sourcePortId, gain);
}
}
return true;
}
ndk::ScopedAStatus Module::resetAudioPatch(int32_t in_patchId) {
auto& patches = getConfig().patches;
auto patchIt = findById<AudioPatch>(patches, in_patchId);

View File

@@ -855,6 +855,11 @@ ndk::ScopedAStatus StreamCommonImpl::setConnectedDevices(
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamCommonImpl::setGain(float gain) {
LOG(DEBUG) << __func__ << ": gain " << gain;
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus StreamCommonImpl::bluetoothParametersUpdated() {
LOG(DEBUG) << __func__;
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);

View File

@@ -260,4 +260,12 @@ ndk::ScopedAStatus StreamSwitcher::bluetoothParametersUpdated() {
return mStream->bluetoothParametersUpdated();
}
ndk::ScopedAStatus StreamSwitcher::setGain(float gain) {
if (mStream == nullptr) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
return mStream->setGain(gain);
}
} // namespace aidl::android::hardware::audio::core

View File

@@ -110,6 +110,7 @@ StreamAlsa::~StreamAlsa() {
mReadWriteRetries);
maxLatency = proxy_get_latency(mAlsaDeviceProxies[0].get());
} else {
alsa::applyGain(buffer, mGain, bytesToTransfer, mConfig.value().format, mConfig->channels);
for (auto& proxy : mAlsaDeviceProxies) {
proxy_write_with_retries(proxy.get(), buffer, bytesToTransfer, mReadWriteRetries);
maxLatency = std::max(maxLatency, proxy_get_latency(proxy.get()));
@@ -159,4 +160,9 @@ void StreamAlsa::shutdown() {
mAlsaDeviceProxies.clear();
}
ndk::ScopedAStatus StreamAlsa::setGain(float gain) {
mGain = gain;
return ndk::ScopedAStatus::ok();
}
} // namespace aidl::android::hardware::audio::core

View File

@@ -22,6 +22,8 @@
#include <aidl/android/media/audio/common/AudioFormatType.h>
#include <aidl/android/media/audio/common/PcmType.h>
#include <android-base/logging.h>
#include <audio_utils/primitives.h>
#include <cutils/compiler.h>
#include "Utils.h"
#include "core-impl/utils.h"
@@ -343,4 +345,68 @@ pcm_format aidl2c_AudioFormatDescription_pcm_format(const AudioFormatDescription
return findValueOrDefault(getAudioFormatDescriptorToPcmFormatMap(), aidl, PCM_FORMAT_INVALID);
}
void applyGain(void* buffer, float gain, size_t bytesToTransfer, enum pcm_format pcmFormat,
int channelCount) {
if (channelCount != 1 && channelCount != 2) {
LOG(WARNING) << __func__ << ": unsupported channel count " << channelCount;
return;
}
if (!getPcmFormatToAudioFormatDescMap().contains(pcmFormat)) {
LOG(WARNING) << __func__ << ": unsupported pcm format " << pcmFormat;
return;
}
const float unityGainFloat = 1.0f;
if (std::abs(gain - unityGainFloat) < 1e-6) {
return;
}
int numFrames;
switch (pcmFormat) {
case PCM_FORMAT_S16_LE: {
const uint16_t unityGainQ4_12 = u4_12_from_float(unityGainFloat);
const uint16_t vl = u4_12_from_float(gain);
const uint32_t vrl = (vl << 16) | vl;
if (channelCount == 2) {
numFrames = bytesToTransfer / sizeof(uint32_t);
uint32_t* intBuffer = (uint32_t*)buffer;
if (CC_UNLIKELY(vl > unityGainQ4_12)) {
// volume is boosted, so we might need to clamp even though
// we process only one track.
do {
int32_t l = mulRL(1, *intBuffer, vrl) >> 12;
int32_t r = mulRL(0, *intBuffer, vrl) >> 12;
l = clamp16(l);
r = clamp16(r);
*intBuffer++ = (r << 16) | (l & 0xFFFF);
} while (--numFrames);
} else {
do {
int32_t l = mulRL(1, *intBuffer, vrl) >> 12;
int32_t r = mulRL(0, *intBuffer, vrl) >> 12;
*intBuffer++ = (r << 16) | (l & 0xFFFF);
} while (--numFrames);
}
} else {
numFrames = bytesToTransfer / sizeof(uint16_t);
int16_t* intBuffer = (int16_t*)buffer;
if (CC_UNLIKELY(vl > unityGainQ4_12)) {
// volume is boosted, so we might need to clamp even though
// we process only one track.
do {
int32_t mono = mulRL(1, *intBuffer, vrl) >> 12;
*intBuffer++ = clamp16(mono);
} while (--numFrames);
} else {
do {
int32_t mono = mulRL(1, *intBuffer, vrl) >> 12;
*intBuffer++ = static_cast<int16_t>(mono & 0xFFFF);
} while (--numFrames);
}
}
} break;
default:
// TODO(336370745): Implement gain for other supported formats
break;
}
}
} // namespace aidl::android::hardware::audio::core::alsa

View File

@@ -59,6 +59,8 @@ class DeviceProxy {
AlsaProxy mProxy;
};
void applyGain(void* buffer, float gain, size_t bytesToTransfer, enum pcm_format pcmFormat,
int channelCount);
::aidl::android::media::audio::common::AudioChannelLayout getChannelLayoutMaskFromChannelCount(
unsigned int channelCount, int isInput);
::aidl::android::media::audio::common::AudioChannelLayout getChannelIndexMaskFromChannelCount(

View File

@@ -263,6 +263,9 @@ class Module : public BnModule {
::aidl::android::media::audio::common::AudioPortConfig* out_suggested, bool* applied);
ndk::ScopedAStatus updateStreamsConnectedState(const AudioPatch& oldPatch,
const AudioPatch& newPatch);
bool setAudioPortConfigGain(
const ::aidl::android::media::audio::common::AudioPort& port,
const ::aidl::android::media::audio::common::AudioGainConfig& gainRequested);
};
std::ostream& operator<<(std::ostream& os, Module::Type t);

View File

@@ -38,6 +38,7 @@
#include <aidl/android/media/audio/common/AudioIoFlags.h>
#include <aidl/android/media/audio/common/AudioOffloadInfo.h>
#include <aidl/android/media/audio/common/MicrophoneInfo.h>
#include <android-base/thread_annotations.h>
#include <error/expected_utils.h>
#include <fmq/AidlMessageQueue.h>
#include <system/thread_defs.h>
@@ -342,6 +343,7 @@ struct StreamCommonInterface {
virtual ndk::ScopedAStatus setConnectedDevices(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) = 0;
virtual ndk::ScopedAStatus bluetoothParametersUpdated() = 0;
virtual ndk::ScopedAStatus setGain(float gain) = 0;
};
// This is equivalent to automatically generated 'IStreamCommonDelegator' but uses
@@ -443,6 +445,7 @@ class StreamCommonImpl : virtual public StreamCommonInterface, virtual public Dr
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices)
override;
ndk::ScopedAStatus bluetoothParametersUpdated() override;
ndk::ScopedAStatus setGain(float gain) override;
protected:
static StreamWorkerInterface::CreateInstance getDefaultInWorkerCreator() {
@@ -609,6 +612,12 @@ class StreamWrapper {
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus setGain(float gain) {
auto s = mStream.lock();
if (s) return s->setGain(gain);
return ndk::ScopedAStatus::ok();
}
private:
std::weak_ptr<StreamCommonInterface> mStream;
ndk::SpAIBinder mStreamBinder;
@@ -644,6 +653,12 @@ class Streams {
return isOk ? ndk::ScopedAStatus::ok()
: ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus setGain(int32_t portId, float gain) {
if (auto it = mStreams.find(portId); it != mStreams.end()) {
return it->second.setGain(gain);
}
return ndk::ScopedAStatus::ok();
}
private:
// Maps port ids and port config ids to streams. Multimap because a port

View File

@@ -45,6 +45,7 @@ class StreamAlsa : public StreamCommonImpl {
int32_t* latencyMs) override;
::android::status_t refinePosition(StreamDescriptor::Position* position) override;
void shutdown() override;
ndk::ScopedAStatus setGain(float gain) override;
protected:
// Called from 'start' to initialize 'mAlsaDeviceProxies', the vector must be non-empty.
@@ -58,6 +59,9 @@ class StreamAlsa : public StreamCommonImpl {
const int mReadWriteRetries;
// All fields below are only used on the worker thread.
std::vector<alsa::DeviceProxy> mAlsaDeviceProxies;
private:
std::atomic<float> mGain = 1.0;
};
} // namespace aidl::android::hardware::audio::core

View File

@@ -130,6 +130,7 @@ class StreamSwitcher : virtual public StreamCommonInterface {
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices)
override;
ndk::ScopedAStatus bluetoothParametersUpdated() override;
ndk::ScopedAStatus setGain(float gain) override;
protected:
// Since switching a stream requires closing down the current stream, StreamSwitcher