mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-17 15:47:53 +01:00
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}
This commit is contained in:
parent
1896cece01
commit
a05ee82c4c
22 changed files with 1460 additions and 45 deletions
|
@ -15,6 +15,10 @@ rtc_source_set("agc2") {
|
||||||
"fixed_digital_level_estimator.h",
|
"fixed_digital_level_estimator.h",
|
||||||
"fixed_gain_controller.cc",
|
"fixed_gain_controller.cc",
|
||||||
"fixed_gain_controller.h",
|
"fixed_gain_controller.h",
|
||||||
|
"gain_curve_applier.cc",
|
||||||
|
"gain_curve_applier.h",
|
||||||
|
"interpolated_gain_curve.cc",
|
||||||
|
"interpolated_gain_curve.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
configs += [ "..:apm_debug_dump" ]
|
configs += [ "..:apm_debug_dump" ]
|
||||||
|
@ -25,6 +29,7 @@ rtc_source_set("agc2") {
|
||||||
"../../../api:array_view",
|
"../../../api:array_view",
|
||||||
"../../../common_audio",
|
"../../../common_audio",
|
||||||
"../../../rtc_base:checks",
|
"../../../rtc_base:checks",
|
||||||
|
"../../../rtc_base:gtest_prod",
|
||||||
"../../../rtc_base:rtc_base_approved",
|
"../../../rtc_base:rtc_base_approved",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -34,9 +39,18 @@ rtc_source_set("fixed_digital_unittests") {
|
||||||
configs += [ "..:apm_debug_dump" ]
|
configs += [ "..:apm_debug_dump" ]
|
||||||
|
|
||||||
sources = [
|
sources = [
|
||||||
|
"agc2_testing_common.cc",
|
||||||
"agc2_testing_common.h",
|
"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_digital_level_estimator_unittest.cc",
|
||||||
"fixed_gain_controller_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.cc",
|
||||||
"vector_float_frame.h",
|
"vector_float_frame.h",
|
||||||
]
|
]
|
||||||
|
@ -46,6 +60,8 @@ rtc_source_set("fixed_digital_unittests") {
|
||||||
"..:audio_frame_view",
|
"..:audio_frame_view",
|
||||||
"../../../api:array_view",
|
"../../../api:array_view",
|
||||||
"../../../common_audio",
|
"../../../common_audio",
|
||||||
|
"../../../rtc_base:checks",
|
||||||
|
"../../../rtc_base:rtc_base_approved",
|
||||||
"../../../rtc_base:rtc_base_tests_utils",
|
"../../../rtc_base:rtc_base_tests_utils",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,28 +11,36 @@
|
||||||
#ifndef MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
|
#ifndef MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
|
||||||
#define MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
|
#define MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "rtc_base/basictypes.h"
|
#include "rtc_base/basictypes.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
constexpr float kMinSampleValue = -32768.f;
|
constexpr float kMinFloatS16Value = -32768.f;
|
||||||
constexpr float kMaxSampleValue = 32767.f;
|
constexpr float kMaxFloatS16Value = 32767.f;
|
||||||
|
constexpr double kMaxAbsFloatS16Value = 32768.0;
|
||||||
|
|
||||||
constexpr size_t kFrameDurationMs = 10;
|
constexpr size_t kFrameDurationMs = 10;
|
||||||
constexpr size_t kSubFramesInFrame = 20;
|
constexpr size_t kSubFramesInFrame = 20;
|
||||||
|
constexpr size_t kMaximalNumberOfSamplesPerChannel = 480;
|
||||||
|
|
||||||
constexpr float kAttackFilterConstant = 0.f;
|
constexpr float kAttackFilterConstant = 0.f;
|
||||||
|
|
||||||
constexpr size_t kMaximalNumberOfSamplesPerChannel = 480;
|
|
||||||
|
|
||||||
// This is computed from kDecayMs by
|
// This is computed from kDecayMs by
|
||||||
// 10 ** (-1/20 * subframe_duration / kDecayMs).
|
// 10 ** (-1/20 * subframe_duration / kDecayMs).
|
||||||
// |subframe_duration| is |kFrameDurationMs / kSubFramesInFrame|.
|
// |subframe_duration| is |kFrameDurationMs / kSubFramesInFrame|.
|
||||||
// kDecayMs is defined in agc2_testing_common.h
|
// kDecayMs is defined in agc2_testing_common.h
|
||||||
constexpr float kDecayFilterConstant = 0.9998848773724686f;
|
constexpr float kDecayFilterConstant = 0.9998848773724686f;
|
||||||
|
|
||||||
// TODO(aleloi): add the other constants as more AGC2 components are
|
// Number of interpolation points for each region of the limiter.
|
||||||
// added.
|
// 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
|
} // namespace webrtc
|
||||||
|
|
||||||
#endif // MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
|
#endif // MODULES_AUDIO_PROCESSING_AGC2_AGC2_COMMON_H_
|
||||||
|
|
33
modules/audio_processing/agc2/agc2_testing_common.cc
Normal file
33
modules/audio_processing/agc2/agc2_testing_common.cc
Normal file
|
@ -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
|
|
@ -11,11 +11,24 @@
|
||||||
#ifndef MODULES_AUDIO_PROCESSING_AGC2_AGC2_TESTING_COMMON_H_
|
#ifndef MODULES_AUDIO_PROCESSING_AGC2_AGC2_TESTING_COMMON_H_
|
||||||
#define 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 {
|
namespace webrtc {
|
||||||
|
|
||||||
// Level Estimator test params.
|
namespace test {
|
||||||
|
|
||||||
|
// Level Estimator test parameters.
|
||||||
constexpr float kDecayMs = 500.f;
|
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
|
} // namespace webrtc
|
||||||
|
|
||||||
#endif // MODULES_AUDIO_PROCESSING_AGC2_AGC2_TESTING_COMMON_H_
|
#endif // MODULES_AUDIO_PROCESSING_AGC2_AGC2_TESTING_COMMON_H_
|
||||||
|
|
|
@ -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
|
228
modules/audio_processing/agc2/compute_interpolated_gain_curve.cc
Normal file
228
modules/audio_processing/agc2/compute_interpolated_gain_curve.cc
Normal file
|
@ -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
|
|
@ -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_
|
|
@ -122,7 +122,7 @@ TEST(AutomaticGainController2LevelEstimator,
|
||||||
TEST(AutomaticGainController2LevelEstimator, TimeToDecreaseForLowLevel) {
|
TEST(AutomaticGainController2LevelEstimator, TimeToDecreaseForLowLevel) {
|
||||||
constexpr float kLevelReductionDb = 25;
|
constexpr float kLevelReductionDb = 25;
|
||||||
constexpr float kInitialLowLevel = -40;
|
constexpr float kInitialLowLevel = -40;
|
||||||
constexpr float kExpectedTime = kLevelReductionDb * kDecayMs;
|
constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs;
|
||||||
|
|
||||||
const float time_to_decrease =
|
const float time_to_decrease =
|
||||||
TimeMsToDecreaseLevel(22000, 1, kInitialLowLevel, kLevelReductionDb);
|
TimeMsToDecreaseLevel(22000, 1, kInitialLowLevel, kLevelReductionDb);
|
||||||
|
@ -133,7 +133,7 @@ TEST(AutomaticGainController2LevelEstimator, TimeToDecreaseForLowLevel) {
|
||||||
|
|
||||||
TEST(AutomaticGainController2LevelEstimator, TimeToDecreaseForFullScaleLevel) {
|
TEST(AutomaticGainController2LevelEstimator, TimeToDecreaseForFullScaleLevel) {
|
||||||
constexpr float kLevelReductionDb = 25;
|
constexpr float kLevelReductionDb = 25;
|
||||||
constexpr float kExpectedTime = kLevelReductionDb * kDecayMs;
|
constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs;
|
||||||
|
|
||||||
const float time_to_decrease =
|
const float time_to_decrease =
|
||||||
TimeMsToDecreaseLevel(26000, 1, 0, kLevelReductionDb);
|
TimeMsToDecreaseLevel(26000, 1, 0, kLevelReductionDb);
|
||||||
|
@ -145,7 +145,7 @@ TEST(AutomaticGainController2LevelEstimator, TimeToDecreaseForFullScaleLevel) {
|
||||||
TEST(AutomaticGainController2LevelEstimator,
|
TEST(AutomaticGainController2LevelEstimator,
|
||||||
TimeToDecreaseForMultipleChannels) {
|
TimeToDecreaseForMultipleChannels) {
|
||||||
constexpr float kLevelReductionDb = 25;
|
constexpr float kLevelReductionDb = 25;
|
||||||
constexpr float kExpectedTime = kLevelReductionDb * kDecayMs;
|
constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs;
|
||||||
constexpr size_t kNumChannels = 10;
|
constexpr size_t kNumChannels = 10;
|
||||||
|
|
||||||
const float time_to_decrease =
|
const float time_to_decrease =
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "api/array_view.h"
|
#include "api/array_view.h"
|
||||||
#include "common_audio/include/audio_util.h"
|
#include "common_audio/include/audio_util.h"
|
||||||
#include "modules/audio_processing/agc2/agc2_common.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 "modules/audio_processing/logging/apm_data_dumper.h"
|
||||||
#include "rtc_base/checks.h"
|
#include "rtc_base/checks.h"
|
||||||
#include "rtc_base/logging.h"
|
#include "rtc_base/logging.h"
|
||||||
|
@ -27,27 +28,30 @@ namespace {
|
||||||
// Returns true when the gain factor is so close to 1 that it would
|
// Returns true when the gain factor is so close to 1 that it would
|
||||||
// not affect int16 samples.
|
// not affect int16 samples.
|
||||||
bool CloseToOne(float gain_factor) {
|
bool CloseToOne(float gain_factor) {
|
||||||
return 1.f - 1.f / kMaxSampleValue <= gain_factor &&
|
return 1.f - 1.f / kMaxFloatS16Value <= gain_factor &&
|
||||||
gain_factor <= 1.f + 1.f / kMaxSampleValue;
|
gain_factor <= 1.f + 1.f / kMaxFloatS16Value;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
FixedGainController::FixedGainController(ApmDataDumper* apm_data_dumper)
|
FixedGainController::FixedGainController(ApmDataDumper* apm_data_dumper)
|
||||||
: apm_data_dumper_(apm_data_dumper) {
|
: apm_data_dumper_(apm_data_dumper),
|
||||||
RTC_DCHECK_LT(0.f, gain_to_apply_);
|
gain_curve_applier_(48000, apm_data_dumper_) {}
|
||||||
RTC_DLOG(LS_INFO) << "Gain to apply: " << gain_to_apply_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FixedGainController::SetGain(float gain_to_apply_db) {
|
void FixedGainController::SetGain(float gain_to_apply_db) {
|
||||||
// Changes in gain_to_apply_ cause discontinuities. We assume
|
// Changes in gain_to_apply_ cause discontinuities. We assume
|
||||||
// gain_to_apply_ is set in the beginning of the call. If it is
|
// gain_to_apply_ is set in the beginning of the call. If it is
|
||||||
// frequently changed, we should add interpolation between the
|
// frequently changed, we should add interpolation between the
|
||||||
// values.
|
// 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);
|
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) {
|
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) {
|
void FixedGainController::EnableLimiter(bool enable_limiter) {
|
||||||
|
@ -70,8 +74,7 @@ void FixedGainController::Process(AudioFrameView<float> signal) {
|
||||||
|
|
||||||
// Use the limiter (if configured to).
|
// Use the limiter (if configured to).
|
||||||
if (enable_limiter_) {
|
if (enable_limiter_) {
|
||||||
// TODO(aleloi): Process the signal with the
|
gain_curve_applier_.Process(signal);
|
||||||
// GainCurveApplier. This will be done in the upcoming CLs.
|
|
||||||
|
|
||||||
// Dump data for debug.
|
// Dump data for debug.
|
||||||
const auto channel_view = signal.channel(0);
|
const auto channel_view = signal.channel(0);
|
||||||
|
@ -83,7 +86,7 @@ void FixedGainController::Process(AudioFrameView<float> signal) {
|
||||||
for (size_t k = 0; k < signal.num_channels(); ++k) {
|
for (size_t k = 0; k < signal.num_channels(); ++k) {
|
||||||
rtc::ArrayView<float> channel_view = signal.channel(k);
|
rtc::ArrayView<float> channel_view = signal.channel(k);
|
||||||
for (auto& sample : channel_view) {
|
for (auto& sample : channel_view) {
|
||||||
sample = rtc::SafeClamp(sample, kMinSampleValue, kMaxSampleValue);
|
sample = rtc::SafeClamp(sample, kMinFloatS16Value, kMaxFloatS16Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#ifndef MODULES_AUDIO_PROCESSING_AGC2_FIXED_GAIN_CONTROLLER_H_
|
#ifndef MODULES_AUDIO_PROCESSING_AGC2_FIXED_GAIN_CONTROLLER_H_
|
||||||
#define 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"
|
#include "modules/audio_processing/include/audio_frame_view.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
@ -22,13 +23,16 @@ class FixedGainController {
|
||||||
|
|
||||||
void Process(AudioFrameView<float> signal);
|
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 SetGain(float gain_to_apply_db);
|
||||||
void SetSampleRate(size_t sample_rate_hz);
|
void SetSampleRate(size_t sample_rate_hz);
|
||||||
void EnableLimiter(bool enable_limiter);
|
void EnableLimiter(bool enable_limiter);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float gain_to_apply_ = 1.f;
|
float gain_to_apply_ = 1.f;
|
||||||
ApmDataDumper* apm_data_dumper_;
|
ApmDataDumper* apm_data_dumper_ = nullptr;
|
||||||
|
GainCurveApplier gain_curve_applier_;
|
||||||
bool enable_limiter_ = true;
|
bool enable_limiter_ = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,11 @@
|
||||||
#include "modules/audio_processing/agc2/fixed_gain_controller.h"
|
#include "modules/audio_processing/agc2/fixed_gain_controller.h"
|
||||||
|
|
||||||
#include "api/array_view.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/agc2/vector_float_frame.h"
|
||||||
#include "modules/audio_processing/logging/apm_data_dumper.h"
|
#include "modules/audio_processing/logging/apm_data_dumper.h"
|
||||||
#include "rtc_base/gunit.h"
|
#include "rtc_base/gunit.h"
|
||||||
|
#include "rtc_base/ptr_util.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -45,13 +47,15 @@ float RunFixedGainControllerWithConstantInput(FixedGainController* fixed_gc,
|
||||||
}
|
}
|
||||||
ApmDataDumper test_data_dumper(0);
|
ApmDataDumper test_data_dumper(0);
|
||||||
|
|
||||||
FixedGainController CreateFixedGainController(float gain_to_apply,
|
std::unique_ptr<FixedGainController> CreateFixedGainController(
|
||||||
|
float gain_to_apply,
|
||||||
size_t rate,
|
size_t rate,
|
||||||
bool enable_limiter) {
|
bool enable_limiter) {
|
||||||
FixedGainController fgc(&test_data_dumper);
|
std::unique_ptr<FixedGainController> fgc =
|
||||||
fgc.SetGain(gain_to_apply);
|
rtc::MakeUnique<FixedGainController>(&test_data_dumper);
|
||||||
fgc.SetSampleRate(gain_to_apply);
|
fgc->SetGain(gain_to_apply);
|
||||||
fgc.EnableLimiter(enable_limiter);
|
fgc->SetSampleRate(rate);
|
||||||
|
fgc->EnableLimiter(enable_limiter);
|
||||||
return fgc;
|
return fgc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,51 +63,128 @@ FixedGainController CreateFixedGainController(float gain_to_apply,
|
||||||
|
|
||||||
TEST(AutomaticGainController2FixedDigital, CreateUseWithoutLimiter) {
|
TEST(AutomaticGainController2FixedDigital, CreateUseWithoutLimiter) {
|
||||||
const int kSampleRate = 48000;
|
const int kSampleRate = 48000;
|
||||||
FixedGainController fixed_gc =
|
std::unique_ptr<FixedGainController> fixed_gc =
|
||||||
CreateFixedGainController(kGainToApplyDb, kSampleRate, false);
|
CreateFixedGainController(kGainToApplyDb, kSampleRate, false);
|
||||||
VectorFloatFrame vectors_with_float_frame(
|
VectorFloatFrame vectors_with_float_frame(
|
||||||
1, rtc::CheckedDivExact(kSampleRate, 100), kInputLevelLinear);
|
1, rtc::CheckedDivExact(kSampleRate, 100), kInputLevelLinear);
|
||||||
auto float_frame = vectors_with_float_frame.float_frame_view();
|
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);
|
const auto channel = float_frame.channel(0);
|
||||||
EXPECT_LT(kInputLevelLinear, channel[0]);
|
EXPECT_LT(kInputLevelLinear, channel[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AutomaticGainController2FixedDigital, CreateUseWithLimiter) {
|
TEST(AutomaticGainController2FixedDigital, CreateUseWithLimiter) {
|
||||||
const int kSampleRate = 44000;
|
const int kSampleRate = 44000;
|
||||||
FixedGainController fixed_gc =
|
std::unique_ptr<FixedGainController> fixed_gc =
|
||||||
CreateFixedGainController(kGainToApplyDb, kSampleRate, true);
|
CreateFixedGainController(kGainToApplyDb, kSampleRate, true);
|
||||||
VectorFloatFrame vectors_with_float_frame(
|
VectorFloatFrame vectors_with_float_frame(
|
||||||
1, rtc::CheckedDivExact(kSampleRate, 100), kInputLevelLinear);
|
1, rtc::CheckedDivExact(kSampleRate, 100), kInputLevelLinear);
|
||||||
auto float_frame = vectors_with_float_frame.float_frame_view();
|
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);
|
const auto channel = float_frame.channel(0);
|
||||||
EXPECT_LT(kInputLevelLinear, channel[0]);
|
EXPECT_LT(kInputLevelLinear, channel[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(AutomaticGainController2FixedDigital, GainShouldChangeOnSetGain) {
|
TEST(AutomaticGainController2FixedDigital, CheckSaturationBehaviorWithLimiter) {
|
||||||
constexpr float input_level = 1000.f;
|
const float kInputLevel = 32767.f;
|
||||||
constexpr size_t num_frames = 5;
|
const size_t kNumFrames = 5;
|
||||||
constexpr size_t kSampleRate = 8000;
|
const size_t kSampleRate = 42000;
|
||||||
constexpr float gain_db_no_change = 0.f;
|
|
||||||
constexpr float gain_db_factor_10 = 20.f;
|
|
||||||
|
|
||||||
FixedGainController fixed_gc_no_saturation =
|
const auto gains_no_saturation =
|
||||||
CreateFixedGainController(gain_db_no_change, kSampleRate, false);
|
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.
|
// Signal level is unchanged with 0 db gain.
|
||||||
EXPECT_FLOAT_EQ(
|
EXPECT_FLOAT_EQ(
|
||||||
RunFixedGainControllerWithConstantInput(
|
RunFixedGainControllerWithConstantInput(
|
||||||
&fixed_gc_no_saturation, input_level, num_frames, kSampleRate),
|
fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
|
||||||
input_level);
|
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.
|
// +20db should increase signal by a factor of 10.
|
||||||
EXPECT_FLOAT_EQ(
|
EXPECT_FLOAT_EQ(
|
||||||
RunFixedGainControllerWithConstantInput(
|
RunFixedGainControllerWithConstantInput(
|
||||||
&fixed_gc_no_saturation, input_level, num_frames, kSampleRate),
|
fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate),
|
||||||
input_level * 10);
|
kInputLevel * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|
131
modules/audio_processing/agc2/gain_curve_applier.cc
Normal file
131
modules/audio_processing/agc2/gain_curve_applier.cc
Normal file
|
@ -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
|
56
modules/audio_processing/agc2/gain_curve_applier.h
Normal file
56
modules/audio_processing/agc2/gain_curve_applier.h
Normal file
|
@ -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_
|
59
modules/audio_processing/agc2/gain_curve_applier_unittest.cc
Normal file
59
modules/audio_processing/agc2/gain_curve_applier_unittest.cc
Normal file
|
@ -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
|
105
modules/audio_processing/agc2/interpolated_gain_curve.cc
Normal file
105
modules/audio_processing/agc2/interpolated_gain_curve.cc
Normal file
|
@ -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
|
122
modules/audio_processing/agc2/interpolated_gain_curve.h
Normal file
122
modules/audio_processing/agc2/interpolated_gain_curve.h
Normal file
|
@ -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_
|
|
@ -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
|
137
modules/audio_processing/agc2/limiter.cc
Normal file
137
modules/audio_processing/agc2/limiter.cc
Normal file
|
@ -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
|
74
modules/audio_processing/agc2/limiter.h
Normal file
74
modules/audio_processing/agc2/limiter.h
Normal file
|
@ -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_
|
60
modules/audio_processing/agc2/limiter_unittest.cc
Normal file
60
modules/audio_processing/agc2/limiter_unittest.cc
Normal file
|
@ -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
|
|
@ -445,6 +445,7 @@ webrtc_fuzzer_test("audio_processing_fuzzer") {
|
||||||
":audio_processing_fuzzer_helper",
|
":audio_processing_fuzzer_helper",
|
||||||
":fuzz_data_helper",
|
":fuzz_data_helper",
|
||||||
"../../modules/audio_processing",
|
"../../modules/audio_processing",
|
||||||
|
"../../rtc_base:rtc_base_approved",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "modules/audio_processing/include/audio_processing.h"
|
#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/audio_processing_fuzzer_helper.h"
|
||||||
#include "test/fuzzers/fuzz_data_helper.h"
|
#include "test/fuzzers/fuzz_data_helper.h"
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ std::unique_ptr<AudioProcessing> CreateApm(test::FuzzDataHelper* fuzz_data) {
|
||||||
bool use_le = fuzz_data->ReadOrDefaultValue(true);
|
bool use_le = fuzz_data->ReadOrDefaultValue(true);
|
||||||
bool use_vad = fuzz_data->ReadOrDefaultValue(true);
|
bool use_vad = fuzz_data->ReadOrDefaultValue(true);
|
||||||
bool use_agc_limiter = 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.
|
// Filter out incompatible settings that lead to CHECK failures.
|
||||||
if (use_aecm && use_aec) {
|
if (use_aecm && use_aec) {
|
||||||
|
@ -70,6 +72,12 @@ std::unique_ptr<AudioProcessing> CreateApm(test::FuzzDataHelper* fuzz_data) {
|
||||||
apm_config.residual_echo_detector.enabled = red;
|
apm_config.residual_echo_detector.enabled = red;
|
||||||
apm_config.level_controller.enabled = lc;
|
apm_config.level_controller.enabled = lc;
|
||||||
apm_config.high_pass_filter.enabled = hpf;
|
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);
|
apm->ApplyConfig(apm_config);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue