Fix AGC2 fixed-adaptive gain controllers order.

This CL refactors AGC2 and fixes the order with which the fixed
and the adaptive digital gain controllers are applied - i.e., fixed
first, then adaptive and finally limiter.

FixedGainController has been removed since we need to split the
processing done by the gain applier and the limiter.
Also, GainApplier and Limiter are easy enough to be used without
a wrapper and a wrapper would need 2 separated calls in the right
order - i.e., error prone.

FrameCombiner in audio mixer has been adapted and now only uses the
limiter (which is what is needed since no gain is applied).

The unit tests for FixedGainController have been moved to
gain_controller2_unittests. They have been re-adapted and
ChangeFixedGainShouldBeFastAndTimeInvariant has been re-tuned.

Bug: webrtc:7494
Change-Id: I4d7daeae917257ac019a645b74deba6642f77322
Reviewed-on: https://webrtc-review.googlesource.com/c/108624
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Alex Loiko <aleloi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25477}
diff --git a/modules/audio_mixer/frame_combiner.cc b/modules/audio_mixer/frame_combiner.cc
index cff2282..d3493f5 100644
--- a/modules/audio_mixer/frame_combiner.cc
+++ b/modules/audio_mixer/frame_combiner.cc
@@ -92,10 +92,10 @@
   return mixing_buffer;
 }
 
-void RunLimiter(AudioFrameView<float> mixing_buffer_view,
-                FixedGainController* limiter) {
+void RunLimiter(AudioFrameView<float> mixing_buffer_view, Limiter* limiter) {
   const size_t sample_rate = mixing_buffer_view.samples_per_channel() * 1000 /
                              AudioMixerImpl::kFrameDurationInMs;
+  // TODO(alessiob): Avoid calling SetSampleRate every time.
   limiter->SetSampleRate(sample_rate);
   limiter->Process(mixing_buffer_view);
 }
@@ -117,10 +117,8 @@
 
 FrameCombiner::FrameCombiner(bool use_limiter)
     : data_dumper_(new ApmDataDumper(0)),
-      limiter_(data_dumper_.get(), "AudioMixer"),
-      use_limiter_(use_limiter) {
-  limiter_.SetGain(0.f);
-}
+      limiter_(static_cast<size_t>(48000), data_dumper_.get(), "AudioMixer"),
+      use_limiter_(use_limiter) {}
 
 FrameCombiner::~FrameCombiner() = default;
 
diff --git a/modules/audio_mixer/frame_combiner.h b/modules/audio_mixer/frame_combiner.h
index 70ac027..1c1cd53 100644
--- a/modules/audio_mixer/frame_combiner.h
+++ b/modules/audio_mixer/frame_combiner.h
@@ -15,11 +15,10 @@
 #include <vector>
 
 #include "api/audio/audio_frame.h"
-#include "modules/audio_processing/agc2/fixed_gain_controller.h"
+#include "modules/audio_processing/agc2/limiter.h"
 
 namespace webrtc {
 class ApmDataDumper;
-class FixedGainController;
 
 class FrameCombiner {
  public:
@@ -45,7 +44,7 @@
                       size_t number_of_streams) const;
 
   std::unique_ptr<ApmDataDumper> data_dumper_;
-  FixedGainController limiter_;
+  Limiter limiter_;
   const bool use_limiter_;
   mutable int uma_logging_counter_ = 0;
 };
diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn
index 3b55892..a5bcaf2 100644
--- a/modules/audio_processing/BUILD.gn
+++ b/modules/audio_processing/BUILD.gn
@@ -393,6 +393,7 @@
       "agc2:biquad_filter_unittests",
       "agc2:fixed_digital_unittests",
       "agc2:noise_estimator_unittests",
+      "agc2:test_utils",
       "agc2/rnn_vad:unittests",
       "test/conversational_speech:unittest",
       "utility:block_mean_calculator_unittest",
diff --git a/modules/audio_processing/agc2/BUILD.gn b/modules/audio_processing/agc2/BUILD.gn
index 73d600d..18f2d78 100644
--- a/modules/audio_processing/agc2/BUILD.gn
+++ b/modules/audio_processing/agc2/BUILD.gn
@@ -96,8 +96,6 @@
   sources = [
     "fixed_digital_level_estimator.cc",
     "fixed_digital_level_estimator.h",
-    "fixed_gain_controller.cc",
-    "fixed_gain_controller.h",
     "interpolated_gain_curve.cc",
     "interpolated_gain_curve.h",
     "limiter.cc",
@@ -217,7 +215,6 @@
     "compute_interpolated_gain_curve.cc",
     "compute_interpolated_gain_curve.h",
     "fixed_digital_level_estimator_unittest.cc",
-    "fixed_gain_controller_unittest.cc",
     "interpolated_gain_curve_unittest.cc",
     "limiter_db_gain_curve.cc",
     "limiter_db_gain_curve.h",
@@ -262,7 +259,10 @@
 
 rtc_source_set("test_utils") {
   testonly = true
-  visibility = [ ":*" ]
+  visibility = [
+    ":*",
+    "..:audio_processing_unittests",
+  ]
   sources = [
     "agc2_testing_common.cc",
     "agc2_testing_common.h",
diff --git a/modules/audio_processing/agc2/fixed_gain_controller.h b/modules/audio_processing/agc2/fixed_gain_controller.h
deleted file mode 100644
index 9af64f3..0000000
--- a/modules/audio_processing/agc2/fixed_gain_controller.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *  Copyright (c) 2018 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_AGC2_FIXED_GAIN_CONTROLLER_H_
-#define MODULES_AUDIO_PROCESSING_AGC2_FIXED_GAIN_CONTROLLER_H_
-
-#include "modules/audio_processing/agc2/limiter.h"
-#include "modules/audio_processing/include/audio_frame_view.h"
-
-namespace webrtc {
-class ApmDataDumper;
-
-class FixedGainController {
- public:
-  explicit FixedGainController(ApmDataDumper* apm_data_dumper);
-  FixedGainController(ApmDataDumper* apm_data_dumper,
-                      std::string histogram_name_prefix);
-
-  void Process(AudioFrameView<float> signal);
-
-  // Gain and sample rate may be changed at any time (but not
-  // concurrently with any other method call).
-  void SetGain(float gain_to_apply_db);
-  void SetSampleRate(size_t sample_rate_hz);
-  float LastAudioLevel() const;
-
- private:
-  float gain_to_apply_ = 1.f;
-  ApmDataDumper* apm_data_dumper_ = nullptr;
-  Limiter limiter_;
-};
-
-}  // namespace webrtc
-
-#endif  // MODULES_AUDIO_PROCESSING_AGC2_FIXED_GAIN_CONTROLLER_H_
diff --git a/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc b/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc
deleted file mode 100644
index db1732a..0000000
--- a/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- *  Copyright (c) 2018 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/agc2/fixed_gain_controller.h"
-
-#include "absl/memory/memory.h"
-#include "api/array_view.h"
-#include "modules/audio_processing/agc2/agc2_testing_common.h"
-#include "modules/audio_processing/agc2/vector_float_frame.h"
-#include "modules/audio_processing/logging/apm_data_dumper.h"
-#include "rtc_base/gunit.h"
-#include "system_wrappers/include/metrics.h"
-
-namespace webrtc {
-namespace {
-
-constexpr float kInputLevelLinear = 15000.f;
-
-constexpr float kGainToApplyDb = 15.f;
-
-float RunFixedGainControllerWithConstantInput(FixedGainController* fixed_gc,
-                                              const float input_level,
-                                              const size_t num_frames,
-                                              const int sample_rate) {
-  // Give time to the level etimator to converge.
-  for (size_t i = 0; i < num_frames; ++i) {
-    VectorFloatFrame vectors_with_float_frame(
-        1, rtc::CheckedDivExact(sample_rate, 100), input_level);
-    fixed_gc->Process(vectors_with_float_frame.float_frame_view());
-  }
-
-  // Process the last frame with constant input level.
-  VectorFloatFrame vectors_with_float_frame_last(
-      1, rtc::CheckedDivExact(sample_rate, 100), input_level);
-  fixed_gc->Process(vectors_with_float_frame_last.float_frame_view());
-
-  // Return the last sample from the last processed frame.
-  const auto channel =
-      vectors_with_float_frame_last.float_frame_view().channel(0);
-  return channel[channel.size() - 1];
-}
-
-std::unique_ptr<ApmDataDumper> GetApmDataDumper() {
-  return absl::make_unique<ApmDataDumper>(0);
-}
-
-std::unique_ptr<FixedGainController> CreateFixedGainController(
-    float gain_to_apply,
-    size_t rate,
-    std::string histogram_name_prefix,
-    ApmDataDumper* test_data_dumper) {
-  std::unique_ptr<FixedGainController> fgc =
-      absl::make_unique<FixedGainController>(test_data_dumper,
-                                             histogram_name_prefix);
-  fgc->SetGain(gain_to_apply);
-  fgc->SetSampleRate(rate);
-  return fgc;
-}
-
-std::unique_ptr<FixedGainController> CreateFixedGainController(
-    float gain_to_apply,
-    size_t rate,
-    ApmDataDumper* test_data_dumper) {
-  return CreateFixedGainController(gain_to_apply, rate, "", test_data_dumper);
-}
-
-}  // namespace
-
-TEST(AutomaticGainController2FixedDigital, CreateUse) {
-  const int kSampleRate = 44000;
-  auto test_data_dumper = GetApmDataDumper();
-  std::unique_ptr<FixedGainController> fixed_gc = CreateFixedGainController(
-      kGainToApplyDb, kSampleRate, test_data_dumper.get());
-  VectorFloatFrame vectors_with_float_frame(
-      1, rtc::CheckedDivExact(kSampleRate, 100), kInputLevelLinear);
-  auto float_frame = vectors_with_float_frame.float_frame_view();
-  fixed_gc->Process(float_frame);
-  const auto channel = float_frame.channel(0);
-  EXPECT_LT(kInputLevelLinear, channel[0]);
-}
-
-TEST(AutomaticGainController2FixedDigital, CheckSaturationBehaviorWithLimiter) {
-  const float kInputLevel = 32767.f;
-  const size_t kNumFrames = 5;
-  const size_t kSampleRate = 42000;
-
-  auto test_data_dumper = GetApmDataDumper();
-
-  const auto gains_no_saturation =
-      test::LinSpace(0.1, test::kLimiterMaxInputLevelDbFs - 0.01, 10);
-  for (const auto gain_db : gains_no_saturation) {
-    // Since |test::kLimiterMaxInputLevelDbFs| > |gain_db|, the
-    // limiter will not saturate the signal.
-    std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
-        CreateFixedGainController(gain_db, kSampleRate, test_data_dumper.get());
-
-    // Saturation not expected.
-    SCOPED_TRACE(std::to_string(gain_db));
-    EXPECT_LT(
-        RunFixedGainControllerWithConstantInput(
-            fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
-        32767.f);
-  }
-
-  const auto gains_saturation =
-      test::LinSpace(test::kLimiterMaxInputLevelDbFs + 0.01, 10, 10);
-  for (const auto gain_db : gains_saturation) {
-    // Since |test::kLimiterMaxInputLevelDbFs| < |gain|, the limiter
-    // will saturate the signal.
-    std::unique_ptr<FixedGainController> fixed_gc_saturation =
-        CreateFixedGainController(gain_db, kSampleRate, test_data_dumper.get());
-
-    // Saturation expected.
-    SCOPED_TRACE(std::to_string(gain_db));
-    EXPECT_FLOAT_EQ(
-        RunFixedGainControllerWithConstantInput(
-            fixed_gc_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
-        32767.f);
-  }
-}
-
-TEST(AutomaticGainController2FixedDigital,
-     CheckSaturationBehaviorWithLimiterSingleSample) {
-  const float kInputLevel = 32767.f;
-  const size_t kNumFrames = 5;
-  const size_t kSampleRate = 8000;
-
-  auto test_data_dumper = GetApmDataDumper();
-
-  const auto gains_no_saturation =
-      test::LinSpace(0.1, test::kLimiterMaxInputLevelDbFs - 0.01, 10);
-  for (const auto gain_db : gains_no_saturation) {
-    // Since |gain| > |test::kLimiterMaxInputLevelDbFs|, the limiter will
-    // not saturate the signal.
-    std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
-        CreateFixedGainController(gain_db, kSampleRate, test_data_dumper.get());
-
-    // Saturation not expected.
-    SCOPED_TRACE(std::to_string(gain_db));
-    EXPECT_LT(
-        RunFixedGainControllerWithConstantInput(
-            fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
-        32767.f);
-  }
-
-  const auto gains_saturation =
-      test::LinSpace(test::kLimiterMaxInputLevelDbFs + 0.01, 10, 10);
-  for (const auto gain_db : gains_saturation) {
-    // Singe |gain| < |test::kLimiterMaxInputLevelDbFs|, the limiter will
-    // saturate the signal.
-    std::unique_ptr<FixedGainController> fixed_gc_saturation =
-        CreateFixedGainController(gain_db, kSampleRate, test_data_dumper.get());
-
-    // Saturation expected.
-    SCOPED_TRACE(std::to_string(gain_db));
-    EXPECT_FLOAT_EQ(
-        RunFixedGainControllerWithConstantInput(
-            fixed_gc_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
-        32767.f);
-  }
-}
-
-TEST(AutomaticGainController2FixedDigital, GainShouldChangeOnSetGain) {
-  constexpr float kInputLevel = 1000.f;
-  constexpr size_t kNumFrames = 5;
-  constexpr size_t kSampleRate = 8000;
-  constexpr float kGainDbNoChange = 0.f;
-  constexpr float kGainDbFactor10 = 20.f;
-
-  auto test_data_dumper = GetApmDataDumper();
-  std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
-      CreateFixedGainController(kGainDbNoChange, kSampleRate,
-                                test_data_dumper.get());
-
-  // Signal level is unchanged with 0 db gain.
-  EXPECT_FLOAT_EQ(
-      RunFixedGainControllerWithConstantInput(
-          fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
-      kInputLevel);
-
-  fixed_gc_no_saturation->SetGain(kGainDbFactor10);
-
-  // +20db should increase signal by a factor of 10.
-  EXPECT_FLOAT_EQ(
-      RunFixedGainControllerWithConstantInput(
-          fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
-      kInputLevel * 10);
-}
-
-TEST(AutomaticGainController2FixedDigital,
-     SetGainShouldBeFastAndTimeInvariant) {
-  // Number of frames required for the fixed gain controller to adapt on the
-  // input signal when the gain changes.
-  constexpr size_t kNumFrames = 5;
-
-  constexpr float kInputLevel = 1000.f;
-  constexpr size_t kSampleRate = 8000;
-  constexpr float kGainDbLow = 0.f;
-  constexpr float kGainDbHigh = 40.f;
-  static_assert(kGainDbLow < kGainDbHigh, "");
-
-  auto test_data_dumper = GetApmDataDumper();
-  std::unique_ptr<FixedGainController> fixed_gc = CreateFixedGainController(
-      kGainDbLow, kSampleRate, test_data_dumper.get());
-
-  fixed_gc->SetGain(kGainDbLow);
-  const float output_level_pre = RunFixedGainControllerWithConstantInput(
-      fixed_gc.get(), kInputLevel, kNumFrames, kSampleRate);
-
-  fixed_gc->SetGain(kGainDbHigh);
-  RunFixedGainControllerWithConstantInput(fixed_gc.get(), kInputLevel,
-                                          kNumFrames, kSampleRate);
-
-  fixed_gc->SetGain(kGainDbLow);
-  const float output_level_post = RunFixedGainControllerWithConstantInput(
-      fixed_gc.get(), kInputLevel, kNumFrames, kSampleRate);
-
-  EXPECT_EQ(output_level_pre, output_level_post);
-}
-
-TEST(AutomaticGainController2FixedDigital, RegionHistogramIsUpdated) {
-  constexpr size_t kSampleRate = 8000;
-  constexpr float kGainDb = 0.f;
-  constexpr float kInputLevel = 1000.f;
-  constexpr size_t kNumFrames = 5;
-
-  metrics::Reset();
-
-  auto test_data_dumper = GetApmDataDumper();
-  std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
-      CreateFixedGainController(kGainDb, kSampleRate, "Test",
-                                test_data_dumper.get());
-
-  static_cast<void>(RunFixedGainControllerWithConstantInput(
-      fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate));
-
-  // Destroying FixedGainController should cause the last limiter region to be
-  // logged.
-  fixed_gc_no_saturation.reset();
-
-  EXPECT_EQ(1, metrics::NumSamples(
-                   "WebRTC.Audio.Test.FixedDigitalGainCurveRegion.Identity"));
-  EXPECT_EQ(0, metrics::NumSamples(
-                   "WebRTC.Audio.Test.FixedDigitalGainCurveRegion.Knee"));
-  EXPECT_EQ(0, metrics::NumSamples(
-                   "WebRTC.Audio.Test.FixedDigitalGainCurveRegion.Limiter"));
-  EXPECT_EQ(0, metrics::NumSamples(
-                   "WebRTC.Audio.Test.FixedDigitalGainCurveRegion.Saturation"));
-}
-
-}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/limiter.cc b/modules/audio_processing/agc2/limiter.cc
index a77b140..1589f07 100644
--- a/modules/audio_processing/agc2/limiter.cc
+++ b/modules/audio_processing/agc2/limiter.cc
@@ -15,8 +15,10 @@
 #include <cmath>
 
 #include "api/array_view.h"
+#include "modules/audio_processing/agc2/agc2_common.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "rtc_base/checks.h"
+#include "rtc_base/numerics/safe_minmax.h"
 
 namespace webrtc {
 namespace {
@@ -76,11 +78,18 @@
   for (size_t i = 0; i < signal.num_channels(); ++i) {
     auto channel = signal.channel(i);
     for (size_t j = 0; j < samples_per_channel; ++j) {
-      channel[j] *= per_sample_scaling_factors[j];
+      channel[j] = rtc::SafeClamp(channel[j] * per_sample_scaling_factors[j],
+                                  kMinFloatS16Value, kMaxFloatS16Value);
     }
   }
 }
 
+void CheckLimiterSampleRate(size_t sample_rate_hz) {
+  // Check that per_sample_scaling_factors_ is large enough.
+  RTC_DCHECK_LE(sample_rate_hz,
+                kMaximalNumberOfSamplesPerChannel * 1000 / kFrameDurationMs);
+}
+
 }  // namespace
 
 Limiter::Limiter(size_t sample_rate_hz,
@@ -88,7 +97,9 @@
                  std::string histogram_name)
     : interp_gain_curve_(apm_data_dumper, histogram_name),
       level_estimator_(sample_rate_hz, apm_data_dumper),
-      apm_data_dumper_(apm_data_dumper) {}
+      apm_data_dumper_(apm_data_dumper) {
+  CheckLimiterSampleRate(sample_rate_hz);
+}
 
 Limiter::~Limiter() = default;
 
@@ -124,10 +135,8 @@
 }
 
 void Limiter::SetSampleRate(size_t sample_rate_hz) {
+  CheckLimiterSampleRate(sample_rate_hz);
   level_estimator_.SetSampleRate(sample_rate_hz);
-  // Check that per_sample_scaling_factors_ is large enough.
-  RTC_DCHECK_LE(sample_rate_hz,
-                kMaximalNumberOfSamplesPerChannel * 1000 / kFrameDurationMs);
 }
 
 void Limiter::Reset() {
diff --git a/modules/audio_processing/agc2/limiter.h b/modules/audio_processing/agc2/limiter.h
index 91fc0b3..1e0ab71 100644
--- a/modules/audio_processing/agc2/limiter.h
+++ b/modules/audio_processing/agc2/limiter.h
@@ -31,6 +31,7 @@
   Limiter& operator=(const Limiter& limiter) = delete;
   ~Limiter();
 
+  // Applies limiter and hard-clipping to |signal|.
   void Process(AudioFrameView<float> signal);
   InterpolatedGainCurve::Stats GetGainCurveStats() const;
 
diff --git a/modules/audio_processing/gain_controller2.cc b/modules/audio_processing/gain_controller2.cc
index 0921b28..f256be0 100644
--- a/modules/audio_processing/gain_controller2.cc
+++ b/modules/audio_processing/gain_controller2.cc
@@ -10,6 +10,7 @@
 
 #include "modules/audio_processing/gain_controller2.h"
 
+#include "common_audio/include/audio_util.h"
 #include "modules/audio_processing/audio_buffer.h"
 #include "modules/audio_processing/include/audio_frame_view.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
@@ -24,8 +25,10 @@
 GainController2::GainController2()
     : data_dumper_(
           new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
-      fixed_gain_controller_(data_dumper_.get()),
-      adaptive_agc_(new AdaptiveAgc(data_dumper_.get())) {}
+      gain_applier_(/*hard_clip_samples=*/false,
+                    /*initial_gain_factor=*/0.f),
+      adaptive_agc_(new AdaptiveAgc(data_dumper_.get())),
+      limiter_(static_cast<size_t>(48000), data_dumper_.get(), "Agc2") {}
 
 GainController2::~GainController2() = default;
 
@@ -34,7 +37,7 @@
              sample_rate_hz == AudioProcessing::kSampleRate16kHz ||
              sample_rate_hz == AudioProcessing::kSampleRate32kHz ||
              sample_rate_hz == AudioProcessing::kSampleRate48kHz);
-  fixed_gain_controller_.SetSampleRate(sample_rate_hz);
+  limiter_.SetSampleRate(sample_rate_hz);
   data_dumper_->InitiateNewSetOfRecordings();
   data_dumper_->DumpRaw("sample_rate_hz", sample_rate_hz);
 }
@@ -42,11 +45,12 @@
 void GainController2::Process(AudioBuffer* audio) {
   AudioFrameView<float> float_frame(audio->channels_f(), audio->num_channels(),
                                     audio->num_frames());
+  // Apply fixed gain first, then the adaptive one.
+  gain_applier_.ApplyGain(float_frame);
   if (adaptive_digital_mode_) {
-    adaptive_agc_->Process(float_frame,
-                           fixed_gain_controller_.LastAudioLevel());
+    adaptive_agc_->Process(float_frame, limiter_.LastAudioLevel());
   }
-  fixed_gain_controller_.Process(float_frame);
+  limiter_.Process(float_frame);
 }
 
 void GainController2::NotifyAnalogLevel(int level) {
@@ -60,7 +64,12 @@
     const AudioProcessing::Config::GainController2& config) {
   RTC_DCHECK(Validate(config));
   config_ = config;
-  fixed_gain_controller_.SetGain(config_.fixed_gain_db);
+  if (gain_applier_.GetGainFactor() != config_.fixed_gain_db) {
+    // Reset the limiter to quickly react on abrupt level changes caused by
+    // large changes of the fixed gain.
+    limiter_.Reset();
+  }
+  gain_applier_.SetGainFactor(DbToRatio(config_.fixed_gain_db));
   adaptive_digital_mode_ = config_.adaptive_digital_mode;
   adaptive_agc_.reset(
       new AdaptiveAgc(data_dumper_.get(), config_.extra_saturation_margin_db));
diff --git a/modules/audio_processing/gain_controller2.h b/modules/audio_processing/gain_controller2.h
index b49b8d0..013385d 100644
--- a/modules/audio_processing/gain_controller2.h
+++ b/modules/audio_processing/gain_controller2.h
@@ -15,7 +15,8 @@
 #include <string>
 
 #include "modules/audio_processing/agc2/adaptive_agc.h"
-#include "modules/audio_processing/agc2/fixed_gain_controller.h"
+#include "modules/audio_processing/agc2/gain_applier.h"
+#include "modules/audio_processing/agc2/limiter.h"
 #include "modules/audio_processing/include/audio_processing.h"
 #include "rtc_base/constructormagic.h"
 
@@ -43,9 +44,10 @@
  private:
   static int instance_count_;
   std::unique_ptr<ApmDataDumper> data_dumper_;
-  FixedGainController fixed_gain_controller_;
   AudioProcessing::Config::GainController2 config_;
+  GainApplier gain_applier_;
   std::unique_ptr<AdaptiveAgc> adaptive_agc_;
+  Limiter limiter_;
   int analog_level_ = -1;
   bool adaptive_digital_mode_ = true;
 
diff --git a/modules/audio_processing/gain_controller2_unittest.cc b/modules/audio_processing/gain_controller2_unittest.cc
index bff64b6..caaefdd 100644
--- a/modules/audio_processing/gain_controller2_unittest.cc
+++ b/modules/audio_processing/gain_controller2_unittest.cc
@@ -10,7 +10,9 @@
 
 #include <algorithm>
 
+#include "absl/memory/memory.h"
 #include "api/array_view.h"
+#include "modules/audio_processing/agc2/agc2_testing_common.h"
 #include "modules/audio_processing/audio_buffer.h"
 #include "modules/audio_processing/gain_controller2.h"
 #include "modules/audio_processing/test/audio_buffer_tools.h"
@@ -20,12 +22,8 @@
 
 namespace webrtc {
 namespace test {
-
 namespace {
 
-constexpr size_t kFrameSizeMs = 10u;
-constexpr size_t kStereo = 2u;
-
 void SetAudioBufferSamples(float value, AudioBuffer* ab) {
   // Sets all the samples in |ab| to |value|.
   for (size_t k = 0; k < ab->num_channels(); ++k) {
@@ -34,6 +32,75 @@
   }
 }
 
+float RunAgc2WithConstantInput(GainController2* agc2,
+                               float input_level,
+                               size_t num_frames,
+                               int sample_rate) {
+  const int num_samples = rtc::CheckedDivExact(sample_rate, 100);
+  AudioBuffer ab(num_samples, 1, num_samples, 1, num_samples);
+
+  // Give time to the level estimator to converge.
+  for (size_t i = 0; i < num_frames + 1; ++i) {
+    SetAudioBufferSamples(input_level, &ab);
+    agc2->Process(&ab);
+  }
+
+  // Return the last sample from the last processed frame.
+  return ab.channels_f()[0][num_samples - 1];
+}
+
+AudioProcessing::Config::GainController2 CreateAgc2FixedDigitalModeConfig(
+    float fixed_gain_db) {
+  AudioProcessing::Config::GainController2 config;
+  config.adaptive_digital_mode = false;
+  config.fixed_gain_db = fixed_gain_db;
+  // TODO(alessiob): Check why ASSERT_TRUE() below does not compile.
+  EXPECT_TRUE(GainController2::Validate(config));
+  return config;
+}
+
+std::unique_ptr<GainController2> CreateAgc2FixedDigitalMode(
+    float fixed_gain_db,
+    size_t sample_rate_hz) {
+  auto agc2 = absl::make_unique<GainController2>();
+  agc2->ApplyConfig(CreateAgc2FixedDigitalModeConfig(fixed_gain_db));
+  agc2->Initialize(sample_rate_hz);
+  return agc2;
+}
+
+float GainAfterProcessingFile(GainController2* gain_controller) {
+  // Set up an AudioBuffer to be filled from the speech file.
+  constexpr size_t kStereo = 2u;
+  const StreamConfig capture_config(AudioProcessing::kSampleRate48kHz, kStereo,
+                                    false);
+  AudioBuffer ab(capture_config.num_frames(), capture_config.num_channels(),
+                 capture_config.num_frames(), capture_config.num_channels(),
+                 capture_config.num_frames());
+  test::InputAudioFile capture_file(
+      test::GetApmCaptureTestVectorFileName(AudioProcessing::kSampleRate48kHz));
+  std::vector<float> capture_input(capture_config.num_frames() *
+                                   capture_config.num_channels());
+
+  // The file should contain at least this many frames. Every iteration, we put
+  // a frame through the gain controller.
+  const int kNumFramesToProcess = 100;
+  for (int frame_no = 0; frame_no < kNumFramesToProcess; ++frame_no) {
+    ReadFloatSamplesFromStereoFile(capture_config.num_frames(),
+                                   capture_config.num_channels(), &capture_file,
+                                   capture_input);
+
+    test::CopyVectorToAudioBuffer(capture_config, capture_input, &ab);
+    gain_controller->Process(&ab);
+  }
+
+  // Send in a last frame with values constant 1 (It's low enough to detect high
+  // gain, and for ease of computation). The applied gain is the result.
+  constexpr float sample_value = 1.f;
+  SetAudioBufferSamples(sample_value, &ab);
+  gain_controller->Process(&ab);
+  return ab.channels_f()[0][0];
+}
+
 }  // namespace
 
 TEST(GainController2, CreateApplyConfig) {
@@ -71,64 +138,131 @@
             GainController2::ToString(config));
 }
 
-TEST(GainController2, Usage) {
-  // Tests GainController2::Process() on an AudioBuffer instance.
-  std::unique_ptr<GainController2> gain_controller2(new GainController2());
-  gain_controller2->Initialize(AudioProcessing::kSampleRate48kHz);
-  const size_t num_frames = rtc::CheckedDivExact<size_t>(
-      kFrameSizeMs * AudioProcessing::kSampleRate48kHz, 1000);
-  AudioBuffer ab(num_frames, kStereo, num_frames, kStereo, num_frames);
-  constexpr float sample_value = 1000.f;
-  SetAudioBufferSamples(sample_value, &ab);
-  AudioProcessing::Config::GainController2 config;
+TEST(GainController2FixedDigital, GainShouldChangeOnSetGain) {
+  constexpr float kInputLevel = 1000.f;
+  constexpr size_t kNumFrames = 5;
+  constexpr size_t kSampleRateHz = 8000;
+  constexpr float kGain0Db = 0.f;
+  constexpr float kGain20Db = 20.f;
 
-  // Check that samples are amplified when the fixed gain is greater than 0 dB.
-  config.fixed_gain_db = 5.f;
-  gain_controller2->ApplyConfig(config);
-  gain_controller2->Process(&ab);
-  EXPECT_LT(sample_value, ab.channels_f()[0][0]);
+  auto agc2_fixed = CreateAgc2FixedDigitalMode(kGain0Db, kSampleRateHz);
+
+  // Signal level is unchanged with 0 db gain.
+  EXPECT_FLOAT_EQ(RunAgc2WithConstantInput(agc2_fixed.get(), kInputLevel,
+                                           kNumFrames, kSampleRateHz),
+                  kInputLevel);
+
+  // +20 db should increase signal by a factor of 10.
+  agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGain20Db));
+  EXPECT_FLOAT_EQ(RunAgc2WithConstantInput(agc2_fixed.get(), kInputLevel,
+                                           kNumFrames, kSampleRateHz),
+                  kInputLevel * 10);
 }
 
-float GainAfterProcessingFile(GainController2* gain_controller) {
-  // Set up an AudioBuffer to be filled from the speech file.
-  const StreamConfig capture_config(AudioProcessing::kSampleRate48kHz, kStereo,
-                                    false);
-  AudioBuffer ab(capture_config.num_frames(), capture_config.num_channels(),
-                 capture_config.num_frames(), capture_config.num_channels(),
-                 capture_config.num_frames());
-  test::InputAudioFile capture_file(
-      test::GetApmCaptureTestVectorFileName(AudioProcessing::kSampleRate48kHz));
-  std::vector<float> capture_input(capture_config.num_frames() *
-                                   capture_config.num_channels());
+TEST(GainController2FixedDigital, ChangeFixedGainShouldBeFastAndTimeInvariant) {
+  // Number of frames required for the fixed gain controller to adapt on the
+  // input signal when the gain changes.
+  constexpr size_t kNumFrames = 5;
 
-  // The file should contain at least this many frames. Every iteration, we put
-  // a frame through the gain controller.
-  const int kNumFramesToProcess = 100;
-  for (int frame_no = 0; frame_no < kNumFramesToProcess; ++frame_no) {
-    ReadFloatSamplesFromStereoFile(capture_config.num_frames(),
-                                   capture_config.num_channels(), &capture_file,
-                                   capture_input);
+  constexpr float kInputLevel = 1000.f;
+  constexpr size_t kSampleRateHz = 8000;
+  constexpr float kGainDbLow = 0.f;
+  constexpr float kGainDbHigh = 25.f;
+  static_assert(kGainDbLow < kGainDbHigh, "");
 
-    test::CopyVectorToAudioBuffer(capture_config, capture_input, &ab);
-    gain_controller->Process(&ab);
+  auto agc2_fixed = CreateAgc2FixedDigitalMode(kGainDbLow, kSampleRateHz);
+
+  // Start with a lower gain.
+  const float output_level_pre = RunAgc2WithConstantInput(
+      agc2_fixed.get(), kInputLevel, kNumFrames, kSampleRateHz);
+
+  // Increase gain.
+  agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGainDbHigh));
+  static_cast<void>(RunAgc2WithConstantInput(agc2_fixed.get(), kInputLevel,
+                                             kNumFrames, kSampleRateHz));
+
+  // Back to the lower gain.
+  agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGainDbLow));
+  const float output_level_post = RunAgc2WithConstantInput(
+      agc2_fixed.get(), 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::Test,
+      public testing::WithParamInterface<FixedDigitalTestParams> {};
+
+TEST_P(FixedDigitalTest, CheckSaturationBehaviorWithLimiter) {
+  const float kInputLevel = 32767.f;
+  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.get(), kInputLevel, kNumFrames, params.sample_rate);
+    if (params.saturation_expected) {
+      EXPECT_FLOAT_EQ(processed_sample, 32767.f);
+    } else {
+      EXPECT_LT(processed_sample, 32767.f);
+    }
   }
-
-  // Send in a last frame with values constant 1 (It's low enough to detect high
-  // gain, and for ease of computation). The applied gain is the result.
-  constexpr float sample_value = 1.f;
-  SetAudioBufferSamples(sample_value, &ab);
-  gain_controller->Process(&ab);
-  return ab.channels_f()[0][0];
 }
 
+static_assert(test::kLimiterMaxInputLevelDbFs < 10, "");
+INSTANTIATE_TEST_CASE_P(
+    GainController2,
+    FixedDigitalTest,
+    ::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),
+        // When gain > |test::kLimiterMaxInputLevelDbFs|, the limiter will
+        // saturate the signal (at any sample rate).
+        FixedDigitalTestParams(test::kLimiterMaxInputLevelDbFs + 0.01f,
+                               10.f,
+                               8000,
+                               true),
+        FixedDigitalTestParams(test::kLimiterMaxInputLevelDbFs + 0.01f,
+                               10.f,
+                               48000,
+                               true)));
+
 TEST(GainController2, UsageSaturationMargin) {
   GainController2 gain_controller2;
   gain_controller2.Initialize(AudioProcessing::kSampleRate48kHz);
 
   AudioProcessing::Config::GainController2 config;
   // Check that samples are not amplified as much when extra margin is
-  // high. They should not be amplified at all, but anly after convergence. GC2
-  // starts with a gain, and it takes time until it's down to 0db.
+  // high. They should not be amplified at all, but only after convergence. GC2
+  // starts with a gain, and it takes time until it's down to 0 dB.
   config.extra_saturation_margin_db = 50.f;
   config.fixed_gain_db = 0.f;
   gain_controller2.ApplyConfig(config);
@@ -148,5 +282,6 @@
 
   EXPECT_GT(GainAfterProcessingFile(&gain_controller2), 2.f);
 }
+
 }  // namespace test
 }  // namespace webrtc