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:
parent
b0588e6368
commit
82ec0faf72
@ -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
|
||||
|
||||
@ -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_;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -130,4 +130,8 @@ void GainCurveApplier::SetSampleRate(size_t sample_rate_hz) {
|
||||
kMaximalNumberOfSamplesPerChannel * 1000 / kFrameDurationMs);
|
||||
}
|
||||
|
||||
void GainCurveApplier::Reset() {
|
||||
level_estimator_.Reset();
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -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_;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user