CSD: Add csd start/stop of BT categorized devices
BT devices should only compute CSD once they are categorized as
headphones/headsets. The AudioService will send as the cached list of BT
devices together with their categorization which will allow an initial
triage of devices that can calculate the dosage values. There is also a
possibility to dynamically change/add new categorizations which will
start/stop the CSD computation.
Test: atest sounddosemanager_tests
Bug: 287011781
Change-Id: I161533a38cb718c28a8c5a8eaf962e0e5055b5a1
diff --git a/services/audioflinger/sounddose/SoundDoseManager.cpp b/services/audioflinger/sounddose/SoundDoseManager.cpp
index 21f346e..97470a2 100644
--- a/services/audioflinger/sounddose/SoundDoseManager.cpp
+++ b/services/audioflinger/sounddose/SoundDoseManager.cpp
@@ -301,6 +301,25 @@
return binder::Status::ok();
}
+binder::Status SoundDoseManager::SoundDose::initCachedAudioDeviceCategories(
+ const std::vector<media::ISoundDose::AudioDeviceCategory>& btDeviceCategories) {
+ ALOGV("%s", __func__);
+ auto soundDoseManager = mSoundDoseManager.promote();
+ if (soundDoseManager != nullptr) {
+ soundDoseManager->initCachedAudioDeviceCategories(btDeviceCategories);
+ }
+ return binder::Status::ok();
+}
+binder::Status SoundDoseManager::SoundDose::setAudioDeviceCategory(
+ const media::ISoundDose::AudioDeviceCategory& btAudioDevice) {
+ ALOGV("%s", __func__);
+ auto soundDoseManager = mSoundDoseManager.promote();
+ if (soundDoseManager != nullptr) {
+ soundDoseManager->setAudioDeviceCategory(btAudioDevice);
+ }
+ return binder::Status::ok();
+}
+
binder::Status SoundDoseManager::SoundDose::getOutputRs2UpperBound(float* value) {
ALOGV("%s", __func__);
auto soundDoseManager = mSoundDoseManager.promote();
@@ -358,7 +377,9 @@
auto melProcessor = mp.second.promote();
if (melProcessor != nullptr) {
auto deviceId = melProcessor->getDeviceId();
- if (mActiveDeviceTypes[deviceId] == deviceType) {
+ const auto deviceTypeIt = mActiveDeviceTypes.find(deviceId);
+ if (deviceTypeIt != mActiveDeviceTypes.end() &&
+ deviceTypeIt->second == deviceType) {
ALOGV("%s: set attenuation for deviceId %d to %f",
__func__, deviceId, attenuationDB);
melProcessor->setAttenuation(attenuationDB);
@@ -390,6 +411,103 @@
return mEnabledCsd;
}
+void SoundDoseManager::initCachedAudioDeviceCategories(
+ const std::vector<media::ISoundDose::AudioDeviceCategory>& deviceCategories) {
+ ALOGV("%s", __func__);
+ {
+ const std::lock_guard _l(mLock);
+ mBluetoothDevicesWithCsd.clear();
+ }
+ for (const auto& btDeviceCategory : deviceCategories) {
+ setAudioDeviceCategory(btDeviceCategory);
+ }
+}
+
+void SoundDoseManager::setAudioDeviceCategory(
+ const media::ISoundDose::AudioDeviceCategory& audioDevice) {
+ ALOGV("%s: set BT audio device type with address %s to headphone %d", __func__,
+ audioDevice.address.c_str(), audioDevice.csdCompatible);
+
+ std::vector<audio_port_handle_t> devicesToStart;
+ std::vector<audio_port_handle_t> devicesToStop;
+ {
+ const std::lock_guard _l(mLock);
+ const auto deviceIt = mBluetoothDevicesWithCsd.find(
+ std::make_pair(audioDevice.address,
+ static_cast<audio_devices_t>(audioDevice.internalAudioType)));
+ if (deviceIt != mBluetoothDevicesWithCsd.end()) {
+ deviceIt->second = audioDevice.csdCompatible;
+ } else {
+ mBluetoothDevicesWithCsd.emplace(
+ std::make_pair(audioDevice.address,
+ static_cast<audio_devices_t>(audioDevice.internalAudioType)),
+ audioDevice.csdCompatible);
+ }
+
+ for (const auto &activeDevice: mActiveDevices) {
+ if (activeDevice.first.address() == audioDevice.address &&
+ activeDevice.first.mType ==
+ static_cast<audio_devices_t>(audioDevice.internalAudioType)) {
+ if (audioDevice.csdCompatible) {
+ devicesToStart.push_back(activeDevice.second);
+ } else {
+ devicesToStop.push_back(activeDevice.second);
+ }
+ }
+ }
+ }
+
+ for (const auto& deviceToStart : devicesToStart) {
+ mMelReporterCallback->startMelComputationForDeviceId(deviceToStart);
+ }
+ for (const auto& deviceToStop : devicesToStop) {
+ mMelReporterCallback->stopMelComputationForDeviceId(deviceToStop);
+ }
+}
+
+bool SoundDoseManager::shouldComputeCsdForDeviceType(audio_devices_t device) {
+ if (!isCsdEnabled()) {
+ ALOGV("%s csd is disabled", __func__);
+ return false;
+ }
+ if (forceComputeCsdOnAllDevices()) {
+ return true;
+ }
+
+ switch (device) {
+ case AUDIO_DEVICE_OUT_WIRED_HEADSET:
+ case AUDIO_DEVICE_OUT_WIRED_HEADPHONE:
+ // TODO(b/278265907): enable A2DP when we can distinguish A2DP headsets
+ // case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP:
+ case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
+ case AUDIO_DEVICE_OUT_USB_HEADSET:
+ case AUDIO_DEVICE_OUT_BLE_HEADSET:
+ case AUDIO_DEVICE_OUT_BLE_BROADCAST:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool SoundDoseManager::shouldComputeCsdForDeviceWithAddress(const audio_devices_t type,
+ const std::string& deviceAddress) {
+ if (!isCsdEnabled()) {
+ ALOGV("%s csd is disabled", __func__);
+ return false;
+ }
+ if (forceComputeCsdOnAllDevices()) {
+ return true;
+ }
+
+ if (!audio_is_ble_out_device(type) && !audio_is_a2dp_device(type)) {
+ return shouldComputeCsdForDeviceType(type);
+ }
+
+ const std::lock_guard _l(mLock);
+ const auto deviceIt = mBluetoothDevicesWithCsd.find(std::make_pair(deviceAddress, type));
+ return deviceIt != mBluetoothDevicesWithCsd.end() && deviceIt->second;
+}
+
void SoundDoseManager::setUseFrameworkMel(bool useFrameworkMel) {
// invalidate any HAL sound dose interface used
setHalSoundDoseInterface(nullptr);
diff --git a/services/audioflinger/sounddose/SoundDoseManager.h b/services/audioflinger/sounddose/SoundDoseManager.h
index 9ed0661..888561f 100644
--- a/services/audioflinger/sounddose/SoundDoseManager.h
+++ b/services/audioflinger/sounddose/SoundDoseManager.h
@@ -32,6 +32,15 @@
using aidl::android::hardware::audio::core::sounddose::ISoundDose;
+class IMelReporterCallback : public virtual RefBase {
+public:
+ IMelReporterCallback() {};
+ virtual ~IMelReporterCallback() {};
+
+ virtual void stopMelComputationForDeviceId(audio_port_handle_t deviceId) = 0;
+ virtual void startMelComputationForDeviceId(audio_port_handle_t deviceId) = 0;
+};
+
class SoundDoseManager : public audio_utils::MelProcessor::MelCallback {
public:
/** CSD is computed with a rolling window of 7 days. */
@@ -39,8 +48,9 @@
/** Default RS2 upper bound in dBA as defined in IEC 62368-1 3rd edition. */
static constexpr float kDefaultRs2UpperBound = 100.f;
- SoundDoseManager()
- : mMelAggregator(sp<audio_utils::MelAggregator>::make(kCsdWindowSeconds)),
+ explicit SoundDoseManager(const sp<IMelReporterCallback>& melReporterCallback)
+ : mMelReporterCallback(melReporterCallback),
+ mMelAggregator(sp<audio_utils::MelAggregator>::make(kCsdWindowSeconds)),
mRs2UpperBound(kDefaultRs2UpperBound) {};
/**
@@ -104,6 +114,21 @@
/** Returns true if CSD is enabled. */
bool isCsdEnabled();
+ void initCachedAudioDeviceCategories(
+ const std::vector<media::ISoundDose::AudioDeviceCategory>& deviceCategories);
+
+ void setAudioDeviceCategory(
+ const media::ISoundDose::AudioDeviceCategory& audioDevice);
+
+ /**
+ * Returns true if the type can compute CSD. For bluetooth devices we rely on whether we
+ * categorized the address as headphones/headsets, only in this case we return true.
+ */
+ bool shouldComputeCsdForDeviceWithAddress(const audio_devices_t type,
+ const std::string& deviceAddress);
+ /** Returns true for all device types which could support CSD computation. */
+ bool shouldComputeCsdForDeviceType(audio_devices_t device);
+
std::string dump() const;
// used for testing only
@@ -139,6 +164,13 @@
binder::Status getOutputRs2UpperBound(float* value) override;
binder::Status setCsdEnabled(bool enabled) override;
+ binder::Status initCachedAudioDeviceCategories(
+ const std::vector<media::ISoundDose::AudioDeviceCategory> &btDeviceCategories)
+ override;
+
+ binder::Status setAudioDeviceCategory(
+ const media::ISoundDose::AudioDeviceCategory& btAudioDevice) override;
+
binder::Status getCsd(float* value) override;
binder::Status forceUseFrameworkMel(bool useFrameworkMel) override;
binder::Status forceComputeCsdOnAllDevices(bool computeCsdOnAllDevices) override;
@@ -179,6 +211,8 @@
mutable std::mutex mLock;
+ const sp<IMelReporterCallback> mMelReporterCallback;
+
// no need for lock since MelAggregator is thread-safe
const sp<audio_utils::MelAggregator> mMelAggregator;
@@ -191,6 +225,17 @@
std::map<AudioDeviceTypeAddr, audio_port_handle_t> mActiveDevices GUARDED_BY(mLock);
std::unordered_map<audio_port_handle_t, audio_devices_t> mActiveDeviceTypes GUARDED_BY(mLock);
+ struct bt_device_type_hash {
+ std::size_t operator() (const std::pair<std::string, audio_devices_t> &deviceType) const {
+ return std::hash<std::string>()(deviceType.first) ^
+ std::hash<audio_devices_t>()(deviceType.second);
+ }
+ };
+ // storing the BT cached information as received from the java side
+ // see SoundDoseManager::setCachedAudioDeviceCategories
+ std::unordered_map<std::pair<std::string, audio_devices_t>, bool, bt_device_type_hash>
+ mBluetoothDevicesWithCsd GUARDED_BY(mLock);
+
float mRs2UpperBound GUARDED_BY(mLock);
std::unordered_map<audio_devices_t, float> mMelAttenuationDB GUARDED_BY(mLock);
diff --git a/services/audioflinger/sounddose/tests/sounddosemanager_tests.cpp b/services/audioflinger/sounddose/tests/sounddosemanager_tests.cpp
index 9fab77d..7d0b3a7 100644
--- a/services/audioflinger/sounddose/tests/sounddosemanager_tests.cpp
+++ b/services/audioflinger/sounddose/tests/sounddosemanager_tests.cpp
@@ -39,10 +39,18 @@
(const std::shared_ptr<ISoundDose::IHalSoundDoseCallback>&), (override));
};
+class MelReporterCallback : public IMelReporterCallback {
+public:
+ MOCK_METHOD(void, startMelComputationForDeviceId, (audio_port_handle_t), (override));
+ MOCK_METHOD(void, stopMelComputationForDeviceId, (audio_port_handle_t), (override));
+};
+
+
class SoundDoseManagerTest : public ::testing::Test {
protected:
void SetUp() override {
- mSoundDoseManager = sp<SoundDoseManager>::make();
+ mMelReporterCallback = sp<MelReporterCallback>::make();
+ mSoundDoseManager = sp<SoundDoseManager>::make(mMelReporterCallback);
mHalSoundDose = ndk::SharedRefBase::make<HalSoundDoseMock>();
ON_CALL(*mHalSoundDose.get(), setOutputRs2UpperBound)
@@ -52,6 +60,7 @@
});
}
+ sp<MelReporterCallback> mMelReporterCallback;
sp<SoundDoseManager> mSoundDoseManager;
std::shared_ptr<HalSoundDoseMock> mHalSoundDose;
};
@@ -243,5 +252,53 @@
EXPECT_TRUE(mSoundDoseManager->forceUseFrameworkMel());
}
+TEST_F(SoundDoseManagerTest, SetAudioDeviceCategoryStopsNonHeadphone) {
+ media::ISoundDose::AudioDeviceCategory device1;
+ device1.address = "dev1";
+ device1.csdCompatible = false;
+ device1.internalAudioType = AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
+ const AudioDeviceTypeAddr dev1Adt{AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, device1.address};
+
+ // this will mark the device as active
+ mSoundDoseManager->mapAddressToDeviceId(dev1Adt, /*deviceId=*/1);
+ EXPECT_CALL(*mMelReporterCallback.get(), stopMelComputationForDeviceId).Times(1);
+
+ mSoundDoseManager->setAudioDeviceCategory(device1);
+}
+
+TEST_F(SoundDoseManagerTest, SetAudioDeviceCategoryStartsHeadphone) {
+ media::ISoundDose::AudioDeviceCategory device1;
+ device1.address = "dev1";
+ device1.csdCompatible = true;
+ device1.internalAudioType = AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
+ const AudioDeviceTypeAddr dev1Adt{AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, device1.address};
+
+ // this will mark the device as active
+ mSoundDoseManager->mapAddressToDeviceId(dev1Adt, /*deviceId=*/1);
+ EXPECT_CALL(*mMelReporterCallback.get(), startMelComputationForDeviceId).Times(1);
+
+ mSoundDoseManager->setAudioDeviceCategory(device1);
+}
+
+TEST_F(SoundDoseManagerTest, InitCachedAudioDevicesStartsOnlyActiveDevices) {
+ media::ISoundDose::AudioDeviceCategory device1;
+ media::ISoundDose::AudioDeviceCategory device2;
+ device1.address = "dev1";
+ device1.csdCompatible = true;
+ device1.internalAudioType = AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
+ device2.address = "dev2";
+ device2.csdCompatible = true;
+ device2.internalAudioType = AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
+ const AudioDeviceTypeAddr dev1Adt{AUDIO_DEVICE_OUT_BLUETOOTH_A2DP, device1.address};
+ std::vector<media::ISoundDose::AudioDeviceCategory> btDevices = {device1, device2};
+
+ // this will mark the device as active
+ mSoundDoseManager->mapAddressToDeviceId(dev1Adt, /*deviceId=*/1);
+ EXPECT_CALL(*mMelReporterCallback.get(), startMelComputationForDeviceId).Times(1);
+
+ mSoundDoseManager->initCachedAudioDeviceCategories(btDevices);
+}
+
+
} // namespace
} // namespace android