Limiter reset when fixed gain controller gain set.

When FixedGainController::SetGain() is called first on a large value (e.g., 40 dB)
and afterwards on a smaller one (e.g., 0 dB), the limiter used by FixedGainController
takes time (about 10-20 seconds) to converge. During that period, the audio is not
audible and the volume slowly increases.

Even if switching from 40 dB to 0 dB is unlikely, this behavior can be corrected by
resetting the limiter every time that FixedGainController::SetGain() is called.
This eliminates the undesired effect described above even when the transient is short.

Bug: webrtc:7494
Change-Id: I419b8986d2181448b4671cdbbd1c256dfb460216
Reviewed-on: https://webrtc-review.googlesource.com/94902
Reviewed-by: Alex Loiko <aleloi@webrtc.org>
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#24451}
This commit is contained in:
Alessio Bazzica 2018-08-27 14:24:16 +02:00 committed by Commit Bot
parent b0588e6368
commit 82ec0faf72
6 changed files with 87 additions and 15 deletions

View File

@ -17,11 +17,17 @@
#include "rtc_base/checks.h"
namespace webrtc {
namespace {
constexpr float kInitialFilterStateLevel = 0.f;
} // namespace
FixedDigitalLevelEstimator::FixedDigitalLevelEstimator(
size_t sample_rate_hz,
ApmDataDumper* apm_data_dumper)
: apm_data_dumper_(apm_data_dumper) {
: apm_data_dumper_(apm_data_dumper),
filter_state_level_(kInitialFilterStateLevel) {
SetSampleRate(sample_rate_hz);
CheckParameterCombination();
RTC_DCHECK(apm_data_dumper_);
@ -97,4 +103,9 @@ void FixedDigitalLevelEstimator::SetSampleRate(size_t sample_rate_hz) {
rtc::CheckedDivExact(samples_in_frame_, kSubFramesInFrame);
CheckParameterCombination();
}
void FixedDigitalLevelEstimator::Reset() {
filter_state_level_ = kInitialFilterStateLevel;
}
} // namespace webrtc

View File

@ -45,11 +45,14 @@ class FixedDigitalLevelEstimator {
// value passed to the constructor. The class is not thread safe.
void SetSampleRate(size_t sample_rate_hz);
// Resets the level estimator internal state.
void Reset();
private:
void CheckParameterCombination();
ApmDataDumper* const apm_data_dumper_ = nullptr;
float filter_state_level_ = 0.f;
float filter_state_level_;
size_t samples_in_frame_;
size_t samples_in_sub_frame_;

View File

@ -54,9 +54,15 @@ void FixedGainController::SetGain(float gain_to_apply_db) {
// The gain
RTC_DCHECK_LE(-50.f, gain_to_apply_db);
RTC_DCHECK_LE(gain_to_apply_db, 50.f);
const float previous_applied_gained = gain_to_apply_;
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.";
// Reset the gain curve applier to quickly react on abrupt level changes
// caused by large changes of the applied gain.
if (previous_applied_gained != gain_to_apply_) {
gain_curve_applier_.Reset();
}
}
void FixedGainController::SetSampleRate(size_t sample_rate_hz) {

View File

@ -46,14 +46,18 @@ float RunFixedGainControllerWithConstantInput(FixedGainController* fixed_gc,
vectors_with_float_frame_last.float_frame_view().channel(0);
return channel[channel.size() - 1];
}
ApmDataDumper test_data_dumper(0);
std::unique_ptr<ApmDataDumper> GetApmDataDumper() {
return absl::make_unique<ApmDataDumper>(0);
}
std::unique_ptr<FixedGainController> CreateFixedGainController(
float gain_to_apply,
size_t rate,
std::string histogram_name_prefix) {
std::string histogram_name_prefix,
ApmDataDumper* test_data_dumper) {
std::unique_ptr<FixedGainController> fgc =
absl::make_unique<FixedGainController>(&test_data_dumper,
absl::make_unique<FixedGainController>(test_data_dumper,
histogram_name_prefix);
fgc->SetGain(gain_to_apply);
fgc->SetSampleRate(rate);
@ -62,16 +66,18 @@ std::unique_ptr<FixedGainController> CreateFixedGainController(
std::unique_ptr<FixedGainController> CreateFixedGainController(
float gain_to_apply,
size_t rate) {
return CreateFixedGainController(gain_to_apply, rate, "");
size_t rate,
ApmDataDumper* test_data_dumper) {
return CreateFixedGainController(gain_to_apply, rate, "", test_data_dumper);
}
} // namespace
TEST(AutomaticGainController2FixedDigital, CreateUse) {
const int kSampleRate = 44000;
std::unique_ptr<FixedGainController> fixed_gc =
CreateFixedGainController(kGainToApplyDb, kSampleRate);
auto test_data_dumper = GetApmDataDumper();
std::unique_ptr<FixedGainController> fixed_gc = CreateFixedGainController(
kGainToApplyDb, kSampleRate, test_data_dumper.get());
VectorFloatFrame vectors_with_float_frame(
1, rtc::CheckedDivExact(kSampleRate, 100), kInputLevelLinear);
auto float_frame = vectors_with_float_frame.float_frame_view();
@ -85,13 +91,15 @@ TEST(AutomaticGainController2FixedDigital, CheckSaturationBehaviorWithLimiter) {
const size_t kNumFrames = 5;
const size_t kSampleRate = 42000;
auto test_data_dumper = GetApmDataDumper();
const auto gains_no_saturation =
test::LinSpace(0.1, test::kLimiterMaxInputLevelDbFs - 0.01, 10);
for (const auto gain_db : gains_no_saturation) {
// Since |test::kLimiterMaxInputLevelDbFs| > |gain_db|, the
// limiter will not saturate the signal.
std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
CreateFixedGainController(gain_db, kSampleRate);
CreateFixedGainController(gain_db, kSampleRate, test_data_dumper.get());
// Saturation not expected.
SCOPED_TRACE(std::to_string(gain_db));
@ -107,7 +115,7 @@ TEST(AutomaticGainController2FixedDigital, CheckSaturationBehaviorWithLimiter) {
// Since |test::kLimiterMaxInputLevelDbFs| < |gain|, the limiter
// will saturate the signal.
std::unique_ptr<FixedGainController> fixed_gc_saturation =
CreateFixedGainController(gain_db, kSampleRate);
CreateFixedGainController(gain_db, kSampleRate, test_data_dumper.get());
// Saturation expected.
SCOPED_TRACE(std::to_string(gain_db));
@ -124,13 +132,15 @@ TEST(AutomaticGainController2FixedDigital,
const size_t kNumFrames = 5;
const size_t kSampleRate = 8000;
auto test_data_dumper = GetApmDataDumper();
const auto gains_no_saturation =
test::LinSpace(0.1, test::kLimiterMaxInputLevelDbFs - 0.01, 10);
for (const auto gain_db : gains_no_saturation) {
// Since |gain| > |test::kLimiterMaxInputLevelDbFs|, the limiter will
// not saturate the signal.
std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
CreateFixedGainController(gain_db, kSampleRate);
CreateFixedGainController(gain_db, kSampleRate, test_data_dumper.get());
// Saturation not expected.
SCOPED_TRACE(std::to_string(gain_db));
@ -146,7 +156,7 @@ TEST(AutomaticGainController2FixedDigital,
// Singe |gain| < |test::kLimiterMaxInputLevelDbFs|, the limiter will
// saturate the signal.
std::unique_ptr<FixedGainController> fixed_gc_saturation =
CreateFixedGainController(gain_db, kSampleRate);
CreateFixedGainController(gain_db, kSampleRate, test_data_dumper.get());
// Saturation expected.
SCOPED_TRACE(std::to_string(gain_db));
@ -164,8 +174,10 @@ TEST(AutomaticGainController2FixedDigital, GainShouldChangeOnSetGain) {
constexpr float kGainDbNoChange = 0.f;
constexpr float kGainDbFactor10 = 20.f;
auto test_data_dumper = GetApmDataDumper();
std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
CreateFixedGainController(kGainDbNoChange, kSampleRate);
CreateFixedGainController(kGainDbNoChange, kSampleRate,
test_data_dumper.get());
// Signal level is unchanged with 0 db gain.
EXPECT_FLOAT_EQ(
@ -182,6 +194,37 @@ TEST(AutomaticGainController2FixedDigital, GainShouldChangeOnSetGain) {
kInputLevel * 10);
}
TEST(AutomaticGainController2FixedDigital,
SetGainShouldBeFastAndTimeInvariant) {
// Number of frames required for the fixed gain controller to adapt on the
// input signal when the gain changes.
constexpr size_t kNumFrames = 5;
constexpr float kInputLevel = 1000.f;
constexpr size_t kSampleRate = 8000;
constexpr float kGainDbLow = 0.f;
constexpr float kGainDbHigh = 40.f;
static_assert(kGainDbLow < kGainDbHigh, "");
auto test_data_dumper = GetApmDataDumper();
std::unique_ptr<FixedGainController> fixed_gc = CreateFixedGainController(
kGainDbLow, kSampleRate, test_data_dumper.get());
fixed_gc->SetGain(kGainDbLow);
const float output_level_pre = RunFixedGainControllerWithConstantInput(
fixed_gc.get(), kInputLevel, kNumFrames, kSampleRate);
fixed_gc->SetGain(kGainDbHigh);
RunFixedGainControllerWithConstantInput(fixed_gc.get(), kInputLevel,
kNumFrames, kSampleRate);
fixed_gc->SetGain(kGainDbLow);
const float output_level_post = RunFixedGainControllerWithConstantInput(
fixed_gc.get(), kInputLevel, kNumFrames, kSampleRate);
EXPECT_EQ(output_level_pre, output_level_post);
}
TEST(AutomaticGainController2FixedDigital, RegionHistogramIsUpdated) {
constexpr size_t kSampleRate = 8000;
constexpr float kGainDb = 0.f;
@ -190,8 +233,10 @@ TEST(AutomaticGainController2FixedDigital, RegionHistogramIsUpdated) {
metrics::Reset();
auto test_data_dumper = GetApmDataDumper();
std::unique_ptr<FixedGainController> fixed_gc_no_saturation =
CreateFixedGainController(kGainDb, kSampleRate, "Test");
CreateFixedGainController(kGainDb, kSampleRate, "Test",
test_data_dumper.get());
static_cast<void>(RunFixedGainControllerWithConstantInput(
fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate));

View File

@ -130,4 +130,8 @@ void GainCurveApplier::SetSampleRate(size_t sample_rate_hz) {
kMaximalNumberOfSamplesPerChannel * 1000 / kFrameDurationMs);
}
void GainCurveApplier::Reset() {
level_estimator_.Reset();
}
} // namespace webrtc

View File

@ -39,6 +39,9 @@ class GainCurveApplier {
// per_sample_scaling_factors_ array.
void SetSampleRate(size_t sample_rate_hz);
// Resets the internal state.
void Reset();
private:
const InterpolatedGainCurve interp_gain_curve_;
FixedDigitalLevelEstimator level_estimator_;