diff --git a/modules/audio_processing/agc/agc_manager_direct.cc b/modules/audio_processing/agc/agc_manager_direct.cc index 2454d1bbb1..ebd978b0d0 100644 --- a/modules/audio_processing/agc/agc_manager_direct.cc +++ b/modules/audio_processing/agc/agc_manager_direct.cc @@ -27,33 +27,26 @@ namespace webrtc { namespace { -// Amount the microphone level is lowered with every clipping event. -const int kClippedLevelStep = 15; -// Proportion of clipped samples required to declare a clipping event. -const float kClippedRatioThreshold = 0.1f; -// Time in frames to wait after a clipping event before checking again. -const int kClippedWaitFrames = 300; - // Amount of error we tolerate in the microphone level (presumably due to OS // quantization) before we assume the user has manually adjusted the microphone. -const int kLevelQuantizationSlack = 25; +constexpr int kLevelQuantizationSlack = 25; -const int kDefaultCompressionGain = 7; -const int kMaxCompressionGain = 12; -const int kMinCompressionGain = 2; +constexpr int kDefaultCompressionGain = 7; +constexpr int kMaxCompressionGain = 12; +constexpr int kMinCompressionGain = 2; // Controls the rate of compression changes towards the target. -const float kCompressionGainStep = 0.05f; +constexpr float kCompressionGainStep = 0.05f; -const int kMaxMicLevel = 255; +constexpr int kMaxMicLevel = 255; static_assert(kGainMapSize > kMaxMicLevel, "gain map too small"); -const int kMinMicLevel = 12; +constexpr int kMinMicLevel = 12; // Prevent very large microphone level changes. -const int kMaxResidualGainChange = 15; +constexpr int kMaxResidualGainChange = 15; // Maximum additional gain allowed to compensate for microphone level // restrictions from clipping events. -const int kSurplusCompressionGain = 6; +constexpr int kSurplusCompressionGain = 6; // Returns whether a fall-back solution to choose the maximum level should be // chosen. @@ -182,19 +175,19 @@ void MonoAgc::Process(const int16_t* audio, } } -void MonoAgc::HandleClipping() { +void MonoAgc::HandleClipping(int clipped_level_step) { // Always decrease the maximum level, even if the current level is below // threshold. - SetMaxLevel(std::max(clipped_level_min_, max_level_ - kClippedLevelStep)); + SetMaxLevel(std::max(clipped_level_min_, max_level_ - clipped_level_step)); if (log_to_histograms_) { RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.AgcClippingAdjustmentAllowed", - level_ - kClippedLevelStep >= clipped_level_min_); + level_ - clipped_level_step >= clipped_level_min_); } if (level_ > clipped_level_min_) { // Don't try to adjust the level if we're already below the limit. As // a consequence, if the user has brought the level above the limit, we // will still not react until the postproc updates the level. - SetLevel(std::max(clipped_level_min_, level_ - kClippedLevelStep)); + SetLevel(std::max(clipped_level_min_, level_ - clipped_level_step)); // Reset the AGCs for all channels since the level has changed. agc_->Reset(); } @@ -404,12 +397,18 @@ int AgcManagerDirect::instance_counter_ = 0; AgcManagerDirect::AgcManagerDirect(Agc* agc, int startup_min_level, int clipped_level_min, - int sample_rate_hz) + int sample_rate_hz, + int clipped_level_step, + float clipped_ratio_threshold, + int clipped_wait_frames) : AgcManagerDirect(/*num_capture_channels*/ 1, startup_min_level, clipped_level_min, /*disable_digital_adaptive*/ false, - sample_rate_hz) { + sample_rate_hz, + clipped_level_step, + clipped_ratio_threshold, + clipped_wait_frames) { RTC_DCHECK(channel_agcs_[0]); RTC_DCHECK(agc); channel_agcs_[0]->set_agc(agc); @@ -419,15 +418,21 @@ AgcManagerDirect::AgcManagerDirect(int num_capture_channels, int startup_min_level, int clipped_level_min, bool disable_digital_adaptive, - int sample_rate_hz) + int sample_rate_hz, + int clipped_level_step, + float clipped_ratio_threshold, + int clipped_wait_frames) : data_dumper_( new ApmDataDumper(rtc::AtomicOps::Increment(&instance_counter_))), use_min_channel_level_(!UseMaxAnalogChannelLevel()), sample_rate_hz_(sample_rate_hz), num_capture_channels_(num_capture_channels), disable_digital_adaptive_(disable_digital_adaptive), - frames_since_clipped_(kClippedWaitFrames), + frames_since_clipped_(clipped_wait_frames), capture_output_used_(true), + clipped_level_step_(clipped_level_step), + clipped_ratio_threshold_(clipped_ratio_threshold), + clipped_wait_frames_(clipped_wait_frames), channel_agcs_(num_capture_channels), new_compressions_to_set_(num_capture_channels) { const int min_mic_level = GetMinMicLevel(); @@ -438,7 +443,13 @@ AgcManagerDirect::AgcManagerDirect(int num_capture_channels, data_dumper_ch, startup_min_level, clipped_level_min, disable_digital_adaptive_, min_mic_level); } - RTC_DCHECK_LT(0, channel_agcs_.size()); + RTC_DCHECK(!channel_agcs_.empty()); + RTC_DCHECK_GT(clipped_level_step, 0); + RTC_DCHECK_LE(clipped_level_step, 255); + RTC_DCHECK_GT(clipped_ratio_threshold, 0.f); + RTC_DCHECK_LT(clipped_ratio_threshold, 1.f); + RTC_DCHECK_GT(clipped_wait_frames, 0); + channel_agcs_[0]->ActivateLogging(); } @@ -489,7 +500,7 @@ void AgcManagerDirect::AnalyzePreProcess(const float* const* audio, return; } - if (frames_since_clipped_ < kClippedWaitFrames) { + if (frames_since_clipped_ < clipped_wait_frames_) { ++frames_since_clipped_; return; } @@ -506,11 +517,11 @@ void AgcManagerDirect::AnalyzePreProcess(const float* const* audio, float clipped_ratio = ComputeClippedRatio(audio, num_capture_channels_, samples_per_channel); - if (clipped_ratio > kClippedRatioThreshold) { + if (clipped_ratio > clipped_ratio_threshold_) { RTC_DLOG(LS_INFO) << "[agc] Clipping detected. clipped_ratio=" << clipped_ratio; for (auto& state_ch : channel_agcs_) { - state_ch->HandleClipping(); + state_ch->HandleClipping(clipped_level_step_); } frames_since_clipped_ = 0; } diff --git a/modules/audio_processing/agc/agc_manager_direct.h b/modules/audio_processing/agc/agc_manager_direct.h index f9417cffff..e0be1a0dd2 100644 --- a/modules/audio_processing/agc/agc_manager_direct.h +++ b/modules/audio_processing/agc/agc_manager_direct.h @@ -34,12 +34,20 @@ class AgcManagerDirect final { // AgcManagerDirect will configure GainControl internally. The user is // responsible for processing the audio using it after the call to Process. // The operating range of startup_min_level is [12, 255] and any input value - // outside that range will be clamped. + // outside that range will be clamped. `clipped_level_step` is the amount + // the microphone level is lowered with every clipping event, limited to + // (0, 255]. `clipped_ratio_threshold` is the proportion of clipped + // samples required to declare a clipping event, limited to (0.f, 1.f). + // `clipped_wait_frames` is the time in frames to wait after a clipping event + // before checking again, limited to values higher than 0. AgcManagerDirect(int num_capture_channels, int startup_min_level, int clipped_level_min, bool disable_digital_adaptive, - int sample_rate_hz); + int sample_rate_hz, + int clipped_level_step, + float clipped_ratio_threshold, + int clipped_wait_frames); ~AgcManagerDirect(); AgcManagerDirect(const AgcManagerDirect&) = delete; @@ -81,13 +89,18 @@ class AgcManagerDirect final { AgcMinMicLevelExperimentEnabled50); FRIEND_TEST_ALL_PREFIXES(AgcManagerDirectStandaloneTest, AgcMinMicLevelExperimentEnabledAboveStartupLevel); + FRIEND_TEST_ALL_PREFIXES(AgcManagerDirectStandaloneTest, + ClippingParametersVerified); // Dependency injection for testing. Don't delete |agc| as the memory is owned // by the manager. AgcManagerDirect(Agc* agc, int startup_min_level, int clipped_level_min, - int sample_rate_hz); + int sample_rate_hz, + int clipped_level_step, + float clipped_ratio_threshold, + int clipped_wait_frames); void AnalyzePreProcess(const float* const* audio, size_t samples_per_channel); @@ -105,6 +118,10 @@ class AgcManagerDirect final { bool capture_output_used_; int channel_controlling_gain_ = 0; + const int clipped_level_step_; + const float clipped_ratio_threshold_; + const int clipped_wait_frames_; + std::vector> channel_agcs_; std::vector> new_compressions_to_set_; }; @@ -123,7 +140,7 @@ class MonoAgc { void Initialize(); void HandleCaptureOutputUsedChange(bool capture_output_used); - void HandleClipping(); + void HandleClipping(int clipped_level_step); void Process(const int16_t* audio, size_t samples_per_channel, diff --git a/modules/audio_processing/agc/agc_manager_direct_unittest.cc b/modules/audio_processing/agc/agc_manager_direct_unittest.cc index 1954ed4b21..6fdfa6d805 100644 --- a/modules/audio_processing/agc/agc_manager_direct_unittest.cc +++ b/modules/audio_processing/agc/agc_manager_direct_unittest.cc @@ -26,13 +26,16 @@ using ::testing::SetArgPointee; namespace webrtc { namespace { -const int kSampleRateHz = 32000; -const int kNumChannels = 1; -const int kSamplesPerChannel = kSampleRateHz / 100; -const int kInitialVolume = 128; +constexpr int kSampleRateHz = 32000; +constexpr int kNumChannels = 1; +constexpr int kSamplesPerChannel = kSampleRateHz / 100; +constexpr int kInitialVolume = 128; constexpr int kClippedMin = 165; // Arbitrary, but different from the default. -const float kAboveClippedThreshold = 0.2f; -const int kMinMicLevel = 12; +constexpr float kAboveClippedThreshold = 0.2f; +constexpr int kMinMicLevel = 12; +constexpr int kClippedLevelStep = 15; +constexpr float kClippedRatioThreshold = 0.1f; +constexpr int kClippedWaitFrames = 300; class MockGainControl : public GainControl { public: @@ -57,10 +60,14 @@ class MockGainControl : public GainControl { }; std::unique_ptr CreateAgcManagerDirect( - int startup_min_level) { + int startup_min_level, + int clipped_level_step, + float clipped_ratio_threshold, + int clipped_wait_frames) { return std::make_unique( /*num_capture_channels=*/1, startup_min_level, kClippedMin, - /*disable_digital_adaptive=*/true, kSampleRateHz); + /*disable_digital_adaptive=*/true, kSampleRateHz, clipped_level_step, + clipped_ratio_threshold, clipped_wait_frames); } } // namespace @@ -69,7 +76,13 @@ class AgcManagerDirectTest : public ::testing::Test { protected: AgcManagerDirectTest() : agc_(new MockAgc), - manager_(agc_, kInitialVolume, kClippedMin, kSampleRateHz), + manager_(agc_, + kInitialVolume, + kClippedMin, + kSampleRateHz, + kClippedLevelStep, + kClippedRatioThreshold, + kClippedWaitFrames), audio(kNumChannels), audio_data(kNumChannels * kSamplesPerChannel, 0.f) { ExpectInitialize(); @@ -705,14 +718,16 @@ TEST(AgcManagerDirectStandaloneTest, DisableDigitalDisablesDigital) { EXPECT_CALL(gctrl, enable_limiter(false)); std::unique_ptr manager = - CreateAgcManagerDirect(kInitialVolume); + CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); manager->Initialize(); manager->SetupDigitalGainControl(&gctrl); } TEST(AgcManagerDirectStandaloneTest, AgcMinMicLevelExperiment) { std::unique_ptr manager = - CreateAgcManagerDirect(kInitialVolume); + CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel); EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume); } @@ -721,7 +736,8 @@ TEST(AgcManagerDirectStandaloneTest, AgcMinMicLevelExperimentDisabled) { test::ScopedFieldTrials field_trial( "WebRTC-Audio-AgcMinMicLevelExperiment/Disabled/"); std::unique_ptr manager = - CreateAgcManagerDirect(kInitialVolume); + CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel); EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume); } @@ -732,7 +748,8 @@ TEST(AgcManagerDirectStandaloneTest, AgcMinMicLevelExperimentOutOfRangeAbove) { test::ScopedFieldTrials field_trial( "WebRTC-Audio-AgcMinMicLevelExperiment/Enabled-256/"); std::unique_ptr manager = - CreateAgcManagerDirect(kInitialVolume); + CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel); EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume); } @@ -743,7 +760,8 @@ TEST(AgcManagerDirectStandaloneTest, AgcMinMicLevelExperimentOutOfRangeBelow) { test::ScopedFieldTrials field_trial( "WebRTC-Audio-AgcMinMicLevelExperiment/Enabled--1/"); std::unique_ptr manager = - CreateAgcManagerDirect(kInitialVolume); + CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), kMinMicLevel); EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume); } @@ -755,7 +773,8 @@ TEST(AgcManagerDirectStandaloneTest, AgcMinMicLevelExperimentEnabled50) { test::ScopedFieldTrials field_trial( "WebRTC-Audio-AgcMinMicLevelExperiment/Enabled-50/"); std::unique_ptr manager = - CreateAgcManagerDirect(kInitialVolume); + CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), 50); EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), kInitialVolume); } @@ -768,9 +787,33 @@ TEST(AgcManagerDirectStandaloneTest, test::ScopedFieldTrials field_trial( "WebRTC-Audio-AgcMinMicLevelExperiment/Enabled-50/"); std::unique_ptr manager = - CreateAgcManagerDirect(/*startup_min_level=*/30); + CreateAgcManagerDirect(/*startup_min_level=*/30, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); EXPECT_EQ(manager->channel_agcs_[0]->min_mic_level(), 50); EXPECT_EQ(manager->channel_agcs_[0]->startup_min_level(), 50); } +// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_level_step`. +// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_ratio_threshold`. +// TODO(bugs.webrtc.org/12774): Test the bahavior of `clipped_wait_frames`. +// Verifies that configurable clipping parameters are initialized as intended. +TEST(AgcManagerDirectStandaloneTest, ClippingParametersVerified) { + std::unique_ptr manager = + CreateAgcManagerDirect(kInitialVolume, kClippedLevelStep, + kClippedRatioThreshold, kClippedWaitFrames); + manager->Initialize(); + EXPECT_EQ(manager->clipped_level_step_, kClippedLevelStep); + EXPECT_EQ(manager->clipped_ratio_threshold_, kClippedRatioThreshold); + EXPECT_EQ(manager->clipped_wait_frames_, kClippedWaitFrames); + std::unique_ptr manager_custom = + CreateAgcManagerDirect(kInitialVolume, + /*clipped_level_step*/ 10, + /*clipped_ratio_threshold*/ 0.2f, + /*clipped_wait_frames*/ 50); + manager_custom->Initialize(); + EXPECT_EQ(manager_custom->clipped_level_step_, 10); + EXPECT_EQ(manager_custom->clipped_ratio_threshold_, 0.2f); + EXPECT_EQ(manager_custom->clipped_wait_frames_, 50); +} + } // namespace webrtc diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc index ac4d6a8b58..3c5d9fb718 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -1918,7 +1918,10 @@ void AudioProcessingImpl::InitializeGainController1() { config_.gain_controller1.analog_gain_controller.clipped_level_min, !config_.gain_controller1.analog_gain_controller .enable_digital_adaptive, - capture_nonlocked_.split_rate)); + capture_nonlocked_.split_rate, + config_.gain_controller1.analog_gain_controller.clipped_level_step, + config_.gain_controller1.analog_gain_controller.clipped_ratio_threshold, + config_.gain_controller1.analog_gain_controller.clipped_wait_frames)); if (re_creation) { submodules_.agc_manager->set_stream_analog_level(stream_analog_level); } diff --git a/modules/audio_processing/include/audio_processing.cc b/modules/audio_processing/include/audio_processing.cc index f50a283bc0..29dcd66040 100644 --- a/modules/audio_processing/include/audio_processing.cc +++ b/modules/audio_processing/include/audio_processing.cc @@ -77,7 +77,11 @@ bool Agc1Config::operator==(const Agc1Config& rhs) const { analog_lhs.startup_min_volume == analog_rhs.startup_min_volume && analog_lhs.clipped_level_min == analog_rhs.clipped_level_min && analog_lhs.enable_digital_adaptive == - analog_rhs.enable_digital_adaptive; + analog_rhs.enable_digital_adaptive && + analog_lhs.clipped_level_step == analog_rhs.clipped_level_step && + analog_lhs.clipped_ratio_threshold == + analog_rhs.clipped_ratio_threshold && + analog_lhs.clipped_wait_frames == analog_rhs.clipped_wait_frames; } bool Agc2Config::AdaptiveDigital::operator==( @@ -157,6 +161,12 @@ std::string AudioProcessing::Config::ToString() const { << gain_controller1.analog_gain_controller.clipped_level_min << ", enable_digital_adaptive: " << gain_controller1.analog_gain_controller.enable_digital_adaptive + << ", clipped_level_step: " + << gain_controller1.analog_gain_controller.clipped_level_step + << ", clipped_ratio_threshold: " + << gain_controller1.analog_gain_controller.clipped_ratio_threshold + << ", clipped_wait_frames: " + << gain_controller1.analog_gain_controller.clipped_wait_frames << " }}, gain_controller2: { enabled: " << gain_controller2.enabled << ", fixed_digital: { gain_db: " << gain_controller2.fixed_digital.gain_db diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h index 6bc50f0554..66220979dd 100644 --- a/modules/audio_processing/include/audio_processing.h +++ b/modules/audio_processing/include/audio_processing.h @@ -59,9 +59,9 @@ class CustomProcessing; // // Must be provided through AudioProcessingBuilder().Create(config). #if defined(WEBRTC_CHROMIUM_BUILD) -static const int kAgcStartupMinVolume = 85; +static constexpr int kAgcStartupMinVolume = 85; #else -static const int kAgcStartupMinVolume = 0; +static constexpr int kAgcStartupMinVolume = 0; #endif // defined(WEBRTC_CHROMIUM_BUILD) static constexpr int kClippedLevelMin = 70; @@ -334,6 +334,15 @@ class RTC_EXPORT AudioProcessing : public rtc::RefCountInterface { // clipping. int clipped_level_min = kClippedLevelMin; bool enable_digital_adaptive = true; + // Amount the microphone level is lowered with every clipping event. + // Limited to (0, 255]. + int clipped_level_step = 15; + // Proportion of clipped samples required to declare a clipping event. + // Limited to (0.f, 1.f). + float clipped_ratio_threshold = 0.1f; + // Time in frames to wait after a clipping event before checking again. + // Limited to values higher than 0. + int clipped_wait_frames = 300; } analog_gain_controller; } gain_controller1;