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
This commit is contained in:
parent
960c33c599
commit
783c48b00e
11 changed files with 466 additions and 32 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -75,6 +75,7 @@ cc_library_static {
|
|||
"Telephony.cpp",
|
||||
"usb/ModuleUsb.cpp",
|
||||
"usb/StreamUsb.cpp",
|
||||
"usb/UsbAlsaMixerControl.cpp",
|
||||
"usb/UsbAlsaUtils.cpp",
|
||||
],
|
||||
generated_sources: [
|
||||
|
|
|
@ -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<AudioRoute> 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;
|
||||
auto result = mDebug.simulateDeviceConnections ? ndk::ScopedAStatus::ok()
|
||||
: onMasterMuteChanged(in_mute);
|
||||
if (result.isOk()) {
|
||||
mMasterMute = in_mute;
|
||||
return ndk::ScopedAStatus::ok();
|
||||
} 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) {
|
||||
auto result = mDebug.simulateDeviceConnections ? ndk::ScopedAStatus::ok()
|
||||
: onMasterVolumeChanged(in_volume);
|
||||
if (result.isOk()) {
|
||||
mMasterVolume = in_volume;
|
||||
return ndk::ScopedAStatus::ok();
|
||||
} 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
|
||||
|
|
|
@ -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<int32_t, int32_t> mPatches;
|
||||
bool mMasterMute = false;
|
||||
float mMasterVolume = 1.0f;
|
||||
bool mMicMute = false;
|
||||
std::shared_ptr<sounddose::ISoundDose> 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
|
||||
|
|
|
@ -28,10 +28,6 @@ class ModuleUsb : public Module {
|
|||
// IModule interfaces
|
||||
ndk::ScopedAStatus getTelephony(std::shared_ptr<ITelephony>* _aidl_return) override;
|
||||
ndk::ScopedAStatus getBluetooth(std::shared_ptr<IBluetooth>* _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
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <aidl/android/media/audio/common/AudioChannelLayout.h>
|
||||
|
||||
|
@ -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<float>* _aidl_return) override;
|
||||
ndk::ScopedAStatus setHwVolume(const std::vector<float>& in_channelVolumes) override;
|
||||
|
||||
int mChannelCount;
|
||||
std::vector<float> mHwVolumes;
|
||||
};
|
||||
|
||||
} // namespace aidl::android::hardware::audio::core
|
||||
|
|
|
@ -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 @@ ndk::ScopedAStatus ModuleUsb::getBluetooth(std::shared_ptr<IBluetooth>* _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<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
|
||||
|
|
|
@ -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::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<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
|
||||
|
|
239
audio/aidl/default/usb/UsbAlsaMixerControl.cpp
Normal file
239
audio/aidl/default/usb/UsbAlsaMixerControl.cpp
Normal file
|
@ -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
|
106
audio/aidl/default/usb/UsbAlsaMixerControl.h
Normal file
106
audio/aidl/default/usb/UsbAlsaMixerControl.h
Normal file
|
@ -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
|
Loading…
Reference in a new issue