Add adaptive notch filter to remove narrowband echo components in AEC3
This CL adds detection of components in the render signal that are of strong narrowband nature and therefore may cause problems for the AEC. This CL also adds functionality in the echo suppressor to suppress these signals BUG=webrtc:7967 Review-Url: https://codereview.webrtc.org/2980493002 Cr-Commit-Position: refs/heads/master@{#18968}
This commit is contained in:
parent
5e6685ff35
commit
14c11a4712
@ -190,8 +190,9 @@ void EchoRemoverImpl::ProcessCapture(
|
||||
|
||||
// A choose and apply echo suppression gain.
|
||||
suppression_gain_.GetGain(E2, R2, cng_.NoiseSpectrum(),
|
||||
aec_state_.SaturatedEcho(), x,
|
||||
aec_state_.ForcedZeroGain(), &high_bands_gain, &G);
|
||||
render_signal_analyzer_, aec_state_.SaturatedEcho(),
|
||||
x, aec_state_.ForcedZeroGain(), &high_bands_gain,
|
||||
&G);
|
||||
suppression_filter_.ApplyGain(comfort_noise, high_band_comfort_noise, G,
|
||||
high_bands_gain, y);
|
||||
|
||||
@ -206,6 +207,8 @@ void EchoRemoverImpl::ProcessCapture(
|
||||
&subtractor_output.s_main[0],
|
||||
LowestBandRate(sample_rate_hz_), 1);
|
||||
data_dumper_->DumpRaw("aec3_output", y0);
|
||||
data_dumper_->DumpRaw("aec3_narrow_render",
|
||||
render_signal_analyzer_.NarrowPeakBand() ? 1 : 0);
|
||||
data_dumper_->DumpRaw("aec3_N2", cng_.NoiseSpectrum());
|
||||
data_dumper_->DumpRaw("aec3_suppressor_gain", G);
|
||||
data_dumper_->DumpWav("aec3_output",
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/render_signal_analyzer.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/rtc_base/checks.h"
|
||||
@ -19,6 +20,72 @@ namespace webrtc {
|
||||
namespace {
|
||||
constexpr size_t kCounterThreshold = 5;
|
||||
|
||||
// Identifies local bands with narrow characteristics.
|
||||
void IdentifySmallNarrowBandRegions(
|
||||
const RenderBuffer& render_buffer,
|
||||
const rtc::Optional<size_t>& delay_partitions,
|
||||
std::array<size_t, kFftLengthBy2 - 1>* narrow_band_counters) {
|
||||
if (!delay_partitions) {
|
||||
narrow_band_counters->fill(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::array<float, kFftLengthBy2Plus1>& X2 =
|
||||
render_buffer.Spectrum(*delay_partitions);
|
||||
|
||||
for (size_t k = 1; k < (X2.size() - 1); ++k) {
|
||||
(*narrow_band_counters)[k - 1] = X2[k] > 3 * std::max(X2[k - 1], X2[k + 1])
|
||||
? (*narrow_band_counters)[k - 1] + 1
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Identifies whether the signal has a single strong narrow-band component.
|
||||
void IdentifyStrongNarrowBandComponent(const RenderBuffer& render_buffer,
|
||||
rtc::Optional<int>* narrow_peak_band,
|
||||
size_t* narrow_peak_counter) {
|
||||
const auto X2_latest = render_buffer.Spectrum(0);
|
||||
|
||||
// Identify the spectral peak.
|
||||
const int peak_bin = static_cast<int>(
|
||||
std::max_element(X2_latest.begin(), X2_latest.end()) - X2_latest.begin());
|
||||
|
||||
// Compute the level around the peak.
|
||||
float non_peak_power = 0.f;
|
||||
for (int k = std::max(5, peak_bin - 14); k < peak_bin - 4; ++k) {
|
||||
non_peak_power = std::max(X2_latest[k], non_peak_power);
|
||||
}
|
||||
for (int k = peak_bin + 5;
|
||||
k < std::min(peak_bin + 15, static_cast<int>(kFftLengthBy2Plus1)); ++k) {
|
||||
non_peak_power = std::max(X2_latest[k], non_peak_power);
|
||||
}
|
||||
|
||||
// Assess the render signal strength
|
||||
const std::vector<std::vector<float>>& x_latest =
|
||||
render_buffer.MostRecentBlock();
|
||||
auto result0 = std::minmax_element(x_latest[0].begin(), x_latest[0].end());
|
||||
float max_abs = std::max(fabs(*result0.first), fabs(*result0.second));
|
||||
|
||||
if (x_latest.size() > 1) {
|
||||
const auto result1 =
|
||||
std::minmax_element(x_latest[1].begin(), x_latest[1].end());
|
||||
max_abs =
|
||||
std::max(max_abs, static_cast<float>(std::max(fabs(*result1.first),
|
||||
fabs(*result1.second))));
|
||||
}
|
||||
|
||||
// Detect whether the spectal peak has as strong narrowband nature.
|
||||
if (peak_bin > 6 && max_abs > 100 &&
|
||||
X2_latest[peak_bin] > 100 * non_peak_power) {
|
||||
*narrow_peak_band = rtc::Optional<int>(peak_bin);
|
||||
*narrow_peak_counter = 0;
|
||||
} else {
|
||||
if (*narrow_peak_band && ++(*narrow_peak_counter) > 7) {
|
||||
*narrow_peak_band = rtc::Optional<int>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderSignalAnalyzer::RenderSignalAnalyzer() {
|
||||
@ -29,20 +96,13 @@ RenderSignalAnalyzer::~RenderSignalAnalyzer() = default;
|
||||
void RenderSignalAnalyzer::Update(
|
||||
const RenderBuffer& render_buffer,
|
||||
const rtc::Optional<size_t>& delay_partitions) {
|
||||
if (!delay_partitions) {
|
||||
narrow_band_counters_.fill(0);
|
||||
return;
|
||||
}
|
||||
// Identify bands of narrow nature.
|
||||
IdentifySmallNarrowBandRegions(render_buffer, delay_partitions,
|
||||
&narrow_band_counters_);
|
||||
|
||||
const std::array<float, kFftLengthBy2Plus1>& X2 =
|
||||
render_buffer.Spectrum(*delay_partitions);
|
||||
|
||||
// Detect narrow band signal regions.
|
||||
for (size_t k = 1; k < (X2.size() - 1); ++k) {
|
||||
narrow_band_counters_[k - 1] = X2[k] > 3 * std::max(X2[k - 1], X2[k + 1])
|
||||
? narrow_band_counters_[k - 1] + 1
|
||||
: 0;
|
||||
}
|
||||
// Identify the presence of a strong narrow band.
|
||||
IdentifyStrongNarrowBandComponent(render_buffer, &narrow_peak_band_,
|
||||
&narrow_peak_counter_);
|
||||
}
|
||||
|
||||
void RenderSignalAnalyzer::MaskRegionsAroundNarrowBands(
|
||||
|
||||
@ -43,8 +43,12 @@ class RenderSignalAnalyzer {
|
||||
void MaskRegionsAroundNarrowBands(
|
||||
std::array<float, kFftLengthBy2Plus1>* v) const;
|
||||
|
||||
rtc::Optional<int> NarrowPeakBand() const { return narrow_peak_band_; }
|
||||
|
||||
private:
|
||||
std::array<size_t, kFftLengthBy2 - 1> narrow_band_counters_;
|
||||
rtc::Optional<int> narrow_peak_band_;
|
||||
size_t narrow_peak_counter_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(RenderSignalAnalyzer);
|
||||
};
|
||||
|
||||
@ -25,6 +25,16 @@
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// Reduce gain to avoid narrow band echo leakage.
|
||||
void NarrowBandAttenuation(int narrow_bin,
|
||||
std::array<float, kFftLengthBy2Plus1>* gain) {
|
||||
const int upper_bin =
|
||||
std::min(narrow_bin + 6, static_cast<int>(kFftLengthBy2Plus1 - 1));
|
||||
for (int k = std::max(0, narrow_bin - 6); k <= upper_bin; ++k) {
|
||||
(*gain)[k] = std::min((*gain)[k], 0.001f);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the gains according to the presence of known external filters.
|
||||
void AdjustForExternalFilters(std::array<float, kFftLengthBy2Plus1>* gain) {
|
||||
// Limit the low frequency gains to avoid the impact of the high-pass filter
|
||||
@ -45,6 +55,7 @@ void AdjustForExternalFilters(std::array<float, kFftLengthBy2Plus1>* gain) {
|
||||
|
||||
// Computes the gain to apply for the bands beyond the first band.
|
||||
float UpperBandsGain(
|
||||
const rtc::Optional<int>& narrow_peak_band,
|
||||
bool saturated_echo,
|
||||
const std::vector<std::vector<float>>& render,
|
||||
const std::array<float, kFftLengthBy2Plus1>& low_band_gain) {
|
||||
@ -53,6 +64,11 @@ float UpperBandsGain(
|
||||
return 1.f;
|
||||
}
|
||||
|
||||
if (narrow_peak_band &&
|
||||
(*narrow_peak_band > static_cast<int>(kFftLengthBy2Plus1 - 10))) {
|
||||
return 0.001f;
|
||||
}
|
||||
|
||||
constexpr size_t kLowBandGainLimit = kFftLengthBy2 / 2;
|
||||
const float gain_below_8_khz = *std::min_element(
|
||||
low_band_gain.begin() + kLowBandGainLimit, low_band_gain.end());
|
||||
@ -193,6 +209,7 @@ void MaskingPower(const std::array<float, kFftLengthBy2Plus1>& nearend,
|
||||
// TODO(peah): Add further optimizations, in particular for the divisions.
|
||||
void SuppressionGain::LowerBandGain(
|
||||
bool low_noise_render,
|
||||
const rtc::Optional<int>& narrow_peak_band,
|
||||
bool saturated_echo,
|
||||
const std::array<float, kFftLengthBy2Plus1>& nearend,
|
||||
const std::array<float, kFftLengthBy2Plus1>& echo,
|
||||
@ -238,6 +255,9 @@ void SuppressionGain::LowerBandGain(
|
||||
GainToNoAudibleEcho(low_noise_render, saturated_echo, nearend, echo, masker,
|
||||
min_gain, max_gain, one_by_echo, gain);
|
||||
AdjustForExternalFilters(gain);
|
||||
if (narrow_peak_band) {
|
||||
NarrowBandAttenuation(*narrow_peak_band, gain);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the allowed maximum gain increase.
|
||||
@ -263,6 +283,7 @@ void SuppressionGain::GetGain(
|
||||
const std::array<float, kFftLengthBy2Plus1>& nearend,
|
||||
const std::array<float, kFftLengthBy2Plus1>& echo,
|
||||
const std::array<float, kFftLengthBy2Plus1>& comfort_noise,
|
||||
const RenderSignalAnalyzer& render_signal_analyzer,
|
||||
bool saturated_echo,
|
||||
const std::vector<std::vector<float>>& render,
|
||||
bool force_zero_gain,
|
||||
@ -283,11 +304,14 @@ void SuppressionGain::GetGain(
|
||||
bool low_noise_render = low_render_detector_.Detect(render);
|
||||
|
||||
// Compute gain for the lower band.
|
||||
LowerBandGain(low_noise_render, saturated_echo, nearend, echo, comfort_noise,
|
||||
low_band_gain);
|
||||
const rtc::Optional<int> narrow_peak_band =
|
||||
render_signal_analyzer.NarrowPeakBand();
|
||||
LowerBandGain(low_noise_render, narrow_peak_band, saturated_echo, nearend,
|
||||
echo, comfort_noise, low_band_gain);
|
||||
|
||||
// Compute the gain for the upper bands.
|
||||
*high_bands_gain = UpperBandsGain(saturated_echo, render, *low_band_gain);
|
||||
*high_bands_gain =
|
||||
UpperBandsGain(narrow_peak_band, saturated_echo, render, *low_band_gain);
|
||||
}
|
||||
|
||||
// Detects when the render signal can be considered to have low power and
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "webrtc/modules/audio_processing/aec3/render_signal_analyzer.h"
|
||||
#include "webrtc/rtc_base/constructormagic.h"
|
||||
|
||||
namespace webrtc {
|
||||
@ -25,6 +26,7 @@ class SuppressionGain {
|
||||
void GetGain(const std::array<float, kFftLengthBy2Plus1>& nearend,
|
||||
const std::array<float, kFftLengthBy2Plus1>& echo,
|
||||
const std::array<float, kFftLengthBy2Plus1>& comfort_noise,
|
||||
const RenderSignalAnalyzer& render_signal_analyzer,
|
||||
bool saturated_echo,
|
||||
const std::vector<std::vector<float>>& render,
|
||||
bool force_zero_gain,
|
||||
@ -33,6 +35,7 @@ class SuppressionGain {
|
||||
|
||||
private:
|
||||
void LowerBandGain(bool stationary_with_low_power,
|
||||
const rtc::Optional<int>& narrow_peak_band,
|
||||
bool saturated_echo,
|
||||
const std::array<float, kFftLengthBy2Plus1>& nearend,
|
||||
const std::array<float, kFftLengthBy2Plus1>& echo,
|
||||
|
||||
@ -30,7 +30,7 @@ TEST(SuppressionGain, NullOutputGains) {
|
||||
N2.fill(0.f);
|
||||
float high_bands_gain;
|
||||
EXPECT_DEATH(SuppressionGain(DetectOptimization())
|
||||
.GetGain(E2, R2, N2, false,
|
||||
.GetGain(E2, R2, N2, RenderSignalAnalyzer(), false,
|
||||
std::vector<std::vector<float>>(
|
||||
3, std::vector<float>(kBlockSize, 0.f)),
|
||||
false, &high_bands_gain, nullptr),
|
||||
@ -42,6 +42,7 @@ TEST(SuppressionGain, NullOutputGains) {
|
||||
// Does a sanity check that the gains are correctly computed.
|
||||
TEST(SuppressionGain, BasicGainComputation) {
|
||||
SuppressionGain suppression_gain(DetectOptimization());
|
||||
RenderSignalAnalyzer analyzer;
|
||||
float high_bands_gain;
|
||||
std::array<float, kFftLengthBy2Plus1> E2;
|
||||
std::array<float, kFftLengthBy2Plus1> R2;
|
||||
@ -54,7 +55,8 @@ TEST(SuppressionGain, BasicGainComputation) {
|
||||
R2.fill(0.1f);
|
||||
N2.fill(100.f);
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
suppression_gain.GetGain(E2, R2, N2, false, x, false, &high_bands_gain, &g);
|
||||
suppression_gain.GetGain(E2, R2, N2, analyzer, false, x, false,
|
||||
&high_bands_gain, &g);
|
||||
}
|
||||
std::for_each(g.begin(), g.end(),
|
||||
[](float a) { EXPECT_NEAR(1.f, a, 0.001); });
|
||||
@ -64,7 +66,8 @@ TEST(SuppressionGain, BasicGainComputation) {
|
||||
R2.fill(0.1f);
|
||||
N2.fill(0.f);
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
suppression_gain.GetGain(E2, R2, N2, false, x, false, &high_bands_gain, &g);
|
||||
suppression_gain.GetGain(E2, R2, N2, analyzer, false, x, false,
|
||||
&high_bands_gain, &g);
|
||||
}
|
||||
std::for_each(g.begin(), g.end(),
|
||||
[](float a) { EXPECT_NEAR(1.f, a, 0.001); });
|
||||
@ -74,13 +77,15 @@ TEST(SuppressionGain, BasicGainComputation) {
|
||||
R2.fill(10000000000000.f);
|
||||
N2.fill(0.f);
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
suppression_gain.GetGain(E2, R2, N2, false, x, false, &high_bands_gain, &g);
|
||||
suppression_gain.GetGain(E2, R2, N2, analyzer, false, x, false,
|
||||
&high_bands_gain, &g);
|
||||
}
|
||||
std::for_each(g.begin(), g.end(),
|
||||
[](float a) { EXPECT_NEAR(0.f, a, 0.001); });
|
||||
|
||||
// Verify the functionality for forcing a zero gain.
|
||||
suppression_gain.GetGain(E2, R2, N2, false, x, true, &high_bands_gain, &g);
|
||||
suppression_gain.GetGain(E2, R2, N2, analyzer, false, x, true,
|
||||
&high_bands_gain, &g);
|
||||
std::for_each(g.begin(), g.end(), [](float a) { EXPECT_FLOAT_EQ(0.f, a); });
|
||||
EXPECT_FLOAT_EQ(0.f, high_bands_gain);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user