Make AgcManagerDirect clipping parameters configurable

Bug: webrtc:12774
Change-Id: I99824b5aabe6f921a5db425dd1c1c1d4c606186c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/219681
Commit-Queue: Hanna Silen <silen@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34069}
diff --git a/modules/audio_processing/agc/agc_manager_direct.cc b/modules/audio_processing/agc/agc_manager_direct.cc
index 2454d1b..ebd978b 100644
--- a/modules/audio_processing/agc/agc_manager_direct.cc
+++ b/modules/audio_processing/agc/agc_manager_direct.cc
@@ -27,33 +27,26 @@
 
 namespace {
 
-// Amount the microphone level is lowered with every clipping event.
-const int kClippedLevelStep = 15;
-// Proportion of clipped samples required to declare a clipping event.
-const float kClippedRatioThreshold = 0.1f;
-// Time in frames to wait after a clipping event before checking again.
-const int kClippedWaitFrames = 300;
-
 // Amount of error we tolerate in the microphone level (presumably due to OS
 // quantization) before we assume the user has manually adjusted the microphone.
-const int kLevelQuantizationSlack = 25;
+constexpr int kLevelQuantizationSlack = 25;
 
-const int kDefaultCompressionGain = 7;
-const int kMaxCompressionGain = 12;
-const int kMinCompressionGain = 2;
+constexpr int kDefaultCompressionGain = 7;
+constexpr int kMaxCompressionGain = 12;
+constexpr int kMinCompressionGain = 2;
 // Controls the rate of compression changes towards the target.
-const float kCompressionGainStep = 0.05f;
+constexpr float kCompressionGainStep = 0.05f;
 
-const int kMaxMicLevel = 255;
+constexpr int kMaxMicLevel = 255;
 static_assert(kGainMapSize > kMaxMicLevel, "gain map too small");
-const int kMinMicLevel = 12;
+constexpr int kMinMicLevel = 12;
 
 // Prevent very large microphone level changes.
-const int kMaxResidualGainChange = 15;
+constexpr int kMaxResidualGainChange = 15;
 
 // Maximum additional gain allowed to compensate for microphone level
 // restrictions from clipping events.
-const int kSurplusCompressionGain = 6;
+constexpr int kSurplusCompressionGain = 6;
 
 // Returns whether a fall-back solution to choose the maximum level should be
 // chosen.
@@ -182,19 +175,19 @@
   }
 }
 
-void MonoAgc::HandleClipping() {
+void MonoAgc::HandleClipping(int clipped_level_step) {
   // Always decrease the maximum level, even if the current level is below
   // threshold.
-  SetMaxLevel(std::max(clipped_level_min_, max_level_ - kClippedLevelStep));
+  SetMaxLevel(std::max(clipped_level_min_, max_level_ - clipped_level_step));
   if (log_to_histograms_) {
     RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.AgcClippingAdjustmentAllowed",
-                          level_ - kClippedLevelStep >= clipped_level_min_);
+                          level_ - clipped_level_step >= clipped_level_min_);
   }
   if (level_ > clipped_level_min_) {
     // Don't try to adjust the level if we're already below the limit. As
     // a consequence, if the user has brought the level above the limit, we
     // will still not react until the postproc updates the level.
-    SetLevel(std::max(clipped_level_min_, level_ - kClippedLevelStep));
+    SetLevel(std::max(clipped_level_min_, level_ - clipped_level_step));
     // Reset the AGCs for all channels since the level has changed.
     agc_->Reset();
   }
@@ -404,12 +397,18 @@
 AgcManagerDirect::AgcManagerDirect(Agc* agc,
                                    int startup_min_level,
                                    int clipped_level_min,
-                                   int sample_rate_hz)
+                                   int sample_rate_hz,
+                                   int clipped_level_step,
+                                   float clipped_ratio_threshold,
+                                   int clipped_wait_frames)
     : AgcManagerDirect(/*num_capture_channels*/ 1,
                        startup_min_level,
                        clipped_level_min,
                        /*disable_digital_adaptive*/ false,
-                       sample_rate_hz) {
+                       sample_rate_hz,
+                       clipped_level_step,
+                       clipped_ratio_threshold,
+                       clipped_wait_frames) {
   RTC_DCHECK(channel_agcs_[0]);
   RTC_DCHECK(agc);
   channel_agcs_[0]->set_agc(agc);
@@ -419,15 +418,21 @@
                                    int startup_min_level,
                                    int clipped_level_min,
                                    bool disable_digital_adaptive,
-                                   int sample_rate_hz)
+                                   int sample_rate_hz,
+                                   int clipped_level_step,
+                                   float clipped_ratio_threshold,
+                                   int clipped_wait_frames)
     : data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_counter_))),
       use_min_channel_level_(!UseMaxAnalogChannelLevel()),
       sample_rate_hz_(sample_rate_hz),
       num_capture_channels_(num_capture_channels),
       disable_digital_adaptive_(disable_digital_adaptive),
-      frames_since_clipped_(kClippedWaitFrames),
+      frames_since_clipped_(clipped_wait_frames),
       capture_output_used_(true),
+      clipped_level_step_(clipped_level_step),
+      clipped_ratio_threshold_(clipped_ratio_threshold),
+      clipped_wait_frames_(clipped_wait_frames),
       channel_agcs_(num_capture_channels),
       new_compressions_to_set_(num_capture_channels) {
   const int min_mic_level = GetMinMicLevel();
@@ -438,7 +443,13 @@
         data_dumper_ch, startup_min_level, clipped_level_min,
         disable_digital_adaptive_, min_mic_level);
   }
-  RTC_DCHECK_LT(0, channel_agcs_.size());
+  RTC_DCHECK(!channel_agcs_.empty());
+  RTC_DCHECK_GT(clipped_level_step, 0);
+  RTC_DCHECK_LE(clipped_level_step, 255);
+  RTC_DCHECK_GT(clipped_ratio_threshold, 0.f);
+  RTC_DCHECK_LT(clipped_ratio_threshold, 1.f);
+  RTC_DCHECK_GT(clipped_wait_frames, 0);
+
   channel_agcs_[0]->ActivateLogging();
 }
 
@@ -489,7 +500,7 @@
     return;
   }
 
-  if (frames_since_clipped_ < kClippedWaitFrames) {
+  if (frames_since_clipped_ < clipped_wait_frames_) {
     ++frames_since_clipped_;
     return;
   }
@@ -506,11 +517,11 @@
   float clipped_ratio =
       ComputeClippedRatio(audio, num_capture_channels_, samples_per_channel);
 
-  if (clipped_ratio > kClippedRatioThreshold) {
+  if (clipped_ratio > clipped_ratio_threshold_) {
     RTC_DLOG(LS_INFO) << "[agc] Clipping detected. clipped_ratio="
                       << clipped_ratio;
     for (auto& state_ch : channel_agcs_) {
-      state_ch->HandleClipping();
+      state_ch->HandleClipping(clipped_level_step_);
     }
     frames_since_clipped_ = 0;
   }
diff --git a/modules/audio_processing/agc/agc_manager_direct.h b/modules/audio_processing/agc/agc_manager_direct.h
index f9417cf..e0be1a0 100644
--- a/modules/audio_processing/agc/agc_manager_direct.h
+++ b/modules/audio_processing/agc/agc_manager_direct.h
@@ -34,12 +34,20 @@
   // AgcManagerDirect will configure GainControl internally. The user is
   // responsible for processing the audio using it after the call to Process.
   // The operating range of startup_min_level is [12, 255] and any input value
-  // outside that range will be clamped.
+  // outside that range will be clamped. `clipped_level_step` is the amount
+  // the microphone level is lowered with every clipping event, limited to
+  // (0, 255]. `clipped_ratio_threshold` is the proportion of clipped
+  // samples required to declare a clipping event, limited to (0.f, 1.f).
+  // `clipped_wait_frames` is the time in frames to wait after a clipping event
+  // before checking again, limited to values higher than 0.
   AgcManagerDirect(int num_capture_channels,
                    int startup_min_level,
                    int clipped_level_min,
                    bool disable_digital_adaptive,
-                   int sample_rate_hz);
+                   int sample_rate_hz,
+                   int clipped_level_step,
+                   float clipped_ratio_threshold,
+                   int clipped_wait_frames);
 
   ~AgcManagerDirect();
   AgcManagerDirect(const AgcManagerDirect&) = delete;
@@ -81,13 +89,18 @@
                            AgcMinMicLevelExperimentEnabled50);
   FRIEND_TEST_ALL_PREFIXES(AgcManagerDirectStandaloneTest,
                            AgcMinMicLevelExperimentEnabledAboveStartupLevel);
+  FRIEND_TEST_ALL_PREFIXES(AgcManagerDirectStandaloneTest,
+                           ClippingParametersVerified);
 
   // Dependency injection for testing. Don't delete |agc| as the memory is owned
   // by the manager.
   AgcManagerDirect(Agc* agc,
                    int startup_min_level,
                    int clipped_level_min,
-                   int sample_rate_hz);
+                   int sample_rate_hz,
+                   int clipped_level_step,
+                   float clipped_ratio_threshold,
+                   int clipped_wait_frames);
 
   void AnalyzePreProcess(const float* const* audio, size_t samples_per_channel);
 
@@ -105,6 +118,10 @@
   bool capture_output_used_;
   int channel_controlling_gain_ = 0;
 
+  const int clipped_level_step_;
+  const float clipped_ratio_threshold_;
+  const int clipped_wait_frames_;
+
   std::vector<std::unique_ptr<MonoAgc>> channel_agcs_;
   std::vector<absl::optional<int>> new_compressions_to_set_;
 };
@@ -123,7 +140,7 @@
   void Initialize();
   void HandleCaptureOutputUsedChange(bool capture_output_used);
 
-  void HandleClipping();
+  void HandleClipping(int clipped_level_step);
 
   void Process(const int16_t* audio,
                size_t samples_per_channel,
diff --git a/modules/audio_processing/agc/agc_manager_direct_unittest.cc b/modules/audio_processing/agc/agc_manager_direct_unittest.cc
index 1954ed4..6fdfa6d 100644
--- a/modules/audio_processing/agc/agc_manager_direct_unittest.cc
+++ b/modules/audio_processing/agc/agc_manager_direct_unittest.cc
@@ -26,13 +26,16 @@
 namespace webrtc {
 namespace {
 
-const int kSampleRateHz = 32000;
-const int kNumChannels = 1;
-const int kSamplesPerChannel = kSampleRateHz / 100;
-const int kInitialVolume = 128;
+constexpr int kSampleRateHz = 32000;
+constexpr int kNumChannels = 1;
+constexpr int kSamplesPerChannel = kSampleRateHz / 100;
+constexpr int kInitialVolume = 128;
 constexpr int kClippedMin = 165;  // Arbitrary, but different from the default.
-const float kAboveClippedThreshold = 0.2f;
-const int kMinMicLevel = 12;
+constexpr float kAboveClippedThreshold = 0.2f;
+constexpr int kMinMicLevel = 12;
+constexpr int kClippedLevelStep = 15;
+constexpr float kClippedRatioThreshold = 0.1f;
+constexpr int kClippedWaitFrames = 300;
 
 class MockGainControl : public GainControl {
  public:
@@ -57,10 +60,14 @@
 };
 
 std::unique_ptr<AgcManagerDirect> CreateAgcManagerDirect(
-    int startup_min_level) {
+    int startup_min_level,
+    int clipped_level_step,
+    float clipped_ratio_threshold,
+    int clipped_wait_frames) {
   return std::make_unique<AgcManagerDirect>(
       /*num_capture_channels=*/1, startup_min_level, kClippedMin,
-      /*disable_digital_adaptive=*/true, kSampleRateHz);
+      /*disable_digital_adaptive=*/true, kSampleRateHz, clipped_level_step,
+      clipped_ratio_threshold, clipped_wait_frames);
 }
 
 }  // namespace
@@ -69,7 +76,13 @@
  protected:
   AgcManagerDirectTest()
       : agc_(new MockAgc),
-        manager_(agc_, kInitialVolume, kClippedMin, kSampleRateHz),
+        manager_(agc_,
+                 kInitialVolume,
+                 kClippedMin,
+                 kSampleRateHz,
+                 kClippedLevelStep,
+                 kClippedRatioThreshold,
+                 kClippedWaitFrames),
         audio(kNumChannels),
         audio_data(kNumChannels * kSamplesPerChannel, 0.f) {
     ExpectInitialize();
@@ -705,14 +718,16 @@
   EXPECT_CALL(gctrl, enable_limiter(false));
 
   std::unique_ptr<AgcManagerDirect> manager =
-      CreateAgcManagerDirect(kInitialVolume);
+      CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep,
+                             kClippedRatioThreshold, kClippedWaitFrames);
   manager->Initialize();
   manager->SetupDigitalGainControl(&gctrl);
 }
 
 TEST(AgcManagerDirectStandaloneTest, AgcMinMicLevelExperiment) {
   std::unique_ptr<AgcManagerDirect> manager =
-      CreateAgcManagerDirect(kInitialVolume);
+      CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep,
+                             kClippedRatioThreshold, kClippedWaitFrames);
   EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
   EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume);
 }
@@ -721,7 +736,8 @@
   test::ScopedFieldTrials field_trial(
       "WebRTC-Audio-AgcMinMicLevelExperiment/Disabled/");
   std::unique_ptr<AgcManagerDirect> manager =
-      CreateAgcManagerDirect(kInitialVolume);
+      CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep,
+                             kClippedRatioThreshold, kClippedWaitFrames);
   EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
   EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume);
 }
@@ -732,7 +748,8 @@
   test::ScopedFieldTrials field_trial(
       "WebRTC-Audio-AgcMinMicLevelExperiment/Enabled-256/");
   std::unique_ptr<AgcManagerDirect> manager =
-      CreateAgcManagerDirect(kInitialVolume);
+      CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep,
+                             kClippedRatioThreshold, kClippedWaitFrames);
   EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
   EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume);
 }
@@ -743,7 +760,8 @@
   test::ScopedFieldTrials field_trial(
       "WebRTC-Audio-AgcMinMicLevelExperiment/Enabled--1/");
   std::unique_ptr<AgcManagerDirect> manager =
-      CreateAgcManagerDirect(kInitialVolume);
+      CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep,
+                             kClippedRatioThreshold, kClippedWaitFrames);
   EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel);
   EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume);
 }
@@ -755,7 +773,8 @@
   test::ScopedFieldTrials field_trial(
       "WebRTC-Audio-AgcMinMicLevelExperiment/Enabled-50/");
   std::unique_ptr<AgcManagerDirect> manager =
-      CreateAgcManagerDirect(kInitialVolume);
+      CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep,
+                             kClippedRatioThreshold, kClippedWaitFrames);
   EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), 50);
   EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume);
 }
@@ -768,9 +787,33 @@
   test::ScopedFieldTrials field_trial(
       "WebRTC-Audio-AgcMinMicLevelExperiment/Enabled-50/");
   std::unique_ptr<AgcManagerDirect> manager =
-      CreateAgcManagerDirect(/*startup_min_level=*/30);
+      CreateAgcManagerDirect(/*startup_min_level=*/30, kClippedLevelStep,
+                             kClippedRatioThreshold, kClippedWaitFrames);
   EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), 50);
   EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), 50);
 }
 
+// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_level_step`.
+// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_ratio_threshold`.
+// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_wait_frames`.
+// Verifies that configurable clipping parameters are initialized as intended.
+TEST(AgcManagerDirectStandaloneTest, ClippingParametersVerified) {
+  std::unique_ptr<AgcManagerDirect> manager =
+      CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep,
+                             kClippedRatioThreshold, kClippedWaitFrames);
+  manager->Initialize();
+  EXPECT_EQ(manager->clipped_level_step_, kClippedLevelStep);
+  EXPECT_EQ(manager->clipped_ratio_threshold_, kClippedRatioThreshold);
+  EXPECT_EQ(manager->clipped_wait_frames_, kClippedWaitFrames);
+  std::unique_ptr<AgcManagerDirect> manager_custom =
+      CreateAgcManagerDirect(kInitialVolume,
+                             /*clipped_level_step*/ 10,
+                             /*clipped_ratio_threshold*/ 0.2f,
+                             /*clipped_wait_frames*/ 50);
+  manager_custom->Initialize();
+  EXPECT_EQ(manager_custom->clipped_level_step_, 10);
+  EXPECT_EQ(manager_custom->clipped_ratio_threshold_, 0.2f);
+  EXPECT_EQ(manager_custom->clipped_wait_frames_, 50);
+}
+
 }  // namespace webrtc
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index ac4d6a8..3c5d9fb 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -1918,7 +1918,10 @@
         config_.gain_controller1.analog_gain_controller.clipped_level_min,
         !config_.gain_controller1.analog_gain_controller
              .enable_digital_adaptive,
-        capture_nonlocked_.split_rate));
+        capture_nonlocked_.split_rate,
+        config_.gain_controller1.analog_gain_controller.clipped_level_step,
+        config_.gain_controller1.analog_gain_controller.clipped_ratio_threshold,
+        config_.gain_controller1.analog_gain_controller.clipped_wait_frames));
     if (re_creation) {
       submodules_.agc_manager->set_stream_analog_level(stream_analog_level);
     }
diff --git a/modules/audio_processing/include/audio_processing.cc b/modules/audio_processing/include/audio_processing.cc
index f50a283..29dcd66 100644
--- a/modules/audio_processing/include/audio_processing.cc
+++ b/modules/audio_processing/include/audio_processing.cc
@@ -77,7 +77,11 @@
          analog_lhs.startup_min_volume == analog_rhs.startup_min_volume &&
          analog_lhs.clipped_level_min == analog_rhs.clipped_level_min &&
          analog_lhs.enable_digital_adaptive ==
-             analog_rhs.enable_digital_adaptive;
+             analog_rhs.enable_digital_adaptive &&
+         analog_lhs.clipped_level_step == analog_rhs.clipped_level_step &&
+         analog_lhs.clipped_ratio_threshold ==
+             analog_rhs.clipped_ratio_threshold &&
+         analog_lhs.clipped_wait_frames == analog_rhs.clipped_wait_frames;
 }
 
 bool Agc2Config::AdaptiveDigital::operator==(
@@ -157,6 +161,12 @@
       << gain_controller1.analog_gain_controller.clipped_level_min
       << ", enable_digital_adaptive: "
       << gain_controller1.analog_gain_controller.enable_digital_adaptive
+      << ", clipped_level_step: "
+      << gain_controller1.analog_gain_controller.clipped_level_step
+      << ", clipped_ratio_threshold: "
+      << gain_controller1.analog_gain_controller.clipped_ratio_threshold
+      << ", clipped_wait_frames: "
+      << gain_controller1.analog_gain_controller.clipped_wait_frames
       << " }}, gain_controller2: { enabled: " << gain_controller2.enabled
       << ", fixed_digital: { gain_db: "
       << gain_controller2.fixed_digital.gain_db
diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h
index 6bc50f0..6622097 100644
--- a/modules/audio_processing/include/audio_processing.h
+++ b/modules/audio_processing/include/audio_processing.h
@@ -59,9 +59,9 @@
 //
 // Must be provided through AudioProcessingBuilder().Create(config).
 #if defined(WEBRTC_CHROMIUM_BUILD)
-static const int kAgcStartupMinVolume = 85;
+static constexpr int kAgcStartupMinVolume = 85;
 #else
-static const int kAgcStartupMinVolume = 0;
+static constexpr int kAgcStartupMinVolume = 0;
 #endif  // defined(WEBRTC_CHROMIUM_BUILD)
 static constexpr int kClippedLevelMin = 70;
 
@@ -334,6 +334,15 @@
         // clipping.
         int clipped_level_min = kClippedLevelMin;
         bool enable_digital_adaptive = true;
+        // Amount the microphone level is lowered with every clipping event.
+        // Limited to (0, 255].
+        int clipped_level_step = 15;
+        // Proportion of clipped samples required to declare a clipping event.
+        // Limited to (0.f, 1.f).
+        float clipped_ratio_threshold = 0.1f;
+        // Time in frames to wait after a clipping event before checking again.
+        // Limited to values higher than 0.
+        int clipped_wait_frames = 300;
       } analog_gain_controller;
     } gain_controller1;