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, ¶meters);
+ PrecomputeBeyondKneeApproxParams(&limiter, ¶meters);
+ 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);