diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn index dd2ded6945..dd63e2f8b4 100644 --- a/modules/audio_processing/BUILD.gn +++ b/modules/audio_processing/BUILD.gn @@ -108,8 +108,6 @@ rtc_static_library("audio_processing") { "agc/loudness_histogram.h", "agc/utility.cc", "agc/utility.h", - "agc2/digital_gain_applier.cc", - "agc2/digital_gain_applier.h", "agc2/gain_controller2.cc", "agc2/gain_controller2.h", "audio_buffer.cc", diff --git a/modules/audio_processing/agc2/digital_gain_applier.cc b/modules/audio_processing/agc2/digital_gain_applier.cc deleted file mode 100644 index ec706eca98..0000000000 --- a/modules/audio_processing/agc2/digital_gain_applier.cc +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "modules/audio_processing/agc2/digital_gain_applier.h" - -#include - -namespace webrtc { -namespace { - -constexpr float kMaxSampleValue = 32767.0f; -constexpr float kMinSampleValue = -32767.0f; - -} // namespace - -DigitalGainApplier::DigitalGainApplier() = default; - -void DigitalGainApplier::Process(float gain, rtc::ArrayView samples) { - if (gain == 1.f) { return; } - for (auto& v : samples) { v *= gain; } - LimitToAllowedRange(samples); -} - -void DigitalGainApplier::LimitToAllowedRange(rtc::ArrayView x) { - for (auto& v : x) { - v = std::max(kMinSampleValue, v); - v = std::min(kMaxSampleValue, v); - } -} - -} // namespace webrtc diff --git a/modules/audio_processing/agc2/digital_gain_applier.h b/modules/audio_processing/agc2/digital_gain_applier.h deleted file mode 100644 index 6e9be8eca6..0000000000 --- a/modules/audio_processing/agc2/digital_gain_applier.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef MODULES_AUDIO_PROCESSING_AGC2_DIGITAL_GAIN_APPLIER_H_ -#define MODULES_AUDIO_PROCESSING_AGC2_DIGITAL_GAIN_APPLIER_H_ - -#include "api/array_view.h" -#include "modules/audio_processing/audio_buffer.h" - -namespace webrtc { - -class DigitalGainApplier { - public: - DigitalGainApplier(); - - // Applies the specified gain to an array of samples. - void Process(float gain, rtc::ArrayView samples); - - private: - void LimitToAllowedRange(rtc::ArrayView x); -}; - -} // namespace webrtc - -#endif // MODULES_AUDIO_PROCESSING_AGC2_DIGITAL_GAIN_APPLIER_H_ diff --git a/modules/audio_processing/agc2/gain_controller2.cc b/modules/audio_processing/agc2/gain_controller2.cc index 6c1ce45e70..4265500ab5 100644 --- a/modules/audio_processing/agc2/gain_controller2.cc +++ b/modules/audio_processing/agc2/gain_controller2.cc @@ -10,55 +10,65 @@ #include "modules/audio_processing/agc2/gain_controller2.h" +#include + #include "modules/audio_processing/audio_buffer.h" #include "modules/audio_processing/logging/apm_data_dumper.h" #include "rtc_base/atomicops.h" #include "rtc_base/checks.h" +#include "rtc_base/safe_minmax.h" namespace webrtc { -namespace { - -constexpr float kGain = 0.5f; - -} // namespace - int GainController2::instance_count_ = 0; -GainController2::GainController2(int sample_rate_hz) - : sample_rate_hz_(sample_rate_hz), - data_dumper_(new ApmDataDumper( - rtc::AtomicOps::Increment(&instance_count_))), - digital_gain_applier_(), - gain_(kGain) { - RTC_DCHECK(sample_rate_hz_ == AudioProcessing::kSampleRate8kHz || - sample_rate_hz_ == AudioProcessing::kSampleRate16kHz || - sample_rate_hz_ == AudioProcessing::kSampleRate32kHz || - sample_rate_hz_ == AudioProcessing::kSampleRate48kHz); - data_dumper_->InitiateNewSetOfRecordings(); - data_dumper_->DumpRaw("gain_", 1, &gain_); -} +GainController2::GainController2() + : data_dumper_( + new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))), + sample_rate_hz_(AudioProcessing::kSampleRate48kHz), + fixed_gain_(1.f) {} GainController2::~GainController2() = default; +void GainController2::Initialize(int sample_rate_hz) { + RTC_DCHECK(sample_rate_hz == AudioProcessing::kSampleRate8kHz || + sample_rate_hz == AudioProcessing::kSampleRate16kHz || + sample_rate_hz == AudioProcessing::kSampleRate32kHz || + sample_rate_hz == AudioProcessing::kSampleRate48kHz); + sample_rate_hz_ = sample_rate_hz; + data_dumper_->InitiateNewSetOfRecordings(); + data_dumper_->DumpRaw("sample_rate_hz", sample_rate_hz_); + data_dumper_->DumpRaw("fixed_gain_linear", fixed_gain_); +} + void GainController2::Process(AudioBuffer* audio) { + if (fixed_gain_ == 1.f) + return; + for (size_t k = 0; k < audio->num_channels(); ++k) { - auto channel_view = rtc::ArrayView( - audio->channels_f()[k], audio->num_frames()); - digital_gain_applier_.Process(gain_, channel_view); + for (size_t j = 0; j < audio->num_frames(); ++j) { + audio->channels_f()[k][j] = rtc::SafeClamp( + fixed_gain_ * audio->channels_f()[k][j], -32768.f, 32767.f); + } } } +void GainController2::ApplyConfig( + const AudioProcessing::Config::GainController2& config) { + RTC_DCHECK(Validate(config)); + fixed_gain_ = std::pow(10.f, config.fixed_gain_db / 20.f); +} + bool GainController2::Validate( const AudioProcessing::Config::GainController2& config) { - return true; + return config.fixed_gain_db >= 0.f; } std::string GainController2::ToString( const AudioProcessing::Config::GainController2& config) { std::stringstream ss; - ss << "{" - << "enabled: " << (config.enabled ? "true" : "false") << "}"; + ss << "{enabled: " << (config.enabled ? "true" : "false") << ", " + << "fixed_gain_dB: " << config.fixed_gain_db << "}"; return ss.str(); } diff --git a/modules/audio_processing/agc2/gain_controller2.h b/modules/audio_processing/agc2/gain_controller2.h index 9ab8656a3a..11706870f4 100644 --- a/modules/audio_processing/agc2/gain_controller2.h +++ b/modules/audio_processing/agc2/gain_controller2.h @@ -14,7 +14,6 @@ #include #include -#include "modules/audio_processing/agc2/digital_gain_applier.h" #include "modules/audio_processing/include/audio_processing.h" #include "rtc_base/constructormagic.h" @@ -26,28 +25,26 @@ class AudioBuffer; // Gain Controller 2 aims to automatically adjust levels by acting on the // microphone gain and/or applying digital gain. // -// It temporarily implements a hard-coded gain mode only. +// Temporarily implements a fixed gain mode with hard-clipping. class GainController2 { public: - explicit GainController2(int sample_rate_hz); + GainController2(); ~GainController2(); - int sample_rate_hz() { return sample_rate_hz_; } - + void Initialize(int sample_rate_hz); void Process(AudioBuffer* audio); + void ApplyConfig(const AudioProcessing::Config::GainController2& config); static bool Validate(const AudioProcessing::Config::GainController2& config); static std::string ToString( const AudioProcessing::Config::GainController2& config); private: - int sample_rate_hz_; - std::unique_ptr data_dumper_; - DigitalGainApplier digital_gain_applier_; static int instance_count_; - // TODO(alessiob): Remove once a meaningful gain controller mode is - // implemented. - const float gain_; + std::unique_ptr data_dumper_; + int sample_rate_hz_; + float fixed_gain_; + RTC_DISALLOW_COPY_AND_ASSIGN(GainController2); }; diff --git a/modules/audio_processing/agc2/gain_controller2_unittest.cc b/modules/audio_processing/agc2/gain_controller2_unittest.cc index 7c9acc6d3a..46f654db62 100644 --- a/modules/audio_processing/agc2/gain_controller2_unittest.cc +++ b/modules/audio_processing/agc2/gain_controller2_unittest.cc @@ -8,13 +8,12 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include -#include +#include #include "api/array_view.h" -#include "modules/audio_processing/agc2/digital_gain_applier.h" #include "modules/audio_processing/agc2/gain_controller2.h" #include "modules/audio_processing/audio_buffer.h" +#include "rtc_base/checks.h" #include "test/gtest.h" namespace webrtc { @@ -22,77 +21,76 @@ namespace test { namespace { -constexpr size_t kNumFrames = 480u; +constexpr size_t kFrameSizeMs = 10u; constexpr size_t kStereo = 2u; void SetAudioBufferSamples(float value, AudioBuffer* ab) { + // Sets all the samples in |ab| to |value|. for (size_t k = 0; k < ab->num_channels(); ++k) { - auto channel = rtc::ArrayView(ab->channels_f()[k], ab->num_frames()); - for (auto& sample : channel) { sample = value; } + std::fill(ab->channels_f()[k], ab->channels_f()[k] + ab->num_frames(), + value); } } -template -bool CheckAudioBufferSamples(Functor validator, AudioBuffer* ab) { - for (size_t k = 0; k < ab->num_channels(); ++k) { - auto channel = rtc::ArrayView(ab->channels_f()[k], ab->num_frames()); - for (auto& sample : channel) { if (!validator(sample)) { return false; } } - } - return true; -} - -bool TestDigitalGainApplier(float sample_value, float gain, float expected) { - AudioBuffer ab(kNumFrames, kStereo, kNumFrames, kStereo, kNumFrames); - SetAudioBufferSamples(sample_value, &ab); - - DigitalGainApplier gain_applier; - for (size_t k = 0; k < ab.num_channels(); ++k) { - auto channel_view = rtc::ArrayView( - ab.channels_f()[k], ab.num_frames()); - gain_applier.Process(gain, channel_view); - } - - auto check_expectation = [expected](float sample) { - return sample == expected; }; - return CheckAudioBufferSamples(check_expectation, &ab); -} - } // namespace -TEST(GainController2, Instance) { - std::unique_ptr gain_controller2; - gain_controller2.reset(new GainController2( - AudioProcessing::kSampleRate48kHz)); +TEST(GainController2, CreateApplyConfig) { + // Instances GainController2 and applies different configurations. + std::unique_ptr gain_controller2(new GainController2()); + + // Check that the default config is valid. + AudioProcessing::Config::GainController2 config; + EXPECT_TRUE(GainController2::Validate(config)); + gain_controller2->ApplyConfig(config); + + // Check that attenuation is not allowed. + config.fixed_gain_db = -5.f; + EXPECT_FALSE(GainController2::Validate(config)); + + // Check that valid configurations are applied. + for (const float& fixed_gain_db : {0.f, 5.f, 10.f, 50.f}) { + config.fixed_gain_db = fixed_gain_db; + EXPECT_TRUE(GainController2::Validate(config)); + gain_controller2->ApplyConfig(config); + } } TEST(GainController2, ToString) { - AudioProcessing::Config config; + // Tests GainController2::ToString(). + AudioProcessing::Config::GainController2 config; + config.fixed_gain_db = 5.f; - config.gain_controller2.enabled = false; - EXPECT_EQ("{enabled: false}", - GainController2::ToString(config.gain_controller2)); + config.enabled = false; + EXPECT_EQ("{enabled: false, fixed_gain_dB: 5}", + GainController2::ToString(config)); - config.gain_controller2.enabled = true; - EXPECT_EQ("{enabled: true}", - GainController2::ToString(config.gain_controller2)); -} - -TEST(GainController2, DigitalGainApplierProcess) { - EXPECT_TRUE(TestDigitalGainApplier(1000.0f, 0.5, 500.0f)); -} - -TEST(GainController2, DigitalGainApplierCheckClipping) { - EXPECT_TRUE(TestDigitalGainApplier(30000.0f, 1.5, 32767.0f)); - EXPECT_TRUE(TestDigitalGainApplier(-30000.0f, 1.5, -32767.0f)); + config.enabled = true; + EXPECT_EQ("{enabled: true, fixed_gain_dB: 5}", + GainController2::ToString(config)); } TEST(GainController2, Usage) { - std::unique_ptr gain_controller2; - gain_controller2.reset(new GainController2( - AudioProcessing::kSampleRate48kHz)); - AudioBuffer ab(kNumFrames, kStereo, kNumFrames, kStereo, kNumFrames); - SetAudioBufferSamples(1000.0f, &ab); + // Tests GainController2::Process() on an AudioBuffer instance. + std::unique_ptr gain_controller2(new GainController2()); + gain_controller2->Initialize(AudioProcessing::kSampleRate48kHz); + const size_t num_frames = rtc::CheckedDivExact( + kFrameSizeMs * AudioProcessing::kSampleRate48kHz, 1000); + AudioBuffer ab(num_frames, kStereo, num_frames, kStereo, num_frames); + constexpr float sample_value = 1000.f; + SetAudioBufferSamples(sample_value, &ab); + AudioProcessing::Config::GainController2 config; + + // Check that samples are not modified when the fixed gain is 0 dB. + ASSERT_EQ(config.fixed_gain_db, 0.f); + gain_controller2->ApplyConfig(config); gain_controller2->Process(&ab); + EXPECT_EQ(ab.channels_f()[0][0], sample_value); + + // Check that samples are amplified when the fixed gain is greater than 0 dB. + config.fixed_gain_db = 5.f; + gain_controller2->ApplyConfig(config); + gain_controller2->Process(&ab); + EXPECT_LT(sample_value, ab.channels_f()[0][0]); } } // namespace test diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc index 7aaad0a20b..c02fec27a8 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -253,7 +253,8 @@ bool AudioProcessingImpl::ApmSubmoduleStates::CaptureMultiBandProcessingActive() bool AudioProcessingImpl::ApmSubmoduleStates::CaptureFullBandProcessingActive() const { - return level_controller_enabled_ || capture_post_processor_enabled_; + return level_controller_enabled_ || gain_controller2_enabled_ || + capture_post_processor_enabled_; } bool AudioProcessingImpl::ApmSubmoduleStates::RenderMultiBandSubModulesActive() @@ -401,6 +402,10 @@ AudioProcessingImpl::AudioProcessingImpl( // is enabled. private_submodules_->level_controller.reset(new LevelController()); + // TODO(alessiob): Move the injected gain controller once injection is + // implemented. + private_submodules_->gain_controller2.reset(new GainController2()); + LOG(LS_INFO) << "Capture post processor activated: " << !!private_submodules_->capture_post_processor; } @@ -714,21 +719,16 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) { config_ok = GainController2::Validate(config_.gain_controller2); if (!config_ok) { LOG(LS_ERROR) << "AudioProcessing module config error" << std::endl - << "gain_controller2: " + << "Gain Controller 2: " << GainController2::ToString(config_.gain_controller2) << std::endl << "Reverting to default parameter set"; config_.gain_controller2 = AudioProcessing::Config::GainController2(); } - - if (config.gain_controller2.enabled != - capture_nonlocked_.gain_controller2_enabled) { - capture_nonlocked_.gain_controller2_enabled = - config_.gain_controller2.enabled; - InitializeGainController2(); - LOG(LS_INFO) << "Gain controller 2 activated: " - << capture_nonlocked_.gain_controller2_enabled; - } + InitializeGainController2(); + private_submodules_->gain_controller2->ApplyConfig(config_.gain_controller2); + LOG(LS_INFO) << "Gain Controller 2 activated: " + << config_.gain_controller2.enabled; } void AudioProcessingImpl::SetExtraOptions(const webrtc::Config& config) { @@ -1305,7 +1305,7 @@ int AudioProcessingImpl::ProcessCaptureStreamLocked() { capture_.key_pressed); } - if (capture_nonlocked_.gain_controller2_enabled) { + if (config_.gain_controller2.enabled) { private_submodules_->gain_controller2->Process(capture_buffer); } @@ -1657,7 +1657,7 @@ bool AudioProcessingImpl::UpdateActiveSubmoduleStates() { capture_nonlocked_.intelligibility_enabled, capture_nonlocked_.beamformer_enabled, public_submodules_->gain_control->is_enabled(), - capture_nonlocked_.gain_controller2_enabled, + config_.gain_controller2.enabled, capture_nonlocked_.level_controller_enabled, capture_nonlocked_.echo_canceller3_enabled, public_submodules_->voice_detection->is_enabled(), @@ -1723,11 +1723,8 @@ void AudioProcessingImpl::InitializeEchoCanceller3() { } void AudioProcessingImpl::InitializeGainController2() { - if (capture_nonlocked_.gain_controller2_enabled) { - private_submodules_->gain_controller2.reset( - new GainController2(proc_sample_rate_hz())); - } else { - private_submodules_->gain_controller2.reset(); + if (config_.gain_controller2.enabled) { + private_submodules_->gain_controller2->Initialize(proc_sample_rate_hz()); } } @@ -1750,7 +1747,7 @@ void AudioProcessingImpl::MaybeUpdateHistograms() { static const int kMinDiffDelayMs = 60; if (echo_cancellation()->is_enabled()) { - // Activate delay_jumps_ counters if we know echo_cancellation is runnning. + // Activate delay_jumps_ counters if we know echo_cancellation is running. // If a stream has echo we know that the echo_cancellation is in process. if (capture_.stream_delay_jumps == -1 && echo_cancellation()->stream_has_echo()) { @@ -1836,6 +1833,9 @@ void AudioProcessingImpl::WriteAecDumpConfigMessage(bool forced) { if (capture_nonlocked_.echo_canceller3_enabled) { experiments_description += "EchoCanceller3;"; } + if (config_.gain_controller2.enabled) { + experiments_description += "GainController2;"; + } InternalAPMConfig apm_config; diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h index 6a58b41fc5..2becc9e26f 100644 --- a/modules/audio_processing/audio_processing_impl.h +++ b/modules/audio_processing/audio_processing_impl.h @@ -374,7 +374,6 @@ class AudioProcessingImpl : public AudioProcessing { bool intelligibility_enabled; bool level_controller_enabled = false; bool echo_canceller3_enabled = false; - bool gain_controller2_enabled = false; } capture_nonlocked_; struct ApmRenderState { diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h index e1828db794..2610300e0d 100644 --- a/modules/audio_processing/include/audio_processing.h +++ b/modules/audio_processing/include/audio_processing.h @@ -340,6 +340,7 @@ class AudioProcessing : public rtc::RefCountInterface { // does not yet have the desired behavior. struct GainController2 { bool enabled = false; + float fixed_gain_db = 0.f; } gain_controller2; // Explicit copy assignment implementation to avoid issues with memory diff --git a/modules/audio_processing/test/audio_processing_simulator.cc b/modules/audio_processing/test/audio_processing_simulator.cc index 0926473b10..16830101e8 100644 --- a/modules/audio_processing/test/audio_processing_simulator.cc +++ b/modules/audio_processing/test/audio_processing_simulator.cc @@ -323,6 +323,7 @@ void AudioProcessingSimulator::CreateAudioProcessor() { } if (settings_.use_agc2) { apm_config.gain_controller2.enabled = *settings_.use_agc2; + apm_config.gain_controller2.fixed_gain_db = settings_.agc2_fixed_gain_db; } if (settings_.use_lc) { apm_config.level_controller.enabled = *settings_.use_lc; diff --git a/modules/audio_processing/test/audio_processing_simulator.h b/modules/audio_processing/test/audio_processing_simulator.h index 9f81e6caa3..41a3f45106 100644 --- a/modules/audio_processing/test/audio_processing_simulator.h +++ b/modules/audio_processing/test/audio_processing_simulator.h @@ -74,6 +74,7 @@ struct SimulationSettings { rtc::Optional agc_target_level; rtc::Optional use_agc_limiter; rtc::Optional agc_compression_gain; + float agc2_fixed_gain_db; rtc::Optional vad_likelihood; rtc::Optional ns_level; rtc::Optional use_refined_adaptive_filter; diff --git a/modules/audio_processing/test/audioproc_float.cc b/modules/audio_processing/test/audioproc_float.cc index df7f43edc9..45ca8f01b7 100644 --- a/modules/audio_processing/test/audioproc_float.cc +++ b/modules/audio_processing/test/audioproc_float.cc @@ -149,6 +149,7 @@ DEFINE_int(agc_limiter, DEFINE_int(agc_compression_gain, kParameterNotSpecifiedValue, "Specify the AGC compression gain (0-90)"); +DEFINE_float(agc2_fixed_gain_db, 0.f, "AGC2 fixed gain (dB) to apply"); DEFINE_int(vad_likelihood, kParameterNotSpecifiedValue, "Specify the VAD likelihood (0-3)"); @@ -214,6 +215,7 @@ SimulationSettings CreateSettings() { settings.use_ns = rtc::Optional(true); settings.use_hpf = rtc::Optional(true); settings.use_agc = rtc::Optional(true); + settings.use_agc2 = rtc::Optional(false); settings.use_aec = rtc::Optional(true); settings.use_aecm = rtc::Optional(false); settings.use_ed = rtc::Optional(false); @@ -269,6 +271,7 @@ SimulationSettings CreateSettings() { SetSettingIfFlagSet(FLAG_agc_limiter, &settings.use_agc_limiter); SetSettingIfSpecified(FLAG_agc_compression_gain, &settings.agc_compression_gain); + settings.agc2_fixed_gain_db = FLAG_agc2_fixed_gain_db; SetSettingIfSpecified(FLAG_vad_likelihood, &settings.vad_likelihood); SetSettingIfSpecified(FLAG_ns_level, &settings.ns_level); SetSettingIfSpecified(FLAG_stream_delay, &settings.stream_delay); @@ -375,6 +378,17 @@ void PerformBasicParameterSanityChecks(const SimulationSettings& settings) { (*settings.agc_compression_gain) > 90), "Error: --agc_compression_gain must be specified between 0 and 90.\n"); + ReportConditionalErrorAndExit( + settings.use_agc && *settings.use_agc && settings.use_agc2 && + *settings.use_agc2, + "Error: --agc and --agc2 cannot be both active.\n"); + + ReportConditionalErrorAndExit( + settings.use_agc2 && *settings.use_agc2 && + ((settings.agc2_fixed_gain_db) < 0 || + (settings.agc2_fixed_gain_db) > 90), + "Error: --agc2_fixed_gain_db must be specified between 0 and 90.\n"); + ReportConditionalErrorAndExit( settings.vad_likelihood && ((*settings.vad_likelihood) < 0 || (*settings.vad_likelihood) > 3),