diff --git a/modules/audio_processing/agc2/gain_applier.cc b/modules/audio_processing/agc2/gain_applier.cc index a777cf3165..f9e276d3a8 100644 --- a/modules/audio_processing/agc2/gain_applier.cc +++ b/modules/audio_processing/agc2/gain_applier.cc @@ -88,6 +88,7 @@ void GainApplier::ApplyGain(AudioFrameView signal) { } } +// TODO(bugs.webrtc.org/7494): Remove once switched to gains in dB. void GainApplier::SetGainFactor(float gain_factor) { RTC_DCHECK_GT(gain_factor, 0.f); current_gain_factor_ = gain_factor; diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc index 318314b674..ee9f5c782e 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -412,7 +412,7 @@ void AudioProcessingImpl::InitializeLocked() { InitializeVoiceDetector(); InitializeResidualEchoDetector(); InitializeEchoController(); - InitializeGainController2(); + InitializeGainController2(/*config_has_changed=*/true); InitializeNoiseSuppressor(); InitializeAnalyzer(); InitializePostProcessor(); @@ -589,9 +589,7 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) { config_.gain_controller2 = AudioProcessing::Config::GainController2(); } - if (agc2_config_changed) { - InitializeGainController2(); - } + InitializeGainController2(agc2_config_changed); if (pre_amplifier_config_changed || gain_adjustment_config_changed) { InitializeCaptureLevelsAdjuster(); @@ -873,7 +871,7 @@ void AudioProcessingImpl::HandleCaptureRuntimeSettings() { float value; setting.GetFloat(&value); config_.gain_controller2.fixed_digital.gain_db = value; - submodules_.gain_controller2->ApplyConfig(config_.gain_controller2); + submodules_.gain_controller2->SetFixedGainDb(value); } break; } @@ -1936,19 +1934,18 @@ void AudioProcessingImpl::InitializeGainController1() { capture_.capture_output_used); } -void AudioProcessingImpl::InitializeGainController2() { - if (config_.gain_controller2.enabled) { - if (!submodules_.gain_controller2) { - // TODO(alessiob): Move the injected gain controller once injection is - // implemented. - submodules_.gain_controller2.reset(new GainController2()); - } - - submodules_.gain_controller2->Initialize(proc_fullband_sample_rate_hz(), - num_input_channels()); - submodules_.gain_controller2->ApplyConfig(config_.gain_controller2); - } else { +void AudioProcessingImpl::InitializeGainController2(bool config_has_changed) { + if (!config_has_changed) { + return; + } + if (!config_.gain_controller2.enabled) { submodules_.gain_controller2.reset(); + return; + } + if (!submodules_.gain_controller2 || config_has_changed) { + submodules_.gain_controller2 = std::make_unique( + config_.gain_controller2, proc_fullband_sample_rate_hz(), + num_input_channels()); } } diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h index 026152b576..89f5d63783 100644 --- a/modules/audio_processing/audio_processing_impl.h +++ b/modules/audio_processing/audio_processing_impl.h @@ -267,7 +267,7 @@ class AudioProcessingImpl : public AudioProcessing { void InitializeEchoController() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_render_, mutex_capture_); - // Initializations of capture-only submodules, requiring the capture lock + // Initializations of capture-only sub-modules, requiring the capture lock // already acquired. void InitializeHighPassFilter(bool forced_reset) RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); @@ -275,7 +275,10 @@ class AudioProcessingImpl : public AudioProcessing { void InitializeGainController1() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); void InitializeTransientSuppressor() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); - void InitializeGainController2() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); + // Initializes the `GainController2` sub-module. If the sub-module is enabled + // and `config_has_changed` is true, recreates the sub-module. + void InitializeGainController2(bool config_has_changed) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); void InitializeNoiseSuppressor() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); void InitializeCaptureLevelsAdjuster() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); diff --git a/modules/audio_processing/gain_controller2.cc b/modules/audio_processing/gain_controller2.cc index 92cda69f0a..416bc78772 100644 --- a/modules/audio_processing/gain_controller2.cc +++ b/modules/audio_processing/gain_controller2.cc @@ -10,6 +10,9 @@ #include "modules/audio_processing/gain_controller2.h" +#include +#include + #include "common_audio/include/audio_util.h" #include "modules/audio_processing/audio_buffer.h" #include "modules/audio_processing/include/audio_frame_view.h" @@ -21,24 +24,53 @@ namespace webrtc { namespace { + +using Agc2Config = AudioProcessing::Config::GainController2; + +constexpr int kUnspecifiedAnalogLevel = -1; constexpr int kLogLimiterStatsPeriodMs = 30'000; constexpr int kFrameLengthMs = 10; constexpr int kLogLimiterStatsPeriodNumFrames = kLogLimiterStatsPeriodMs / kFrameLengthMs; + +// Creates an adaptive digital gain controller if enabled. +std::unique_ptr CreateAdaptiveDigitalController( + const Agc2Config::AdaptiveDigital& config, + int sample_rate_hz, + int num_channels, + ApmDataDumper* data_dumper) { + if (config.enabled) { + // TODO(bugs.webrtc.org/7494): Also init with sample rate and num channels. + auto controller = std::make_unique(data_dumper, config); + // TODO(bugs.webrtc.org/7494): Remove once passed to the ctor. + controller->Initialize(sample_rate_hz, num_channels); + return controller; + } + return nullptr; +} + } // namespace int GainController2::instance_count_ = 0; -GainController2::GainController2() +GainController2::GainController2(const Agc2Config& config, + int sample_rate_hz, + int num_channels) : data_dumper_(rtc::AtomicOps::Increment(&instance_count_)), fixed_gain_applier_(/*hard_clip_samples=*/false, /*initial_gain_factor=*/0.0f), - limiter_(static_cast(48000), &data_dumper_, "Agc2"), - calls_since_last_limiter_log_(0) { - if (config_.adaptive_digital.enabled) { - adaptive_digital_controller_ = - std::make_unique(&data_dumper_, config_.adaptive_digital); - } + adaptive_digital_controller_( + CreateAdaptiveDigitalController(config.adaptive_digital, + sample_rate_hz, + num_channels, + &data_dumper_)), + limiter_(sample_rate_hz, &data_dumper_, /*histogram_name_prefix=*/"Agc2"), + calls_since_last_limiter_log_(0), + analog_level_(kUnspecifiedAnalogLevel) { + RTC_DCHECK(Validate(config)); + data_dumper_.InitiateNewSetOfRecordings(); + // TODO(bugs.webrtc.org/7494): Set gain when `fixed_gain_applier_` is init'd. + fixed_gain_applier_.SetGainFactor(DbToRatio(config.fixed_digital.gain_db)); } GainController2::~GainController2() = default; @@ -48,13 +80,24 @@ void GainController2::Initialize(int sample_rate_hz, int num_channels) { sample_rate_hz == AudioProcessing::kSampleRate16kHz || sample_rate_hz == AudioProcessing::kSampleRate32kHz || sample_rate_hz == AudioProcessing::kSampleRate48kHz); + // TODO(bugs.webrtc.org/7494): Initialize `fixed_gain_applier_`. limiter_.SetSampleRate(sample_rate_hz); if (adaptive_digital_controller_) { adaptive_digital_controller_->Initialize(sample_rate_hz, num_channels); } data_dumper_.InitiateNewSetOfRecordings(); - data_dumper_.DumpRaw("sample_rate_hz", sample_rate_hz); calls_since_last_limiter_log_ = 0; + analog_level_ = kUnspecifiedAnalogLevel; +} + +void GainController2::SetFixedGainDb(float gain_db) { + const float gain_factor = DbToRatio(gain_db); + if (fixed_gain_applier_.GetGainFactor() != gain_factor) { + // Reset the limiter to quickly react on abrupt level changes caused by + // large changes of the fixed gain. + limiter_.Reset(); + } + fixed_gain_applier_.SetGainFactor(gain_factor); } void GainController2::Process(AudioBuffer* audio) { @@ -87,25 +130,6 @@ void GainController2::NotifyAnalogLevel(int level) { analog_level_ = level; } -void GainController2::ApplyConfig( - const AudioProcessing::Config::GainController2& config) { - RTC_DCHECK(Validate(config)); - - config_ = config; - if (config.fixed_digital.gain_db != config_.fixed_digital.gain_db) { - // Reset the limiter to quickly react on abrupt level changes caused by - // large changes of the fixed gain. - limiter_.Reset(); - } - fixed_gain_applier_.SetGainFactor(DbToRatio(config_.fixed_digital.gain_db)); - if (config_.adaptive_digital.enabled) { - adaptive_digital_controller_ = - std::make_unique(&data_dumper_, config_.adaptive_digital); - } else { - adaptive_digital_controller_.reset(); - } -} - bool GainController2::Validate( const AudioProcessing::Config::GainController2& config) { const auto& fixed = config.fixed_digital; diff --git a/modules/audio_processing/gain_controller2.h b/modules/audio_processing/gain_controller2.h index 1a382f6e12..cfe51c2241 100644 --- a/modules/audio_processing/gain_controller2.h +++ b/modules/audio_processing/gain_controller2.h @@ -28,27 +28,35 @@ class AudioBuffer; // microphone gain and/or applying digital gain. class GainController2 { public: - GainController2(); + GainController2(const AudioProcessing::Config::GainController2& config, + int sample_rate_hz, + int num_channels); GainController2(const GainController2&) = delete; GainController2& operator=(const GainController2&) = delete; ~GainController2(); + // Detects and handles changes of sample rate and/or number of channels. void Initialize(int sample_rate_hz, int num_channels); + + // Sets the fixed digital gain. + void SetFixedGainDb(float gain_db); + + // Applies fixed and adaptive digital gains to `audio` and runs a limiter. void Process(AudioBuffer* audio); + + // Handles analog level changes. void NotifyAnalogLevel(int level); - void ApplyConfig(const AudioProcessing::Config::GainController2& config); static bool Validate(const AudioProcessing::Config::GainController2& config); private: static int instance_count_; ApmDataDumper data_dumper_; - AudioProcessing::Config::GainController2 config_; GainApplier fixed_gain_applier_; std::unique_ptr adaptive_digital_controller_; Limiter limiter_; int calls_since_last_limiter_log_; - int analog_level_ = -1; + int analog_level_; }; } // namespace webrtc diff --git a/modules/audio_processing/gain_controller2_unittest.cc b/modules/audio_processing/gain_controller2_unittest.cc index d1c1f5b8d4..b18d943a78 100644 --- a/modules/audio_processing/gain_controller2_unittest.cc +++ b/modules/audio_processing/gain_controller2_unittest.cc @@ -14,6 +14,7 @@ #include #include #include +#include #include "api/array_view.h" #include "modules/audio_processing/agc2/agc2_testing_common.h" @@ -27,6 +28,8 @@ namespace webrtc { namespace test { namespace { +using Agc2Config = AudioProcessing::Config::GainController2; + // Sets all the samples in `ab` to `value`. void SetAudioBufferSamples(float value, AudioBuffer& ab) { for (size_t k = 0; k < ab.num_channels(); ++k) { @@ -51,33 +54,26 @@ float RunAgc2WithConstantInput(GainController2& agc2, return ab.channels()[0][num_samples - 1]; } -AudioProcessing::Config::GainController2 CreateAgc2FixedDigitalModeConfig( - float fixed_gain_db) { - AudioProcessing::Config::GainController2 config; - config.adaptive_digital.enabled = false; - config.fixed_digital.gain_db = fixed_gain_db; - EXPECT_TRUE(GainController2::Validate(config)); - return config; -} - std::unique_ptr CreateAgc2FixedDigitalMode( float fixed_gain_db, int sample_rate_hz) { - auto agc2 = std::make_unique(); - agc2->ApplyConfig(CreateAgc2FixedDigitalModeConfig(fixed_gain_db)); - agc2->Initialize(sample_rate_hz, /*num_channels=*/1); - return agc2; + Agc2Config config; + config.adaptive_digital.enabled = false; + config.fixed_digital.gain_db = fixed_gain_db; + EXPECT_TRUE(GainController2::Validate(config)); + return std::make_unique(config, sample_rate_hz, + /*num_channels=*/1); } } // namespace TEST(GainController2, CheckDefaultConfig) { - AudioProcessing::Config::GainController2 config; + Agc2Config config; EXPECT_TRUE(GainController2::Validate(config)); } TEST(GainController2, CheckFixedDigitalConfig) { - AudioProcessing::Config::GainController2 config; + Agc2Config config; // Attenuation is not allowed. config.fixed_digital.gain_db = -5.0f; EXPECT_FALSE(GainController2::Validate(config)); @@ -90,7 +86,7 @@ TEST(GainController2, CheckFixedDigitalConfig) { } TEST(GainController2, CheckHeadroomDb) { - AudioProcessing::Config::GainController2 config; + Agc2Config config; config.adaptive_digital.headroom_db = -1.0f; EXPECT_FALSE(GainController2::Validate(config)); config.adaptive_digital.headroom_db = 0.0f; @@ -100,7 +96,7 @@ TEST(GainController2, CheckHeadroomDb) { } TEST(GainController2, CheckMaxGainDb) { - AudioProcessing::Config::GainController2 config; + Agc2Config config; config.adaptive_digital.max_gain_db = -1.0f; EXPECT_FALSE(GainController2::Validate(config)); config.adaptive_digital.max_gain_db = 0.0f; @@ -110,7 +106,7 @@ TEST(GainController2, CheckMaxGainDb) { } TEST(GainController2, CheckInitialGainDb) { - AudioProcessing::Config::GainController2 config; + Agc2Config config; config.adaptive_digital.initial_gain_db = -1.0f; EXPECT_FALSE(GainController2::Validate(config)); config.adaptive_digital.initial_gain_db = 0.0f; @@ -120,7 +116,7 @@ TEST(GainController2, CheckInitialGainDb) { } TEST(GainController2, CheckAdaptiveDigitalMaxGainChangeSpeedConfig) { - AudioProcessing::Config::GainController2 config; + Agc2Config config; config.adaptive_digital.max_gain_change_db_per_second = -1.0f; EXPECT_FALSE(GainController2::Validate(config)); config.adaptive_digital.max_gain_change_db_per_second = 0.0f; @@ -130,7 +126,7 @@ TEST(GainController2, CheckAdaptiveDigitalMaxGainChangeSpeedConfig) { } TEST(GainController2, CheckAdaptiveDigitalMaxOutputNoiseLevelConfig) { - AudioProcessing::Config::GainController2 config; + Agc2Config config; config.adaptive_digital.max_output_noise_level_dbfs = 5.0f; EXPECT_FALSE(GainController2::Validate(config)); config.adaptive_digital.max_output_noise_level_dbfs = 0.0f; @@ -141,9 +137,9 @@ TEST(GainController2, CheckAdaptiveDigitalMaxOutputNoiseLevelConfig) { // Checks that the default config is applied. TEST(GainController2, ApplyDefaultConfig) { - auto gain_controller2 = std::make_unique(); - AudioProcessing::Config::GainController2 config; - gain_controller2->ApplyConfig(config); + auto gain_controller2 = std::make_unique( + Agc2Config{}, /*sample_rate_hz=*/16000, /*num_channels=*/2); + EXPECT_TRUE(gain_controller2.get()); } TEST(GainController2FixedDigital, GainShouldChangeOnSetGain) { @@ -161,7 +157,7 @@ TEST(GainController2FixedDigital, GainShouldChangeOnSetGain) { kInputLevel); // +20 db should increase signal by a factor of 10. - agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGain20Db)); + agc2_fixed->SetFixedGainDb(kGain20Db); EXPECT_FLOAT_EQ(RunAgc2WithConstantInput(*agc2_fixed, kInputLevel, kNumFrames, kSampleRateHz), kInputLevel * 10); @@ -185,51 +181,35 @@ TEST(GainController2FixedDigital, ChangeFixedGainShouldBeFastAndTimeInvariant) { *agc2_fixed, kInputLevel, kNumFrames, kSampleRateHz); // Increase gain. - agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGainDbHigh)); + agc2_fixed->SetFixedGainDb(kGainDbHigh); static_cast(RunAgc2WithConstantInput(*agc2_fixed, kInputLevel, kNumFrames, kSampleRateHz)); // Back to the lower gain. - agc2_fixed->ApplyConfig(CreateAgc2FixedDigitalModeConfig(kGainDbLow)); + agc2_fixed->SetFixedGainDb(kGainDbLow); const float output_level_post = RunAgc2WithConstantInput( *agc2_fixed, kInputLevel, kNumFrames, kSampleRateHz); EXPECT_EQ(output_level_pre, output_level_post); } -struct FixedDigitalTestParams { - FixedDigitalTestParams(float gain_db_min, - float gain_db_max, - size_t sample_rate, - bool saturation_expected) - : gain_db_min(gain_db_min), - gain_db_max(gain_db_max), - sample_rate(sample_rate), - saturation_expected(saturation_expected) {} - float gain_db_min; - float gain_db_max; - size_t sample_rate; - bool saturation_expected; +class FixedDigitalTest + : public ::testing::TestWithParam> { + protected: + float gain_db_min() const { return std::get<0>(GetParam()); } + float gain_db_max() const { return std::get<1>(GetParam()); } + int sample_rate_hz() const { return std::get<2>(GetParam()); } + bool saturation_expected() const { return std::get<3>(GetParam()); } }; -class FixedDigitalTest - : public ::testing::Test, - public ::testing::WithParamInterface {}; - TEST_P(FixedDigitalTest, CheckSaturationBehaviorWithLimiter) { - const float kInputLevel = 32767.0f; - const size_t kNumFrames = 5; - - const auto params = GetParam(); - - const auto gains_db = - test::LinSpace(params.gain_db_min, params.gain_db_max, 10); - for (const auto gain_db : gains_db) { - SCOPED_TRACE(std::to_string(gain_db)); - auto agc2_fixed = CreateAgc2FixedDigitalMode(gain_db, params.sample_rate); - const float processed_sample = RunAgc2WithConstantInput( - *agc2_fixed, kInputLevel, kNumFrames, params.sample_rate); - if (params.saturation_expected) { + for (const float gain_db : test::LinSpace(gain_db_min(), gain_db_max(), 10)) { + SCOPED_TRACE(gain_db); + auto agc2_fixed = CreateAgc2FixedDigitalMode(gain_db, sample_rate_hz()); + const float processed_sample = + RunAgc2WithConstantInput(*agc2_fixed, /*input_level=*/32767.0f, + /*num_frames=*/5, sample_rate_hz()); + if (saturation_expected()) { EXPECT_FLOAT_EQ(processed_sample, 32767.0f); } else { EXPECT_LT(processed_sample, 32767.0f); @@ -244,47 +224,43 @@ INSTANTIATE_TEST_SUITE_P( ::testing::Values( // When gain < `test::kLimiterMaxInputLevelDbFs`, the limiter will not // saturate the signal (at any sample rate). - FixedDigitalTestParams(0.1f, - test::kLimiterMaxInputLevelDbFs - 0.01f, - 8000, - false), - FixedDigitalTestParams(0.1, - test::kLimiterMaxInputLevelDbFs - 0.01f, - 48000, - false), + std::make_tuple(0.1f, + test::kLimiterMaxInputLevelDbFs - 0.01f, + 8000, + false), + std::make_tuple(0.1, + test::kLimiterMaxInputLevelDbFs - 0.01f, + 48000, + false), // When gain > `test::kLimiterMaxInputLevelDbFs`, the limiter will // saturate the signal (at any sample rate). - FixedDigitalTestParams(test::kLimiterMaxInputLevelDbFs + 0.01f, - 10.0f, - 8000, - true), - FixedDigitalTestParams(test::kLimiterMaxInputLevelDbFs + 0.01f, - 10.0f, - 48000, - true))); + std::make_tuple(test::kLimiterMaxInputLevelDbFs + 0.01f, + 10.0f, + 8000, + true), + std::make_tuple(test::kLimiterMaxInputLevelDbFs + 0.01f, + 10.0f, + 48000, + true))); // Processes a test audio file and checks that the gain applied at the end of // the recording is close to the expected value. TEST(GainController2, CheckFinalGainWithAdaptiveDigitalController) { - // Create AGC2 enabling only the adaptive digital controller. - GainController2 agc2; - AudioProcessing::Config::GainController2 config; - config.fixed_digital.gain_db = 0.0f; - config.adaptive_digital.enabled = true; - agc2.ApplyConfig(config); - - // The input audio is a 48k stereo recording. constexpr int kSampleRateHz = AudioProcessing::kSampleRate48kHz; constexpr int kStereo = 2; + + // Create AGC2 enabling only the adaptive digital controller. + Agc2Config config; + config.fixed_digital.gain_db = 0.0f; + config.adaptive_digital.enabled = true; + GainController2 agc2(config, kSampleRateHz, kStereo); + test::InputAudioFile input_file( test::GetApmCaptureTestVectorFileName(kSampleRateHz), /*loop_at_end=*/true); const StreamConfig stream_config(kSampleRateHz, kStereo, /*has_keyboard=*/false); - // Initialize AGC2 for the used input. - agc2.Initialize(kSampleRateHz, kStereo); - // Init buffers. constexpr int kFrameDurationMs = 10; std::vector frame(kStereo * stream_config.num_frames());