diff --git a/modules/audio_processing/agc2/fixed_digital_level_estimator.cc b/modules/audio_processing/agc2/fixed_digital_level_estimator.cc index 9a1fd281ca..39cc764185 100644 --- a/modules/audio_processing/agc2/fixed_digital_level_estimator.cc +++ b/modules/audio_processing/agc2/fixed_digital_level_estimator.cc @@ -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 diff --git a/modules/audio_processing/agc2/fixed_digital_level_estimator.h b/modules/audio_processing/agc2/fixed_digital_level_estimator.h index b0e7a6d088..4907ec70e6 100644 --- a/modules/audio_processing/agc2/fixed_digital_level_estimator.h +++ b/modules/audio_processing/agc2/fixed_digital_level_estimator.h @@ -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_; diff --git a/modules/audio_processing/agc2/fixed_gain_controller.cc b/modules/audio_processing/agc2/fixed_gain_controller.cc index 6aa390ea36..d49d18117b 100644 --- a/modules/audio_processing/agc2/fixed_gain_controller.cc +++ b/modules/audio_processing/agc2/fixed_gain_controller.cc @@ -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) { diff --git a/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc b/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc index fa68a01a4b..bd7c356326 100644 --- a/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc +++ b/modules/audio_processing/agc2/fixed_gain_controller_unittest.cc @@ -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 GetApmDataDumper() { + return absl::make_unique(0); +} std::unique_ptr 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 fgc = - absl::make_unique(&test_data_dumper, + absl::make_unique(test_data_dumper, histogram_name_prefix); fgc->SetGain(gain_to_apply); fgc->SetSampleRate(rate); @@ -62,16 +66,18 @@ std::unique_ptr CreateFixedGainController( std::unique_ptr 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 fixed_gc = - CreateFixedGainController(kGainToApplyDb, kSampleRate); + auto test_data_dumper = GetApmDataDumper(); + std::unique_ptr 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 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 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 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 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 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 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 fixed_gc_no_saturation = - CreateFixedGainController(kGainDb, kSampleRate, "Test"); + CreateFixedGainController(kGainDb, kSampleRate, "Test", + test_data_dumper.get()); static_cast(RunFixedGainControllerWithConstantInput( fixed_gc_no_saturation.get(), kInputLevel, kNumFrames, kSampleRate)); diff --git a/modules/audio_processing/agc2/gain_curve_applier.cc b/modules/audio_processing/agc2/gain_curve_applier.cc index 7c1410b232..b52e0d7f57 100644 --- a/modules/audio_processing/agc2/gain_curve_applier.cc +++ b/modules/audio_processing/agc2/gain_curve_applier.cc @@ -130,4 +130,8 @@ void GainCurveApplier::SetSampleRate(size_t sample_rate_hz) { kMaximalNumberOfSamplesPerChannel * 1000 / kFrameDurationMs); } +void GainCurveApplier::Reset() { + level_estimator_.Reset(); +} + } // namespace webrtc diff --git a/modules/audio_processing/agc2/gain_curve_applier.h b/modules/audio_processing/agc2/gain_curve_applier.h index f546f6325d..a7ffa36ade 100644 --- a/modules/audio_processing/agc2/gain_curve_applier.h +++ b/modules/audio_processing/agc2/gain_curve_applier.h @@ -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_;