diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn index 8edf6fe3c5..e1f68772eb 100644 --- a/modules/audio_processing/BUILD.gn +++ b/modules/audio_processing/BUILD.gn @@ -193,6 +193,7 @@ rtc_library("audio_processing") { "../../rtc_base:sanitizer", "../../rtc_base:swap_queue", "../../rtc_base:timeutils", + "../../rtc_base/experiments:field_trial_parser", "../../rtc_base/synchronization:mutex", "../../rtc_base/system:rtc_export", "../../system_wrappers", diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc index 96193fb1a7..52f2fcb14c 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -31,6 +31,7 @@ #include "modules/audio_processing/logging/apm_data_dumper.h" #include "modules/audio_processing/optionally_built_submodule_creators.h" #include "rtc_base/checks.h" +#include "rtc_base/experiments/field_trial_parser.h" #include "rtc_base/logging.h" #include "rtc_base/time_utils.h" #include "rtc_base/trace_event.h" @@ -144,8 +145,6 @@ void PackRenderAudioBufferForEchoDetector(const AudioBuffer& audio, audio.channels_const()[0] + audio.num_frames()); } -constexpr int kUnspecifiedDataDumpInputVolume = -100; - // Options for gracefully handling processing errors. enum class FormatErrorOutputOption { kOutputExactCopyOfInput, @@ -326,6 +325,125 @@ int HandleUnsupportedAudioFormats(const float* const* src, return error_code; } +const absl::optional +GetInputVolumeControllerConfigOverride() { + constexpr char kInputVolumeControllerFieldTrial[] = + "WebRTC-Audio-InputVolumeControllerExperiment"; + + if (!field_trial::IsEnabled(kInputVolumeControllerFieldTrial)) { + return absl::nullopt; + } + + constexpr InputVolumeController::Config kDefaultConfig; + + FieldTrialFlag enabled("Enabled", false); + FieldTrialConstrained clipped_level_min( + "clipped_level_min", kDefaultConfig.clipped_level_min, 0, 255); + FieldTrialConstrained clipped_level_step( + "clipped_level_step", kDefaultConfig.clipped_level_step, 0, 255); + FieldTrialConstrained clipped_ratio_threshold( + "clipped_ratio_threshold", kDefaultConfig.clipped_ratio_threshold, 0, 1); + FieldTrialConstrained clipped_wait_frames( + "clipped_wait_frames", kDefaultConfig.clipped_wait_frames, 0, + absl::nullopt); + FieldTrialParameter enable_clipping_predictor( + "enable_clipping_predictor", kDefaultConfig.enable_clipping_predictor); + FieldTrialConstrained target_range_max_dbfs( + "target_range_max_dbfs", kDefaultConfig.target_range_max_dbfs, -90, 30); + FieldTrialConstrained target_range_min_dbfs( + "target_range_min_dbfs", kDefaultConfig.target_range_min_dbfs, -90, 30); + FieldTrialConstrained update_input_volume_wait_frames( + "update_input_volume_wait_frames", + kDefaultConfig.update_input_volume_wait_frames, 0, absl::nullopt); + FieldTrialConstrained speech_probability_threshold( + "speech_probability_threshold", + kDefaultConfig.speech_probability_threshold, 0, 1); + FieldTrialConstrained speech_ratio_threshold( + "speech_ratio_threshold", kDefaultConfig.speech_ratio_threshold, 0, 1); + + // Field-trial based override for the input volume controller config. + const std::string field_trial_name = + field_trial::FindFullName(kInputVolumeControllerFieldTrial); + + ParseFieldTrial({&enabled, &clipped_level_min, &clipped_level_step, + &clipped_ratio_threshold, &clipped_wait_frames, + &enable_clipping_predictor, &target_range_max_dbfs, + &target_range_min_dbfs, &update_input_volume_wait_frames, + &speech_probability_threshold, &speech_ratio_threshold}, + field_trial_name); + + // Checked already by `IsEnabled()` before parsing, therefore always true. + RTC_DCHECK(enabled); + + return InputVolumeController::Config{ + .clipped_level_min = static_cast(clipped_level_min.Get()), + .clipped_level_step = static_cast(clipped_level_step.Get()), + .clipped_ratio_threshold = + static_cast(clipped_ratio_threshold.Get()), + .clipped_wait_frames = static_cast(clipped_wait_frames.Get()), + .enable_clipping_predictor = + static_cast(enable_clipping_predictor.Get()), + .target_range_max_dbfs = static_cast(target_range_max_dbfs.Get()), + .target_range_min_dbfs = static_cast(target_range_min_dbfs.Get()), + .update_input_volume_wait_frames = + static_cast(update_input_volume_wait_frames.Get()), + .speech_probability_threshold = + static_cast(speech_probability_threshold.Get()), + .speech_ratio_threshold = + static_cast(speech_ratio_threshold.Get()), + }; +} + +// Switches all gain control to AGC2 if experimenting with input volume +// controller. +const AudioProcessing::Config AdjustConfig( + const AudioProcessing::Config& config, + const absl::optional& + input_volume_controller_config_override) { + const bool analog_agc_enabled = + config.gain_controller1.enabled && + (config.gain_controller1.mode == + AudioProcessing::Config::GainController1::kAdaptiveAnalog || + config.gain_controller1.analog_gain_controller.enabled); + + // Do not update the config if none of the analog AGCs is active + // regardless of the input volume controller override. + if (!analog_agc_enabled || + !input_volume_controller_config_override.has_value()) { + return config; + } + + const bool hybrid_agc_config_detected = + config.gain_controller1.enabled && + config.gain_controller1.analog_gain_controller.enabled && + !config.gain_controller1.analog_gain_controller.enable_digital_adaptive && + config.gain_controller2.enabled && + config.gain_controller2.adaptive_digital.enabled; + + const bool full_agc1_config_detected = + config.gain_controller1.enabled && + config.gain_controller1.analog_gain_controller.enabled && + config.gain_controller1.analog_gain_controller.enable_digital_adaptive && + !config.gain_controller2.enabled; + + if (hybrid_agc_config_detected == full_agc1_config_detected || + config.gain_controller2.input_volume_controller.enabled) { + RTC_LOG(LS_ERROR) << "Unexpected AGC config: Config not adjusted."; + return config; + } + + AudioProcessing::Config adjusted_config = config; + adjusted_config.gain_controller1.enabled = false; + adjusted_config.gain_controller1.analog_gain_controller.enabled = false; + adjusted_config.gain_controller2.enabled = true; + adjusted_config.gain_controller2.adaptive_digital.enabled = true; + adjusted_config.gain_controller2.input_volume_controller.enabled = true; + + return adjusted_config; +} + +constexpr int kUnspecifiedDataDumpInputVolume = -100; + } // namespace // Throughout webrtc, it's assumed that success is represented by zero. @@ -448,6 +566,8 @@ AudioProcessingImpl::AudioProcessingImpl( : data_dumper_(new ApmDataDumper(instance_count_.fetch_add(1) + 1)), use_setup_specific_default_aec3_config_( UseSetupSpecificDefaultAec3Congfig()), + input_volume_controller_config_override_( + GetInputVolumeControllerConfigOverride()), use_denormal_disabler_( !field_trial::IsEnabled("WebRTC-ApmDenormalDisablerKillSwitch")), transient_suppressor_vad_mode_(GetTransientSuppressorVadMode()), @@ -456,7 +576,7 @@ AudioProcessingImpl::AudioProcessingImpl( capture_runtime_settings_enqueuer_(&capture_runtime_settings_), render_runtime_settings_enqueuer_(&render_runtime_settings_), echo_control_factory_(std::move(echo_control_factory)), - config_(config), + config_(AdjustConfig(config, input_volume_controller_config_override_)), submodule_states_(!!capture_post_processor, !!render_pre_processor, !!capture_analyzer), @@ -490,6 +610,8 @@ AudioProcessingImpl::AudioProcessingImpl( RTC_LOG(LS_INFO) << "Denormal disabler unsupported"; } + RTC_LOG(LS_INFO) << "AudioProcessing: " << config_.ToString(); + // Mark Echo Controller enabled if a factory is injected. capture_nonlocked_.echo_controller_enabled = static_cast(echo_control_factory_); @@ -681,46 +803,57 @@ void AudioProcessingImpl::InitializeLocked(const ProcessingConfig& config) { } void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) { - RTC_LOG(LS_INFO) << "AudioProcessing::ApplyConfig: " << config.ToString(); - // Run in a single-threaded manner when applying the settings. MutexLock lock_render(&mutex_render_); MutexLock lock_capture(&mutex_capture_); + // TODO(bugs.webrtc.org/7494): Replace `adjusted_config` with `config` after + // "WebRTC-Audio-InputVolumeControllerExperiment" field trial is removed. + const auto adjusted_config = + AdjustConfig(config, input_volume_controller_config_override_); + + RTC_LOG(LS_INFO) << "AudioProcessing::ApplyConfig: " + << adjusted_config.ToString(); + const bool pipeline_config_changed = config_.pipeline.multi_channel_render != - config.pipeline.multi_channel_render || + adjusted_config.pipeline.multi_channel_render || config_.pipeline.multi_channel_capture != - config.pipeline.multi_channel_capture || + adjusted_config.pipeline.multi_channel_capture || config_.pipeline.maximum_internal_processing_rate != - config.pipeline.maximum_internal_processing_rate; + adjusted_config.pipeline.maximum_internal_processing_rate; const bool aec_config_changed = - config_.echo_canceller.enabled != config.echo_canceller.enabled || - config_.echo_canceller.mobile_mode != config.echo_canceller.mobile_mode; + config_.echo_canceller.enabled != + adjusted_config.echo_canceller.enabled || + config_.echo_canceller.mobile_mode != + adjusted_config.echo_canceller.mobile_mode; const bool agc1_config_changed = - config_.gain_controller1 != config.gain_controller1; + config_.gain_controller1 != adjusted_config.gain_controller1; const bool agc2_config_changed = - config_.gain_controller2 != config.gain_controller2; + config_.gain_controller2 != adjusted_config.gain_controller2; const bool ns_config_changed = - config_.noise_suppression.enabled != config.noise_suppression.enabled || - config_.noise_suppression.level != config.noise_suppression.level; + config_.noise_suppression.enabled != + adjusted_config.noise_suppression.enabled || + config_.noise_suppression.level != + adjusted_config.noise_suppression.level; const bool ts_config_changed = config_.transient_suppression.enabled != - config.transient_suppression.enabled; + adjusted_config.transient_suppression.enabled; const bool pre_amplifier_config_changed = - config_.pre_amplifier.enabled != config.pre_amplifier.enabled || + config_.pre_amplifier.enabled != adjusted_config.pre_amplifier.enabled || config_.pre_amplifier.fixed_gain_factor != - config.pre_amplifier.fixed_gain_factor; + adjusted_config.pre_amplifier.fixed_gain_factor; const bool gain_adjustment_config_changed = - config_.capture_level_adjustment != config.capture_level_adjustment; + config_.capture_level_adjustment != + adjusted_config.capture_level_adjustment; - config_ = config; + config_ = adjusted_config; if (aec_config_changed) { InitializeEchoController(); @@ -2123,8 +2256,10 @@ void AudioProcessingImpl::InitializeGainController2(bool config_has_changed) { const bool use_internal_vad = transient_suppressor_vad_mode_ != TransientSuppressor::VadMode::kRnnVad; submodules_.gain_controller2 = std::make_unique( - config_.gain_controller2, proc_fullband_sample_rate_hz(), - num_input_channels(), use_internal_vad); + config_.gain_controller2, + input_volume_controller_config_override_.value_or( + InputVolumeController::Config{}), + proc_fullband_sample_rate_hz(), num_input_channels(), use_internal_vad); submodules_.gain_controller2->SetCaptureOutputUsed( capture_.capture_output_used); } diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h index 191a3eef6b..9a30c8b9f6 100644 --- a/modules/audio_processing/audio_processing_impl.h +++ b/modules/audio_processing/audio_processing_impl.h @@ -160,6 +160,9 @@ class AudioProcessingImpl : public AudioProcessing { ReinitializeTransientSuppressor); FRIEND_TEST_ALL_PREFIXES(ApmWithSubmodulesExcludedTest, BitexactWithDisabledModules); + FRIEND_TEST_ALL_PREFIXES( + AudioProcessingImplInputVolumeControllerExperimentParametrizedTest, + ConfigAdjustedWhenExperimentEnabled); void set_stream_analog_level_locked(int level) RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); @@ -188,6 +191,12 @@ class AudioProcessingImpl : public AudioProcessing { static std::atomic instance_count_; const bool use_setup_specific_default_aec3_config_; + // TODO(bugs.webrtc.org/7494): Remove the the config when the field trial is + // removed. "WebRTC-Audio-InputVolumeControllerExperiment" field trial + // override for the input volume controller config. + const absl::optional + input_volume_controller_config_override_; + const bool use_denormal_disabler_; const TransientSuppressor::VadMode transient_suppressor_vad_mode_; diff --git a/modules/audio_processing/audio_processing_impl_unittest.cc b/modules/audio_processing/audio_processing_impl_unittest.cc index fea7a8c74e..ea61dae2d5 100644 --- a/modules/audio_processing/audio_processing_impl_unittest.cc +++ b/modules/audio_processing/audio_processing_impl_unittest.cc @@ -1188,4 +1188,282 @@ TEST(AudioProcessingImplTest, EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/135), 135); } +TEST(AudioProcessingImplInputVolumeControllerExperimentTest, + ConfigAdjustedWhenExperimentEnabledAndAgc1AnalogEnabled) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Audio-InputVolumeControllerExperiment/" + "Enabled," + "enable_clipping_predictor:true," + "clipped_level_min:20," + "clipped_level_step:30," + "clipped_ratio_threshold:0.4," + "clipped_wait_frames:50," + "target_range_max_dbfs:-6," + "target_range_min_dbfs:-70," + "update_input_volume_wait_frames:80," + "speech_probability_threshold:0.9," + "speech_ratio_threshold:1.0/"); + + AudioProcessingBuilderForTesting apm_builder; + + // Set a config with analog AGC1 enabled. + AudioProcessing::Config config; + config.gain_controller1.enabled = true; + config.gain_controller1.analog_gain_controller.enabled = true; + config.gain_controller1.analog_gain_controller.enable_digital_adaptive = true; + config.gain_controller2.enabled = false; + config.gain_controller1.mode = + AudioProcessing::Config::GainController1::kAdaptiveAnalog; + + EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled); + + apm_builder.SetConfig(config); + + auto apm = apm_builder.Create(); + auto adjusted_config = apm->GetConfig(); + + // Expect the config to be adjusted. + EXPECT_FALSE(adjusted_config.gain_controller1.enabled); + EXPECT_FALSE(adjusted_config.gain_controller1.analog_gain_controller.enabled); + EXPECT_TRUE(adjusted_config.gain_controller2.enabled); + EXPECT_TRUE(adjusted_config.gain_controller2.adaptive_digital.enabled); + EXPECT_TRUE(adjusted_config.gain_controller2.input_volume_controller.enabled); + + // Change config back and compare. + adjusted_config.gain_controller1.enabled = config.gain_controller1.enabled; + adjusted_config.gain_controller1.analog_gain_controller.enabled = + config.gain_controller1.analog_gain_controller.enabled; + adjusted_config.gain_controller2.enabled = config.gain_controller2.enabled; + adjusted_config.gain_controller2.adaptive_digital.enabled = + config.gain_controller2.adaptive_digital.enabled; + adjusted_config.gain_controller2.input_volume_controller.enabled = + config.gain_controller2.input_volume_controller.enabled; + + EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString())); +} + +TEST(AudioProcessingImplInputVolumeControllerExperimentTest, + ConfigAdjustedWhenExperimentEnabledAndHybridAgcEnabled) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Audio-InputVolumeControllerExperiment/" + "Enabled," + "enable_clipping_predictor:true," + "clipped_level_min:20," + "clipped_level_step:30," + "clipped_ratio_threshold:0.4," + "clipped_wait_frames:50," + "target_range_max_dbfs:-6," + "target_range_min_dbfs:-70," + "update_input_volume_wait_frames:80," + "speech_probability_threshold:0.9," + "speech_ratio_threshold:1.0/"); + + AudioProcessingBuilderForTesting apm_builder; + + // Set a config with hybrid AGC enabled. + AudioProcessing::Config config; + config.gain_controller1.enabled = true; + config.gain_controller1.analog_gain_controller.enabled = true; + config.gain_controller1.analog_gain_controller.enable_digital_adaptive = + false; + config.gain_controller2.enabled = true; + config.gain_controller2.adaptive_digital.enabled = true; + config.gain_controller1.mode = + AudioProcessing::Config::GainController1::kAdaptiveAnalog; + + EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled); + + apm_builder.SetConfig(config); + + auto apm = apm_builder.Create(); + auto adjusted_config = apm->GetConfig(); + + // Expect the config to be adjusted. + EXPECT_FALSE(adjusted_config.gain_controller1.enabled); + EXPECT_FALSE(adjusted_config.gain_controller1.analog_gain_controller.enabled); + EXPECT_TRUE(adjusted_config.gain_controller2.enabled); + EXPECT_TRUE(adjusted_config.gain_controller2.adaptive_digital.enabled); + EXPECT_TRUE(adjusted_config.gain_controller2.input_volume_controller.enabled); + + // Change config back and compare. + adjusted_config.gain_controller1.enabled = config.gain_controller1.enabled; + adjusted_config.gain_controller1.analog_gain_controller.enabled = + config.gain_controller1.analog_gain_controller.enabled; + adjusted_config.gain_controller2.enabled = config.gain_controller2.enabled; + adjusted_config.gain_controller2.adaptive_digital.enabled = + config.gain_controller2.adaptive_digital.enabled; + adjusted_config.gain_controller2.input_volume_controller.enabled = + config.gain_controller2.input_volume_controller.enabled; + + EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString())); +} + +TEST(AudioProcessingImplInputVolumeControllerExperimentTest, + ConfigNotAdjustedWhenExperimentEnabledAndAgc1AnalogNotEnabled) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Audio-InputVolumeControllerExperiment/" + "Enabled," + "enable_clipping_predictor:true," + "clipped_level_min:20," + "clipped_level_step:30," + "clipped_ratio_threshold:0.4," + "clipped_wait_frames:50," + "target_range_max_dbfs:-6," + "target_range_min_dbfs:-70," + "update_input_volume_wait_frames:80," + "speech_probability_threshold:0.9," + "speech_ratio_threshold:1.0/"); + + AudioProcessingBuilderForTesting apm_builder; + + // Set a config with analog AGC1 not enabled. + AudioProcessing::Config config; + config.gain_controller1.enabled = false; + config.gain_controller1.analog_gain_controller.enabled = true; + config.gain_controller1.analog_gain_controller.enable_digital_adaptive = true; + config.gain_controller2.enabled = false; + config.gain_controller1.mode = + AudioProcessing::Config::GainController1::kAdaptiveAnalog; + + EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled); + + apm_builder.SetConfig(config); + + auto apm = apm_builder.Create(); + auto adjusted_config = apm->GetConfig(); + + EXPECT_EQ(config.gain_controller1.enabled, + adjusted_config.gain_controller1.enabled); + EXPECT_EQ(config.gain_controller1.analog_gain_controller.enabled, + adjusted_config.gain_controller1.analog_gain_controller.enabled); + EXPECT_EQ(config.gain_controller2.enabled, + adjusted_config.gain_controller2.enabled); + EXPECT_EQ(config.gain_controller2.adaptive_digital.enabled, + adjusted_config.gain_controller2.adaptive_digital.enabled); + EXPECT_FALSE( + adjusted_config.gain_controller2.input_volume_controller.enabled); + + EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString())); +} + +TEST(AudioProcessingImplInputVolumeControllerExperimentTest, + ConfigNotAdjustedWhenExperimentEnabledAndHybridAgcNotEnabled) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Audio-InputVolumeControllerExperiment/" + "Enabled," + "enable_clipping_predictor:true," + "clipped_level_min:20," + "clipped_level_step:30," + "clipped_ratio_threshold:0.4," + "clipped_wait_frames:50," + "target_range_max_dbfs:-6," + "target_range_min_dbfs:-70," + "update_input_volume_wait_frames:80," + "speech_probability_threshold:0.9," + "speech_ratio_threshold:1.0/"); + + AudioProcessingBuilderForTesting apm_builder; + + // Set a config with hybrid AGC analog not enabled. + AudioProcessing::Config config; + config.gain_controller1.enabled = false; + config.gain_controller1.analog_gain_controller.enabled = true; + config.gain_controller1.analog_gain_controller.enable_digital_adaptive = + false; + config.gain_controller2.enabled = true; + config.gain_controller2.adaptive_digital.enabled = true; + config.gain_controller1.mode = + AudioProcessing::Config::GainController1::kAdaptiveAnalog; + + EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled); + + apm_builder.SetConfig(config); + + auto apm = apm_builder.Create(); + auto adjusted_config = apm->GetConfig(); + + EXPECT_EQ(config.gain_controller1.enabled, + adjusted_config.gain_controller1.enabled); + EXPECT_EQ(config.gain_controller1.analog_gain_controller.enabled, + adjusted_config.gain_controller1.analog_gain_controller.enabled); + EXPECT_EQ(config.gain_controller2.enabled, + adjusted_config.gain_controller2.enabled); + EXPECT_EQ(config.gain_controller2.adaptive_digital.enabled, + adjusted_config.gain_controller2.adaptive_digital.enabled); + EXPECT_FALSE( + adjusted_config.gain_controller2.input_volume_controller.enabled); + + EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString())); +} + +TEST(AudioProcessingImplInputVolumeControllerExperimentTest, + ConfigNotAdjustedWhenExperimentNotEnabledAndAgc1AnalogEnabled) { + AudioProcessingBuilderForTesting apm_builder; + + // Set a config with analog AGC1 analog enabled. + AudioProcessing::Config config; + config.gain_controller1.enabled = true; + config.gain_controller1.analog_gain_controller.enabled = true; + config.gain_controller1.analog_gain_controller.enable_digital_adaptive = true; + config.gain_controller2.enabled = false; + config.gain_controller1.mode = + AudioProcessing::Config::GainController1::kAdaptiveAnalog; + + EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled); + + apm_builder.SetConfig(config); + + auto apm = apm_builder.Create(); + auto adjusted_config = apm->GetConfig(); + + EXPECT_EQ(config.gain_controller1.enabled, + adjusted_config.gain_controller1.enabled); + EXPECT_EQ(config.gain_controller1.analog_gain_controller.enabled, + adjusted_config.gain_controller1.analog_gain_controller.enabled); + EXPECT_EQ(config.gain_controller2.enabled, + adjusted_config.gain_controller2.enabled); + EXPECT_EQ(config.gain_controller2.adaptive_digital.enabled, + adjusted_config.gain_controller2.adaptive_digital.enabled); + EXPECT_FALSE( + adjusted_config.gain_controller2.input_volume_controller.enabled); + + EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString())); +} + +TEST(AudioProcessingImplInputVolumeControllerExperimentTest, + ConfigNotAdjustedWhenExperimentNotEnabledAndHybridAgcEnabled) { + AudioProcessingBuilderForTesting apm_builder; + + // Set a config with hybrid AGC enabled. + AudioProcessing::Config config; + config.gain_controller1.enabled = true; + config.gain_controller1.analog_gain_controller.enabled = true; + config.gain_controller1.analog_gain_controller.enable_digital_adaptive = + false; + config.gain_controller2.enabled = true; + config.gain_controller2.adaptive_digital.enabled = true; + config.gain_controller1.mode = + AudioProcessing::Config::GainController1::kAdaptiveAnalog; + + EXPECT_FALSE(config.gain_controller2.input_volume_controller.enabled); + + apm_builder.SetConfig(config); + + auto apm = apm_builder.Create(); + auto adjusted_config = apm->GetConfig(); + + EXPECT_EQ(config.gain_controller1.enabled, + adjusted_config.gain_controller1.enabled); + EXPECT_EQ(config.gain_controller1.analog_gain_controller.enabled, + adjusted_config.gain_controller1.analog_gain_controller.enabled); + EXPECT_EQ(config.gain_controller2.enabled, + adjusted_config.gain_controller2.enabled); + EXPECT_EQ(config.gain_controller2.adaptive_digital.enabled, + adjusted_config.gain_controller2.adaptive_digital.enabled); + EXPECT_FALSE( + adjusted_config.gain_controller2.input_volume_controller.enabled); + + EXPECT_THAT(adjusted_config.ToString(), ::testing::StrEq(config.ToString())); +} + } // namespace webrtc diff --git a/modules/audio_processing/gain_controller2.cc b/modules/audio_processing/gain_controller2.cc index 70b598df9c..2a9c8620b7 100644 --- a/modules/audio_processing/gain_controller2.cc +++ b/modules/audio_processing/gain_controller2.cc @@ -27,6 +27,7 @@ namespace webrtc { namespace { using Agc2Config = AudioProcessing::Config::GainController2; +using InputVolumeControllerConfig = InputVolumeController::Config; constexpr int kLogLimiterStatsPeriodMs = 30'000; constexpr int kFrameLengthMs = 10; @@ -64,10 +65,10 @@ std::unique_ptr CreateAdaptiveDigitalController( // Creates an input volume controller if `enabled` is true. std::unique_ptr CreateInputVolumeController( bool enabled, + const InputVolumeControllerConfig& config, int num_channels) { if (enabled) { - return std::make_unique( - num_channels, InputVolumeController::Config()); + return std::make_unique(num_channels, config); } return nullptr; } @@ -76,10 +77,12 @@ std::unique_ptr CreateInputVolumeController( std::atomic GainController2::instance_count_(0); -GainController2::GainController2(const Agc2Config& config, - int sample_rate_hz, - int num_channels, - bool use_internal_vad) +GainController2::GainController2( + const Agc2Config& config, + const InputVolumeControllerConfig& input_volume_controller_config, + int sample_rate_hz, + int num_channels, + bool use_internal_vad) : cpu_features_(GetAllowedCpuFeatures()), data_dumper_(instance_count_.fetch_add(1) + 1), fixed_gain_applier_( @@ -92,6 +95,7 @@ GainController2::GainController2(const Agc2Config& config, &data_dumper_)), input_volume_controller_( CreateInputVolumeController(config.input_volume_controller.enabled, + input_volume_controller_config, num_channels)), limiter_(sample_rate_hz, &data_dumper_, /*histogram_name_prefix=*/"Agc2"), calls_since_last_limiter_log_(0) { diff --git a/modules/audio_processing/gain_controller2.h b/modules/audio_processing/gain_controller2.h index 3341cd22d0..0d41eaa148 100644 --- a/modules/audio_processing/gain_controller2.h +++ b/modules/audio_processing/gain_controller2.h @@ -34,10 +34,12 @@ class GainController2 { public: // Ctor. If `use_internal_vad` is true, an internal voice activity // detector is used for digital adaptive gain. - GainController2(const AudioProcessing::Config::GainController2& config, - int sample_rate_hz, - int num_channels, - bool use_internal_vad); + GainController2( + const AudioProcessing::Config::GainController2& config, + const InputVolumeController::Config& input_volume_controller_config, + int sample_rate_hz, + int num_channels, + bool use_internal_vad); GainController2(const GainController2&) = delete; GainController2& operator=(const GainController2&) = delete; ~GainController2(); diff --git a/modules/audio_processing/gain_controller2_unittest.cc b/modules/audio_processing/gain_controller2_unittest.cc index 7fb0c26fc8..f7e5db2b60 100644 --- a/modules/audio_processing/gain_controller2_unittest.cc +++ b/modules/audio_processing/gain_controller2_unittest.cc @@ -33,6 +33,7 @@ using ::testing::Eq; using ::testing::Optional; using Agc2Config = AudioProcessing::Config::GainController2; +using InputVolumeControllerConfig = InputVolumeController::Config; // Sets all the samples in `ab` to `value`. void SetAudioBufferSamples(float value, AudioBuffer& ab) { @@ -73,11 +74,25 @@ std::unique_ptr CreateAgc2FixedDigitalMode( 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, - /*use_internal_vad=*/true); + return std::make_unique( + config, InputVolumeControllerConfig{}, sample_rate_hz, + /*num_channels=*/1, + /*use_internal_vad=*/true); } +constexpr InputVolumeControllerConfig kTestInputVolumeControllerConfig{ + .clipped_level_min = 20, + .clipped_level_step = 30, + .clipped_ratio_threshold = 0.4, + .clipped_wait_frames = 50, + .enable_clipping_predictor = true, + .target_range_max_dbfs = -6, + .target_range_min_dbfs = -70, + .update_input_volume_wait_frames = 100, + .speech_probability_threshold = 0.9, + .speech_ratio_threshold = 1, +}; + } // namespace TEST(GainController2, CheckDefaultConfig) { @@ -160,9 +175,41 @@ TEST(GainController2, Agc2Config config; config.input_volume_controller.enabled = false; - auto gain_controller = - std::make_unique(config, kSampleRateHz, kNumChannels, - /*use_internal_vad=*/true); + auto gain_controller = std::make_unique( + config, InputVolumeControllerConfig{}, kSampleRateHz, kNumChannels, + /*use_internal_vad=*/true); + + EXPECT_FALSE(gain_controller->GetRecommendedInputVolume().has_value()); + + // Run AGC for a signal with no clipping or detected speech. + RunAgc2WithConstantInput(*gain_controller, kLowInputLevel, kNumFrames, + kSampleRateHz, kNumChannels, kInitialInputVolume); + + EXPECT_FALSE(gain_controller->GetRecommendedInputVolume().has_value()); + + // Run AGC for a signal with clipping. + RunAgc2WithConstantInput(*gain_controller, kHighInputLevel, kNumFrames, + kSampleRateHz, kNumChannels, kInitialInputVolume); + + EXPECT_FALSE(gain_controller->GetRecommendedInputVolume().has_value()); +} + +TEST( + GainController2, + CheckGetRecommendedInputVolumeWhenInputVolumeControllerNotEnabledAndSpecificConfigUsed) { + constexpr float kHighInputLevel = 32767.0f; + constexpr float kLowInputLevel = 1000.0f; + constexpr int kInitialInputVolume = 100; + constexpr int kNumChannels = 2; + constexpr int kNumFrames = 5; + constexpr int kSampleRateHz = 16000; + + Agc2Config config; + config.input_volume_controller.enabled = false; + + auto gain_controller = std::make_unique( + config, kTestInputVolumeControllerConfig, kSampleRateHz, kNumChannels, + /*use_internal_vad=*/true); EXPECT_FALSE(gain_controller->GetRecommendedInputVolume().has_value()); @@ -192,9 +239,42 @@ TEST(GainController2, config.input_volume_controller.enabled = true; config.adaptive_digital.enabled = true; - auto gain_controller = - std::make_unique(config, kSampleRateHz, kNumChannels, - /*use_internal_vad=*/true); + auto gain_controller = std::make_unique( + config, InputVolumeControllerConfig{}, kSampleRateHz, kNumChannels, + /*use_internal_vad=*/true); + + EXPECT_TRUE(gain_controller->GetRecommendedInputVolume().has_value()); + + // Run AGC for a signal with no clipping or detected speech. + RunAgc2WithConstantInput(*gain_controller, kLowInputLevel, kNumFrames, + kSampleRateHz, kNumChannels, kInitialInputVolume); + + EXPECT_TRUE(gain_controller->GetRecommendedInputVolume().has_value()); + + // Run AGC for a signal with clipping. + RunAgc2WithConstantInput(*gain_controller, kHighInputLevel, kNumFrames, + kSampleRateHz, kNumChannels, kInitialInputVolume); + + EXPECT_TRUE(gain_controller->GetRecommendedInputVolume().has_value()); +} + +TEST( + GainController2, + CheckGetRecommendedInputVolumeWhenInputVolumeControllerEnabledAndSpecificConfigUsed) { + constexpr float kHighInputLevel = 32767.0f; + constexpr float kLowInputLevel = 1000.0f; + constexpr int kInitialInputVolume = 100; + constexpr int kNumChannels = 2; + constexpr int kNumFrames = 5; + constexpr int kSampleRateHz = 16000; + + Agc2Config config; + config.input_volume_controller.enabled = true; + config.adaptive_digital.enabled = true; + + auto gain_controller = std::make_unique( + config, kTestInputVolumeControllerConfig, kSampleRateHz, kNumChannels, + /*use_internal_vad=*/true); EXPECT_TRUE(gain_controller->GetRecommendedInputVolume().has_value()); @@ -214,7 +294,8 @@ TEST(GainController2, // Checks that the default config is applied. TEST(GainController2, ApplyDefaultConfig) { auto gain_controller2 = std::make_unique( - Agc2Config{}, /*sample_rate_hz=*/16000, /*num_channels=*/2, + Agc2Config{}, InputVolumeControllerConfig{}, + /*sample_rate_hz=*/16000, /*num_channels=*/2, /*use_internal_vad=*/true); EXPECT_TRUE(gain_controller2.get()); } @@ -330,7 +411,8 @@ TEST(GainController2, CheckFinalGainWithAdaptiveDigitalController) { Agc2Config config; config.fixed_digital.gain_db = 0.0f; config.adaptive_digital.enabled = true; - GainController2 agc2(config, kSampleRateHz, kStereo, + GainController2 agc2(config, /*input_volume_controller_config=*/{}, + kSampleRateHz, kStereo, /*use_internal_vad=*/true); test::InputAudioFile input_file( @@ -385,9 +467,11 @@ TEST(GainController2, Agc2Config config; config.fixed_digital.gain_db = 0.0f; config.adaptive_digital.enabled = true; - GainController2 agc2(config, kSampleRateHz, kStereo, + GainController2 agc2(config, /*input_volume_controller_config=*/{}, + kSampleRateHz, kStereo, /*use_internal_vad=*/true); - GainController2 agc2_reference(config, kSampleRateHz, kStereo, + GainController2 agc2_reference(config, /*input_volume_controller_config=*/{}, + kSampleRateHz, kStereo, /*use_internal_vad=*/true); test::InputAudioFile input_file( @@ -452,9 +536,11 @@ TEST(GainController2, Agc2Config config; config.fixed_digital.gain_db = 0.0f; config.adaptive_digital.enabled = true; - GainController2 agc2(config, kSampleRateHz, kStereo, + GainController2 agc2(config, /*input_volume_controller_config=*/{}, + kSampleRateHz, kStereo, /*use_internal_vad=*/false); - GainController2 agc2_reference(config, kSampleRateHz, kStereo, + GainController2 agc2_reference(config, /*input_volume_controller_config=*/{}, + kSampleRateHz, kStereo, /*use_internal_vad=*/true); test::InputAudioFile input_file( @@ -521,9 +607,11 @@ TEST(GainController2, Agc2Config config; config.fixed_digital.gain_db = 0.0f; config.adaptive_digital.enabled = true; - GainController2 agc2(config, kSampleRateHz, kStereo, + GainController2 agc2(config, /*input_volume_controller_config=*/{}, + kSampleRateHz, kStereo, /*use_internal_vad=*/false); - GainController2 agc2_reference(config, kSampleRateHz, kStereo, + GainController2 agc2_reference(config, /*input_volume_controller_config=*/{}, + kSampleRateHz, kStereo, /*use_internal_vad=*/true); VoiceActivityDetectorWrapper vad(config.adaptive_digital.vad_reset_period_ms, GetAvailableCpuFeatures(), kSampleRateHz);