diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn index 7fbae5172c..08574af74c 100644 --- a/modules/audio_processing/BUILD.gn +++ b/modules/audio_processing/BUILD.gn @@ -204,6 +204,7 @@ rtc_library("audio_processing") { "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 4bb8c5494b..d2a25635f3 100644 --- a/modules/audio_processing/agc/BUILD.gn +++ b/modules/audio_processing/agc/BUILD.gn @@ -41,6 +41,20 @@ rtc_library("agc") { 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 @@ if (rtc_include_tests) { 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 @@ if (rtc_include_tests) { deps = [ ":agc", + ":analog_gain_stats_reporter", ":clipping_predictor", ":clipping_predictor_evaluator", ":clipping_predictor_level_buffer", @@ -161,6 +177,7 @@ if (rtc_include_tests) { "../../../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 0000000000..0d8753a7c8 --- /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 + +#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(sum_updates) / + static_cast(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 0000000000..c9442e8a43 --- /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 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 0000000000..cab52876f6 --- /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 2b02a23b3d..169ffe411d 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -1149,6 +1149,7 @@ int AudioProcessingImpl::ProcessCaptureStreamLocked() { 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 bf1d22e148..22cdaddb2f 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 @@ class AudioProcessingImpl : public AudioProcessing { 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, RenderQueueItemVerifier>>