Add AnalogGainStatsReporter to compute and report analog gain statistics
Implement AnalogGainStatsReporter and add it in AudioProcessingImpl.
This class computes statistics for analog gain updates and
periodically reports them into a histogram.
The added histograms for analog gain update statistics:
- WebRTC.Audio.ApmAnalogGainDecreaseRate
- WebRTC.Audio.ApmAnalogGainIncreaseRate
- WebRTC.Audio.ApmAnalogGainUpdateRate
- WebRTC.Audio.ApmAnalogGainDecreaseAverage
- WebRTC.Audio.ApmAnalogGainIncreaseAverage
- WebRTC.Audio.ApmAnalogGainUpdateAverage
The histograms are defined in
https://chromium-review.googlesource.com/c/chromium/src/+/3207987
Bug: webrtc:12774
Change-Id: I3c58d4bb3eb034a11c3f39ab8edb2bc67c5fd5e4
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/234140
Commit-Queue: Hanna Silen <silen@webrtc.org>
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35301}
diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn
index 7fbae51..08574af 100644
--- a/modules/audio_processing/BUILD.gn
+++ b/modules/audio_processing/BUILD.gn
@@ -204,6 +204,7 @@
"aec_dump:aec_dump",
"aecm:aecm_core",
"agc",
+ "agc:analog_gain_stats_reporter",
"agc:gain_control_interface",
"agc:legacy_agc",
"capture_levels_adjuster",
diff --git a/modules/audio_processing/agc/BUILD.gn b/modules/audio_processing/agc/BUILD.gn
index 4bb8c54..d2a2563 100644
--- a/modules/audio_processing/agc/BUILD.gn
+++ b/modules/audio_processing/agc/BUILD.gn
@@ -41,6 +41,20 @@
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
+rtc_library("analog_gain_stats_reporter") {
+ sources = [
+ "analog_gain_stats_reporter.cc",
+ "analog_gain_stats_reporter.h",
+ ]
+ deps = [
+ "../../../rtc_base:gtest_prod",
+ "../../../rtc_base:logging",
+ "../../../rtc_base:safe_minmax",
+ "../../../system_wrappers:metrics",
+ ]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+}
+
rtc_library("clipping_predictor") {
sources = [
"clipping_predictor.cc",
@@ -142,6 +156,7 @@
testonly = true
sources = [
"agc_manager_direct_unittest.cc",
+ "analog_gain_stats_reporter_unittest.cc",
"clipping_predictor_evaluator_unittest.cc",
"clipping_predictor_level_buffer_unittest.cc",
"clipping_predictor_unittest.cc",
@@ -152,6 +167,7 @@
deps = [
":agc",
+ ":analog_gain_stats_reporter",
":clipping_predictor",
":clipping_predictor_evaluator",
":clipping_predictor_level_buffer",
@@ -161,6 +177,7 @@
"../../../rtc_base:checks",
"../../../rtc_base:rtc_base_approved",
"../../../rtc_base:safe_conversions",
+ "../../../system_wrappers:metrics",
"../../../test:field_trial",
"../../../test:fileutils",
"../../../test:test_support",
diff --git a/modules/audio_processing/agc/analog_gain_stats_reporter.cc b/modules/audio_processing/agc/analog_gain_stats_reporter.cc
new file mode 100644
index 0000000..0d8753a
--- /dev/null
+++ b/modules/audio_processing/agc/analog_gain_stats_reporter.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/audio_processing/agc/analog_gain_stats_reporter.h"
+
+#include <cmath>
+
+#include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_minmax.h"
+#include "system_wrappers/include/metrics.h"
+
+namespace webrtc {
+namespace {
+
+constexpr int kFramesIn60Seconds = 6000;
+constexpr int kMinGain = 0;
+constexpr int kMaxGain = 255;
+constexpr int kMaxUpdate = kMaxGain - kMinGain;
+
+float ComputeAverageUpdate(int sum_updates, int num_updates) {
+ RTC_DCHECK_GE(sum_updates, 0);
+ RTC_DCHECK_LE(sum_updates, kMaxUpdate * kFramesIn60Seconds);
+ RTC_DCHECK_GE(num_updates, 0);
+ RTC_DCHECK_LE(num_updates, kFramesIn60Seconds);
+ if (num_updates == 0) {
+ return 0.0f;
+ }
+ return std::round(static_cast<float>(sum_updates) /
+ static_cast<float>(num_updates));
+}
+} // namespace
+
+AnalogGainStatsReporter::AnalogGainStatsReporter() = default;
+
+AnalogGainStatsReporter::~AnalogGainStatsReporter() = default;
+
+void AnalogGainStatsReporter::UpdateStatistics(int analog_mic_level) {
+ RTC_DCHECK_GE(analog_mic_level, kMinGain);
+ RTC_DCHECK_LE(analog_mic_level, kMaxGain);
+ if (previous_analog_mic_level_.has_value() &&
+ analog_mic_level != previous_analog_mic_level_.value()) {
+ const int level_change =
+ analog_mic_level - previous_analog_mic_level_.value();
+ if (level_change < 0) {
+ ++level_update_stats_.num_decreases;
+ level_update_stats_.sum_decreases -= level_change;
+ } else {
+ ++level_update_stats_.num_increases;
+ level_update_stats_.sum_increases += level_change;
+ }
+ }
+ // Periodically log analog gain change metrics.
+ if (++log_level_update_stats_counter_ >= kFramesIn60Seconds) {
+ LogLevelUpdateStats();
+ level_update_stats_ = {};
+ log_level_update_stats_counter_ = 0;
+ }
+ previous_analog_mic_level_ = analog_mic_level;
+}
+
+void AnalogGainStatsReporter::LogLevelUpdateStats() const {
+ const float average_decrease = ComputeAverageUpdate(
+ level_update_stats_.sum_decreases, level_update_stats_.num_decreases);
+ const float average_increase = ComputeAverageUpdate(
+ level_update_stats_.sum_increases, level_update_stats_.num_increases);
+ const int num_updates =
+ level_update_stats_.num_decreases + level_update_stats_.num_increases;
+ const float average_update = ComputeAverageUpdate(
+ level_update_stats_.sum_decreases + level_update_stats_.sum_increases,
+ num_updates);
+ RTC_DLOG(LS_INFO) << "Analog gain update rate: "
+ << "num_updates=" << num_updates
+ << ", num_decreases=" << level_update_stats_.num_decreases
+ << ", num_increases=" << level_update_stats_.num_increases;
+ RTC_DLOG(LS_INFO) << "Analog gain update average: "
+ << "average_update=" << average_update
+ << ", average_decrease=" << average_decrease
+ << ", average_increase=" << average_increase;
+ RTC_HISTOGRAM_COUNTS_LINEAR(
+ /*name=*/"WebRTC.Audio.ApmAnalogGainDecreaseRate",
+ /*sample=*/level_update_stats_.num_decreases,
+ /*min=*/1,
+ /*max=*/kFramesIn60Seconds,
+ /*bucket_count=*/50);
+ if (level_update_stats_.num_decreases > 0) {
+ RTC_HISTOGRAM_COUNTS_LINEAR(
+ /*name=*/"WebRTC.Audio.ApmAnalogGainDecreaseAverage",
+ /*sample=*/average_decrease,
+ /*min=*/1,
+ /*max=*/kMaxUpdate,
+ /*bucket_count=*/50);
+ }
+ RTC_HISTOGRAM_COUNTS_LINEAR(
+ /*name=*/"WebRTC.Audio.ApmAnalogGainIncreaseRate",
+ /*sample=*/level_update_stats_.num_increases,
+ /*min=*/1,
+ /*max=*/kFramesIn60Seconds,
+ /*bucket_count=*/50);
+ if (level_update_stats_.num_increases > 0) {
+ RTC_HISTOGRAM_COUNTS_LINEAR(
+ /*name=*/"WebRTC.Audio.ApmAnalogGainIncreaseAverage",
+ /*sample=*/average_increase,
+ /*min=*/1,
+ /*max=*/kMaxUpdate,
+ /*bucket_count=*/50);
+ }
+ RTC_HISTOGRAM_COUNTS_LINEAR(
+ /*name=*/"WebRTC.Audio.ApmAnalogGainUpdateRate",
+ /*sample=*/num_updates,
+ /*min=*/1,
+ /*max=*/kFramesIn60Seconds,
+ /*bucket_count=*/50);
+ if (num_updates > 0) {
+ RTC_HISTOGRAM_COUNTS_LINEAR(
+ /*name=*/"WebRTC.Audio.ApmAnalogGainUpdateAverage",
+ /*sample=*/average_update,
+ /*min=*/1,
+ /*max=*/kMaxUpdate,
+ /*bucket_count=*/50);
+ }
+}
+
+} // namespace webrtc
diff --git a/modules/audio_processing/agc/analog_gain_stats_reporter.h b/modules/audio_processing/agc/analog_gain_stats_reporter.h
new file mode 100644
index 0000000..c9442e8
--- /dev/null
+++ b/modules/audio_processing/agc/analog_gain_stats_reporter.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_AUDIO_PROCESSING_AGC_ANALOG_GAIN_STATS_REPORTER_H_
+#define MODULES_AUDIO_PROCESSING_AGC_ANALOG_GAIN_STATS_REPORTER_H_
+
+#include "absl/types/optional.h"
+#include "rtc_base/gtest_prod_util.h"
+
+namespace webrtc {
+
+// Analog gain statistics calculator. Computes aggregate stats based on the
+// framewise mic levels processed in `UpdateStatistics()`. Periodically logs the
+// statistics into a histogram.
+class AnalogGainStatsReporter {
+ public:
+ AnalogGainStatsReporter();
+ AnalogGainStatsReporter(const AnalogGainStatsReporter&) = delete;
+ AnalogGainStatsReporter operator=(const AnalogGainStatsReporter&) = delete;
+ ~AnalogGainStatsReporter();
+
+ // Updates the stats based on the `analog_mic_level`. Periodically logs the
+ // stats into a histogram.
+ void UpdateStatistics(int analog_mic_level);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+ CheckLevelUpdateStatsForEmptyStats);
+ FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+ CheckLevelUpdateStatsAfterNoGainChange);
+ FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+ CheckLevelUpdateStatsAfterGainIncrease);
+ FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+ CheckLevelUpdateStatsAfterGainDecrease);
+ FRIEND_TEST_ALL_PREFIXES(AnalogGainStatsReporterTest,
+ CheckLevelUpdateStatsAfterReset);
+
+ // Stores analog gain update stats to enable calculation of update rate and
+ // average update separately for gain increases and decreases.
+ struct LevelUpdateStats {
+ int num_decreases = 0;
+ int num_increases = 0;
+ int sum_decreases = 0;
+ int sum_increases = 0;
+ } level_update_stats_;
+
+ // Returns a copy of the stored statistics. Use only for testing.
+ const LevelUpdateStats level_update_stats() const {
+ return level_update_stats_;
+ }
+
+ // Computes aggregate stat and logs them into a histogram.
+ void LogLevelUpdateStats() const;
+
+ int log_level_update_stats_counter_ = 0;
+ absl::optional<int> previous_analog_mic_level_ = absl::nullopt;
+};
+} // namespace webrtc
+
+#endif // MODULES_AUDIO_PROCESSING_AGC_ANALOG_GAIN_STATS_REPORTER_H_
diff --git a/modules/audio_processing/agc/analog_gain_stats_reporter_unittest.cc b/modules/audio_processing/agc/analog_gain_stats_reporter_unittest.cc
new file mode 100644
index 0000000..cab5287
--- /dev/null
+++ b/modules/audio_processing/agc/analog_gain_stats_reporter_unittest.cc
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/audio_processing/agc/analog_gain_stats_reporter.h"
+
+#include "system_wrappers/include/metrics.h"
+#include "test/gmock.h"
+
+namespace webrtc {
+namespace {
+
+constexpr int kFramesIn60Seconds = 6000;
+
+TEST(AnalogGainStatsReporterTest, CheckLogLevelUpdateStatsEmpty) {
+ AnalogGainStatsReporter stats_reporter;
+ constexpr int kMicLevel = 10;
+ stats_reporter.UpdateStatistics(kMicLevel);
+ // Update almost until the periodic logging and reset.
+ for (int i = 0; i < kFramesIn60Seconds - 2; i += 2) {
+ stats_reporter.UpdateStatistics(kMicLevel + 2);
+ stats_reporter.UpdateStatistics(kMicLevel);
+ }
+ EXPECT_METRIC_THAT(metrics::Samples("WebRTC.Audio.ApmAnalogGainUpdateRate"),
+ ::testing::ElementsAre());
+ EXPECT_METRIC_THAT(metrics::Samples("WebRTC.Audio.ApmAnalogGainDecreaseRate"),
+ ::testing::ElementsAre());
+ EXPECT_METRIC_THAT(metrics::Samples("WebRTC.Audio.ApmAnalogGainIncreaseRate"),
+ ::testing::ElementsAre());
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainUpdateAverage"),
+ ::testing::ElementsAre());
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainDecreaseAverage"),
+ ::testing::ElementsAre());
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainIncreaseAverage"),
+ ::testing::ElementsAre());
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLogLevelUpdateStatsNotEmpty) {
+ AnalogGainStatsReporter stats_reporter;
+ constexpr int kMicLevel = 10;
+ stats_reporter.UpdateStatistics(kMicLevel);
+ // Update until periodic logging.
+ for (int i = 0; i < kFramesIn60Seconds; i += 2) {
+ stats_reporter.UpdateStatistics(kMicLevel + 2);
+ stats_reporter.UpdateStatistics(kMicLevel);
+ }
+ // Update until periodic logging.
+ for (int i = 0; i < kFramesIn60Seconds; i += 2) {
+ stats_reporter.UpdateStatistics(kMicLevel + 3);
+ stats_reporter.UpdateStatistics(kMicLevel);
+ }
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainUpdateRate"),
+ ::testing::ElementsAre(::testing::Pair(kFramesIn60Seconds - 1, 1),
+ ::testing::Pair(kFramesIn60Seconds, 1)));
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainDecreaseRate"),
+ ::testing::ElementsAre(::testing::Pair(kFramesIn60Seconds / 2 - 1, 1),
+ ::testing::Pair(kFramesIn60Seconds / 2, 1)));
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainIncreaseRate"),
+ ::testing::ElementsAre(::testing::Pair(kFramesIn60Seconds / 2, 2)));
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainUpdateAverage"),
+ ::testing::ElementsAre(::testing::Pair(2, 1), ::testing::Pair(3, 1)));
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainDecreaseAverage"),
+ ::testing::ElementsAre(::testing::Pair(2, 1), ::testing::Pair(3, 1)));
+ EXPECT_METRIC_THAT(
+ metrics::Samples("WebRTC.Audio.ApmAnalogGainIncreaseAverage"),
+ ::testing::ElementsAre(::testing::Pair(2, 1), ::testing::Pair(3, 1)));
+}
+} // namespace
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsForEmptyStats) {
+ AnalogGainStatsReporter stats_reporter;
+ const auto& update_stats = stats_reporter.level_update_stats();
+ EXPECT_EQ(update_stats.num_decreases, 0);
+ EXPECT_EQ(update_stats.sum_decreases, 0);
+ EXPECT_EQ(update_stats.num_increases, 0);
+ EXPECT_EQ(update_stats.sum_increases, 0);
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsAfterNoGainChange) {
+ constexpr int kMicLevel = 10;
+ AnalogGainStatsReporter stats_reporter;
+ stats_reporter.UpdateStatistics(kMicLevel);
+ stats_reporter.UpdateStatistics(kMicLevel);
+ stats_reporter.UpdateStatistics(kMicLevel);
+ const auto& update_stats = stats_reporter.level_update_stats();
+ EXPECT_EQ(update_stats.num_decreases, 0);
+ EXPECT_EQ(update_stats.sum_decreases, 0);
+ EXPECT_EQ(update_stats.num_increases, 0);
+ EXPECT_EQ(update_stats.sum_increases, 0);
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsAfterGainIncrease) {
+ constexpr int kMicLevel = 10;
+ AnalogGainStatsReporter stats_reporter;
+ stats_reporter.UpdateStatistics(kMicLevel);
+ stats_reporter.UpdateStatistics(kMicLevel + 4);
+ stats_reporter.UpdateStatistics(kMicLevel + 5);
+ const auto& update_stats = stats_reporter.level_update_stats();
+ EXPECT_EQ(update_stats.num_decreases, 0);
+ EXPECT_EQ(update_stats.sum_decreases, 0);
+ EXPECT_EQ(update_stats.num_increases, 2);
+ EXPECT_EQ(update_stats.sum_increases, 5);
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsAfterGainDecrease) {
+ constexpr int kMicLevel = 10;
+ AnalogGainStatsReporter stats_reporter;
+ stats_reporter.UpdateStatistics(kMicLevel);
+ stats_reporter.UpdateStatistics(kMicLevel - 4);
+ stats_reporter.UpdateStatistics(kMicLevel - 5);
+ const auto& stats_update = stats_reporter.level_update_stats();
+ EXPECT_EQ(stats_update.num_decreases, 2);
+ EXPECT_EQ(stats_update.sum_decreases, 5);
+ EXPECT_EQ(stats_update.num_increases, 0);
+ EXPECT_EQ(stats_update.sum_increases, 0);
+}
+
+TEST(AnalogGainStatsReporterTest, CheckLevelUpdateStatsAfterReset) {
+ AnalogGainStatsReporter stats_reporter;
+ constexpr int kMicLevel = 10;
+ stats_reporter.UpdateStatistics(kMicLevel);
+ // Update until the periodic reset.
+ for (int i = 0; i < kFramesIn60Seconds - 2; i += 2) {
+ stats_reporter.UpdateStatistics(kMicLevel + 2);
+ stats_reporter.UpdateStatistics(kMicLevel);
+ }
+ const auto& stats_before_reset = stats_reporter.level_update_stats();
+ EXPECT_EQ(stats_before_reset.num_decreases, kFramesIn60Seconds / 2 - 1);
+ EXPECT_EQ(stats_before_reset.sum_decreases, kFramesIn60Seconds - 2);
+ EXPECT_EQ(stats_before_reset.num_increases, kFramesIn60Seconds / 2 - 1);
+ EXPECT_EQ(stats_before_reset.sum_increases, kFramesIn60Seconds - 2);
+ stats_reporter.UpdateStatistics(kMicLevel + 2);
+ const auto& stats_during_reset = stats_reporter.level_update_stats();
+ EXPECT_EQ(stats_during_reset.num_decreases, 0);
+ EXPECT_EQ(stats_during_reset.sum_decreases, 0);
+ EXPECT_EQ(stats_during_reset.num_increases, 0);
+ EXPECT_EQ(stats_during_reset.sum_increases, 0);
+ stats_reporter.UpdateStatistics(kMicLevel);
+ stats_reporter.UpdateStatistics(kMicLevel + 3);
+ const auto& stats_after_reset = stats_reporter.level_update_stats();
+ EXPECT_EQ(stats_after_reset.num_decreases, 1);
+ EXPECT_EQ(stats_after_reset.sum_decreases, 2);
+ EXPECT_EQ(stats_after_reset.num_increases, 1);
+ EXPECT_EQ(stats_after_reset.sum_increases, 3);
+}
+
+} // namespace webrtc
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index 2b02a23..169ffe4 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -1149,6 +1149,7 @@
capture_.prev_analog_mic_level != analog_mic_level &&
capture_.prev_analog_mic_level != -1;
capture_.prev_analog_mic_level = analog_mic_level;
+ analog_gain_stats_reporter_.UpdateStatistics(analog_mic_level);
if (submodules_.echo_controller) {
capture_.echo_path_gain_change = analog_mic_level_changed;
diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h
index bf1d22e..22cdadd 100644
--- a/modules/audio_processing/audio_processing_impl.h
+++ b/modules/audio_processing/audio_processing_impl.h
@@ -21,6 +21,7 @@
#include "api/function_view.h"
#include "modules/audio_processing/aec3/echo_canceller3.h"
#include "modules/audio_processing/agc/agc_manager_direct.h"
+#include "modules/audio_processing/agc/analog_gain_stats_reporter.h"
#include "modules/audio_processing/agc/gain_control.h"
#include "modules/audio_processing/audio_buffer.h"
#include "modules/audio_processing/capture_levels_adjuster/capture_levels_adjuster.h"
@@ -531,6 +532,9 @@
RmsLevel capture_output_rms_ RTC_GUARDED_BY(mutex_capture_);
int capture_rms_interval_counter_ RTC_GUARDED_BY(mutex_capture_) = 0;
+ AnalogGainStatsReporter analog_gain_stats_reporter_
+ RTC_GUARDED_BY(mutex_capture_);
+
// Lock protection not needed.
std::unique_ptr<
SwapQueue<std::vector<int16_t>, RenderQueueItemVerifier<int16_t>>>