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
diff --git a/audio/aidl/default/usb/ModuleUsb.cpp b/audio/aidl/default/usb/ModuleUsb.cpp
index e803420..511ba74 100644
--- a/audio/aidl/default/usb/ModuleUsb.cpp
+++ b/audio/aidl/default/usb/ModuleUsb.cpp
@@ -22,6 +22,7 @@
#include <android-base/logging.h>
#include <tinyalsa/asoundlib.h>
+#include "UsbAlsaMixerControl.h"
#include "UsbAlsaUtils.h"
#include "core-impl/ModuleUsb.h"
@@ -86,26 +87,6 @@
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 @@
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<AudioPortExt::Tag::device>().device.address;
+ if (address.getTag() != AudioDeviceAddress::alsa) {
+ return;
+ }
+ const int card = address.get<AudioDeviceAddress::alsa>()[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 bd53a0e..d6f757c 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 <android-base/logging.h>
+#include <Utils.h>
+
+#include "UsbAlsaMixerControl.h"
#include "UsbAlsaUtils.h"
#include "core-impl/Module.h"
#include "core-impl/StreamUsb.h"
@@ -30,8 +33,12 @@
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 @@
// The default worker implementation is used.
return new StreamOutWorker(ctx, driver);
},
- offloadInfo) {}
+ offloadInfo) {
+ mChannelCount = getChannelCount(mContext.getChannelLayout());
+}
+
+ndk::ScopedAStatus StreamOutUsb::getHwVolume(std::vector<float>* _aidl_return) {
+ *_aidl_return = mHwVolumes;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamOutUsb::setHwVolume(const std::vector<float>& 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<AudioDeviceAddress::alsa>()[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 0000000..b5337d1
--- /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 <android-base/logging.h>
+
+#include <cmath>
+#include <string>
+#include <vector>
+
+#include <android/binder_status.h>
+
+#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::Control, std::vector<AlsaMixer::ControlNamesAndExpectedCtlType>>
+ 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::Control, std::shared_ptr<MixerControl>> AlsaMixer::initializeMixerControls(
+ struct mixer* mixer) {
+ std::map<AlsaMixer::Control, std::shared_ptr<MixerControl>> 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<MixerControl>(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<float>(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<int> 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<int> 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<float> 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<int> 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<AlsaMixer>(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<float> 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<AlsaMixer> 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<int, std::shared_ptr<AlsaMixer>> 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 0000000..cbcddd8
--- /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 <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+#include <android/binder_auto_utils.h>
+
+extern "C" {
+#include <tinyalsa/mixer.h>
+}
+
+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<float> volumes);
+
+ private:
+ enum Control {
+ MASTER_SWITCH,
+ MASTER_VOLUME,
+ HW_VOLUME,
+ };
+ using ControlNamesAndExpectedCtlType = std::pair<std::string, enum mixer_ctl_type>;
+ static const std::map<Control, std::vector<ControlNamesAndExpectedCtlType>> kPossibleControls;
+ static std::map<Control, std::shared_ptr<MixerControl>> 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<Control, std::shared_ptr<MixerControl>> 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<float> volumes);
+
+ private:
+ std::shared_ptr<AlsaMixer> getAlsaMixer(int card);
+ std::map<int, std::shared_ptr<AlsaMixer>> getAlsaMixers();
+
+ std::mutex mLock;
+ // A map whose key is the card number and value is a shared pointer to corresponding
+ // AlsaMixer object.
+ std::map<int, std::shared_ptr<AlsaMixer>> mMixerControls GUARDED_BY(mLock);
+};
+
+} // namespace aidl::android::hardware::audio::core::usb