audio: Simplify and extend alsa::Mixer
Remove alsa::MixerControl. tinyALSA contains utility
functions for setting values in percents, they use
the same logic as used to be there for handling
the "volume" control. Use access serialization at
the mixer level, rather than for each control.
Move the call to 'mixer_open' to alsa::Mixer.
Add controls for capture (mic) mute and gain. They
will be used by the primary HAL.
Bug: 264712385
Test: atest VtsHalAudioCoreTargetTest
Change-Id: I0fad994153de96aceec3eb8f2fec19805ec912f8
diff --git a/audio/aidl/default/alsa/Mixer.cpp b/audio/aidl/default/alsa/Mixer.cpp
index f0393e3..126c033 100644
--- a/audio/aidl/default/alsa/Mixer.cpp
+++ b/audio/aidl/default/alsa/Mixer.cpp
@@ -14,44 +14,17 @@
* limitations under the License.
*/
-#define LOG_TAG "AHAL_AlsaMixer"
-#include <android-base/logging.h>
-
+#include <algorithm>
#include <cmath>
+#define LOG_TAG "AHAL_AlsaMixer"
+#include <android-base/logging.h>
#include <android/binder_status.h>
#include "Mixer.h"
namespace aidl::android::hardware::audio::core::alsa {
-//-----------------------------------------------------------------------------
-
-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<Mixer::Control, std::vector<Mixer::ControlNamesAndExpectedCtlType>>
Mixer::kPossibleControls = {
@@ -60,18 +33,20 @@
{Mixer::HW_VOLUME,
{{"Headphone Playback Volume", MIXER_CTL_TYPE_INT},
{"Headset Playback Volume", MIXER_CTL_TYPE_INT},
- {"PCM Playback Volume", MIXER_CTL_TYPE_INT}}}};
+ {"PCM Playback Volume", MIXER_CTL_TYPE_INT}}},
+ {Mixer::MIC_SWITCH, {{"Capture Switch", MIXER_CTL_TYPE_BOOL}}},
+ {Mixer::MIC_GAIN, {{"Capture Volume", MIXER_CTL_TYPE_INT}}}};
// static
-std::map<Mixer::Control, std::shared_ptr<MixerControl>> Mixer::initializeMixerControls(
- struct mixer* mixer) {
- std::map<Mixer::Control, std::shared_ptr<MixerControl>> mixerControls;
+Mixer::Controls Mixer::initializeMixerControls(struct mixer* mixer) {
+ if (mixer == nullptr) return {};
+ Controls 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));
+ mixerControls.emplace(control, ctl);
if (!mixerCtlNames.empty()) {
mixerCtlNames += ",";
}
@@ -84,71 +59,141 @@
return mixerControls;
}
-Mixer::Mixer(struct mixer* mixer)
- : mMixer(mixer), mMixerControls(initializeMixerControls(mMixer)) {}
+std::ostream& operator<<(std::ostream& s, Mixer::Control c) {
+ switch (c) {
+ case Mixer::Control::MASTER_SWITCH:
+ s << "master mute";
+ break;
+ case Mixer::Control::MASTER_VOLUME:
+ s << "master volume";
+ break;
+ case Mixer::Control::HW_VOLUME:
+ s << "volume";
+ break;
+ case Mixer::Control::MIC_SWITCH:
+ s << "mic mute";
+ break;
+ case Mixer::Control::MIC_GAIN:
+ s << "mic gain";
+ break;
+ }
+ return s;
+}
+
+Mixer::Mixer(int card) : mMixer(mixer_open(card)), mMixerControls(initializeMixerControls(mMixer)) {
+ if (!isValid()) {
+ PLOG(ERROR) << __func__ << ": failed to open mixer for card=" << card;
+ }
+}
Mixer::~Mixer() {
- mixer_close(mMixer);
+ if (isValid()) {
+ std::lock_guard l(mMixerAccess);
+ mixer_close(mMixer);
+ }
}
-namespace {
-
-int volumeFloatToInteger(float fValue, int maxValue, int minValue) {
- return minValue + std::ceil((maxValue - minValue) * fValue);
-}
-
-} // namespace
-
ndk::ScopedAStatus Mixer::setMasterMute(bool muted) {
- auto it = mMixerControls.find(Mixer::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();
+ return setMixerControlMute(MASTER_SWITCH, muted);
}
ndk::ScopedAStatus Mixer::setMasterVolume(float volume) {
- auto it = mMixerControls.find(Mixer::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();
+ return setMixerControlVolume(MASTER_VOLUME, volume);
+}
+
+ndk::ScopedAStatus Mixer::setMicGain(float gain) {
+ return setMixerControlVolume(MIC_GAIN, gain);
+}
+
+ndk::ScopedAStatus Mixer::setMicMute(bool muted) {
+ return setMixerControlMute(MIC_SWITCH, muted);
}
ndk::ScopedAStatus Mixer::setVolumes(const std::vector<float>& volumes) {
+ if (!isValid()) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
auto it = mMixerControls.find(Mixer::HW_VOLUME);
if (it == mMixerControls.end()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
- const int numValues = it->second->getNumValues();
- if (numValues < 0) {
- LOG(FATAL) << __func__ << ": negative number of values: " << numValues;
- }
- const int maxValue = it->second->getMaxValue();
- const int minValue = it->second->getMinValue();
- std::vector<int> values;
- size_t i = 0;
- for (; i < static_cast<size_t>(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) {
+ std::vector<int> percents;
+ std::transform(
+ volumes.begin(), volumes.end(), std::back_inserter(percents),
+ [](float volume) -> int { return std::floor(std::clamp(volume, 0.0f, 1.0f) * 100); });
+ std::lock_guard l(mMixerAccess);
+ if (int err = setMixerControlPercent(it->second, percents); err != 0) {
LOG(ERROR) << __func__ << ": failed to set volume, err=" << err;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
return ndk::ScopedAStatus::ok();
}
+ndk::ScopedAStatus Mixer::setMixerControlMute(Mixer::Control ctl, bool muted) {
+ if (!isValid()) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ auto it = mMixerControls.find(ctl);
+ if (it == mMixerControls.end()) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+ std::lock_guard l(mMixerAccess);
+ if (int err = setMixerControlValue(it->second, muted ? 0 : 1); err != 0) {
+ LOG(ERROR) << __func__ << ": failed to set " << ctl << " to " << muted << ", err=" << err;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Mixer::setMixerControlVolume(Control ctl, float volume) {
+ if (!isValid()) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ auto it = mMixerControls.find(ctl);
+ if (it == mMixerControls.end()) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+ volume = std::clamp(volume, 0.0f, 1.0f);
+ std::lock_guard l(mMixerAccess);
+ if (int err = setMixerControlPercent(it->second, std::floor(volume * 100)); err != 0) {
+ LOG(ERROR) << __func__ << ": failed to set " << ctl << " to " << volume << ", err=" << err;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+int Mixer::setMixerControlPercent(struct mixer_ctl* ctl, int percent) {
+ int ret = 0;
+ const unsigned int n = mixer_ctl_get_num_values(ctl);
+ for (unsigned int id = 0; id < n; id++) {
+ if (int error = mixer_ctl_set_percent(ctl, id, percent); error != 0) {
+ ret = error;
+ }
+ }
+ return ret;
+}
+
+int Mixer::setMixerControlPercent(struct mixer_ctl* ctl, const std::vector<int>& percents) {
+ int ret = 0;
+ const unsigned int n = mixer_ctl_get_num_values(ctl);
+ for (unsigned int id = 0; id < n; id++) {
+ if (int error = mixer_ctl_set_percent(ctl, id, id < percents.size() ? percents[id] : 0);
+ error != 0) {
+ ret = error;
+ }
+ }
+ return ret;
+}
+
+int Mixer::setMixerControlValue(struct mixer_ctl* ctl, int value) {
+ int ret = 0;
+ const unsigned int n = mixer_ctl_get_num_values(ctl);
+ for (unsigned int id = 0; id < n; id++) {
+ if (int error = mixer_ctl_set_value(ctl, id, value); error != 0) {
+ ret = error;
+ }
+ }
+ return ret;
+}
+
} // namespace aidl::android::hardware::audio::core::alsa
diff --git a/audio/aidl/default/alsa/Mixer.h b/audio/aidl/default/alsa/Mixer.h
index de9e6f4..78728c2 100644
--- a/audio/aidl/default/alsa/Mixer.h
+++ b/audio/aidl/default/alsa/Mixer.h
@@ -16,6 +16,7 @@
#pragma once
+#include <iostream>
#include <map>
#include <memory>
#include <mutex>
@@ -31,34 +32,17 @@
namespace aidl::android::hardware::audio::core::alsa {
-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 Mixer {
public:
- explicit Mixer(struct mixer* mixer);
-
+ explicit Mixer(int card);
~Mixer();
bool isValid() const { return mMixer != nullptr; }
ndk::ScopedAStatus setMasterMute(bool muted);
ndk::ScopedAStatus setMasterVolume(float volume);
+ ndk::ScopedAStatus setMicGain(float gain);
+ ndk::ScopedAStatus setMicMute(bool muted);
ndk::ScopedAStatus setVolumes(const std::vector<float>& volumes);
private:
@@ -66,17 +50,32 @@
MASTER_SWITCH,
MASTER_VOLUME,
HW_VOLUME,
+ MIC_SWITCH,
+ MIC_GAIN,
};
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);
+ using Controls = std::map<Control, struct mixer_ctl*>;
+ friend std::ostream& operator<<(std::ostream&, Control);
+ static const std::map<Control, std::vector<ControlNamesAndExpectedCtlType>> kPossibleControls;
+ static Controls initializeMixerControls(struct mixer* mixer);
+
+ ndk::ScopedAStatus setMixerControlMute(Control ctl, bool muted);
+ ndk::ScopedAStatus setMixerControlVolume(Control ctl, float volume);
+
+ int setMixerControlPercent(struct mixer_ctl* ctl, int percent) REQUIRES(mMixerAccess);
+ int setMixerControlPercent(struct mixer_ctl* ctl, const std::vector<int>& percents)
+ REQUIRES(mMixerAccess);
+ int setMixerControlValue(struct mixer_ctl* ctl, int value) REQUIRES(mMixerAccess);
+
+ // Since ALSA functions do not use internal locking, enforce thread safety at our level.
+ std::mutex mMixerAccess;
// The mixer object is owned by ALSA and will be released when the mixer is closed.
- struct mixer* mMixer;
+ struct mixer* const 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;
+ // read but not be modified. Each mixer_ctl object is owned by ALSA, it's life span is
+ // the same as of the mixer itself.
+ const Controls mMixerControls;
};
} // namespace aidl::android::hardware::audio::core::alsa
diff --git a/audio/aidl/default/usb/UsbAlsaMixerControl.cpp b/audio/aidl/default/usb/UsbAlsaMixerControl.cpp
index 769d739..0a49446 100644
--- a/audio/aidl/default/usb/UsbAlsaMixerControl.cpp
+++ b/audio/aidl/default/usb/UsbAlsaMixerControl.cpp
@@ -33,12 +33,10 @@
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;
+ auto alsaMixer = std::make_shared<alsa::Mixer>(card);
+ if (!alsaMixer->isValid()) {
return;
}
- auto alsaMixer = std::make_shared<alsa::Mixer>(mixer);
alsaMixer->setMasterMute(masterMuted);
alsaMixer->setMasterVolume(masterVolume);
const std::lock_guard guard(mLock);