Fixed Digital mode of AGC2 implementation finished.

This CL adds the GainCurveApplier (GCA). It owns a
FixedDigitalLevelEstimator (LE) and an InterpolatedGainCurve
(IGC). The GCA uses the LE to compute the input signal level, looks up
a gain from IGC and applies it on the signal.

The other IGC and LE submodules were added in previous CLs [1] and
[2].

This CL also turns on AGC2 in the APM fuzzer.

[1] https://webrtc-review.googlesource.com/c/src/+/51920
[2] https://webrtc-review.googlesource.com/c/src/+/52381

Bug: webrtc:7949
Change-Id: Idb10cc3ca9d6d2e4ac5824cc3391ed8aa680f6cd
Reviewed-on: https://webrtc-review.googlesource.com/54361
Commit-Queue: Alex Loiko <aleloi@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22103}
diff --git a/modules/audio_processing/agc2/BUILD.gn b/modules/audio_processing/agc2/BUILD.gn
index e589010..4aa212f 100644
--- a/modules/audio_processing/agc2/BUILD.gn
+++ b/modules/audio_processing/agc2/BUILD.gn
@@ -15,6 +15,10 @@
     "fixed_digital_level_estimator.h",
     "fixed_gain_controller.cc",
     "fixed_gain_controller.h",
+    "gain_curve_applier.cc",
+    "gain_curve_applier.h",
+    "interpolated_gain_curve.cc",
+    "interpolated_gain_curve.h",
   ]
 
   configs += [ "..:apm_debug_dump" ]
@@ -25,6 +29,7 @@
     "../../../api:array_view",
     "../../../common_audio",
     "../../../rtc_base:checks",
+    "../../../rtc_base:gtest_prod",
     "../../../rtc_base:rtc_base_approved",
   ]
 }
@@ -34,9 +39,18 @@
   configs += [ "..:apm_debug_dump" ]
 
   sources = [
+    "agc2_testing_common.cc",
     "agc2_testing_common.h",
+    "agc2_testing_common_unittest.cc",
+    "compute_interpolated_gain_curve.cc",
+    "compute_interpolated_gain_curve.h",
     "fixed_digital_level_estimator_unittest.cc",
     "fixed_gain_controller_unittest.cc",
+    "gain_curve_applier_unittest.cc",
+    "interpolated_gain_curve_unittest.cc",
+    "limiter.cc",
+    "limiter.h",
+    "limiter_unittest.cc",
     "vector_float_frame.cc",
     "vector_float_frame.h",
   ]
@@ -46,6 +60,8 @@
     "..:audio_frame_view",
     "../../../api:array_view",
     "../../../common_audio",
+    "../../../rtc_base:checks",
+    "../../../rtc_base:rtc_base_approved",
     "../../../rtc_base:rtc_base_tests_utils",
   ]
 }
diff --git a/modules/audio_processing/agc2/agc2_common.h b/modules/audio_processing/agc2/agc2_common.h
index af13dd0..ad0ab4e 100644
--- a/modules/audio_processing/agc2/agc2_common.h
+++ b/modules/audio_processing/agc2/agc2_common.h
@@ -11,28 +11,36 @@
 #ifndef MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
 #define MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
 
+#include <cmath>
+
 #include "rtc_base/basictypes.h"
 
 namespace webrtc {
 
-constexpr float kMinSampleValue = -32768.f;
-constexpr float kMaxSampleValue = 32767.f;
+constexpr float kMinFloatS16Value = -32768.f;
+constexpr float kMaxFloatS16Value = 32767.f;
+constexpr double kMaxAbsFloatS16Value = 32768.0;
 
 constexpr size_t kFrameDurationMs = 10;
 constexpr size_t kSubFramesInFrame = 20;
+constexpr size_t kMaximalNumberOfSamplesPerChannel = 480;
 
 constexpr float kAttackFilterConstant = 0.f;
 
-constexpr size_t kMaximalNumberOfSamplesPerChannel = 480;
-
 // This is computed from kDecayMs by
 // 10 ** (-1/20 * subframe_duration / kDecayMs).
 // |subframe_duration| is |kFrameDurationMs / kSubFramesInFrame|.
 // kDecayMs is defined in agc2_testing_common.h
 constexpr float kDecayFilterConstant = 0.9998848773724686f;
 
-// TODO(aleloi): add the other constants as more AGC2 components are
-// added.
+// Number of interpolation points for each region of the limiter.
+// These values have been tuned to limit the interpolated gain curve error given
+// the limiter parameters and allowing a maximum error of +/- 32768^-1.
+constexpr size_t kInterpolatedGainCurveKneePoints = 22;
+constexpr size_t kInterpolatedGainCurveBeyondKneePoints = 10;
+constexpr size_t kInterpolatedGainCurveTotalPoints =
+    kInterpolatedGainCurveKneePoints + kInterpolatedGainCurveBeyondKneePoints;
+
 }  // namespace webrtc
 
 #endif  // MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
diff --git a/modules/audio_processing/agc2/agc2_testing_common.cc b/modules/audio_processing/agc2/agc2_testing_common.cc
new file mode 100644
index 0000000..6c22492
--- /dev/null
+++ b/modules/audio_processing/agc2/agc2_testing_common.cc
@@ -0,0 +1,33 @@
+/*
+ *  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/agc2_testing_common.h"
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+namespace test {
+
+std::vector<double> LinSpace(const double l,
+                             const double r,
+                             size_t num_points) {
+  RTC_CHECK(num_points >= 2);
+  std::vector<double> points(num_points);
+  const double step = (r - l) / (num_points - 1.0);
+  points[0] = l;
+  for (size_t i = 1; i < num_points - 1; i++) {
+    points[i] = static_cast<double>(l) + i * step;
+  }
+  points[num_points - 1] = r;
+  return points;
+}
+}  // namespace test
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/agc2_testing_common.h b/modules/audio_processing/agc2/agc2_testing_common.h
index 7e27a24..a176282 100644
--- a/modules/audio_processing/agc2/agc2_testing_common.h
+++ b/modules/audio_processing/agc2/agc2_testing_common.h
@@ -11,11 +11,24 @@
 #ifndef MODULES_AUDIO_PROCESSING_AGC2_AGC2_TESTING_COMMON_H_
 #define MODULES_AUDIO_PROCESSING_AGC2_AGC2_TESTING_COMMON_H_
 
+#include <vector>
+
+#include "rtc_base/basictypes.h"
+
 namespace webrtc {
 
-// Level Estimator test params.
+namespace test {
+
+// Level Estimator test parameters.
 constexpr float kDecayMs = 500.f;
 
+// Limiter parameters.
+constexpr float kLimiterMaxInputLevelDbFs = 1.f;
+constexpr float kLimiterKneeSmoothnessDb = 1.f;
+constexpr float kLimiterCompressionRatio = 5.f;
+
+std::vector<double> LinSpace(const double l, const double r, size_t num_points);
+}  // namespace test
 }  // namespace webrtc
 
 #endif  // MODULES_AUDIO_PROCESSING_AGC2_AGC2_TESTING_COMMON_H_
diff --git a/modules/audio_processing/agc2/agc2_testing_common_unittest.cc b/modules/audio_processing/agc2/agc2_testing_common_unittest.cc
new file mode 100644
index 0000000..b9f7126
--- /dev/null
+++ b/modules/audio_processing/agc2/agc2_testing_common_unittest.cc
@@ -0,0 +1,26 @@
+/*
+ *  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/agc2_testing_common.h"
+#include "rtc_base/gunit.h"
+
+namespace webrtc {
+
+TEST(AutomaticGainController2Common, TestLinSpace) {
+  std::vector<double> points1 = test::LinSpace(-1.0, 2.0, 4);
+  const std::vector<double> expected_points1{{-1.0, 0.0, 1.0, 2.0}};
+  EXPECT_EQ(expected_points1, points1);
+
+  std::vector<double> points2 = test::LinSpace(0.0, 1.0, 4);
+  const std::vector<double> expected_points2{{0.0, 1.0 / 3.0, 2.0 / 3.0, 1.0}};
+  EXPECT_EQ(points2, expected_points2);
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/compute_interpolated_gain_curve.cc b/modules/audio_processing/agc2/compute_interpolated_gain_curve.cc
new file mode 100644
index 0000000..f395bce
--- /dev/null
+++ b/modules/audio_processing/agc2/compute_interpolated_gain_curve.cc
@@ -0,0 +1,228 @@
+/*
+ *  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/compute_interpolated_gain_curve.h"
+
+#include <algorithm>
+#include <cmath>
+#include <queue>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "modules/audio_processing/agc2/agc2_common.h"
+#include "modules/audio_processing/agc2/agc2_testing_common.h"
+#include "modules/audio_processing/agc2/limiter.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+namespace {
+
+std::pair<double, double> ComputeLinearApproximationParams(
+    const Limiter* limiter,
+    const double x) {
+  const double m = limiter->GetGainFirstDerivativeLinear(x);
+  const double q = limiter->GetGainLinear(x) - m * x;
+  return {m, q};
+}
+
+double ComputeAreaUnderPiecewiseLinearApproximation(const Limiter* limiter,
+                                                    const double x0,
+                                                    const double x1) {
+  RTC_CHECK_LT(x0, x1);
+
+  // Linear approximation in x0 and x1.
+  double m0, q0, m1, q1;
+  std::tie(m0, q0) = ComputeLinearApproximationParams(limiter, x0);
+  std::tie(m1, q1) = ComputeLinearApproximationParams(limiter, x1);
+
+  // Intersection point between two adjacent linear pieces.
+  RTC_CHECK_NE(m1, m0);
+  const double x_split = (q0 - q1) / (m1 - m0);
+  RTC_CHECK_LT(x0, x_split);
+  RTC_CHECK_LT(x_split, x1);
+
+  auto area_under_linear_piece = [](double x_l, double x_r, double m,
+                                    double q) {
+    return x_r * (m * x_r / 2.0 + q) - x_l * (m * x_l / 2.0 + q);
+  };
+  return area_under_linear_piece(x0, x_split, m0, q0) +
+         area_under_linear_piece(x_split, x1, m1, q1);
+}
+
+// Computes the approximation error in the limiter region for a given interval.
+// The error is computed as the difference between the areas beneath the limiter
+// curve to approximate and its linear under-approximation.
+double LimiterUnderApproximationNegativeError(const Limiter* limiter,
+                                              const double x0,
+                                              const double x1) {
+  const double area_limiter = limiter->GetGainIntegralLinear(x0, x1);
+  const double area_interpolated_curve =
+      ComputeAreaUnderPiecewiseLinearApproximation(limiter, x0, x1);
+  RTC_CHECK_GE(area_limiter, area_interpolated_curve);
+  return area_limiter - area_interpolated_curve;
+}
+
+// Automatically finds where to sample the beyond-knee region of a limiter using
+// a greedy optimization algorithm that iteratively decreases the approximation
+// error.
+// The solution is sub-optimal because the algorithm is greedy and the points
+// are assigned by halving intervals (starting with the whole beyond-knee region
+// as a single interval). However, even if sub-optimal, this algorithm works
+// well in practice and it is efficiently implemented using priority queues.
+std::vector<double> SampleLimiterRegion(const Limiter* limiter) {
+  static_assert(kInterpolatedGainCurveBeyondKneePoints > 2, "");
+
+  struct Interval {
+    Interval() = default;  // Ctor required by std::priority_queue.
+    Interval(double l, double r, double e) : x0(l), x1(r), error(e) {
+      RTC_CHECK(x0 < x1);
+    }
+    bool operator<(const Interval& other) const { return error < other.error; }
+
+    double x0;
+    double x1;
+    double error;
+  };
+
+  std::priority_queue<Interval, std::vector<Interval>> q;
+  q.emplace(limiter->limiter_start_linear(), limiter->max_input_level_linear(),
+            LimiterUnderApproximationNegativeError(
+                limiter, limiter->limiter_start_linear(),
+                limiter->max_input_level_linear()));
+
+  // Iteratively find points by halving the interval with greatest error.
+  while (q.size() < kInterpolatedGainCurveBeyondKneePoints) {
+    // Get the interval with highest error.
+    const auto interval = q.top();
+    q.pop();
+
+    // Split |interval| and enqueue.
+    double x_split = (interval.x0 + interval.x1) / 2.0;
+    q.emplace(interval.x0, x_split,
+              LimiterUnderApproximationNegativeError(limiter, interval.x0,
+                                                     x_split));  // Left.
+    q.emplace(x_split, interval.x1,
+              LimiterUnderApproximationNegativeError(limiter, x_split,
+                                                     interval.x1));  // Right.
+  }
+
+  // Copy x1 values and sort them.
+  RTC_CHECK_EQ(q.size(), kInterpolatedGainCurveBeyondKneePoints);
+  std::vector<double> samples(kInterpolatedGainCurveBeyondKneePoints);
+  for (size_t i = 0; i < kInterpolatedGainCurveBeyondKneePoints; ++i) {
+    const auto interval = q.top();
+    q.pop();
+    samples[i] = interval.x1;
+  }
+  RTC_CHECK(q.empty());
+  std::sort(samples.begin(), samples.end());
+
+  return samples;
+}
+
+// Compute the parameters to over-approximate the knee region via linear
+// interpolation. Over-approximating is saturation-safe since the knee region is
+// convex.
+void PrecomputeKneeApproxParams(const Limiter* limiter,
+                                test::InterpolatedParameters* parameters) {
+  static_assert(kInterpolatedGainCurveKneePoints > 2, "");
+  // Get |kInterpolatedGainCurveKneePoints| - 1 equally spaced points.
+  const std::vector<double> points = test::LinSpace(
+      limiter->knee_start_linear(), limiter->limiter_start_linear(),
+      kInterpolatedGainCurveKneePoints - 1);
+
+  // Set the first two points. The second is computed to help with the beginning
+  // of the knee region, which has high curvature.
+  parameters->computed_approximation_params_x[0] = points[0];
+  parameters->computed_approximation_params_x[1] =
+      (points[0] + points[1]) / 2.0;
+  // Copy the remaining points.
+  std::copy(std::begin(points) + 1, std::end(points),
+            std::begin(parameters->computed_approximation_params_x) + 2);
+
+  // Compute (m, q) pairs for each linear piece y = mx + q.
+  for (size_t i = 0; i < kInterpolatedGainCurveKneePoints - 1; ++i) {
+    const double x0 = parameters->computed_approximation_params_x[i];
+    const double x1 = parameters->computed_approximation_params_x[i + 1];
+    const double y0 = limiter->GetGainLinear(x0);
+    const double y1 = limiter->GetGainLinear(x1);
+    RTC_CHECK_NE(x1, x0);
+    parameters->computed_approximation_params_m[i] = (y1 - y0) / (x1 - x0);
+    parameters->computed_approximation_params_q[i] =
+        y0 - parameters->computed_approximation_params_m[i] * x0;
+  }
+}
+
+// Compute the parameters to under-approximate the beyond-knee region via linear
+// interpolation and greedy sampling. Under-approximating is saturation-safe
+// since the beyond-knee region is concave.
+void PrecomputeBeyondKneeApproxParams(
+    const Limiter* limiter,
+    test::InterpolatedParameters* parameters) {
+  // Find points on which the linear pieces are tangent to the gain curve.
+  const auto samples = SampleLimiterRegion(limiter);
+
+  // Parametrize each linear piece.
+  double m, q;
+  std::tie(m, q) = ComputeLinearApproximationParams(
+      limiter,
+      parameters
+          ->computed_approximation_params_x[kInterpolatedGainCurveKneePoints -
+                                            1]);
+  parameters
+      ->computed_approximation_params_m[kInterpolatedGainCurveKneePoints - 1] =
+      m;
+  parameters
+      ->computed_approximation_params_q[kInterpolatedGainCurveKneePoints - 1] =
+      q;
+  for (size_t i = 0; i < samples.size(); ++i) {
+    std::tie(m, q) = ComputeLinearApproximationParams(limiter, samples[i]);
+    parameters
+        ->computed_approximation_params_m[i +
+                                          kInterpolatedGainCurveKneePoints] = m;
+    parameters
+        ->computed_approximation_params_q[i +
+                                          kInterpolatedGainCurveKneePoints] = q;
+  }
+
+  // Find the point of intersection between adjacent linear pieces. They will be
+  // used as boundaries between adjacent linear pieces.
+  for (size_t i = kInterpolatedGainCurveKneePoints;
+       i < kInterpolatedGainCurveKneePoints +
+               kInterpolatedGainCurveBeyondKneePoints;
+       ++i) {
+    RTC_CHECK_NE(parameters->computed_approximation_params_m[i],
+                 parameters->computed_approximation_params_m[i - 1]);
+    parameters->computed_approximation_params_x[i] =
+        (  // Formula: (q0 - q1) / (m1 - m0).
+            parameters->computed_approximation_params_q[i - 1] -
+            parameters->computed_approximation_params_q[i]) /
+        (parameters->computed_approximation_params_m[i] -
+         parameters->computed_approximation_params_m[i - 1]);
+  }
+}
+
+}  // namespace
+
+namespace test {
+
+InterpolatedParameters ComputeInterpolatedGainCurveApproximationParams() {
+  InterpolatedParameters parameters;
+  Limiter limiter;
+  parameters.computed_approximation_params_x.fill(0.0f);
+  parameters.computed_approximation_params_m.fill(0.0f);
+  parameters.computed_approximation_params_q.fill(0.0f);
+  PrecomputeKneeApproxParams(&limiter, &parameters);
+  PrecomputeBeyondKneeApproxParams(&limiter, &parameters);
+  return parameters;
+}
+}  // namespace test
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/compute_interpolated_gain_curve.h b/modules/audio_processing/agc2/compute_interpolated_gain_curve.h
new file mode 100644
index 0000000..5f52441
--- /dev/null
+++ b/modules/audio_processing/agc2/compute_interpolated_gain_curve.h
@@ -0,0 +1,48 @@
+/*
+ *  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_COMPUTE_INTERPOLATED_GAIN_CURVE_H_
+#define MODULES_AUDIO_PROCESSING_AGC2_COMPUTE_INTERPOLATED_GAIN_CURVE_H_
+
+#include <array>
+
+#include "modules/audio_processing/agc2/agc2_common.h"
+
+namespace webrtc {
+
+namespace test {
+
+// Parameters for interpolated gain curve using under-approximation to
+// avoid saturation.
+//
+// The saturation gain is defined in order to let hard-clipping occur for
+// those samples having a level that falls in the saturation region. It is an
+// upper bound of the actual gain to apply - i.e., that returned by the
+// limiter.
+
+// Knee and beyond-knee regions approximation parameters.
+// The gain curve is approximated as a piece-wise linear function.
+// |approx_params_x_| are the boundaries between adjacent linear pieces,
+// |approx_params_m_| and |approx_params_q_| are the slope and the y-intercept
+// values of each piece.
+struct InterpolatedParameters {
+  std::array<float, kInterpolatedGainCurveTotalPoints>
+      computed_approximation_params_x;
+  std::array<float, kInterpolatedGainCurveTotalPoints>
+      computed_approximation_params_m;
+  std::array<float, kInterpolatedGainCurveTotalPoints>
+      computed_approximation_params_q;
+};
+
+InterpolatedParameters ComputeInterpolatedGainCurveApproximationParams();
+}  // namespace test
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AGC2_COMPUTE_INTERPOLATED_GAIN_CURVE_H_
diff --git a/modules/audio_processing/agc2/fixed_digital_level_estimator_unittest.cc b/modules/audio_processing/agc2/fixed_digital_level_estimator_unittest.cc
index d530ecc..7547f8e 100644
--- a/modules/audio_processing/agc2/fixed_digital_level_estimator_unittest.cc
+++ b/modules/audio_processing/agc2/fixed_digital_level_estimator_unittest.cc
@@ -122,7 +122,7 @@
 TEST(AutomaticGainController2LevelEstimator, TimeToDecreaseForLowLevel) {
   constexpr float kLevelReductionDb = 25;
   constexpr float kInitialLowLevel = -40;
-  constexpr float kExpectedTime = kLevelReductionDb * kDecayMs;
+  constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs;
 
   const float time_to_decrease =
       TimeMsToDecreaseLevel(22000, 1, kInitialLowLevel, kLevelReductionDb);
@@ -133,7 +133,7 @@
 
 TEST(AutomaticGainController2LevelEstimator, TimeToDecreaseForFullScaleLevel) {
   constexpr float kLevelReductionDb = 25;
-  constexpr float kExpectedTime = kLevelReductionDb * kDecayMs;
+  constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs;
 
   const float time_to_decrease =
       TimeMsToDecreaseLevel(26000, 1, 0, kLevelReductionDb);
@@ -145,7 +145,7 @@
 TEST(AutomaticGainController2LevelEstimator,
      TimeToDecreaseForMultipleChannels) {
   constexpr float kLevelReductionDb = 25;
-  constexpr float kExpectedTime = kLevelReductionDb * kDecayMs;
+  constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs;
   constexpr size_t kNumChannels = 10;
 
   const float time_to_decrease =
diff --git a/modules/audio_processing/agc2/fixed_gain_controller.cc b/modules/audio_processing/agc2/fixed_gain_controller.cc
index a332365..a565613 100644
--- a/modules/audio_processing/agc2/fixed_gain_controller.cc
+++ b/modules/audio_processing/agc2/fixed_gain_controller.cc
@@ -16,6 +16,7 @@
 #include "api/array_view.h"
 #include "common_audio/include/audio_util.h"
 #include "modules/audio_processing/agc2/agc2_common.h"
+#include "modules/audio_processing/agc2/interpolated_gain_curve.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
@@ -27,27 +28,30 @@
 // Returns true when the gain factor is so close to 1 that it would
 // not affect int16 samples.
 bool CloseToOne(float gain_factor) {
-  return 1.f - 1.f / kMaxSampleValue <= gain_factor &&
-         gain_factor <= 1.f + 1.f / kMaxSampleValue;
+  return 1.f - 1.f / kMaxFloatS16Value <= gain_factor &&
+         gain_factor <= 1.f + 1.f / kMaxFloatS16Value;
 }
 }  // namespace
 
 FixedGainController::FixedGainController(ApmDataDumper* apm_data_dumper)
-    : apm_data_dumper_(apm_data_dumper) {
-  RTC_DCHECK_LT(0.f, gain_to_apply_);
-  RTC_DLOG(LS_INFO) << "Gain to apply: " << gain_to_apply_;
-}
+    : apm_data_dumper_(apm_data_dumper),
+      gain_curve_applier_(48000, apm_data_dumper_) {}
 
 void FixedGainController::SetGain(float gain_to_apply_db) {
   // Changes in gain_to_apply_ cause discontinuities. We assume
   // gain_to_apply_ is set in the beginning of the call. If it is
   // frequently changed, we should add interpolation between the
   // values.
+  // The gain
+  RTC_DCHECK_LE(-50.f, gain_to_apply_db);
+  RTC_DCHECK_LE(gain_to_apply_db, 50.f);
   gain_to_apply_ = DbToRatio(gain_to_apply_db);
+  RTC_DCHECK_LT(0.f, gain_to_apply_);
+  RTC_DLOG(LS_INFO) << "Gain to apply: " << gain_to_apply_db << " db.";
 }
 
 void FixedGainController::SetSampleRate(size_t sample_rate_hz) {
-  // TODO(aleloi): propagate the new sample rate to the GainCurveApplier.
+  gain_curve_applier_.SetSampleRate(sample_rate_hz);
 }
 
 void FixedGainController::EnableLimiter(bool enable_limiter) {
@@ -70,8 +74,7 @@
 
   // Use the limiter (if configured to).
   if (enable_limiter_) {
-    // TODO(aleloi): Process the signal with the
-    // GainCurveApplier. This will be done in the upcoming CLs.
+    gain_curve_applier_.Process(signal);
 
     // Dump data for debug.
     const auto channel_view = signal.channel(0);
@@ -83,7 +86,7 @@
   for (size_t k = 0; k < signal.num_channels(); ++k) {
     rtc::ArrayView<float> channel_view = signal.channel(k);
     for (auto& sample : channel_view) {
-      sample = rtc::SafeClamp(sample, kMinSampleValue, kMaxSampleValue);
+      sample = rtc::SafeClamp(sample, kMinFloatS16Value, kMaxFloatS16Value);
     }
   }
 }
diff --git a/modules/audio_processing/agc2/fixed_gain_controller.h b/modules/audio_processing/agc2/fixed_gain_controller.h
index decc22e..fd80348 100644
--- a/modules/audio_processing/agc2/fixed_gain_controller.h
+++ b/modules/audio_processing/agc2/fixed_gain_controller.h
@@ -11,6 +11,7 @@
 #ifndef MODULES_AUDIO_PROCESSING_AGC2_FIXED_GAIN_CONTROLLER_H_
 #define MODULES_AUDIO_PROCESSING_AGC2_FIXED_GAIN_CONTROLLER_H_
 
+#include "modules/audio_processing/agc2/gain_curve_applier.h"
 #include "modules/audio_processing/include/audio_frame_view.h"
 
 namespace webrtc {
@@ -22,13 +23,16 @@
 
   void Process(AudioFrameView<float> signal);
 
+  // Rate and gain 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);
   void EnableLimiter(bool enable_limiter);
 
  private:
   float gain_to_apply_ = 1.f;
-  ApmDataDumper* apm_data_dumper_;
+  ApmDataDumper* apm_data_dumper_ = nullptr;
+  GainCurveApplier gain_curve_applier_;
   bool enable_limiter_ = true;
 };
 
diff --git a/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc b/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc
index 9336fd7..1d6c2ae 100644
--- a/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc
+++ b/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc
@@ -11,9 +11,11 @@
 #include "modules/audio_processing/agc2/fixed_gain_controller.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 "rtc_base/ptr_util.h"
 
 namespace webrtc {
 namespace {
@@ -45,13 +47,15 @@
 }
 ApmDataDumper test_data_dumper(0);
 
-FixedGainController CreateFixedGainController(float gain_to_apply,
-                                              size_t rate,
-                                              bool enable_limiter) {
-  FixedGainController fgc(&test_data_dumper);
-  fgc.SetGain(gain_to_apply);
-  fgc.SetSampleRate(gain_to_apply);
-  fgc.EnableLimiter(enable_limiter);
+std::unique_ptr<FixedGainController> CreateFixedGainController(
+    float gain_to_apply,
+    size_t rate,
+    bool enable_limiter) {
+  std::unique_ptr<FixedGainController> fgc =
+      rtc::MakeUnique<FixedGainController>(&test_data_dumper);
+  fgc->SetGain(gain_to_apply);
+  fgc->SetSampleRate(rate);
+  fgc->EnableLimiter(enable_limiter);
   return fgc;
 }
 
@@ -59,51 +63,128 @@
 
 TEST(AutomaticGainController2FixedDigital, CreateUseWithoutLimiter) {
   const int kSampleRate = 48000;
-  FixedGainController fixed_gc =
+  std::unique_ptr<FixedGainController> fixed_gc =
       CreateFixedGainController(kGainToApplyDb, kSampleRate, false);
   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);
+  fixed_gc->Process(float_frame);
   const auto channel = float_frame.channel(0);
   EXPECT_LT(kInputLevelLinear, channel[0]);
 }
 
 TEST(AutomaticGainController2FixedDigital, CreateUseWithLimiter) {
   const int kSampleRate = 44000;
-  FixedGainController fixed_gc =
+  std::unique_ptr<FixedGainController> fixed_gc =
       CreateFixedGainController(kGainToApplyDb, kSampleRate, true);
   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);
+  fixed_gc->Process(float_frame);
   const auto channel = float_frame.channel(0);
   EXPECT_LT(kInputLevelLinear, channel[0]);
 }
 
-TEST(AutomaticGainController2FixedDigital, GainShouldChangeOnSetGain) {
-  constexpr float input_level = 1000.f;
-  constexpr size_t num_frames = 5;
-  constexpr size_t kSampleRate = 8000;
-  constexpr float gain_db_no_change = 0.f;
-  constexpr float gain_db_factor_10 = 20.f;
+TEST(AutomaticGainController2FixedDigital, CheckSaturationBehaviorWithLimiter) {
+  const float kInputLevel = 32767.f;
+  const size_t kNumFrames = 5;
+  const size_t kSampleRate = 42000;
 
-  FixedGainController fixed_gc_no_saturation =
-      CreateFixedGainController(gain_db_no_change, kSampleRate, false);
+  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, true);
+
+    // 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, true);
+
+    // 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;
+
+  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, true);
+
+    // 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, true);
+
+    // 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;
+
+  std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
+      CreateFixedGainController(kGainDbNoChange, kSampleRate, false);
 
   // Signal level is unchanged with 0 db gain.
   EXPECT_FLOAT_EQ(
       RunFixedGainControllerWithConstantInput(
-          &fixed_gc_no_saturation, input_level, num_frames, kSampleRate),
-      input_level);
+          fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
+      kInputLevel);
 
-  fixed_gc_no_saturation.SetGain(gain_db_factor_10);
+  fixed_gc_no_saturation->SetGain(kGainDbFactor10);
 
   // +20db should increase signal by a factor of 10.
   EXPECT_FLOAT_EQ(
       RunFixedGainControllerWithConstantInput(
-          &fixed_gc_no_saturation, input_level, num_frames, kSampleRate),
-      input_level * 10);
+          fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
+      kInputLevel * 10);
 }
 
 }  // namespace webrtc
diff --git a/modules/audio_processing/agc2/gain_curve_applier.cc b/modules/audio_processing/agc2/gain_curve_applier.cc
new file mode 100644
index 0000000..1610c4a
--- /dev/null
+++ b/modules/audio_processing/agc2/gain_curve_applier.cc
@@ -0,0 +1,131 @@
+/*
+ *  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/gain_curve_applier.h"
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+
+#include "api/array_view.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+namespace {
+
+// This constant affects the way scaling factors are interpolated for the first
+// sub-frame of a frame. Only in the case in which the first sub-frame has an
+// estimated level which is greater than the that of the previous analyzed
+// sub-frame, linear interpolation is replaced with a power function which
+// reduces the chances of over-shooting (and hence saturation), however reducing
+// the fixed gain effectiveness.
+constexpr float kAttackFirstSubframeInterpolationPower = 8.f;
+
+void InterpolateFirstSubframe(float last_factor,
+                              float current_factor,
+                              rtc::ArrayView<float> subframe) {
+  const auto n = subframe.size();
+  constexpr auto p = kAttackFirstSubframeInterpolationPower;
+  for (size_t i = 0; i < n; ++i) {
+    subframe[i] = std::pow(1.f - i / n, p) * (last_factor - current_factor) +
+                  current_factor;
+  }
+}
+
+void ComputePerSampleSubframeFactors(
+    const std::array<float, kSubFramesInFrame + 1>& scaling_factors,
+    size_t samples_per_channel,
+    rtc::ArrayView<float> per_sample_scaling_factors) {
+  const size_t num_subframes = scaling_factors.size() - 1;
+  const size_t subframe_size =
+      rtc::CheckedDivExact(samples_per_channel, num_subframes);
+
+  // Handle first sub-frame differently in case of attack.
+  const bool is_attack = scaling_factors[0] > scaling_factors[1];
+  if (is_attack) {
+    InterpolateFirstSubframe(
+        scaling_factors[0], scaling_factors[1],
+        rtc::ArrayView<float>(
+            per_sample_scaling_factors.subview(0, subframe_size)));
+  }
+
+  for (size_t i = is_attack ? 1 : 0; i < num_subframes; ++i) {
+    const size_t subframe_start = i * subframe_size;
+    const float scaling_start = scaling_factors[i];
+    const float scaling_end = scaling_factors[i + 1];
+    const float scaling_diff = (scaling_end - scaling_start) / subframe_size;
+    for (size_t j = 0; j < subframe_size; ++j) {
+      per_sample_scaling_factors[subframe_start + j] =
+          scaling_start + scaling_diff * j;
+    }
+  }
+}
+
+void ScaleSamples(rtc::ArrayView<const float> per_sample_scaling_factors,
+                  AudioFrameView<float> signal) {
+  const size_t samples_per_channel = signal.samples_per_channel();
+  RTC_DCHECK_EQ(samples_per_channel, per_sample_scaling_factors.size());
+  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];
+    }
+  }
+}
+
+}  // namespace
+
+GainCurveApplier::GainCurveApplier(size_t sample_rate_hz,
+                                   ApmDataDumper* apm_data_dumper)
+    : interp_gain_curve_(apm_data_dumper),
+      level_estimator_(sample_rate_hz, apm_data_dumper),
+      apm_data_dumper_(apm_data_dumper) {}
+
+GainCurveApplier::~GainCurveApplier() = default;
+
+void GainCurveApplier::Process(AudioFrameView<float> signal) {
+  const auto level_estimate = level_estimator_.ComputeLevel(signal);
+
+  RTC_DCHECK_EQ(level_estimate.size() + 1, scaling_factors_.size());
+  scaling_factors_[0] = last_scaling_factor_;
+  std::transform(level_estimate.begin(), level_estimate.end(),
+                 scaling_factors_.begin() + 1, [this](float x) {
+                   return interp_gain_curve_.LookUpGainToApply(x);
+                 });
+
+  const size_t samples_per_channel = signal.samples_per_channel();
+  RTC_DCHECK_LE(samples_per_channel, kMaximalNumberOfSamplesPerChannel);
+
+  auto per_sample_scaling_factors = rtc::ArrayView<float>(
+      &per_sample_scaling_factors_[0], samples_per_channel);
+  ComputePerSampleSubframeFactors(scaling_factors_, samples_per_channel,
+                                  per_sample_scaling_factors);
+  ScaleSamples(per_sample_scaling_factors, signal);
+
+  last_scaling_factor_ = scaling_factors_.back();
+
+  // Dump data for debug.
+  apm_data_dumper_->DumpRaw("agc2_gain_curve_applier_scaling_factors",
+                            samples_per_channel,
+                            per_sample_scaling_factors_.data());
+}
+
+InterpolatedGainCurve::Stats GainCurveApplier::GetGainCurveStats() const {
+  return interp_gain_curve_.get_stats();
+}
+
+void GainCurveApplier::SetSampleRate(size_t 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);
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/gain_curve_applier.h b/modules/audio_processing/agc2/gain_curve_applier.h
new file mode 100644
index 0000000..86ca251
--- /dev/null
+++ b/modules/audio_processing/agc2/gain_curve_applier.h
@@ -0,0 +1,56 @@
+/*
+ *  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_GAIN_CURVE_APPLIER_H_
+#define MODULES_AUDIO_PROCESSING_AGC2_GAIN_CURVE_APPLIER_H_
+
+#include <vector>
+
+#include "modules/audio_processing/agc2/fixed_digital_level_estimator.h"
+#include "modules/audio_processing/agc2/interpolated_gain_curve.h"
+#include "modules/audio_processing/include/audio_frame_view.h"
+#include "rtc_base/constructormagic.h"
+
+namespace webrtc {
+class ApmDataDumper;
+
+class GainCurveApplier {
+ public:
+  GainCurveApplier(size_t sample_rate_hz, ApmDataDumper* apm_data_dumper);
+
+  ~GainCurveApplier();
+
+  void Process(AudioFrameView<float> signal);
+  InterpolatedGainCurve::Stats GetGainCurveStats() const;
+
+  // Supported rates must be
+  // * supported by FixedDigitalLevelEstimator
+  // * below kMaximalNumberOfSamplesPerChannel*1000/kFrameDurationMs
+  //   so that samples_per_channel fit in the
+  //   per_sample_scaling_factors_ array.
+  void SetSampleRate(size_t sample_rate_hz);
+
+ private:
+  const InterpolatedGainCurve interp_gain_curve_;
+  FixedDigitalLevelEstimator level_estimator_;
+  ApmDataDumper* const apm_data_dumper_ = nullptr;
+
+  // Work array containing the sub-frame scaling factors to be interpolated.
+  std::array<float, kSubFramesInFrame + 1> scaling_factors_ = {};
+  std::array<float, kMaximalNumberOfSamplesPerChannel>
+      per_sample_scaling_factors_ = {};
+  float last_scaling_factor_ = 1.f;
+
+  RTC_DISALLOW_COPY_AND_ASSIGN(GainCurveApplier);
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AGC2_GAIN_CURVE_APPLIER_H_
diff --git a/modules/audio_processing/agc2/gain_curve_applier_unittest.cc b/modules/audio_processing/agc2/gain_curve_applier_unittest.cc
new file mode 100644
index 0000000..a7cb1b6
--- /dev/null
+++ b/modules/audio_processing/agc2/gain_curve_applier_unittest.cc
@@ -0,0 +1,59 @@
+/*
+ *  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/gain_curve_applier.h"
+
+#include "common_audio/include/audio_util.h"
+#include "modules/audio_processing/agc2/agc2_common.h"
+#include "modules/audio_processing/agc2/agc2_testing_common.h"
+#include "modules/audio_processing/agc2/vector_float_frame.h"
+#include "rtc_base/gunit.h"
+
+namespace webrtc {
+
+TEST(GainCurveApplier, GainCurveApplierShouldConstructAndRun) {
+  const int sample_rate_hz = 48000;
+  ApmDataDumper apm_data_dumper(0);
+
+  GainCurveApplier gain_curve_applier(sample_rate_hz, &apm_data_dumper);
+
+  VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100,
+                                            kMaxAbsFloatS16Value);
+  gain_curve_applier.Process(vectors_with_float_frame.float_frame_view());
+}
+
+TEST(GainCurveApplier, OutputVolumeAboveThreshold) {
+  const int sample_rate_hz = 48000;
+  const float input_level =
+      (kMaxAbsFloatS16Value + DbfsToFloatS16(test::kLimiterMaxInputLevelDbFs)) /
+      2.f;
+  ApmDataDumper apm_data_dumper(0);
+
+  GainCurveApplier gain_curve_applier(sample_rate_hz, &apm_data_dumper);
+
+  // Give the level estimator time to adapt.
+  for (int i = 0; i < 5; ++i) {
+    VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100,
+                                              input_level);
+    gain_curve_applier.Process(vectors_with_float_frame.float_frame_view());
+  }
+
+  VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100,
+                                            input_level);
+  gain_curve_applier.Process(vectors_with_float_frame.float_frame_view());
+  rtc::ArrayView<const float> channel =
+      vectors_with_float_frame.float_frame_view().channel(0);
+
+  for (const auto& sample : channel) {
+    EXPECT_LT(0.9f * kMaxAbsFloatS16Value, sample);
+  }
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/interpolated_gain_curve.cc b/modules/audio_processing/agc2/interpolated_gain_curve.cc
new file mode 100644
index 0000000..0cc8f90
--- /dev/null
+++ b/modules/audio_processing/agc2/interpolated_gain_curve.cc
@@ -0,0 +1,105 @@
+/*
+ *  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/interpolated_gain_curve.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/logging.h"
+
+namespace webrtc {
+
+constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
+    InterpolatedGainCurve::approximation_params_x_;
+
+constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
+    InterpolatedGainCurve::approximation_params_m_;
+
+constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
+    InterpolatedGainCurve::approximation_params_q_;
+
+InterpolatedGainCurve::InterpolatedGainCurve(ApmDataDumper* apm_data_dumper)
+    : apm_data_dumper_(apm_data_dumper) {}
+
+InterpolatedGainCurve::~InterpolatedGainCurve() {
+  if (stats_.available) {
+    // TODO(alessiob): We might want to add these stats as RTC metrics.
+    RTC_DCHECK(apm_data_dumper_);
+    apm_data_dumper_->DumpRaw("agc2_interp_gain_curve_lookups_identity",
+                              stats_.look_ups_identity_region);
+    apm_data_dumper_->DumpRaw("agc2_interp_gain_curve_lookups_knee",
+                              stats_.look_ups_knee_region);
+    apm_data_dumper_->DumpRaw("agc2_interp_gain_curve_lookups_limiter",
+                              stats_.look_ups_limiter_region);
+    apm_data_dumper_->DumpRaw("agc2_interp_gain_curve_lookups_saturation",
+                              stats_.look_ups_saturation_region);
+  }
+}
+
+void InterpolatedGainCurve::UpdateStats(float input_level) const {
+  stats_.available = true;
+
+  if (input_level < approximation_params_x_[0]) {
+    stats_.look_ups_identity_region++;
+  } else if (input_level <
+             approximation_params_x_[kInterpolatedGainCurveKneePoints - 1]) {
+    stats_.look_ups_knee_region++;
+  } else if (input_level < kMaxInputLevelLinear) {
+    stats_.look_ups_limiter_region++;
+  } else {
+    stats_.look_ups_saturation_region++;
+  }
+}
+
+// Looks up a gain to apply given a non-negative input level.
+// The cost of this operation depends on the region in which |input_level|
+// falls.
+// For the identity and the saturation regions the cost is O(1).
+// For the other regions, namely knee and limiter, the cost is
+// O(2 + log2(|LightkInterpolatedGainCurveTotalPoints|), plus O(1) for the
+// linear interpolation (one product and one sum).
+float InterpolatedGainCurve::LookUpGainToApply(float input_level) const {
+  UpdateStats(input_level);
+
+  if (input_level <= approximation_params_x_[0]) {
+    // Identity region.
+    return 1.0f;
+  }
+
+  if (input_level >= kMaxInputLevelLinear) {
+    // Saturating lower bound. The saturing samples exactly hit the clipping
+    // level. This method achieves has the lowest harmonic distorsion, but it
+    // may reduce the amplitude of the non-saturating samples too much.
+    return 32768.f / input_level;
+  }
+
+  // Knee and limiter regions; find the linear piece index. Spelling
+  // out the complete type was the only way to silence both the clang
+  // plugin and the windows compilers.
+  std::array<float, kInterpolatedGainCurveTotalPoints>::const_iterator it =
+      std::lower_bound(approximation_params_x_.begin(),
+                       approximation_params_x_.end(), input_level);
+  const size_t index = std::distance(approximation_params_x_.begin(), it) - 1;
+  RTC_DCHECK_LE(0, index);
+  RTC_DCHECK_LT(index, approximation_params_m_.size());
+  RTC_DCHECK_LE(approximation_params_x_[index], input_level);
+  if (index < approximation_params_m_.size() - 1) {
+    RTC_DCHECK_LE(input_level, approximation_params_x_[index + 1]);
+  }
+
+  // Piece-wise linear interploation.
+  const float gain = approximation_params_m_[index] * input_level +
+                     approximation_params_q_[index];
+  RTC_DCHECK_LE(0.f, gain);
+  return gain;
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/interpolated_gain_curve.h b/modules/audio_processing/agc2/interpolated_gain_curve.h
new file mode 100644
index 0000000..de79c14
--- /dev/null
+++ b/modules/audio_processing/agc2/interpolated_gain_curve.h
@@ -0,0 +1,122 @@
+/*
+ *  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_INTERPOLATED_GAIN_CURVE_H_
+#define MODULES_AUDIO_PROCESSING_AGC2_INTERPOLATED_GAIN_CURVE_H_
+
+#include <array>
+
+#include "modules/audio_processing/agc2/agc2_common.h"
+#include "modules/audio_processing/logging/apm_data_dumper.h"
+#include "rtc_base/basictypes.h"
+#include "rtc_base/gtest_prod_util.h"
+
+namespace webrtc {
+
+class ApmDataDumper;
+
+constexpr float kInputLevelScalingFactor = 32768.0f;
+
+// Defined as DbfsToLinear(kLimiterMaxInputLevelDbFs)
+constexpr float kMaxInputLevelLinear = static_cast<float>(36766.300710566735);
+
+// Interpolated gain curve using under-approximation to avoid saturation.
+//
+// The goal of this class is allowing fast look ups to get an accurate
+// estimates of the gain to apply given an estimated input level.
+class InterpolatedGainCurve {
+ public:
+  struct Stats {
+    // Region in which the output level equals the input one.
+    size_t look_ups_identity_region = 0;
+    // Smoothing between the identity and the limiter regions.
+    size_t look_ups_knee_region = 0;
+    // Limiter region in which the output and input levels are linearly related.
+    size_t look_ups_limiter_region = 0;
+    // Region in which saturation may occur since the input level is beyond the
+    // maximum expected by the limiter.
+    size_t look_ups_saturation_region = 0;
+    // True if stats have been populated.
+    bool available = false;
+  };
+
+  // InterpolatedGainCurve(InterpolatedGainCurve&&);
+  explicit InterpolatedGainCurve(ApmDataDumper* apm_data_dumper);
+  ~InterpolatedGainCurve();
+
+  Stats get_stats() const { return stats_; }
+
+  // Given a non-negative input level (linear scale), a scalar factor to apply
+  // to a sub-frame is returned.
+  // Levels above kLimiterMaxInputLevelDbFs will be reduced to 0 dBFS
+  // after applying this gain
+  float LookUpGainToApply(float input_level) const;
+
+ private:
+  // For comparing 'approximation_params_*_' with ones computed by
+  // ComputeInterpolatedGainCurve.
+  FRIEND_TEST_ALL_PREFIXES(AutomaticGainController2InterpolatedGainCurve,
+                           CheckApproximationParams);
+  void UpdateStats(float input_level) const;
+
+  ApmDataDumper* const apm_data_dumper_;
+
+  static constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
+      approximation_params_x_ = {
+          {30057.296875,    30148.986328125, 30240.67578125,  30424.052734375,
+           30607.4296875,   30790.806640625, 30974.18359375,  31157.560546875,
+           31340.939453125, 31524.31640625,  31707.693359375, 31891.0703125,
+           32074.447265625, 32257.82421875,  32441.201171875, 32624.580078125,
+           32807.95703125,  32991.33203125,  33174.7109375,   33358.08984375,
+           33541.46484375,  33724.84375,     33819.53515625,  34009.5390625,
+           34200.05859375,  34389.81640625,  34674.48828125,  35054.375,
+           35434.86328125,  35814.81640625,  36195.16796875,  36575.03125}};
+  static constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
+      approximation_params_m_ = {
+          {-3.515235675877192989e-07, -1.050251626111275982e-06,
+           -2.085213736791047268e-06, -3.443004743530764244e-06,
+           -4.773849468620028347e-06, -6.077375928725814447e-06,
+           -7.353257842623861507e-06, -8.601219633419532329e-06,
+           -9.821013009059242904e-06, -1.101243378798244521e-05,
+           -1.217532644659513608e-05, -1.330956911260727793e-05,
+           -1.441507538402220234e-05, -1.549179251014720649e-05,
+           -1.653970684856176376e-05, -1.755882840370759368e-05,
+           -1.854918446042574942e-05, -1.951086778717581183e-05,
+           -2.044398024736437947e-05, -2.1348627342376858e-05,
+           -2.222496914328075945e-05, -2.265374678245279938e-05,
+           -2.242570917587727308e-05, -2.220122041762806475e-05,
+           -2.19802095671184361e-05,  -2.176260204578284174e-05,
+           -2.133731686626560986e-05, -2.092481918225530535e-05,
+           -2.052459603874012828e-05, -2.013615448959171772e-05,
+           -1.975903069251216948e-05, -1.939277899509761482e-05}};
+
+  static constexpr std::array<float, kInterpolatedGainCurveTotalPoints>
+      approximation_params_q_ = {
+          {1.010565876960754395, 1.031631827354431152, 1.062929749488830566,
+           1.104239225387573242, 1.144973039627075195, 1.185109615325927734,
+           1.224629044532775879, 1.263512492179870605, 1.301741957664489746,
+           1.339300632476806641, 1.376173257827758789, 1.412345528602600098,
+           1.447803974151611328, 1.482536554336547852, 1.516532182693481445,
+           1.549780607223510742, 1.582272171974182129, 1.613999366760253906,
+           1.644955039024353027, 1.675132393836975098, 1.704526185989379883,
+           1.718986630439758301, 1.711274504661560059, 1.703639745712280273,
+           1.696081161499023438, 1.688597679138183594, 1.673851132392883301,
+           1.659391283988952637, 1.645209431648254395, 1.631297469139099121,
+           1.617647409439086914, 1.604251742362976074}};
+
+  // Stats.
+  mutable Stats stats_;
+
+  // RTC_DISALLOW_COPY_AND_ASSIGN(InterpolatedGainCurve);
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AGC2_INTERPOLATED_GAIN_CURVE_H_
diff --git a/modules/audio_processing/agc2/interpolated_gain_curve_unittest.cc b/modules/audio_processing/agc2/interpolated_gain_curve_unittest.cc
new file mode 100644
index 0000000..d75ddbd
--- /dev/null
+++ b/modules/audio_processing/agc2/interpolated_gain_curve_unittest.cc
@@ -0,0 +1,202 @@
+/*
+ *  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 <array>
+#include <vector>
+
+#include "api/array_view.h"
+#include "common_audio/include/audio_util.h"
+#include "modules/audio_processing/agc2/agc2_common.h"
+#include "modules/audio_processing/agc2/compute_interpolated_gain_curve.h"
+#include "modules/audio_processing/agc2/interpolated_gain_curve.h"
+#include "modules/audio_processing/agc2/limiter.h"
+#include "modules/audio_processing/logging/apm_data_dumper.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/gunit.h"
+
+namespace webrtc {
+namespace {
+
+constexpr double kLevelEpsilon = 1e-2 * kMaxAbsFloatS16Value;
+constexpr float kInterpolatedGainCurveTolerance = 1.f / 32768.f;
+ApmDataDumper apm_data_dumper(0);
+const Limiter limiter;
+
+}  // namespace
+
+TEST(AutomaticGainController2InterpolatedGainCurve, CreateUse) {
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const auto levels = test::LinSpace(
+      kLevelEpsilon, DbfsToFloatS16(limiter.max_input_level_db() + 1), 500);
+  for (const auto level : levels) {
+    EXPECT_GE(igc.LookUpGainToApply(level), 0.0f);
+  }
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve, CheckValidOutput) {
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const auto levels = test::LinSpace(
+      kLevelEpsilon, limiter.max_input_level_linear() * 2.0, 500);
+  for (const auto level : levels) {
+    SCOPED_TRACE(std::to_string(level));
+    const float gain = igc.LookUpGainToApply(level);
+    EXPECT_LE(0.0f, gain);
+    EXPECT_LE(gain, 1.0f);
+  }
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve, CheckMonotonicity) {
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const auto levels = test::LinSpace(
+      kLevelEpsilon, limiter.max_input_level_linear() + kLevelEpsilon + 0.5,
+      500);
+  float prev_gain = igc.LookUpGainToApply(0.0f);
+  for (const auto level : levels) {
+    const float gain = igc.LookUpGainToApply(level);
+    EXPECT_GE(prev_gain, gain);
+    prev_gain = gain;
+  }
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve, CheckApproximation) {
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const auto levels = test::LinSpace(
+      kLevelEpsilon, limiter.max_input_level_linear() - kLevelEpsilon, 500);
+  for (const auto level : levels) {
+    SCOPED_TRACE(std::to_string(level));
+    EXPECT_LT(
+        std::fabs(limiter.GetGainLinear(level) - igc.LookUpGainToApply(level)),
+        kInterpolatedGainCurveTolerance);
+  }
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve, CheckRegionBoundaries) {
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const std::vector<double> levels{
+      {kLevelEpsilon, limiter.knee_start_linear() + kLevelEpsilon,
+       limiter.limiter_start_linear() + kLevelEpsilon,
+       limiter.max_input_level_linear() + kLevelEpsilon}};
+  for (const auto level : levels) {
+    igc.LookUpGainToApply(level);
+  }
+
+  const auto stats = igc.get_stats();
+  EXPECT_EQ(1ul, stats.look_ups_identity_region);
+  EXPECT_EQ(1ul, stats.look_ups_knee_region);
+  EXPECT_EQ(1ul, stats.look_ups_limiter_region);
+  EXPECT_EQ(1ul, stats.look_ups_saturation_region);
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve, CheckIdentityRegion) {
+  constexpr size_t kNumSteps = 10;
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const auto levels =
+      test::LinSpace(kLevelEpsilon, limiter.knee_start_linear(), kNumSteps);
+  for (const auto level : levels) {
+    SCOPED_TRACE(std::to_string(level));
+    EXPECT_EQ(1.0f, igc.LookUpGainToApply(level));
+  }
+
+  const auto stats = igc.get_stats();
+  EXPECT_EQ(kNumSteps - 1, stats.look_ups_identity_region);
+  EXPECT_EQ(1ul, stats.look_ups_knee_region);
+  EXPECT_EQ(0ul, stats.look_ups_limiter_region);
+  EXPECT_EQ(0ul, stats.look_ups_saturation_region);
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve,
+     CheckNoOverApproximationKnee) {
+  constexpr size_t kNumSteps = 10;
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const auto levels =
+      test::LinSpace(limiter.knee_start_linear() + kLevelEpsilon,
+                     limiter.limiter_start_linear(), kNumSteps);
+  for (const auto level : levels) {
+    SCOPED_TRACE(std::to_string(level));
+    // Small tolerance added (needed because comparing a float with a double).
+    EXPECT_LE(igc.LookUpGainToApply(level),
+              limiter.GetGainLinear(level) + 1e-7);
+  }
+
+  const auto stats = igc.get_stats();
+  EXPECT_EQ(0ul, stats.look_ups_identity_region);
+  EXPECT_EQ(kNumSteps - 1, stats.look_ups_knee_region);
+  EXPECT_EQ(1ul, stats.look_ups_limiter_region);
+  EXPECT_EQ(0ul, stats.look_ups_saturation_region);
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve,
+     CheckNoOverApproximationBeyondKnee) {
+  constexpr size_t kNumSteps = 10;
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const auto levels = test::LinSpace(
+      limiter.limiter_start_linear() + kLevelEpsilon,
+      limiter.max_input_level_linear() - kLevelEpsilon, kNumSteps);
+  for (const auto level : levels) {
+    SCOPED_TRACE(std::to_string(level));
+    // Small tolerance added (needed because comparing a float with a double).
+    EXPECT_LE(igc.LookUpGainToApply(level),
+              limiter.GetGainLinear(level) + 1e-7);
+  }
+
+  const auto stats = igc.get_stats();
+  EXPECT_EQ(0ul, stats.look_ups_identity_region);
+  EXPECT_EQ(0ul, stats.look_ups_knee_region);
+  EXPECT_EQ(kNumSteps, stats.look_ups_limiter_region);
+  EXPECT_EQ(0ul, stats.look_ups_saturation_region);
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve,
+     CheckNoOverApproximationWithSaturation) {
+  constexpr size_t kNumSteps = 3;
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  const auto levels = test::LinSpace(
+      limiter.max_input_level_linear() + kLevelEpsilon,
+      limiter.max_input_level_linear() + kLevelEpsilon + 0.5, kNumSteps);
+  for (const auto level : levels) {
+    SCOPED_TRACE(std::to_string(level));
+    EXPECT_LE(igc.LookUpGainToApply(level), limiter.GetGainLinear(level));
+  }
+
+  const auto stats = igc.get_stats();
+  EXPECT_EQ(0ul, stats.look_ups_identity_region);
+  EXPECT_EQ(0ul, stats.look_ups_knee_region);
+  EXPECT_EQ(0ul, stats.look_ups_limiter_region);
+  EXPECT_EQ(kNumSteps, stats.look_ups_saturation_region);
+}
+
+TEST(AutomaticGainController2InterpolatedGainCurve, CheckApproximationParams) {
+  test::InterpolatedParameters parameters =
+      test::ComputeInterpolatedGainCurveApproximationParams();
+
+  InterpolatedGainCurve igc(&apm_data_dumper);
+
+  for (size_t i = 0; i < kInterpolatedGainCurveTotalPoints; ++i) {
+    // The tolerance levels are chosen to account for deviations due
+    // to computing with single precision floating point numbers.
+    EXPECT_NEAR(igc.approximation_params_x_[i],
+                parameters.computed_approximation_params_x[i], 0.9f);
+    EXPECT_NEAR(igc.approximation_params_m_[i],
+                parameters.computed_approximation_params_m[i], 0.00001f);
+    EXPECT_NEAR(igc.approximation_params_q_[i],
+                parameters.computed_approximation_params_q[i], 0.001f);
+  }
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/limiter.cc b/modules/audio_processing/agc2/limiter.cc
new file mode 100644
index 0000000..d2b9877
--- /dev/null
+++ b/modules/audio_processing/agc2/limiter.cc
@@ -0,0 +1,137 @@
+/*
+ *  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/limiter.h"
+
+#include <cmath>
+
+#include "common_audio/include/audio_util.h"
+#include "modules/audio_processing/agc2/agc2_common.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+namespace {
+
+double ComputeKneeStart(double max_input_level_db,
+                        double knee_smoothness_db,
+                        double compression_ratio) {
+  RTC_CHECK_LT((compression_ratio - 1.0) * knee_smoothness_db /
+                   (2.0 * compression_ratio),
+               max_input_level_db);
+  return -knee_smoothness_db / 2.0 -
+         max_input_level_db / (compression_ratio - 1.0);
+}
+
+std::array<double, 3> ComputeKneeRegionPolynomial(double knee_start_dbfs,
+                                                  double knee_smoothness_db,
+                                                  double compression_ratio) {
+  const double a = (1.0 - compression_ratio) /
+                   (2.0 * knee_smoothness_db * compression_ratio);
+  const double b = 1.0 - 2.0 * a * knee_start_dbfs;
+  const double c = a * knee_start_dbfs * knee_start_dbfs;
+  return {{a, b, c}};
+}
+
+double ComputeLimiterD1(double max_input_level_db, double compression_ratio) {
+  return (std::pow(10.0, -max_input_level_db / (20.0 * compression_ratio)) *
+          (1.0 - compression_ratio) / compression_ratio) /
+         kMaxAbsFloatS16Value;
+}
+
+constexpr double ComputeLimiterD2(double compression_ratio) {
+  return (1.0 - 2.0 * compression_ratio) / compression_ratio;
+}
+
+double ComputeLimiterI2(double max_input_level_db,
+                        double compression_ratio,
+                        double gain_curve_limiter_i1) {
+  RTC_CHECK_NE(gain_curve_limiter_i1, 0.f);
+  return std::pow(10.0, -max_input_level_db / (20.0 * compression_ratio)) /
+         gain_curve_limiter_i1 /
+         std::pow(kMaxAbsFloatS16Value, gain_curve_limiter_i1 - 1);
+}
+
+}  // namespace
+
+Limiter::Limiter()
+    : max_input_level_linear_(DbfsToFloatS16(max_input_level_db_)),
+      knee_start_dbfs_(ComputeKneeStart(max_input_level_db_,
+                                        knee_smoothness_db_,
+                                        compression_ratio_)),
+      knee_start_linear_(DbfsToFloatS16(knee_start_dbfs_)),
+      limiter_start_dbfs_(knee_start_dbfs_ + knee_smoothness_db_),
+      limiter_start_linear_(DbfsToFloatS16(limiter_start_dbfs_)),
+      knee_region_polynomial_(ComputeKneeRegionPolynomial(knee_start_dbfs_,
+                                                          knee_smoothness_db_,
+                                                          compression_ratio_)),
+      gain_curve_limiter_d1_(
+          ComputeLimiterD1(max_input_level_db_, compression_ratio_)),
+      gain_curve_limiter_d2_(ComputeLimiterD2(compression_ratio_)),
+      gain_curve_limiter_i1_(1.0 / compression_ratio_),
+      gain_curve_limiter_i2_(ComputeLimiterI2(max_input_level_db_,
+                                              compression_ratio_,
+                                              gain_curve_limiter_i1_)) {
+  static_assert(knee_smoothness_db_ > 0.0f, "");
+  static_assert(compression_ratio_ > 1.0f, "");
+  RTC_CHECK_GE(max_input_level_db_, knee_start_dbfs_ + knee_smoothness_db_);
+}
+
+constexpr double Limiter::max_input_level_db_;
+constexpr double Limiter::knee_smoothness_db_;
+constexpr double Limiter::compression_ratio_;
+
+double Limiter::GetOutputLevelDbfs(double input_level_dbfs) const {
+  if (input_level_dbfs < knee_start_dbfs_) {
+    return input_level_dbfs;
+  } else if (input_level_dbfs < limiter_start_dbfs_) {
+    return GetKneeRegionOutputLevelDbfs(input_level_dbfs);
+  }
+  return GetCompressorRegionOutputLevelDbfs(input_level_dbfs);
+}
+
+double Limiter::GetGainLinear(double input_level_linear) const {
+  if (input_level_linear < knee_start_linear_) {
+    return 1.0;
+  }
+  return DbfsToFloatS16(
+             GetOutputLevelDbfs(FloatS16ToDbfs(input_level_linear))) /
+         input_level_linear;
+}
+
+// Computes the first derivative of GetGainLinear() in |x|.
+double Limiter::GetGainFirstDerivativeLinear(double x) const {
+  // Beyond-knee region only.
+  RTC_CHECK_GE(x, limiter_start_linear_ - 1e-7 * kMaxAbsFloatS16Value);
+  return gain_curve_limiter_d1_ *
+         std::pow(x / kMaxAbsFloatS16Value, gain_curve_limiter_d2_);
+}
+
+// Computes the integral of GetGainLinear() in the range [x0, x1].
+double Limiter::GetGainIntegralLinear(double x0, double x1) const {
+  RTC_CHECK_LE(x0, x1);                     // Valid interval.
+  RTC_CHECK_GE(x0, limiter_start_linear_);  // Beyond-knee region only.
+  auto limiter_integral = [this](const double& x) {
+    return gain_curve_limiter_i2_ * std::pow(x, gain_curve_limiter_i1_);
+  };
+  return limiter_integral(x1) - limiter_integral(x0);
+}
+
+double Limiter::GetKneeRegionOutputLevelDbfs(double input_level_dbfs) const {
+  return knee_region_polynomial_[0] * input_level_dbfs * input_level_dbfs +
+         knee_region_polynomial_[1] * input_level_dbfs +
+         knee_region_polynomial_[2];
+}
+
+double Limiter::GetCompressorRegionOutputLevelDbfs(
+    double input_level_dbfs) const {
+  return (input_level_dbfs - max_input_level_db_) / compression_ratio_;
+}
+
+}  // namespace webrtc
diff --git a/modules/audio_processing/agc2/limiter.h b/modules/audio_processing/agc2/limiter.h
new file mode 100644
index 0000000..f350bae
--- /dev/null
+++ b/modules/audio_processing/agc2/limiter.h
@@ -0,0 +1,74 @@
+/*
+ *  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_LIMITER_H_
+#define MODULES_AUDIO_PROCESSING_AGC2_LIMITER_H_
+
+#include <array>
+
+#include "modules/audio_processing/agc2/agc2_testing_common.h"
+
+namespace webrtc {
+
+// A class for computing gain curve parameters. The gain curve is
+// defined by constants kLimiterMaxInputLevelDbFs, kLimiterKneeSmoothnessDb,
+// kLimiterCompressionRatio. The curve consints of one linear part,
+// one quadratic polynomial part and another linear part. The
+// constants define the parameters of the parts.
+class Limiter {
+ public:
+  Limiter();
+
+  double max_input_level_db() const { return max_input_level_db_; }
+  double max_input_level_linear() const { return max_input_level_linear_; }
+  double knee_start_linear() const { return knee_start_linear_; }
+  double limiter_start_linear() const { return limiter_start_linear_; }
+
+  // These methods can be marked 'constexpr' in C++ 14.
+  double GetOutputLevelDbfs(double input_level_dbfs) const;
+  double GetGainLinear(double input_level_linear) const;
+  double GetGainFirstDerivativeLinear(double x) const;
+  double GetGainIntegralLinear(double x0, double x1) const;
+
+ private:
+  double GetKneeRegionOutputLevelDbfs(double input_level_dbfs) const;
+  double GetCompressorRegionOutputLevelDbfs(double input_level_dbfs) const;
+
+  static constexpr double max_input_level_db_ = test::kLimiterMaxInputLevelDbFs;
+  static constexpr double knee_smoothness_db_ = test::kLimiterKneeSmoothnessDb;
+  static constexpr double compression_ratio_ = test::kLimiterCompressionRatio;
+
+  const double max_input_level_linear_;
+
+  // Do not modify signal with level <= knee_start_dbfs_.
+  const double knee_start_dbfs_;
+  const double knee_start_linear_;
+
+  // The upper end of the knee region, which is between knee_start_dbfs_ and
+  // limiter_start_dbfs_.
+  const double limiter_start_dbfs_;
+  const double limiter_start_linear_;
+
+  // Coefficients {a, b, c} of the knee region polynomial
+  // ax^2 + bx + c in the DB scale.
+  const std::array<double, 3> knee_region_polynomial_;
+
+  // Parameters for the computation of the first derivative of GetGainLinear().
+  const double gain_curve_limiter_d1_;
+  const double gain_curve_limiter_d2_;
+
+  // Parameters for the computation of the integral of GetGainLinear().
+  const double gain_curve_limiter_i1_;
+  const double gain_curve_limiter_i2_;
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_AUDIO_PROCESSING_AGC2_LIMITER_H_
diff --git a/modules/audio_processing/agc2/limiter_unittest.cc b/modules/audio_processing/agc2/limiter_unittest.cc
new file mode 100644
index 0000000..7079812
--- /dev/null
+++ b/modules/audio_processing/agc2/limiter_unittest.cc
@@ -0,0 +1,60 @@
+/*
+ *  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/limiter.h"
+
+#include "rtc_base/gunit.h"
+
+namespace webrtc {
+
+TEST(FixedDigitalGainController2Limiter, ConstructDestruct) {
+  Limiter l;
+}
+
+TEST(FixedDigitalGainController2Limiter, GainCurveShouldBeMonotone) {
+  Limiter l;
+  float last_output_level = 0.f;
+  bool has_last_output_level = false;
+  for (float level = -90.f; level <= l.max_input_level_db(); level += 0.5f) {
+    const float current_output_level = l.GetOutputLevelDbfs(level);
+    if (!has_last_output_level) {
+      last_output_level = current_output_level;
+      has_last_output_level = true;
+    }
+    EXPECT_LE(last_output_level, current_output_level);
+    last_output_level = current_output_level;
+  }
+}
+
+TEST(FixedDigitalGainController2Limiter, GainCurveShouldBeContinuous) {
+  Limiter l;
+  float last_output_level = 0.f;
+  bool has_last_output_level = false;
+  constexpr float kMaxDelta = 0.5f;
+  for (float level = -90.f; level <= l.max_input_level_db(); level += 0.5f) {
+    const float current_output_level = l.GetOutputLevelDbfs(level);
+    if (!has_last_output_level) {
+      last_output_level = current_output_level;
+      has_last_output_level = true;
+    }
+    EXPECT_LE(current_output_level, last_output_level + kMaxDelta);
+    last_output_level = current_output_level;
+  }
+}
+
+TEST(FixedDigitalGainController2Limiter, OutputGainShouldBeLessThanFullScale) {
+  Limiter l;
+  for (float level = -90.f; level <= l.max_input_level_db(); level += 0.5f) {
+    const float current_output_level = l.GetOutputLevelDbfs(level);
+    EXPECT_LE(current_output_level, 0.f);
+  }
+}
+
+}  // namespace webrtc
diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn
index d2716aa..fad746d 100644
--- a/test/fuzzers/BUILD.gn
+++ b/test/fuzzers/BUILD.gn
@@ -445,6 +445,7 @@
     ":audio_processing_fuzzer_helper",
     ":fuzz_data_helper",
     "../../modules/audio_processing",
+    "../../rtc_base:rtc_base_approved",
   ]
 }
 
diff --git a/test/fuzzers/audio_processing_configs_fuzzer.cc b/test/fuzzers/audio_processing_configs_fuzzer.cc
index 81baab1..18913d8 100644
--- a/test/fuzzers/audio_processing_configs_fuzzer.cc
+++ b/test/fuzzers/audio_processing_configs_fuzzer.cc
@@ -9,6 +9,7 @@
  */
 
 #include "modules/audio_processing/include/audio_processing.h"
+#include "rtc_base/numerics/safe_minmax.h"
 #include "test/fuzzers/audio_processing_fuzzer_helper.h"
 #include "test/fuzzers/fuzz_data_helper.h"
 
@@ -36,6 +37,7 @@
   bool use_le = fuzz_data->ReadOrDefaultValue(true);
   bool use_vad = fuzz_data->ReadOrDefaultValue(true);
   bool use_agc_limiter = fuzz_data->ReadOrDefaultValue(true);
+  bool use_agc2_limiter = fuzz_data->ReadOrDefaultValue(true);
 
   // Filter out incompatible settings that lead to CHECK failures.
   if (use_aecm && use_aec) {
@@ -70,6 +72,12 @@
   apm_config.residual_echo_detector.enabled = red;
   apm_config.level_controller.enabled = lc;
   apm_config.high_pass_filter.enabled = hpf;
+  apm_config.gain_controller2.enabled = use_agc2_limiter;
+
+  // Read an int8 value, but don't let it be too large or small.
+  const float gain_db =
+      rtc::SafeClamp<int>(fuzz_data->ReadOrDefaultValue<int8_t>(0), -50, 50);
+  apm_config.gain_controller2.fixed_gain_db = gain_db;
 
   apm->ApplyConfig(apm_config);