From 783c48b00e59a1bb0bb3322f9f24e0bbb6dc88b0 Mon Sep 17 00:00:00 2001 From: jiabin Date: Tue, 28 Feb 2023 18:28:06 +0000 Subject: [PATCH] AHAL: support volume control for USB audio HAL. Use mixer control to support master mute, master volume and hardware volume for USB audio HAL. Bug: 266216550 Test: atest VtsHalAudioCoreTargetTest Change-Id: Iad544ba517cbfc778ebdf96dd161944886383b73 --- .../android/hardware/audio/core/IModule.aidl | 3 + .../hardware/audio/core/IStreamOut.aidl | 3 +- audio/aidl/default/Android.bp | 1 + audio/aidl/default/Module.cpp | 44 +++- audio/aidl/default/include/core-impl/Module.h | 9 +- .../default/include/core-impl/ModuleUsb.h | 9 +- .../default/include/core-impl/StreamUsb.h | 7 + audio/aidl/default/usb/ModuleUsb.cpp | 43 ++-- audio/aidl/default/usb/StreamUsb.cpp | 34 ++- .../aidl/default/usb/UsbAlsaMixerControl.cpp | 239 ++++++++++++++++++ audio/aidl/default/usb/UsbAlsaMixerControl.h | 106 ++++++++ 11 files changed, 466 insertions(+), 32 deletions(-) create mode 100644 audio/aidl/default/usb/UsbAlsaMixerControl.cpp create mode 100644 audio/aidl/default/usb/UsbAlsaMixerControl.h diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl index edfb9f27f6..ce0ac23784 100644 --- a/audio/aidl/android/hardware/audio/core/IModule.aidl +++ b/audio/aidl/android/hardware/audio/core/IModule.aidl @@ -596,6 +596,7 @@ interface IModule { * @param mute Whether the output from the module is muted. * @throws EX_UNSUPPORTED_OPERATION If muting of combined output * is not supported by the module. + * @throws EX_ILLEGAL_STATE If any error happens while muting of combined output. */ void setMasterMute(boolean mute); @@ -627,6 +628,8 @@ interface IModule { * accepted range. * @throws EX_UNSUPPORTED_OPERATION If attenuation of combined output * is not supported by the module. + * @throws EX_ILLEGAL_STATE If any error happens while updating attenuation of + combined output. */ void setMasterVolume(float volume); diff --git a/audio/aidl/android/hardware/audio/core/IStreamOut.aidl b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl index 0e58addd7d..825553def4 100644 --- a/audio/aidl/android/hardware/audio/core/IStreamOut.aidl +++ b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl @@ -85,7 +85,8 @@ interface IStreamOut { * @throws EX_ILLEGAL_ARGUMENT If the number of elements in the provided * array does not match the channel count, or * attenuation values are out of range. - * @throws EX_ILLEGAL_STATE If the stream is closed. + * @throws EX_ILLEGAL_STATE If the stream is closed or there is any error happens + when applying hardware volume. * @throws EX_UNSUPPORTED_OPERATION If hardware volume control is not supported. */ void setHwVolume(in float[] channelVolumes); diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp index 21616be67d..93362c62d4 100644 --- a/audio/aidl/default/Android.bp +++ b/audio/aidl/default/Android.bp @@ -75,6 +75,7 @@ cc_library_static { "Telephony.cpp", "usb/ModuleUsb.cpp", "usb/StreamUsb.cpp", + "usb/UsbAlsaMixerControl.cpp", "usb/UsbAlsaUtils.cpp", ], generated_sources: [ diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp index 5440b8d640..b546597ada 100644 --- a/audio/aidl/default/Module.cpp +++ b/audio/aidl/default/Module.cpp @@ -457,6 +457,7 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA connectedPort.profiles = connectedProfilesIt->second; } ports.push_back(connectedPort); + onExternalDeviceConnectionChanged(connectedPort, true /*connected*/); *_aidl_return = std::move(connectedPort); std::vector newRoutes; @@ -510,6 +511,7 @@ ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) { << configIt->id; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); } + onExternalDeviceConnectionChanged(*portIt, false /*connected*/); ports.erase(portIt); mConnectedDevicePorts.erase(in_portId); LOG(DEBUG) << __func__ << ": connected device port " << in_portId << " released"; @@ -980,8 +982,17 @@ ndk::ScopedAStatus Module::getMasterMute(bool* _aidl_return) { ndk::ScopedAStatus Module::setMasterMute(bool in_mute) { LOG(DEBUG) << __func__ << ": " << in_mute; - mMasterMute = in_mute; - return ndk::ScopedAStatus::ok(); + auto result = mDebug.simulateDeviceConnections ? ndk::ScopedAStatus::ok() + : onMasterMuteChanged(in_mute); + if (result.isOk()) { + mMasterMute = in_mute; + } else { + LOG(ERROR) << __func__ << ": failed calling onMasterMuteChanged(" << in_mute + << "), error=" << result; + // Reset master mute if it failed. + onMasterMuteChanged(mMasterMute); + } + return std::move(result); } ndk::ScopedAStatus Module::getMasterVolume(float* _aidl_return) { @@ -993,8 +1004,17 @@ ndk::ScopedAStatus Module::getMasterVolume(float* _aidl_return) { ndk::ScopedAStatus Module::setMasterVolume(float in_volume) { LOG(DEBUG) << __func__ << ": " << in_volume; if (in_volume >= 0.0f && in_volume <= 1.0f) { - mMasterVolume = in_volume; - return ndk::ScopedAStatus::ok(); + auto result = mDebug.simulateDeviceConnections ? ndk::ScopedAStatus::ok() + : onMasterVolumeChanged(in_volume); + if (result.isOk()) { + mMasterVolume = in_volume; + } else { + // Reset master volume if it failed. + LOG(ERROR) << __func__ << ": failed calling onMasterVolumeChanged(" << in_volume + << "), error=" << result; + onMasterVolumeChanged(mMasterVolume); + } + return std::move(result); } LOG(ERROR) << __func__ << ": invalid master volume value: " << in_volume; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); @@ -1262,4 +1282,20 @@ ndk::ScopedAStatus Module::checkAudioPatchEndpointsMatch( return ndk::ScopedAStatus::ok(); } +void Module::onExternalDeviceConnectionChanged( + const ::aidl::android::media::audio::common::AudioPort& audioPort __unused, + bool connected __unused) { + LOG(DEBUG) << __func__ << ": do nothing and return"; +} + +ndk::ScopedAStatus Module::onMasterMuteChanged(bool mute __unused) { + LOG(VERBOSE) << __func__ << ": do nothing and return ok"; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::onMasterVolumeChanged(float volume __unused) { + LOG(VERBOSE) << __func__ << ": do nothing and return ok"; + return ndk::ScopedAStatus::ok(); +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h index 8365b3438f..a5b3182a59 100644 --- a/audio/aidl/default/include/core-impl/Module.h +++ b/audio/aidl/default/include/core-impl/Module.h @@ -163,8 +163,6 @@ class Module : public BnModule { // Maps port ids and port config ids to patch ids. // Multimap because both ports and configs can be used by multiple patches. std::multimap mPatches; - bool mMasterMute = false; - float mMasterVolume = 1.0f; bool mMicMute = false; std::shared_ptr mSoundDose; ndk::SpAIBinder mSoundDoseBinder; @@ -180,6 +178,13 @@ class Module : public BnModule { virtual ndk::ScopedAStatus checkAudioPatchEndpointsMatch( const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sources, const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sinks); + virtual void onExternalDeviceConnectionChanged( + const ::aidl::android::media::audio::common::AudioPort& audioPort, bool connected); + virtual ndk::ScopedAStatus onMasterMuteChanged(bool mute); + virtual ndk::ScopedAStatus onMasterVolumeChanged(float volume); + + bool mMasterMute = false; + float mMasterVolume = 1.0f; }; } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/ModuleUsb.h b/audio/aidl/default/include/core-impl/ModuleUsb.h index 7b177e86e1..1aa22445b4 100644 --- a/audio/aidl/default/include/core-impl/ModuleUsb.h +++ b/audio/aidl/default/include/core-impl/ModuleUsb.h @@ -28,10 +28,6 @@ class ModuleUsb : public Module { // IModule interfaces ndk::ScopedAStatus getTelephony(std::shared_ptr* _aidl_return) override; ndk::ScopedAStatus getBluetooth(std::shared_ptr* _aidl_return) override; - ndk::ScopedAStatus getMasterMute(bool* _aidl_return) override; - ndk::ScopedAStatus setMasterMute(bool in_mute) override; - ndk::ScopedAStatus getMasterVolume(float* _aidl_return) override; - ndk::ScopedAStatus setMasterVolume(float in_volume) override; ndk::ScopedAStatus getMicMute(bool* _aidl_return) override; ndk::ScopedAStatus setMicMute(bool in_mute) override; @@ -42,6 +38,11 @@ class ModuleUsb : public Module { const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sources, const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sinks) override; + void onExternalDeviceConnectionChanged( + const ::aidl::android::media::audio::common::AudioPort& audioPort, + bool connected) override; + ndk::ScopedAStatus onMasterMuteChanged(bool mute) override; + ndk::ScopedAStatus onMasterVolumeChanged(float volume) override; }; } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/StreamUsb.h b/audio/aidl/default/include/core-impl/StreamUsb.h index c04dc66162..f1815ddbf5 100644 --- a/audio/aidl/default/include/core-impl/StreamUsb.h +++ b/audio/aidl/default/include/core-impl/StreamUsb.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include @@ -93,6 +94,12 @@ class StreamOutUsb final : public StreamOut { StreamContext&& context, const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>& offloadInfo); + + ndk::ScopedAStatus getHwVolume(std::vector* _aidl_return) override; + ndk::ScopedAStatus setHwVolume(const std::vector& in_channelVolumes) override; + + int mChannelCount; + std::vector mHwVolumes; }; } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/usb/ModuleUsb.cpp b/audio/aidl/default/usb/ModuleUsb.cpp index e8034209d8..511ba74e91 100644 --- a/audio/aidl/default/usb/ModuleUsb.cpp +++ b/audio/aidl/default/usb/ModuleUsb.cpp @@ -22,6 +22,7 @@ #include #include +#include "UsbAlsaMixerControl.h" #include "UsbAlsaUtils.h" #include "core-impl/ModuleUsb.h" @@ -86,26 +87,6 @@ ndk::ScopedAStatus ModuleUsb::getBluetooth(std::shared_ptr* _aidl_re return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus ModuleUsb::getMasterMute(bool* _aidl_return __unused) { - LOG(DEBUG) << __func__ << ": is not supported"; - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); -} - -ndk::ScopedAStatus ModuleUsb::setMasterMute(bool in_mute __unused) { - LOG(DEBUG) << __func__ << ": is not supported"; - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); -} - -ndk::ScopedAStatus ModuleUsb::getMasterVolume(float* _aidl_return __unused) { - LOG(DEBUG) << __func__ << ": is not supported"; - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); -} - -ndk::ScopedAStatus ModuleUsb::setMasterVolume(float in_volume __unused) { - LOG(DEBUG) << __func__ << ": is not supported"; - return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); -} - ndk::ScopedAStatus ModuleUsb::getMicMute(bool* _aidl_return __unused) { LOG(DEBUG) << __func__ << ": is not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); @@ -180,4 +161,26 @@ ndk::ScopedAStatus ModuleUsb::checkAudioPatchEndpointsMatch( return ndk::ScopedAStatus::ok(); } +void ModuleUsb::onExternalDeviceConnectionChanged( + const ::aidl::android::media::audio::common::AudioPort& audioPort, bool connected) { + if (audioPort.ext.getTag() != AudioPortExt::Tag::device) { + return; + } + const auto& address = audioPort.ext.get().device.address; + if (address.getTag() != AudioDeviceAddress::alsa) { + return; + } + const int card = address.get()[0]; + usb::UsbAlsaMixerControl::getInstance().setDeviceConnectionState(card, mMasterMute, + mMasterVolume, connected); +} + +ndk::ScopedAStatus ModuleUsb::onMasterMuteChanged(bool mute) { + return usb::UsbAlsaMixerControl::getInstance().setMasterMute(mute); +} + +ndk::ScopedAStatus ModuleUsb::onMasterVolumeChanged(float volume) { + return usb::UsbAlsaMixerControl::getInstance().setMasterVolume(volume); +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/usb/StreamUsb.cpp b/audio/aidl/default/usb/StreamUsb.cpp index bd53a0ef2a..d6f757c7a9 100644 --- a/audio/aidl/default/usb/StreamUsb.cpp +++ b/audio/aidl/default/usb/StreamUsb.cpp @@ -17,6 +17,9 @@ #define LOG_TAG "AHAL_StreamUsb" #include +#include + +#include "UsbAlsaMixerControl.h" #include "UsbAlsaUtils.h" #include "core-impl/Module.h" #include "core-impl/StreamUsb.h" @@ -30,8 +33,12 @@ using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::media::audio::common::AudioDevice; using aidl::android::media::audio::common::AudioDeviceAddress; using aidl::android::media::audio::common::AudioOffloadInfo; +using aidl::android::media::audio::common::AudioPortExt; using aidl::android::media::audio::common::MicrophoneDynamicInfo; using aidl::android::media::audio::common::MicrophoneInfo; +using android::OK; +using android::status_t; +using android::hardware::audio::common::getChannelCount; namespace aidl::android::hardware::audio::core { @@ -239,6 +246,31 @@ StreamOutUsb::StreamOutUsb(const SourceMetadata& sourceMetadata, StreamContext&& // The default worker implementation is used. return new StreamOutWorker(ctx, driver); }, - offloadInfo) {} + offloadInfo) { + mChannelCount = getChannelCount(mContext.getChannelLayout()); +} + +ndk::ScopedAStatus StreamOutUsb::getHwVolume(std::vector* _aidl_return) { + *_aidl_return = mHwVolumes; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StreamOutUsb::setHwVolume(const std::vector& in_channelVolumes) { + for (const auto& device : mConnectedDevices) { + if (device.address.getTag() != AudioDeviceAddress::alsa) { + LOG(DEBUG) << __func__ << ": skip as the device address is not alsa"; + continue; + } + const int card = device.address.get()[0]; + if (auto result = + usb::UsbAlsaMixerControl::getInstance().setVolumes(card, in_channelVolumes); + !result.isOk()) { + LOG(ERROR) << __func__ << ": failed to set volume for device, card=" << card; + return result; + } + } + mHwVolumes = in_channelVolumes; + return ndk::ScopedAStatus::ok(); +} } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/usb/UsbAlsaMixerControl.cpp b/audio/aidl/default/usb/UsbAlsaMixerControl.cpp new file mode 100644 index 0000000000..b5337d1642 --- /dev/null +++ b/audio/aidl/default/usb/UsbAlsaMixerControl.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2023 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. + */ + +#define LOG_TAG "AHAL_UsbAlsaMixerControl" +#include + +#include +#include +#include + +#include + +#include "UsbAlsaMixerControl.h" + +namespace aidl::android::hardware::audio::core::usb { + +//----------------------------------------------------------------------------- + +MixerControl::MixerControl(struct mixer_ctl* ctl) + : mCtl(ctl), + mNumValues(mixer_ctl_get_num_values(ctl)), + mMinValue(mixer_ctl_get_range_min(ctl)), + mMaxValue(mixer_ctl_get_range_max(ctl)) {} + +unsigned int MixerControl::getNumValues() const { + return mNumValues; +} + +int MixerControl::getMaxValue() const { + return mMaxValue; +} + +int MixerControl::getMinValue() const { + return mMinValue; +} + +int MixerControl::setArray(const void* array, size_t count) { + const std::lock_guard guard(mLock); + return mixer_ctl_set_array(mCtl, array, count); +} + +//----------------------------------------------------------------------------- + +// static +const std::map> + AlsaMixer::kPossibleControls = { + {AlsaMixer::MASTER_SWITCH, {{"Master Playback Switch", MIXER_CTL_TYPE_BOOL}}}, + {AlsaMixer::MASTER_VOLUME, {{"Master Playback Volume", MIXER_CTL_TYPE_INT}}}, + {AlsaMixer::HW_VOLUME, + {{"Headphone Playback Volume", MIXER_CTL_TYPE_INT}, + {"Headset Playback Volume", MIXER_CTL_TYPE_INT}, + {"PCM Playback Volume", MIXER_CTL_TYPE_INT}}}}; + +// static +std::map> AlsaMixer::initializeMixerControls( + struct mixer* mixer) { + std::map> mixerControls; + std::string mixerCtlNames; + for (const auto& [control, possibleCtls] : kPossibleControls) { + for (const auto& [ctlName, expectedCtlType] : possibleCtls) { + struct mixer_ctl* ctl = mixer_get_ctl_by_name(mixer, ctlName.c_str()); + if (ctl != nullptr && mixer_ctl_get_type(ctl) == expectedCtlType) { + mixerControls.emplace(control, std::make_unique(ctl)); + if (!mixerCtlNames.empty()) { + mixerCtlNames += ","; + } + mixerCtlNames += ctlName; + break; + } + } + } + LOG(DEBUG) << __func__ << ": available mixer control names=[" << mixerCtlNames << "]"; + return mixerControls; +} + +AlsaMixer::AlsaMixer(struct mixer* mixer) + : mMixer(mixer), mMixerControls(initializeMixerControls(mMixer)) {} + +AlsaMixer::~AlsaMixer() { + mixer_close(mMixer); +} + +namespace { + +int volumeFloatToInteger(float fValue, int maxValue, int minValue) { + return minValue + std::ceil((maxValue - minValue) * fValue); +} + +float volumeIntegerToFloat(int iValue, int maxValue, int minValue) { + if (iValue > maxValue) { + return 1.0f; + } + if (iValue < minValue) { + return 0.0f; + } + return static_cast(iValue - minValue) / (maxValue - minValue); +} + +} // namespace + +ndk::ScopedAStatus AlsaMixer::setMasterMute(bool muted) { + auto it = mMixerControls.find(AlsaMixer::MASTER_SWITCH); + if (it == mMixerControls.end()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + const int numValues = it->second->getNumValues(); + std::vector values(numValues, muted ? 0 : 1); + if (int err = it->second->setArray(values.data(), numValues); err != 0) { + LOG(ERROR) << __func__ << ": failed to set master mute, err=" << err; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus AlsaMixer::setMasterVolume(float volume) { + auto it = mMixerControls.find(AlsaMixer::MASTER_VOLUME); + if (it == mMixerControls.end()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + const int numValues = it->second->getNumValues(); + std::vector values(numValues, volumeFloatToInteger(volume, it->second->getMaxValue(), + it->second->getMinValue())); + if (int err = it->second->setArray(values.data(), numValues); err != 0) { + LOG(ERROR) << __func__ << ": failed to set master volume, err=" << err; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus AlsaMixer::setVolumes(std::vector volumes) { + auto it = mMixerControls.find(AlsaMixer::HW_VOLUME); + if (it == mMixerControls.end()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + const int numValues = it->second->getNumValues(); + const int maxValue = it->second->getMaxValue(); + const int minValue = it->second->getMinValue(); + std::vector values; + size_t i = 0; + for (; i < numValues && i < values.size(); ++i) { + values.emplace_back(volumeFloatToInteger(volumes[i], maxValue, minValue)); + } + if (int err = it->second->setArray(values.data(), values.size()); err != 0) { + LOG(ERROR) << __func__ << ": failed to set volume, err=" << err; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + return ndk::ScopedAStatus::ok(); +} + +//----------------------------------------------------------------------------- + +// static +UsbAlsaMixerControl& UsbAlsaMixerControl::getInstance() { + static UsbAlsaMixerControl gInstance; + return gInstance; +} + +void UsbAlsaMixerControl::setDeviceConnectionState(int card, bool masterMuted, float masterVolume, + bool connected) { + LOG(DEBUG) << __func__ << ": card=" << card << ", connected=" << connected; + if (connected) { + struct mixer* mixer = mixer_open(card); + if (mixer == nullptr) { + PLOG(ERROR) << __func__ << ": failed to open mixer for card=" << card; + return; + } + auto alsaMixer = std::make_shared(mixer); + alsaMixer->setMasterMute(masterMuted); + alsaMixer->setMasterVolume(masterVolume); + const std::lock_guard guard(mLock); + mMixerControls.emplace(card, alsaMixer); + } else { + const std::lock_guard guard(mLock); + mMixerControls.erase(card); + } +} + +ndk::ScopedAStatus UsbAlsaMixerControl::setMasterMute(bool mute) { + auto alsaMixers = getAlsaMixers(); + for (auto it = alsaMixers.begin(); it != alsaMixers.end(); ++it) { + if (auto result = it->second->setMasterMute(mute); !result.isOk()) { + // Return illegal state if there are multiple devices connected and one of them fails + // to set master mute. Otherwise, return the error from calling `setMasterMute`. + LOG(ERROR) << __func__ << ": failed to set master mute for card=" << it->first; + return alsaMixers.size() > 1 ? ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE) + : std::move(result); + } + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus UsbAlsaMixerControl::setMasterVolume(float volume) { + auto alsaMixers = getAlsaMixers(); + for (auto it = alsaMixers.begin(); it != alsaMixers.end(); ++it) { + if (auto result = it->second->setMasterVolume(volume); !result.isOk()) { + // Return illegal state if there are multiple devices connected and one of them fails + // to set master volume. Otherwise, return the error from calling `setMasterVolume`. + LOG(ERROR) << __func__ << ": failed to set master volume for card=" << it->first; + return alsaMixers.size() > 1 ? ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE) + : std::move(result); + } + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus UsbAlsaMixerControl::setVolumes(int card, std::vector volumes) { + auto alsaMixer = getAlsaMixer(card); + if (alsaMixer == nullptr) { + LOG(ERROR) << __func__ << ": no mixer control found for card=" << card; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + return alsaMixer->setVolumes(volumes); +} + +std::shared_ptr UsbAlsaMixerControl::getAlsaMixer(int card) { + const std::lock_guard guard(mLock); + const auto it = mMixerControls.find(card); + return it == mMixerControls.end() ? nullptr : it->second; +} + +std::map> UsbAlsaMixerControl::getAlsaMixers() { + const std::lock_guard guard(mLock); + return mMixerControls; +} + +} // namespace aidl::android::hardware::audio::core::usb diff --git a/audio/aidl/default/usb/UsbAlsaMixerControl.h b/audio/aidl/default/usb/UsbAlsaMixerControl.h new file mode 100644 index 0000000000..cbcddd82c6 --- /dev/null +++ b/audio/aidl/default/usb/UsbAlsaMixerControl.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +extern "C" { +#include +} + +namespace aidl::android::hardware::audio::core::usb { + +class MixerControl { + public: + explicit MixerControl(struct mixer_ctl* ctl); + + unsigned int getNumValues() const; + int getMaxValue() const; + int getMinValue() const; + int setArray(const void* array, size_t count); + + private: + std::mutex mLock; + // The mixer_ctl object is owned by ALSA and will be released when the mixer is closed. + struct mixer_ctl* mCtl GUARDED_BY(mLock); + const unsigned int mNumValues; + const int mMinValue; + const int mMaxValue; +}; + +class AlsaMixer { + public: + explicit AlsaMixer(struct mixer* mixer); + + ~AlsaMixer(); + + bool isValid() const { return mMixer != nullptr; } + + ndk::ScopedAStatus setMasterMute(bool muted); + ndk::ScopedAStatus setMasterVolume(float volume); + ndk::ScopedAStatus setVolumes(std::vector volumes); + + private: + enum Control { + MASTER_SWITCH, + MASTER_VOLUME, + HW_VOLUME, + }; + using ControlNamesAndExpectedCtlType = std::pair; + static const std::map> kPossibleControls; + static std::map> initializeMixerControls( + struct mixer* mixer); + + // The mixer object is owned by ALSA and will be released when the mixer is closed. + struct mixer* mMixer; + // `mMixerControls` will only be initialized in constructor. After that, it wil only be + // read but not be modified. + const std::map> mMixerControls; +}; + +class UsbAlsaMixerControl { + public: + static UsbAlsaMixerControl& getInstance(); + + void setDeviceConnectionState(int card, bool masterMuted, float masterVolume, bool connected); + + // Master volume settings will be applied to all sound cards, it is only set by the + // USB module. + ndk::ScopedAStatus setMasterMute(bool muted); + ndk::ScopedAStatus setMasterVolume(float volume); + // The volume settings can be different on sound cards. It is controlled by streams. + ndk::ScopedAStatus setVolumes(int card, std::vector volumes); + + private: + std::shared_ptr getAlsaMixer(int card); + std::map> getAlsaMixers(); + + std::mutex mLock; + // A map whose key is the card number and value is a shared pointer to corresponding + // AlsaMixer object. + std::map> mMixerControls GUARDED_BY(mLock); +}; + +} // namespace aidl::android::hardware::audio::core::usb