CSD: extract SoundDoseManager for MEL handling
The SoundDoseManager owns the aggregator which computes the sound dose.
This is a central place for passing the sound dose properties up to the
higher layers.
Test: UT and dumpsys media.audio_flinger
Bug: 255943034
Change-Id: I2d8139b9115bdd793d4ed418f2da6bc8782d32b3
diff --git a/services/audioflinger/Android.bp b/services/audioflinger/Android.bp
index 27ac212..48d3e6f 100644
--- a/services/audioflinger/Android.bp
+++ b/services/audioflinger/Android.bp
@@ -87,6 +87,7 @@
"libmemunreachable",
"libmedia_helper",
"libshmemcompat",
+ "libsounddose",
"libvibrator",
"packagemanager_aidl-cpp",
],
@@ -103,6 +104,7 @@
"libaudiohal_headers",
"libaudioutils_headers",
"libmedia_headers",
+ "libsounddose_headers",
],
export_shared_lib_headers: [
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 6a94164..d2a033e 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -87,6 +87,8 @@
#include <audio_utils/SimpleLog.h>
#include <audio_utils/TimestampVerifier.h>
+#include <sounddose/SoundDoseManager.h>
+
#include "FastCapture.h"
#include "FastMixer.h"
#include <media/nbaio/NBAIO.h>
diff --git a/services/audioflinger/MelReporter.cpp b/services/audioflinger/MelReporter.cpp
index 9a579dd..88500b8 100644
--- a/services/audioflinger/MelReporter.cpp
+++ b/services/audioflinger/MelReporter.cpp
@@ -18,13 +18,11 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "AudioFlinger::MelReporter"
-#include <cinttypes>
-#include <utils/Log.h>
-#include <android-base/stringprintf.h>
-#include <audio_utils/power.h>
-
#include "AudioFlinger.h"
+#include <audio_utils/power.h>
+#include <utils/Log.h>
+
namespace android {
bool AudioFlinger::MelReporter::shouldComputeMelForDeviceType(audio_devices_t device) {
@@ -67,7 +65,7 @@
std::lock_guard _lAf(mAudioFlinger.mLock);
auto thread = mAudioFlinger.checkPlaybackThread_l(streamHandle);
if (thread != nullptr) {
- thread->startMelComputation(mMelAggregator.getOrCreateCallbackForDevice(
+ thread->startMelComputation(mSoundDoseManager.getOrCreateCallbackForDevice(
deviceId,
newPatch.streamHandle));
}
@@ -103,24 +101,13 @@
if (thread != nullptr) {
thread->stopMelComputation();
}
- mMelAggregator.removeStreamCallback(melPatch.streamHandle);
+ mSoundDoseManager.removeStreamCallback(melPatch.streamHandle);
}
std::string AudioFlinger::MelReporter::dump() {
std::lock_guard _l(mLock);
- std::string output;
-
- base::StringAppendF(&output, "\nMel Reporter:\n");
- mMelAggregator.foreach([&output](const audio_utils::MelRecord& melRecord) {
- base::StringAppendF(&output, "Continuous MELs for portId=%d, ", melRecord.portId);
- base::StringAppendF(&output, "starting at timestamp %" PRId64 ": ", melRecord.timestamp);
-
- for (const auto& mel : melRecord.mels) {
- base::StringAppendF(&output, "%d ", mel);
- }
- base::StringAppendF(&output, "\n");
- });
-
+ std::string output("\nSound Dose:\n");
+ output.append(mSoundDoseManager.dump());
return output;
}
diff --git a/services/audioflinger/MelReporter.h b/services/audioflinger/MelReporter.h
index 905a4cd..8a78f6e 100644
--- a/services/audioflinger/MelReporter.h
+++ b/services/audioflinger/MelReporter.h
@@ -19,8 +19,9 @@
#error This header file should only be included from AudioFlinger.h
#endif
-#include <unordered_map>
#include <mutex>
+#include <sounddose/SoundDoseManager.h>
+#include <unordered_map>
constexpr static int kMaxTimestampDeltaInSec = 120;
@@ -31,8 +32,7 @@
class MelReporter : public PatchCommandThread::PatchCommandListener {
public:
explicit MelReporter(AudioFlinger& audioFlinger)
- : mAudioFlinger(audioFlinger),
- mMelAggregator(kMaxTimestampDeltaInSec) {}
+ : mAudioFlinger(audioFlinger) {}
void onFirstRef() override {
mAudioFlinger.mPatchCommandThread->addListener(this);
@@ -54,7 +54,7 @@
private:
AudioFlinger& mAudioFlinger; // does not own the object
- audio_utils::MelAggregator mMelAggregator;
+ SoundDoseManager mSoundDoseManager;
struct ActiveMelPatch {
audio_io_handle_t streamHandle{AUDIO_IO_HANDLE_NONE};
diff --git a/services/audioflinger/sounddose/Android.bp b/services/audioflinger/sounddose/Android.bp
new file mode 100644
index 0000000..eb9517e
--- /dev/null
+++ b/services/audioflinger/sounddose/Android.bp
@@ -0,0 +1,43 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_av_services_audioflinger_license"],
+}
+
+cc_library {
+ name: "libsounddose",
+
+ product_available: true,
+ double_loadable: true,
+ host_supported: true,
+
+ srcs: [
+ "SoundDoseManager.cpp",
+ ],
+
+ shared_libs: [
+ "libaudioutils",
+ "libbase",
+ "liblog",
+ "libutils",
+ ],
+
+ header_libs: [
+ "libaudioutils_headers",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+}
+
+cc_library_headers {
+ name: "libsounddose_headers",
+ host_supported: true,
+ device_supported: true,
+ export_include_dirs: ["."],
+}
diff --git a/services/audioflinger/sounddose/SoundDoseManager.cpp b/services/audioflinger/sounddose/SoundDoseManager.cpp
new file mode 100644
index 0000000..780d796
--- /dev/null
+++ b/services/audioflinger/sounddose/SoundDoseManager.cpp
@@ -0,0 +1,122 @@
+/*
+**
+** Copyright 2022, 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_NDEBUG 0
+#define LOG_TAG "SoundDoseManager"
+
+#include "SoundDoseManager.h"
+
+#include <android-base/stringprintf.h>
+#include <cinttypes>
+#include <utils/Log.h>
+#include <time.h>
+
+namespace android {
+
+namespace {
+
+int64_t getMonotonicSecond() {
+ struct timespec now_ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &now_ts) != 0) {
+ ALOGE("%s: cannot get timestamp", __func__);
+ return -1;
+ }
+ return now_ts.tv_sec;
+}
+
+} // namespace
+
+sp<audio_utils::MelProcessor::MelCallback> SoundDoseManager::getOrCreateCallbackForDevice(
+ audio_port_handle_t deviceId,
+ audio_io_handle_t streamHandle)
+{
+ std::lock_guard _l(mLock);
+
+ auto streamHandleCallback = mActiveCallbacks.find(streamHandle);
+ if (streamHandleCallback != mActiveCallbacks.end()) {
+ ALOGV("%s: found callback for stream %d", __func__, streamHandle);
+ auto callback = streamHandleCallback->second;
+ callback->mDeviceHandle = deviceId;
+ return callback;
+ } else {
+ ALOGV("%s: creating new callback for device %d", __func__, streamHandle);
+ sp<Callback> melCallback = sp<Callback>::make(*this, deviceId);
+ mActiveCallbacks[streamHandle] = melCallback;
+ return melCallback;
+ }
+}
+
+void SoundDoseManager::removeStreamCallback(audio_io_handle_t streamHandle)
+{
+ std::unordered_map<audio_io_handle_t, sp<Callback>>::iterator callbackToRemove;
+
+ std::lock_guard _l(mLock);
+ callbackToRemove = mActiveCallbacks.find(streamHandle);
+ if (callbackToRemove != mActiveCallbacks.end()) {
+ mActiveCallbacks.erase(callbackToRemove);
+ }
+}
+
+void SoundDoseManager::Callback::onNewMelValues(const std::vector<float>& mels,
+ size_t offset,
+ size_t length) const
+{
+ ALOGV("%s", __func__);
+ std::lock_guard _l(mSoundDoseManager.mLock);
+
+ int64_t timestampSec = getMonotonicSecond();
+
+ // only for internal callbacks
+ mSoundDoseManager.mMelAggregator.aggregateAndAddNewMelRecord(
+ audio_utils::MelRecord(mDeviceHandle, std::vector<float>(
+ mels.begin() + offset,
+ mels.begin() + offset + length),
+ timestampSec - length));
+}
+
+std::string SoundDoseManager::dump() const
+{
+ std::string output;
+ mMelAggregator.foreachCsd([&output](audio_utils::CsdRecord csdRecord) {
+ base::StringAppendF(&output,
+ "CSD %f with average MEL %f in interval [%" PRId64 ", %" PRId64 "]",
+ csdRecord.value,
+ csdRecord.averageMel,
+ csdRecord.timestamp,
+ csdRecord.timestamp + csdRecord.duration);
+ base::StringAppendF(&output, "\n");
+ });
+
+ base::StringAppendF(&output, "\nCached Mel Records:\n");
+ mMelAggregator.foreachCachedMel([&output](const audio_utils::MelRecord& melRecord) {
+ base::StringAppendF(&output, "Continuous MELs for portId=%d, ", melRecord.portId);
+ base::StringAppendF(&output, "starting at timestamp %" PRId64 ": ", melRecord.timestamp);
+
+ for (const auto& mel : melRecord.mels) {
+ base::StringAppendF(&output, "%.2f ", mel);
+ }
+ base::StringAppendF(&output, "\n");
+ });
+
+ return output;
+}
+
+size_t SoundDoseManager::getCachedMelRecordsSize() const {
+ return mMelAggregator.getCachedMelRecordsSize();
+}
+
+} // namespace android
diff --git a/services/audioflinger/sounddose/SoundDoseManager.h b/services/audioflinger/sounddose/SoundDoseManager.h
new file mode 100644
index 0000000..fae5ba7
--- /dev/null
+++ b/services/audioflinger/sounddose/SoundDoseManager.h
@@ -0,0 +1,80 @@
+/*
+**
+** Copyright 2022, 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 <audio_utils/MelProcessor.h>
+#include <audio_utils/MelAggregator.h>
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+
+/** CSD is computed with a rolling window of 7 days. */
+constexpr int64_t kCsdWindowSeconds = 604800; // 60 * 60 * 24 * 7
+
+class SoundDoseManager {
+public:
+ SoundDoseManager() : mMelAggregator(kCsdWindowSeconds) {};
+
+ /**
+ * \brief Creates or gets the callback assigned to the streamHandle
+ *
+ * \param deviceId id for the devices where the stream is active.
+ * \param streanHandle handle to the stream
+ */
+ sp<audio_utils::MelProcessor::MelCallback> getOrCreateCallbackForDevice(
+ audio_port_handle_t deviceId,
+ audio_io_handle_t streamHandle);
+
+ /**
+ * \brief Removes stream callback when MEL computation is not needed anymore
+ *
+ * \param streanHandle handle to the stream
+ */
+ void removeStreamCallback(audio_io_handle_t streamHandle);
+
+ std::string dump() const;
+
+ // used for testing
+ size_t getCachedMelRecordsSize() const;
+private:
+ /**
+ * An implementation of the MelProcessor::MelCallback that is assigned to a
+ * specific device.
+ */
+ class Callback : public audio_utils::MelProcessor::MelCallback {
+ public:
+ Callback(SoundDoseManager& soundDoseManager, audio_port_handle_t deviceHandle)
+ : mSoundDoseManager(soundDoseManager), mDeviceHandle(deviceHandle) {}
+
+ void onNewMelValues(const std::vector<float>& mels,
+ size_t offset,
+ size_t length) const override;
+
+ SoundDoseManager& mSoundDoseManager;
+ audio_port_handle_t mDeviceHandle;
+ };
+
+ // no need for lock since MelAggregator is thread-safe
+ audio_utils::MelAggregator mMelAggregator;
+
+ std::mutex mLock;
+ std::unordered_map<audio_io_handle_t, sp<Callback>> mActiveCallbacks GUARDED_BY(mLock);
+};
+
+} // namespace android
diff --git a/services/audioflinger/sounddose/tests/Android.bp b/services/audioflinger/sounddose/tests/Android.bp
new file mode 100644
index 0000000..cb10950
--- /dev/null
+++ b/services/audioflinger/sounddose/tests/Android.bp
@@ -0,0 +1,40 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_av_services_audioflinger_license"],
+}
+
+cc_test {
+ name: "sounddosemanager_tests",
+ host_supported: true,
+
+ srcs: [
+ "sounddosemanager_tests.cpp"
+ ],
+
+ shared_libs: [
+ "libaudioutils",
+ "libbase",
+ "liblog",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libgmock",
+ "libsounddose",
+ ],
+
+ header_libs: [
+ "libaudioutils_headers",
+ "libsounddose_headers",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+}
\ No newline at end of file
diff --git a/services/audioflinger/sounddose/tests/sounddosemanager_tests.cpp b/services/audioflinger/sounddose/tests/sounddosemanager_tests.cpp
new file mode 100644
index 0000000..3fa7d6c
--- /dev/null
+++ b/services/audioflinger/sounddose/tests/sounddosemanager_tests.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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_NDEBUG 0
+#define LOG_TAG "sounddosemanager_tests"
+
+#include <SoundDoseManager.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace {
+
+TEST(SoundDoseManagerTest, GetCallbackForExistingStream) {
+ SoundDoseManager soundDoseManager;
+ sp<audio_utils::MelProcessor::MelCallback> callback1 =
+ soundDoseManager.getOrCreateCallbackForDevice(/*deviceId=*/1, /*streamHandle=*/1);
+ sp<audio_utils::MelProcessor::MelCallback> callback2 =
+ soundDoseManager.getOrCreateCallbackForDevice(/*deviceId=*/2, /*streamHandle=*/1);
+
+ EXPECT_EQ(callback1, callback2);
+}
+
+TEST(SoundDoseManagerTest, RemoveExistingStream) {
+ SoundDoseManager soundDoseManager;
+ sp<audio_utils::MelProcessor::MelCallback> callback1 =
+ soundDoseManager.getOrCreateCallbackForDevice(/*deviceId=*/1, /*streamHandle=*/1);
+
+ soundDoseManager.removeStreamCallback(1);
+ sp<audio_utils::MelProcessor::MelCallback> callback2 =
+ soundDoseManager.getOrCreateCallbackForDevice(/*deviceId=*/2, /*streamHandle=*/1);
+
+ EXPECT_NE(callback1, callback2);
+}
+
+TEST(SoundDoseManagerTest, NewMelValuesCacheNewRecord) {
+ SoundDoseManager soundDoseManager;
+ std::vector<float>mels{1, 1};
+ sp<audio_utils::MelProcessor::MelCallback> callback =
+ soundDoseManager.getOrCreateCallbackForDevice(/*deviceId=*/1, /*streamHandle=*/1);
+
+ callback->onNewMelValues(mels, 0, mels.size());
+
+ EXPECT_EQ(soundDoseManager.getCachedMelRecordsSize(), size_t{1});
+}
+
+} // namespace
+} // namespace android