AGC2: GainController::ApplyConfig removed

When `AudioProcessingImpl::ApplyConfig()` is called, AGC2 is initialized
and then the new config is applied. That is error prone and for example
breaks bit exactness in [1].

Changes:
- `GainController2` must be created by passing configuration,
  sample rate and number of channels
- `GainController2::ApplyConfig()` removed

Bit exactness verified with audioproc_f on a collection of AEC dumps
and Wav files (42 recordings in total).

[1] https://webrtc-review.googlesource.com/c/src/+/234587.

Bug: webrtc:7494
Change-Id: I251e03603394a4fc8769b9b5c197a157893676a9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/235060
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35206}
diff --git a/modules/audio_processing/agc2/gain_applier.cc b/modules/audio_processing/agc2/gain_applier.cc
index a777cf3..f9e276d 100644
--- a/modules/audio_processing/agc2/gain_applier.cc
+++ b/modules/audio_processing/agc2/gain_applier.cc
@@ -88,6 +88,7 @@
   }
 }
 
+// TODO(bugs.webrtc.org/7494): Remove once switched to gains in dB.
 void GainApplier::SetGainFactor(float gain_factor) {
   RTC_DCHECK_GT(gain_factor, 0.f);
   current_gain_factor_ = gain_factor;
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index 318314b..ee9f5c7 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -412,7 +412,7 @@
   InitializeVoiceDetector();
   InitializeResidualEchoDetector();
   InitializeEchoController();
-  InitializeGainController2();
+  InitializeGainController2(/*config_has_changed=*/true);
   InitializeNoiseSuppressor();
   InitializeAnalyzer();
   InitializePostProcessor();
@@ -589,9 +589,7 @@
     config_.gain_controller2 = AudioProcessing::Config::GainController2();
   }
 
-  if (agc2_config_changed) {
-    InitializeGainController2();
-  }
+  InitializeGainController2(agc2_config_changed);
 
   if (pre_amplifier_config_changed || gain_adjustment_config_changed) {
     InitializeCaptureLevelsAdjuster();
@@ -873,7 +871,7 @@
           float value;
           setting.GetFloat(&value);
           config_.gain_controller2.fixed_digital.gain_db = value;
-          submodules_.gain_controller2->ApplyConfig(config_.gain_controller2);
+          submodules_.gain_controller2->SetFixedGainDb(value);
         }
         break;
       }
@@ -1936,19 +1934,18 @@
       capture_.capture_output_used);
 }
 
-void AudioProcessingImpl::InitializeGainController2() {
-  if (config_.gain_controller2.enabled) {
-    if (!submodules_.gain_controller2) {
-      // TODO(alessiob): Move the injected gain controller once injection is
-      // implemented.
-      submodules_.gain_controller2.reset(new GainController2());
-    }
-
-    submodules_.gain_controller2->Initialize(proc_fullband_sample_rate_hz(),
-                                             num_input_channels());
-    submodules_.gain_controller2->ApplyConfig(config_.gain_controller2);
-  } else {
+void AudioProcessingImpl::InitializeGainController2(bool config_has_changed) {
+  if (!config_has_changed) {
+    return;
+  }
+  if (!config_.gain_controller2.enabled) {
     submodules_.gain_controller2.reset();
+    return;
+  }
+  if (!submodules_.gain_controller2 || config_has_changed) {
+    submodules_.gain_controller2 = std::make_unique<GainController2>(
+        config_.gain_controller2, proc_fullband_sample_rate_hz(),
+        num_input_channels());
   }
 }
 
diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h
index 026152b..89f5d63 100644
--- a/modules/audio_processing/audio_processing_impl.h
+++ b/modules/audio_processing/audio_processing_impl.h
@@ -267,7 +267,7 @@
   void InitializeEchoController()
       RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_render_, mutex_capture_);
 
-  // Initializations of capture-only submodules, requiring the capture lock
+  // Initializations of capture-only sub-modules, requiring the capture lock
   // already acquired.
   void InitializeHighPassFilter(bool forced_reset)
       RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
@@ -275,7 +275,10 @@
   void InitializeGainController1() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
   void InitializeTransientSuppressor()
       RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
-  void InitializeGainController2() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
+  // Initializes the `GainController2` sub-module. If the sub-module is enabled
+  // and `config_has_changed` is true, recreates the sub-module.
+  void InitializeGainController2(bool config_has_changed)
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
   void InitializeNoiseSuppressor() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
   void InitializeCaptureLevelsAdjuster()
       RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_);
diff --git a/modules/audio_processing/gain_controller2.cc b/modules/audio_processing/gain_controller2.cc
index 92cda69..416bc78 100644
--- a/modules/audio_processing/gain_controller2.cc
+++ b/modules/audio_processing/gain_controller2.cc
@@ -10,6 +10,9 @@
 
 #include "modules/audio_processing/gain_controller2.h"
 
+#include <memory>
+#include <utility>
+
 #include "common_audio/include/audio_util.h"
 #include "modules/audio_processing/audio_buffer.h"
 #include "modules/audio_processing/include/audio_frame_view.h"
@@ -21,24 +24,53 @@
 
 namespace webrtc {
 namespace {
+
+using Agc2Config = AudioProcessing::Config::GainController2;
+
+constexpr int kUnspecifiedAnalogLevel = -1;
 constexpr int kLogLimiterStatsPeriodMs = 30'000;
 constexpr int kFrameLengthMs = 10;
 constexpr int kLogLimiterStatsPeriodNumFrames =
     kLogLimiterStatsPeriodMs / kFrameLengthMs;
+
+// Creates an adaptive digital gain controller if enabled.
+std::unique_ptr<AdaptiveAgc> CreateAdaptiveDigitalController(
+    const Agc2Config::AdaptiveDigital& config,
+    int sample_rate_hz,
+    int num_channels,
+    ApmDataDumper* data_dumper) {
+  if (config.enabled) {
+    // TODO(bugs.webrtc.org/7494): Also init with sample rate and num channels.
+    auto controller = std::make_unique<AdaptiveAgc>(data_dumper, config);
+    // TODO(bugs.webrtc.org/7494): Remove once passed to the ctor.
+    controller->Initialize(sample_rate_hz, num_channels);
+    return controller;
+  }
+  return nullptr;
+}
+
 }  // namespace
 
 int GainController2::instance_count_ = 0;
 
-GainController2::GainController2()
+GainController2::GainController2(const Agc2Config& config,
+                                 int sample_rate_hz,
+                                 int num_channels)
     : data_dumper_(rtc::AtomicOps::Increment(&instance_count_)),
       fixed_gain_applier_(/*hard_clip_samples=*/false,
                           /*initial_gain_factor=*/0.0f),
-      limiter_(static_cast<size_t>(48000), &data_dumper_, "Agc2"),
-      calls_since_last_limiter_log_(0) {
-  if (config_.adaptive_digital.enabled) {
-    adaptive_digital_controller_ =
-        std::make_unique<AdaptiveAgc>(&data_dumper_, config_.adaptive_digital);
-  }
+      adaptive_digital_controller_(
+          CreateAdaptiveDigitalController(config.adaptive_digital,
+                                          sample_rate_hz,
+                                          num_channels,
+                                          &data_dumper_)),
+      limiter_(sample_rate_hz, &data_dumper_, /*histogram_name_prefix=*/"Agc2"),
+      calls_since_last_limiter_log_(0),
+      analog_level_(kUnspecifiedAnalogLevel) {
+  RTC_DCHECK(Validate(config));
+  data_dumper_.InitiateNewSetOfRecordings();
+  // TODO(bugs.webrtc.org/7494): Set gain when `fixed_gain_applier_` is init'd.
+  fixed_gain_applier_.SetGainFactor(DbToRatio(config.fixed_digital.gain_db));
 }
 
 GainController2::~GainController2() = default;
@@ -48,13 +80,24 @@
              sample_rate_hz == AudioProcessing::kSampleRate16kHz ||
              sample_rate_hz == AudioProcessing::kSampleRate32kHz ||
              sample_rate_hz == AudioProcessing::kSampleRate48kHz);
+  // TODO(bugs.webrtc.org/7494): Initialize `fixed_gain_applier_`.
   limiter_.SetSampleRate(sample_rate_hz);
   if (adaptive_digital_controller_) {
     adaptive_digital_controller_->Initialize(sample_rate_hz, num_channels);
   }
   data_dumper_.InitiateNewSetOfRecordings();
-  data_dumper_.DumpRaw("sample_rate_hz", sample_rate_hz);
   calls_since_last_limiter_log_ = 0;
+  analog_level_ = kUnspecifiedAnalogLevel;
+}
+
+void GainController2::SetFixedGainDb(float gain_db) {
+  const float gain_factor = DbToRatio(gain_db);
+  if (fixed_gain_applier_.GetGainFactor() != gain_factor) {
+    // Reset the limiter to quickly react on abrupt level changes caused by
+    // large changes of the fixed gain.
+    limiter_.Reset();
+  }
+  fixed_gain_applier_.SetGainFactor(gain_factor);
 }
 
 void GainController2::Process(AudioBuffer* audio) {
@@ -87,25 +130,6 @@
   analog_level_ = level;
 }
 
-void GainController2::ApplyConfig(
-    const AudioProcessing::Config::GainController2& config) {
-  RTC_DCHECK(Validate(config));
-
-  config_ = config;
-  if (config.fixed_digital.gain_db != config_.fixed_digital.gain_db) {
-    // Reset the limiter to quickly react on abrupt level changes caused by
-    // large changes of the fixed gain.
-    limiter_.Reset();
-  }
-  fixed_gain_applier_.SetGainFactor(DbToRatio(config_.fixed_digital.gain_db));
-  if (config_.adaptive_digital.enabled) {
-    adaptive_digital_controller_ =
-        std::make_unique<AdaptiveAgc>(&data_dumper_, config_.adaptive_digital);
-  } else {
-    adaptive_digital_controller_.reset();
-  }
-}
-
 bool GainController2::Validate(
     const AudioProcessing::Config::GainController2& config) {
   const auto& fixed = config.fixed_digital;
diff --git a/modules/audio_processing/gain_controller2.h b/modules/audio_processing/gain_controller2.h
index 1a382f6..cfe51c2 100644
--- a/modules/audio_processing/gain_controller2.h
+++ b/modules/audio_processing/gain_controller2.h
@@ -28,27 +28,35 @@
 // microphone gain and/or applying digital gain.
 class GainController2 {
  public:
-  GainController2();
+  GainController2(const AudioProcessing::Config::GainController2& config,
+                  int sample_rate_hz,
+                  int num_channels);
   GainController2(const GainController2&) = delete;
   GainController2& operator=(const GainController2&) = delete;
   ~GainController2();
 
+  // Detects and handles changes of sample rate and/or number of channels.
   void Initialize(int sample_rate_hz, int num_channels);
+
+  // Sets the fixed digital gain.
+  void SetFixedGainDb(float gain_db);
+
+  // Applies fixed and adaptive digital gains to `audio` and runs a limiter.
   void Process(AudioBuffer* audio);
+
+  // Handles analog level changes.
   void NotifyAnalogLevel(int level);
 
-  void ApplyConfig(const AudioProcessing::Config::GainController2& config);
   static bool Validate(const AudioProcessing::Config::GainController2& config);
 
  private:
   static int instance_count_;
   ApmDataDumper data_dumper_;
-  AudioProcessing::Config::GainController2 config_;
   GainApplier fixed_gain_applier_;
   std::unique_ptr<AdaptiveAgc> adaptive_digital_controller_;
   Limiter limiter_;
   int calls_since_last_limiter_log_;
-  int analog_level_ = -1;
+  int analog_level_;
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/gain_controller2_unittest.cc b/modules/audio_processing/gain_controller2_unittest.cc
index d1c1f5b..b18d943 100644
--- a/modules/audio_processing/gain_controller2_unittest.cc
+++ b/modules/audio_processing/gain_controller2_unittest.cc
@@ -14,6 +14,7 @@
 #include <cmath>
 #include <memory>
 #include <numeric>
+#include <tuple>
 
 #include "api/array_view.h"
 #include "modules/audio_processing/agc2/agc2_testing_common.h"
@@ -27,6 +28,8 @@
 namespace test {
 namespace {
 
+using Agc2Config = AudioProcessing::Config::GainController2;
+
 // Sets all the samples in `ab` to `value`.
 void SetAudioBufferSamples(float value, AudioBuffer& ab) {
   for (size_t k = 0; k < ab.num_channels(); ++k) {
@@ -51,33 +54,26 @@
   return ab.channels()[0][num_samples - 1];
 }
 
-AudioProcessing::Config::GainController2 CreateAgc2FixedDigitalModeConfig(
-    float fixed_gain_db) {
-  AudioProcessing::Config::GainController2 config;
-  config.adaptive_digital.enabled = false;
-  config.fixed_digital.gain_db = fixed_gain_db;
-  EXPECT_TRUE(GainController2::Validate(config));
-  return config;
-}
-
 std::unique_ptr<GainController2> CreateAgc2FixedDigitalMode(
     float fixed_gain_db,
     int sample_rate_hz) {
-  auto agc2 = std::make_unique<GainController2>();
-  agc2->ApplyConfig(CreateAgc2FixedDigitalModeConfig(fixed_gain_db));
-  agc2->Initialize(sample_rate_hz, /*num_channels=*/1);
-  return agc2;
+  Agc2Config config;
+  config.adaptive_digital.enabled = false;
+  config.fixed_digital.gain_db = fixed_gain_db;
+  EXPECT_TRUE(GainController2::Validate(config));
+  return std::make_unique<GainController2>(config, sample_rate_hz,
+                                           /*num_channels=*/1);
 }
 
 }  // namespace
 
 TEST(GainController2, CheckDefaultConfig) {
-  AudioProcessing::Config::GainController2 config;
+  Agc2Config config;
   EXPECT_TRUE(GainController2::Validate(config));
 }
 
 TEST(GainController2, CheckFixedDigitalConfig) {
-  AudioProcessing::Config::GainController2 config;
+  Agc2Config config;
   // Attenuation is not allowed.
   config.fixed_digital.gain_db = -5.0f;
   EXPECT_FALSE(GainController2::Validate(config));
@@ -90,7 +86,7 @@
 }
 
 TEST(GainController2, CheckHeadroomDb) {
-  AudioProcessing::Config::GainController2 config;
+  Agc2Config config;
   config.adaptive_digital.headroom_db = -1.0f;
   EXPECT_FALSE(GainController2::Validate(config));
   config.adaptive_digital.headroom_db = 0.0f;
@@ -100,7 +96,7 @@
 }
 
 TEST(GainController2, CheckMaxGainDb) {
-  AudioProcessing::Config::GainController2 config;
+  Agc2Config config;
   config.adaptive_digital.max_gain_db = -1.0f;
   EXPECT_FALSE(GainController2::Validate(config));
   config.adaptive_digital.max_gain_db = 0.0f;
@@ -110,7 +106,7 @@
 }
 
 TEST(GainController2, CheckInitialGainDb) {
-  AudioProcessing::Config::GainController2 config;
+  Agc2Config config;
   config.adaptive_digital.initial_gain_db = -1.0f;
   EXPECT_FALSE(GainController2::Validate(config));
   config.adaptive_digital.initial_gain_db = 0.0f;
@@ -120,7 +116,7 @@
 }
 
 TEST(GainController2, CheckAdaptiveDigitalMaxGainChangeSpeedConfig) {
-  AudioProcessing::Config::GainController2 config;
+  Agc2Config config;
   config.adaptive_digital.max_gain_change_db_per_second = -1.0f;
   EXPECT_FALSE(GainController2::Validate(config));
   config.adaptive_digital.max_gain_change_db_per_second = 0.0f;
@@ -130,7 +126,7 @@
 }
 
 TEST(GainController2, CheckAdaptiveDigitalMaxOutputNoiseLevelConfig) {
-  AudioProcessing::Config::GainController2 config;
+  Agc2Config config;
   config.adaptive_digital.max_output_noise_level_dbfs = 5.0f;
   EXPECT_FALSE(GainController2::Validate(config));
   config.adaptive_digital.max_output_noise_level_dbfs = 0.0f;
@@ -141,9 +137,9 @@
 
 // Checks that the default config is applied.
 TEST(GainController2, ApplyDefaultConfig) {
-  auto gain_controller2 = std::make_unique<GainController2>();
-  AudioProcessing::Config::GainController2 config;
-  gain_controller2->ApplyConfig(config);
+  auto gain_controller2 = std::make_unique<GainController2>(
+      Agc2Config{}, /*sample_rate_hz=*/16000, /*num_channels=*/2);
+  EXPECT_TRUE(gain_controller2.get());
 }
 
 TEST(GainController2FixedDigital, GainShouldChangeOnSetGain) {
@@ -161,7 +157,7 @@
                   kInputLevel);
 
   // +20 db should increase signal by a factor of 10.
-  agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGain20Db));
+  agc2_fixed->SetFixedGainDb(kGain20Db);
   EXPECT_FLOAT_EQ(RunAgc2WithConstantInput(*agc2_fixed, kInputLevel, kNumFrames,
                                            kSampleRateHz),
                   kInputLevel * 10);
@@ -185,51 +181,35 @@
       *agc2_fixed, kInputLevel, kNumFrames, kSampleRateHz);
 
   // Increase gain.
-  agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGainDbHigh));
+  agc2_fixed->SetFixedGainDb(kGainDbHigh);
   static_cast<void>(RunAgc2WithConstantInput(*agc2_fixed, kInputLevel,
                                              kNumFrames, kSampleRateHz));
 
   // Back to the lower gain.
-  agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGainDbLow));
+  agc2_fixed->SetFixedGainDb(kGainDbLow);
   const float output_level_post = RunAgc2WithConstantInput(
       *agc2_fixed, kInputLevel, kNumFrames, kSampleRateHz);
 
   EXPECT_EQ(output_level_pre, output_level_post);
 }
 
-struct FixedDigitalTestParams {
-  FixedDigitalTestParams(float gain_db_min,
-                         float gain_db_max,
-                         size_t sample_rate,
-                         bool saturation_expected)
-      : gain_db_min(gain_db_min),
-        gain_db_max(gain_db_max),
-        sample_rate(sample_rate),
-        saturation_expected(saturation_expected) {}
-  float gain_db_min;
-  float gain_db_max;
-  size_t sample_rate;
-  bool saturation_expected;
+class FixedDigitalTest
+    : public ::testing::TestWithParam<std::tuple<float, float, int, bool>> {
+ protected:
+  float gain_db_min() const { return std::get<0>(GetParam()); }
+  float gain_db_max() const { return std::get<1>(GetParam()); }
+  int sample_rate_hz() const { return std::get<2>(GetParam()); }
+  bool saturation_expected() const { return std::get<3>(GetParam()); }
 };
 
-class FixedDigitalTest
-    : public ::testing::Test,
-      public ::testing::WithParamInterface<FixedDigitalTestParams> {};
-
 TEST_P(FixedDigitalTest, CheckSaturationBehaviorWithLimiter) {
-  const float kInputLevel = 32767.0f;
-  const size_t kNumFrames = 5;
-
-  const auto params = GetParam();
-
-  const auto gains_db =
-      test::LinSpace(params.gain_db_min, params.gain_db_max, 10);
-  for (const auto gain_db : gains_db) {
-    SCOPED_TRACE(std::to_string(gain_db));
-    auto agc2_fixed = CreateAgc2FixedDigitalMode(gain_db, params.sample_rate);
-    const float processed_sample = RunAgc2WithConstantInput(
-        *agc2_fixed, kInputLevel, kNumFrames, params.sample_rate);
-    if (params.saturation_expected) {
+  for (const float gain_db : test::LinSpace(gain_db_min(), gain_db_max(), 10)) {
+    SCOPED_TRACE(gain_db);
+    auto agc2_fixed = CreateAgc2FixedDigitalMode(gain_db, sample_rate_hz());
+    const float processed_sample =
+        RunAgc2WithConstantInput(*agc2_fixed, /*input_level=*/32767.0f,
+                                 /*num_frames=*/5, sample_rate_hz());
+    if (saturation_expected()) {
       EXPECT_FLOAT_EQ(processed_sample, 32767.0f);
     } else {
       EXPECT_LT(processed_sample, 32767.0f);
@@ -244,47 +224,43 @@
     ::testing::Values(
         // When gain < `test::kLimiterMaxInputLevelDbFs`, the limiter will not
         // saturate the signal (at any sample rate).
-        FixedDigitalTestParams(0.1f,
-                               test::kLimiterMaxInputLevelDbFs - 0.01f,
-                               8000,
-                               false),
-        FixedDigitalTestParams(0.1,
-                               test::kLimiterMaxInputLevelDbFs - 0.01f,
-                               48000,
-                               false),
+        std::make_tuple(0.1f,
+                        test::kLimiterMaxInputLevelDbFs - 0.01f,
+                        8000,
+                        false),
+        std::make_tuple(0.1,
+                        test::kLimiterMaxInputLevelDbFs - 0.01f,
+                        48000,
+                        false),
         // When gain > `test::kLimiterMaxInputLevelDbFs`, the limiter will
         // saturate the signal (at any sample rate).
-        FixedDigitalTestParams(test::kLimiterMaxInputLevelDbFs + 0.01f,
-                               10.0f,
-                               8000,
-                               true),
-        FixedDigitalTestParams(test::kLimiterMaxInputLevelDbFs + 0.01f,
-                               10.0f,
-                               48000,
-                               true)));
+        std::make_tuple(test::kLimiterMaxInputLevelDbFs + 0.01f,
+                        10.0f,
+                        8000,
+                        true),
+        std::make_tuple(test::kLimiterMaxInputLevelDbFs + 0.01f,
+                        10.0f,
+                        48000,
+                        true)));
 
 // Processes a test audio file and checks that the gain applied at the end of
 // the recording is close to the expected value.
 TEST(GainController2, CheckFinalGainWithAdaptiveDigitalController) {
-  // Create AGC2 enabling only the adaptive digital controller.
-  GainController2 agc2;
-  AudioProcessing::Config::GainController2 config;
-  config.fixed_digital.gain_db = 0.0f;
-  config.adaptive_digital.enabled = true;
-  agc2.ApplyConfig(config);
-
-  // The input audio is a 48k stereo recording.
   constexpr int kSampleRateHz = AudioProcessing::kSampleRate48kHz;
   constexpr int kStereo = 2;
+
+  // Create AGC2 enabling only the adaptive digital controller.
+  Agc2Config config;
+  config.fixed_digital.gain_db = 0.0f;
+  config.adaptive_digital.enabled = true;
+  GainController2 agc2(config, kSampleRateHz, kStereo);
+
   test::InputAudioFile input_file(
       test::GetApmCaptureTestVectorFileName(kSampleRateHz),
       /*loop_at_end=*/true);
   const StreamConfig stream_config(kSampleRateHz, kStereo,
                                    /*has_keyboard=*/false);
 
-  // Initialize AGC2 for the used input.
-  agc2.Initialize(kSampleRateHz, kStereo);
-
   // Init buffers.
   constexpr int kFrameDurationMs = 10;
   std::vector<float> frame(kStereo * stream_config.num_frames());