diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp index c14d06ec18..45ce5ef1d1 100644 --- a/audio/aidl/default/Module.cpp +++ b/audio/aidl/default/Module.cpp @@ -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(patches, in_patchId); diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp index 3e4650df7f..de66293d3e 100644 --- a/audio/aidl/default/Stream.cpp +++ b/audio/aidl/default/Stream.cpp @@ -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); diff --git a/audio/aidl/default/StreamSwitcher.cpp b/audio/aidl/default/StreamSwitcher.cpp index 8ba15a83e6..005288993f 100644 --- a/audio/aidl/default/StreamSwitcher.cpp +++ b/audio/aidl/default/StreamSwitcher.cpp @@ -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 diff --git a/audio/aidl/default/alsa/StreamAlsa.cpp b/audio/aidl/default/alsa/StreamAlsa.cpp index 372e38a5b3..c77bfca644 100644 --- a/audio/aidl/default/alsa/StreamAlsa.cpp +++ b/audio/aidl/default/alsa/StreamAlsa.cpp @@ -117,6 +117,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())); @@ -166,4 +167,9 @@ void StreamAlsa::shutdown() { mAlsaDeviceProxies.clear(); } +ndk::ScopedAStatus StreamAlsa::setGain(float gain) { + mGain = gain; + return ndk::ScopedAStatus::ok(); +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/alsa/Utils.cpp b/audio/aidl/default/alsa/Utils.cpp index 8eaf162aa8..10374f2316 100644 --- a/audio/aidl/default/alsa/Utils.cpp +++ b/audio/aidl/default/alsa/Utils.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #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(mono & 0xFFFF); + } while (--numFrames); + } + } + } break; + default: + // TODO(336370745): Implement gain for other supported formats + break; + } +} + } // namespace aidl::android::hardware::audio::core::alsa diff --git a/audio/aidl/default/alsa/Utils.h b/audio/aidl/default/alsa/Utils.h index 980f685548..a97ea10cec 100644 --- a/audio/aidl/default/alsa/Utils.h +++ b/audio/aidl/default/alsa/Utils.h @@ -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( diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h index 00eeb4ee20..8548aff5f3 100644 --- a/audio/aidl/default/include/core-impl/Module.h +++ b/audio/aidl/default/include/core-impl/Module.h @@ -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); diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h index 100b4c8e17..5dca739039 100644 --- a/audio/aidl/default/include/core-impl/Stream.h +++ b/audio/aidl/default/include/core-impl/Stream.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -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 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 diff --git a/audio/aidl/default/include/core-impl/StreamAlsa.h b/audio/aidl/default/include/core-impl/StreamAlsa.h index 035694614d..8bdf208f30 100644 --- a/audio/aidl/default/include/core-impl/StreamAlsa.h +++ b/audio/aidl/default/include/core-impl/StreamAlsa.h @@ -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 mAlsaDeviceProxies; + + private: + std::atomic mGain = 1.0; }; } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/StreamSwitcher.h b/audio/aidl/default/include/core-impl/StreamSwitcher.h index 5764ad6018..2d75e85e7b 100644 --- a/audio/aidl/default/include/core-impl/StreamSwitcher.h +++ b/audio/aidl/default/include/core-impl/StreamSwitcher.h @@ -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