diff --git a/modules/audio_processing/agc2/adaptive_agc.cc b/modules/audio_processing/agc2/adaptive_agc.cc index 0ab7998f05..795b8b5258 100644 --- a/modules/audio_processing/agc2/adaptive_agc.cc +++ b/modules/audio_processing/agc2/adaptive_agc.cc @@ -25,6 +25,15 @@ AdaptiveAgc::AdaptiveAgc(ApmDataDumper* apm_data_dumper) RTC_DCHECK(apm_data_dumper); } +AdaptiveAgc::AdaptiveAgc(ApmDataDumper* apm_data_dumper, + float extra_saturation_margin_db) + : speech_level_estimator_(apm_data_dumper, extra_saturation_margin_db), + gain_applier_(apm_data_dumper), + apm_data_dumper_(apm_data_dumper), + noise_level_estimator_(apm_data_dumper) { + RTC_DCHECK(apm_data_dumper); +} + AdaptiveAgc::~AdaptiveAgc() = default; void AdaptiveAgc::Process(AudioFrameView float_frame, diff --git a/modules/audio_processing/agc2/adaptive_agc.h b/modules/audio_processing/agc2/adaptive_agc.h index 7bfd3c04ae..6c0917af4d 100644 --- a/modules/audio_processing/agc2/adaptive_agc.h +++ b/modules/audio_processing/agc2/adaptive_agc.h @@ -23,6 +23,7 @@ class ApmDataDumper; class AdaptiveAgc { public: explicit AdaptiveAgc(ApmDataDumper* apm_data_dumper); + AdaptiveAgc(ApmDataDumper* apm_data_dumper, float extra_saturation_margin_db); ~AdaptiveAgc(); void Process(AudioFrameView float_frame, float last_audio_level); diff --git a/modules/audio_processing/agc2/adaptive_mode_level_estimator.cc b/modules/audio_processing/agc2/adaptive_mode_level_estimator.cc index c8df913811..138faec0b7 100644 --- a/modules/audio_processing/agc2/adaptive_mode_level_estimator.cc +++ b/modules/audio_processing/agc2/adaptive_mode_level_estimator.cc @@ -22,6 +22,12 @@ AdaptiveModeLevelEstimator::AdaptiveModeLevelEstimator( : saturation_protector_(apm_data_dumper), apm_data_dumper_(apm_data_dumper) {} +AdaptiveModeLevelEstimator::AdaptiveModeLevelEstimator( + ApmDataDumper* apm_data_dumper, + float extra_saturation_margin_db) + : saturation_protector_(apm_data_dumper, extra_saturation_margin_db), + apm_data_dumper_(apm_data_dumper) {} + void AdaptiveModeLevelEstimator::UpdateEstimation( const VadWithLevel::LevelAndProbability& vad_data) { RTC_DCHECK_GT(vad_data.speech_rms_dbfs, -150.f); diff --git a/modules/audio_processing/agc2/adaptive_mode_level_estimator.h b/modules/audio_processing/agc2/adaptive_mode_level_estimator.h index b315420626..f887268b0e 100644 --- a/modules/audio_processing/agc2/adaptive_mode_level_estimator.h +++ b/modules/audio_processing/agc2/adaptive_mode_level_estimator.h @@ -23,6 +23,8 @@ class ApmDataDumper; class AdaptiveModeLevelEstimator { public: explicit AdaptiveModeLevelEstimator(ApmDataDumper* apm_data_dumper); + AdaptiveModeLevelEstimator(ApmDataDumper* apm_data_dumper, + float extra_saturation_margin_db); void UpdateEstimation(const VadWithLevel::LevelAndProbability& vad_data); float LatestLevelEstimate() const; void Reset(); diff --git a/modules/audio_processing/agc2/saturation_protector.cc b/modules/audio_processing/agc2/saturation_protector.cc index 8efb572921..94a52eaaca 100644 --- a/modules/audio_processing/agc2/saturation_protector.cc +++ b/modules/audio_processing/agc2/saturation_protector.cc @@ -57,9 +57,14 @@ float SaturationProtector::PeakEnveloper::Query() const { } SaturationProtector::SaturationProtector(ApmDataDumper* apm_data_dumper) + : SaturationProtector(apm_data_dumper, GetExtraSaturationMarginOffsetDb()) { +} + +SaturationProtector::SaturationProtector(ApmDataDumper* apm_data_dumper, + float extra_saturation_margin_db) : apm_data_dumper_(apm_data_dumper), last_margin_(GetInitialSaturationMarginDb()), - extra_saturation_margin_db_(GetExtraSaturationMarginOffsetDb()) {} + extra_saturation_margin_db_(extra_saturation_margin_db) {} void SaturationProtector::UpdateMargin( const VadWithLevel::LevelAndProbability& vad_data, diff --git a/modules/audio_processing/agc2/saturation_protector.h b/modules/audio_processing/agc2/saturation_protector.h index 1705f6ac5f..e637469070 100644 --- a/modules/audio_processing/agc2/saturation_protector.h +++ b/modules/audio_processing/agc2/saturation_protector.h @@ -24,6 +24,9 @@ class SaturationProtector { public: explicit SaturationProtector(ApmDataDumper* apm_data_dumper); + SaturationProtector(ApmDataDumper* apm_data_dumper, + float extra_saturation_margin_db); + // Update and return margin estimate. This method should be called // whenever a frame is reliably classified as 'speech'. // @@ -60,7 +63,7 @@ class SaturationProtector { float last_margin_; PeakEnveloper peak_enveloper_; - float extra_saturation_margin_db_; + const float extra_saturation_margin_db_; }; } // namespace webrtc diff --git a/modules/audio_processing/gain_controller2.cc b/modules/audio_processing/gain_controller2.cc index 29af962e74..0921b28855 100644 --- a/modules/audio_processing/gain_controller2.cc +++ b/modules/audio_processing/gain_controller2.cc @@ -25,7 +25,7 @@ GainController2::GainController2() : data_dumper_( new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))), fixed_gain_controller_(data_dumper_.get()), - adaptive_agc_(data_dumper_.get()) {} + adaptive_agc_(new AdaptiveAgc(data_dumper_.get())) {} GainController2::~GainController2() = default; @@ -43,14 +43,15 @@ void GainController2::Process(AudioBuffer* audio) { AudioFrameView float_frame(audio->channels_f(), audio->num_channels(), audio->num_frames()); if (adaptive_digital_mode_) { - adaptive_agc_.Process(float_frame, fixed_gain_controller_.LastAudioLevel()); + adaptive_agc_->Process(float_frame, + fixed_gain_controller_.LastAudioLevel()); } fixed_gain_controller_.Process(float_frame); } void GainController2::NotifyAnalogLevel(int level) { if (analog_level_ != level && adaptive_digital_mode_) { - adaptive_agc_.Reset(); + adaptive_agc_->Reset(); } analog_level_ = level; } @@ -61,11 +62,15 @@ void GainController2::ApplyConfig( config_ = config; fixed_gain_controller_.SetGain(config_.fixed_gain_db); adaptive_digital_mode_ = config_.adaptive_digital_mode; + adaptive_agc_.reset( + new AdaptiveAgc(data_dumper_.get(), config_.extra_saturation_margin_db)); } bool GainController2::Validate( const AudioProcessing::Config::GainController2& config) { - return config.fixed_gain_db >= 0.f; + return config.fixed_gain_db >= 0.f && + config.extra_saturation_margin_db >= 0.f && + config.extra_saturation_margin_db <= 100.f; } std::string GainController2::ToString( diff --git a/modules/audio_processing/gain_controller2.h b/modules/audio_processing/gain_controller2.h index b4727d0e77..b49b8d0992 100644 --- a/modules/audio_processing/gain_controller2.h +++ b/modules/audio_processing/gain_controller2.h @@ -45,7 +45,7 @@ class GainController2 { std::unique_ptr data_dumper_; FixedGainController fixed_gain_controller_; AudioProcessing::Config::GainController2 config_; - AdaptiveAgc adaptive_agc_; + std::unique_ptr adaptive_agc_; int analog_level_ = -1; bool adaptive_digital_mode_ = true; diff --git a/modules/audio_processing/gain_controller2_unittest.cc b/modules/audio_processing/gain_controller2_unittest.cc index 5bedccd64a..bff64b6c0c 100644 --- a/modules/audio_processing/gain_controller2_unittest.cc +++ b/modules/audio_processing/gain_controller2_unittest.cc @@ -13,6 +13,8 @@ #include "api/array_view.h" #include "modules/audio_processing/audio_buffer.h" #include "modules/audio_processing/gain_controller2.h" +#include "modules/audio_processing/test/audio_buffer_tools.h" +#include "modules/audio_processing/test/bitexactness_tools.h" #include "rtc_base/checks.h" #include "test/gtest.h" @@ -87,5 +89,64 @@ TEST(GainController2, Usage) { EXPECT_LT(sample_value, ab.channels_f()[0][0]); } +float GainAfterProcessingFile(GainController2* gain_controller) { + // Set up an AudioBuffer to be filled from the speech file. + const StreamConfig capture_config(AudioProcessing::kSampleRate48kHz, kStereo, + false); + AudioBuffer ab(capture_config.num_frames(), capture_config.num_channels(), + capture_config.num_frames(), capture_config.num_channels(), + capture_config.num_frames()); + test::InputAudioFile capture_file( + test::GetApmCaptureTestVectorFileName(AudioProcessing::kSampleRate48kHz)); + std::vector capture_input(capture_config.num_frames() * + capture_config.num_channels()); + + // The file should contain at least this many frames. Every iteration, we put + // a frame through the gain controller. + const int kNumFramesToProcess = 100; + for (int frame_no = 0; frame_no < kNumFramesToProcess; ++frame_no) { + ReadFloatSamplesFromStereoFile(capture_config.num_frames(), + capture_config.num_channels(), &capture_file, + capture_input); + + test::CopyVectorToAudioBuffer(capture_config, capture_input, &ab); + gain_controller->Process(&ab); + } + + // Send in a last frame with values constant 1 (It's low enough to detect high + // gain, and for ease of computation). The applied gain is the result. + constexpr float sample_value = 1.f; + SetAudioBufferSamples(sample_value, &ab); + gain_controller->Process(&ab); + return ab.channels_f()[0][0]; +} + +TEST(GainController2, UsageSaturationMargin) { + GainController2 gain_controller2; + gain_controller2.Initialize(AudioProcessing::kSampleRate48kHz); + + AudioProcessing::Config::GainController2 config; + // Check that samples are not amplified as much when extra margin is + // high. They should not be amplified at all, but anly after convergence. GC2 + // starts with a gain, and it takes time until it's down to 0db. + config.extra_saturation_margin_db = 50.f; + config.fixed_gain_db = 0.f; + gain_controller2.ApplyConfig(config); + + EXPECT_LT(GainAfterProcessingFile(&gain_controller2), 2.f); +} + +TEST(GainController2, UsageNoSaturationMargin) { + GainController2 gain_controller2; + gain_controller2.Initialize(AudioProcessing::kSampleRate48kHz); + + AudioProcessing::Config::GainController2 config; + // Check that some gain is applied if there is no margin. + config.extra_saturation_margin_db = 0.f; + config.fixed_gain_db = 0.f; + gain_controller2.ApplyConfig(config); + + EXPECT_GT(GainAfterProcessingFile(&gain_controller2), 2.f); +} } // namespace test } // namespace webrtc diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h index bd4a18e613..0dc747e6d0 100644 --- a/modules/audio_processing/include/audio_processing.h +++ b/modules/audio_processing/include/audio_processing.h @@ -273,6 +273,7 @@ class AudioProcessing : public rtc::RefCountInterface { struct GainController2 { bool enabled = false; bool adaptive_digital_mode = true; + float extra_saturation_margin_db = 2.f; float fixed_gain_db = 0.f; } gain_controller2;