From cf69d2209bc9dfc04a6487a5c1ce5ab0f06a4870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20de=20Vicente=20Pe=C3=B1a?= Date: Tue, 27 Nov 2018 09:24:29 +0100 Subject: [PATCH] AEC3: Optimizing the Update method of the FilterAnalyzer class. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this CL the analysis of the impulse response that is done in the FilterAnalyzed class is changed in order to reduce its complexity. Instead of analyzing the whole impulse response in each Update call a smaller region is analyzed. That region is changed at each Update call which implies that several calls are needed in order to analyze the complete impulse response. Bug: webrtc:10032,chromium:909007 Change-Id: Ic58be34ba18485311c63e0fed9b6e892f9cb864c Reviewed-on: https://webrtc-review.googlesource.com/c/111602 Reviewed-by: Per Åhgren Commit-Queue: Per Åhgren Commit-Queue: Jesus de Vicente Pena Cr-Commit-Position: refs/heads/master@{#25817} --- modules/audio_processing/aec3/aec_state.cc | 3 +- .../audio_processing/aec3/filter_analyzer.cc | 226 ++++++++++++------ .../audio_processing/aec3/filter_analyzer.h | 45 +++- 3 files changed, 191 insertions(+), 83 deletions(-) diff --git a/modules/audio_processing/aec3/aec_state.cc b/modules/audio_processing/aec3/aec_state.cc index d5f256b1c1..45b361fc59 100644 --- a/modules/audio_processing/aec3/aec_state.cc +++ b/modules/audio_processing/aec3/aec_state.cc @@ -151,8 +151,7 @@ void AecState::Update( subtractor_output_analyzer_.Update(subtractor_output); // Analyze the properties of the filter. - filter_analyzer_.Update(adaptive_filter_impulse_response, - adaptive_filter_frequency_response, render_buffer); + filter_analyzer_.Update(adaptive_filter_impulse_response, render_buffer); // Estimate the direct path delay of the filter. delay_state_.Update(filter_analyzer_, external_delay, diff --git a/modules/audio_processing/aec3/filter_analyzer.cc b/modules/audio_processing/aec3/filter_analyzer.cc index 5b890d7474..3e69be6585 100644 --- a/modules/audio_processing/aec3/filter_analyzer.cc +++ b/modules/audio_processing/aec3/filter_analyzer.cc @@ -25,18 +25,22 @@ namespace webrtc { namespace { -size_t FindPeakIndex(rtc::ArrayView filter_time_domain) { - size_t peak_index = 0; - float max_h2 = filter_time_domain[0] * filter_time_domain[0]; - for (size_t k = 1; k < filter_time_domain.size(); ++k) { +size_t FindPeakIndex(rtc::ArrayView filter_time_domain, + size_t peak_index_in, + size_t start_sample, + size_t end_sample) { + size_t peak_index_out = peak_index_in; + float max_h2 = + filter_time_domain[peak_index_out] * filter_time_domain[peak_index_out]; + for (size_t k = start_sample; k <= end_sample; ++k) { float tmp = filter_time_domain[k] * filter_time_domain[k]; if (tmp > max_h2) { - peak_index = k; + peak_index_out = k; max_h2 = tmp; } } - return peak_index; + return peak_index_out; } bool EnableFilterPreprocessing() { @@ -44,6 +48,11 @@ bool EnableFilterPreprocessing() { "WebRTC-Aec3FilterAnalyzerPreprocessorKillSwitch"); } +bool EnableIncrementalAnalysis() { + return !field_trial::IsEnabled( + "WebRTC-Aec3FilterAnalyzerIncrementalAnalysisKillSwitch"); +} + } // namespace int FilterAnalyzer::instance_count_ = 0; @@ -54,46 +63,37 @@ FilterAnalyzer::FilterAnalyzer(const EchoCanceller3Config& config) use_preprocessed_filter_(EnableFilterPreprocessing()), bounded_erl_(config.ep_strength.bounded_erl), default_gain_(config.ep_strength.lf), - active_render_threshold_(config.render_levels.active_render_limit * - config.render_levels.active_render_limit * - kFftLengthBy2), + use_incremental_analysis_(EnableIncrementalAnalysis()), h_highpass_(GetTimeDomainLength(config.filter.main.length_blocks), 0.f), - filter_length_blocks_(config.filter.main_initial.length_blocks) { + filter_length_blocks_(config.filter.main_initial.length_blocks), + consistent_filter_detector_(config) { Reset(); } -void FilterAnalyzer::PreProcessFilter( - rtc::ArrayView filter_time_domain) { - RTC_DCHECK_GE(h_highpass_.capacity(), filter_time_domain.size()); - h_highpass_.resize(filter_time_domain.size()); - // Minimum phase high-pass filter with cutoff frequency at about 600 Hz. - constexpr std::array h = {{0.7929742f, -0.36072128f, -0.47047766f}}; - - std::fill(h_highpass_.begin(), h_highpass_.end(), 0.f); - for (size_t k = h.size() - 1; k < filter_time_domain.size(); ++k) { - for (size_t j = 0; j < h.size(); ++j) { - h_highpass_[k] += filter_time_domain[k - j] * h[j]; - } - } -} - FilterAnalyzer::~FilterAnalyzer() = default; void FilterAnalyzer::Reset() { delay_blocks_ = 0; - consistent_estimate_ = false; blocks_since_reset_ = 0; - consistent_estimate_ = false; - consistent_estimate_counter_ = 0; - consistent_delay_reference_ = -10; gain_ = default_gain_; + peak_index_ = 0; + ResetRegion(); + consistent_filter_detector_.Reset(); } -void FilterAnalyzer::Update( +void FilterAnalyzer::Update(rtc::ArrayView filter_time_domain, + const RenderBuffer& render_buffer) { + SetRegionToAnalyze(filter_time_domain); + AnalyzeRegion(filter_time_domain, render_buffer); +} + +void FilterAnalyzer::AnalyzeRegion( rtc::ArrayView filter_time_domain, - const std::vector>& - filter_freq_response, const RenderBuffer& render_buffer) { + RTC_DCHECK_LT(region_.start_sample_, filter_time_domain.size()); + RTC_DCHECK_LT(peak_index_, filter_time_domain.size()); + RTC_DCHECK_LT(region_.end_sample_, filter_time_domain.size()); + // Preprocess the filter to avoid issues with low-frequency components in the // filter. PreProcessFilter(filter_time_domain); @@ -103,51 +103,15 @@ void FilterAnalyzer::Update( use_preprocessed_filter_ ? h_highpass_ : filter_time_domain; RTC_DCHECK_EQ(filter_to_analyze.size(), filter_time_domain.size()); - size_t peak_index = FindPeakIndex(filter_to_analyze); - delay_blocks_ = peak_index >> kBlockSizeLog2; - UpdateFilterGain(filter_to_analyze, peak_index); - - float filter_floor = 0; - float filter_secondary_peak = 0; - size_t limit1 = peak_index < 64 ? 0 : peak_index - 64; - size_t limit2 = - peak_index > filter_to_analyze.size() - 129 ? 0 : peak_index + 128; - - for (size_t k = 0; k < limit1; ++k) { - float abs_h = fabsf(filter_to_analyze[k]); - filter_floor += abs_h; - filter_secondary_peak = std::max(filter_secondary_peak, abs_h); - } - for (size_t k = limit2; k < filter_to_analyze.size(); ++k) { - float abs_h = fabsf(filter_to_analyze[k]); - filter_floor += abs_h; - filter_secondary_peak = std::max(filter_secondary_peak, abs_h); - } - - filter_floor /= (limit1 + filter_to_analyze.size() - limit2); - - float abs_peak = fabsf(filter_to_analyze[peak_index]); - bool significant_peak_index = - abs_peak > 10.f * filter_floor && abs_peak > 2.f * filter_secondary_peak; - - if (consistent_delay_reference_ != delay_blocks_ || !significant_peak_index) { - consistent_estimate_counter_ = 0; - consistent_delay_reference_ = delay_blocks_; - } else { - const auto& x = render_buffer.Block(-delay_blocks_)[0]; - const float x_energy = - std::inner_product(x.begin(), x.end(), x.begin(), 0.f); - const bool active_render_block = x_energy > active_render_threshold_; - - if (active_render_block) { - ++consistent_estimate_counter_; - } - } - - consistent_estimate_ = - consistent_estimate_counter_ > 1.5f * kNumBlocksPerSecond; - + peak_index_ = FindPeakIndex(filter_to_analyze, peak_index_, + region_.start_sample_, region_.end_sample_); + delay_blocks_ = peak_index_ >> kBlockSizeLog2; + UpdateFilterGain(filter_to_analyze, peak_index_); filter_length_blocks_ = filter_time_domain.size() * (1.f / kBlockSize); + + consistent_estimate_ = consistent_filter_detector_.Detect( + filter_to_analyze, region_, render_buffer.Block(-delay_blocks_)[0], + peak_index_, delay_blocks_); } void FilterAnalyzer::UpdateFilterGain( @@ -169,4 +133,114 @@ void FilterAnalyzer::UpdateFilterGain( } } +void FilterAnalyzer::PreProcessFilter( + rtc::ArrayView filter_time_domain) { + RTC_DCHECK_GE(h_highpass_.capacity(), filter_time_domain.size()); + h_highpass_.resize(filter_time_domain.size()); + // Minimum phase high-pass filter with cutoff frequency at about 600 Hz. + constexpr std::array h = {{0.7929742f, -0.36072128f, -0.47047766f}}; + + std::fill(h_highpass_.begin() + region_.start_sample_, + h_highpass_.begin() + region_.end_sample_ + 1, 0.f); + for (size_t k = std::max(h.size() - 1, region_.start_sample_); + k <= region_.end_sample_; ++k) { + for (size_t j = 0; j < h.size(); ++j) { + h_highpass_[k] += filter_time_domain[k - j] * h[j]; + } + } +} + +void FilterAnalyzer::ResetRegion() { + region_.start_sample_ = 0; + region_.end_sample_ = 0; +} + +void FilterAnalyzer::SetRegionToAnalyze( + rtc::ArrayView filter_time_domain) { + constexpr size_t kNumberBlocksToUpdate = 1; + auto& r = region_; + if (use_incremental_analysis_) { + r.start_sample_ = + r.end_sample_ == filter_time_domain.size() - 1 ? 0 : r.end_sample_ + 1; + r.end_sample_ = + std::min(r.start_sample_ + kNumberBlocksToUpdate * kBlockSize - 1, + filter_time_domain.size() - 1); + + } else { + r.start_sample_ = 0; + r.end_sample_ = filter_time_domain.size() - 1; + } +} + +FilterAnalyzer::ConsistentFilterDetector::ConsistentFilterDetector( + const EchoCanceller3Config& config) + : active_render_threshold_(config.render_levels.active_render_limit * + config.render_levels.active_render_limit * + kFftLengthBy2) {} + +void FilterAnalyzer::ConsistentFilterDetector::Reset() { + significant_peak_ = false; + filter_floor_accum_ = 0.f; + filter_secondary_peak_ = 0.f; + filter_floor_low_limit_ = 0; + filter_floor_high_limit_ = 0; + consistent_estimate_counter_ = 0; + consistent_delay_reference_ = -10; +} + +bool FilterAnalyzer::ConsistentFilterDetector::Detect( + rtc::ArrayView filter_to_analyze, + const FilterRegion& region, + rtc::ArrayView x_block, + size_t peak_index, + int delay_blocks) { + if (region.start_sample_ == 0) { + filter_floor_accum_ = 0.f; + filter_secondary_peak_ = 0.f; + filter_floor_low_limit_ = peak_index < 64 ? 0 : peak_index - 64; + filter_floor_high_limit_ = + peak_index > filter_to_analyze.size() - 129 ? 0 : peak_index + 128; + } + + for (size_t k = region.start_sample_; + k < std::min(region.end_sample_ + 1, filter_floor_low_limit_); ++k) { + float abs_h = fabsf(filter_to_analyze[k]); + filter_floor_accum_ += abs_h; + filter_secondary_peak_ = std::max(filter_secondary_peak_, abs_h); + } + + for (size_t k = std::max(filter_floor_high_limit_, region.start_sample_); + k <= region.end_sample_; ++k) { + float abs_h = fabsf(filter_to_analyze[k]); + filter_floor_accum_ += abs_h; + filter_secondary_peak_ = std::max(filter_secondary_peak_, abs_h); + } + + if (region.end_sample_ == filter_to_analyze.size() - 1) { + float filter_floor = filter_floor_accum_ / + (filter_floor_low_limit_ + filter_to_analyze.size() - + filter_floor_high_limit_); + + float abs_peak = fabsf(filter_to_analyze[peak_index]); + significant_peak_ = abs_peak > 10.f * filter_floor && + abs_peak > 2.f * filter_secondary_peak_; + } + + if (significant_peak_) { + const float x_energy = std::inner_product(x_block.begin(), x_block.end(), + x_block.begin(), 0.f); + const bool active_render_block = x_energy > active_render_threshold_; + + if (consistent_delay_reference_ == delay_blocks) { + if (active_render_block) { + ++consistent_estimate_counter_; + } + } else { + consistent_estimate_counter_ = 0; + consistent_delay_reference_ = delay_blocks; + } + } + return consistent_estimate_counter_ > 1.5f * kNumBlocksPerSecond; +} + } // namespace webrtc diff --git a/modules/audio_processing/aec3/filter_analyzer.h b/modules/audio_processing/aec3/filter_analyzer.h index 99a0e25973..e0fd0695cb 100644 --- a/modules/audio_processing/aec3/filter_analyzer.h +++ b/modules/audio_processing/aec3/filter_analyzer.h @@ -37,8 +37,6 @@ class FilterAnalyzer { // Updates the estimates with new input data. void Update(rtc::ArrayView filter_time_domain, - const std::vector>& - filter_freq_response, const RenderBuffer& render_buffer); // Returns the delay of the filter in terms of blocks. @@ -58,24 +56,61 @@ class FilterAnalyzer { rtc::ArrayView GetAdjustedFilter() const { return h_highpass_; } private: + void AnalyzeRegion(rtc::ArrayView filter_time_domain, + const RenderBuffer& render_buffer); + void UpdateFilterGain(rtc::ArrayView filter_time_domain, size_t max_index); void PreProcessFilter(rtc::ArrayView filter_time_domain); + void ResetRegion(); + + void SetRegionToAnalyze(rtc::ArrayView filter_time_domain); + + struct FilterRegion { + size_t start_sample_; + size_t end_sample_; + }; + + // This class checks whether the shape of the impulse response has been + // consistent over time. + class ConsistentFilterDetector { + public: + explicit ConsistentFilterDetector(const EchoCanceller3Config& config); + void Reset(); + bool Detect(rtc::ArrayView filter_to_analyze, + const FilterRegion& region, + rtc::ArrayView x_block, + size_t peak_index, + int delay_blocks); + + private: + bool significant_peak_; + float filter_floor_accum_; + float filter_secondary_peak_; + size_t filter_floor_low_limit_; + size_t filter_floor_high_limit_; + const float active_render_threshold_; + size_t consistent_estimate_counter_ = 0; + int consistent_delay_reference_ = -10; + }; + static int instance_count_; std::unique_ptr data_dumper_; const bool use_preprocessed_filter_; const bool bounded_erl_; const float default_gain_; - const float active_render_threshold_; + const bool use_incremental_analysis_; std::vector h_highpass_; int delay_blocks_ = 0; size_t blocks_since_reset_ = 0; bool consistent_estimate_ = false; - size_t consistent_estimate_counter_ = 0; - int consistent_delay_reference_ = -10; float gain_; + size_t peak_index_; int filter_length_blocks_; + FilterRegion region_; + ConsistentFilterDetector consistent_filter_detector_; + RTC_DISALLOW_COPY_AND_ASSIGN(FilterAnalyzer); };