Robustification of the echo suppression behavior during headset usage.

This CL robustifies the echo removal behavior when headsets are used.
In particular it:
-Introduces a secondary, more refined alignment when no alignment can
be found using the delay estimator.
-Changes decision logic for when to use the linear filter output.
-Changes the decision logic for when to be transparent.
-Changes the way that the transparent mode works.
-Makes the nonlinear mode less aggressive.
-Removes the detector for non-audible echoes.
-Makes the attenuation when there are signals with strong narrowband
characteristics more mild in scenarios with low render.

Furthermore the CL:
-Removes the input of external echo leakage information.


Bug: webrtc:9047,chromium:824111,webrtc:8314,webrtc:8671,webrtc:5201,webrtc:5919
Change-Id: Ied1fe0c0a35d3c31b47606ed2db319a73644d406
Reviewed-on: https://webrtc-review.googlesource.com/60866
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22548}
This commit is contained in:
Per Åhgren 2018-03-22 00:29:25 +01:00 committed by Commit Bot
parent 5bdc82a60f
commit 5c532d3774
36 changed files with 568 additions and 332 deletions

View File

@ -18,7 +18,6 @@ namespace webrtc {
// Configuration struct for EchoCanceller3
struct EchoCanceller3Config {
EchoCanceller3Config();
struct Delay {
size_t default_delay = 5;
size_t down_sampling_factor = 4;
@ -57,14 +56,14 @@ struct EchoCanceller3Config {
struct Erle {
float min = 1.f;
float max_l = 8.f;
float max_l = 4.f;
float max_h = 1.5f;
} erle;
struct EpStrength {
float lf = 10.f;
float mf = 10.f;
float hf = 10.f;
float lf = 2.f;
float mf = 2.f;
float hf = 2.f;
float default_len = 0.f;
bool echo_can_saturate = true;
bool bounded_erl = false;

View File

@ -53,6 +53,8 @@ rtc_static_library("aec3") {
"fft_buffer.cc",
"fft_buffer.h",
"fft_data.h",
"filter_analyzer.cc",
"filter_analyzer.h",
"frame_blocker.cc",
"frame_blocker.h",
"main_filter_update_gain.cc",

View File

@ -351,7 +351,7 @@ TEST(AdaptiveFirFilter, FilterAndAdapt) {
CascadedBiQuadFilter y_hp_filter(kHighPassFilterCoefficients, 1);
SCOPED_TRACE(ProduceDebugText(delay_samples));
for (size_t k = 0; k < kNumBlocksToProcess; ++k) {
for (size_t j = 0; j < kNumBlocksToProcess; ++j) {
RandomizeSampleVector(&random_generator, x[0]);
delay_buffer.Delay(x[0], y);
@ -365,13 +365,14 @@ TEST(AdaptiveFirFilter, FilterAndAdapt) {
y_hp_filter.Process(y);
render_delay_buffer->Insert(x);
if (k == 0) {
if (j == 0) {
render_delay_buffer->Reset();
}
render_delay_buffer->PrepareCaptureProcessing();
const auto& render_buffer = render_delay_buffer->GetRenderBuffer();
render_signal_analyzer.Update(*render_buffer, aec_state.FilterDelay());
render_signal_analyzer.Update(*render_buffer,
aec_state.FilterDelayBlocks());
filter.Filter(*render_buffer, &S);
fft.Ifft(S, &s_scratch);
@ -392,15 +393,14 @@ TEST(AdaptiveFirFilter, FilterAndAdapt) {
filter.Adapt(*render_buffer, G);
aec_state.HandleEchoPathChange(EchoPathVariability(
false, EchoPathVariability::DelayAdjustment::kNone, false));
aec_state.Update(delay_estimate, filter.FilterFrequencyResponse(),
filter.FilterImpulseResponse(), true, *render_buffer,
E2_main, Y2, s, false);
filter.FilterImpulseResponse(), true, false,
*render_buffer, E2_main, Y2, s);
}
// Verify that the filter is able to perform well.
EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
EXPECT_EQ(delay_samples / kBlockSize,
static_cast<size_t>(aec_state.FilterDelay()));
}
}
} // namespace aec3

View File

@ -23,35 +23,14 @@
namespace webrtc {
namespace {
// Computes delay of the adaptive filter.
int EstimateFilterDelay(
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
adaptive_filter_frequency_response) {
const auto& H2 = adaptive_filter_frequency_response;
constexpr size_t kUpperBin = kFftLengthBy2 - 5;
RTC_DCHECK_GE(kMaxAdaptiveFilterLength, H2.size());
std::array<int, kMaxAdaptiveFilterLength> delays;
delays.fill(0);
for (size_t k = 1; k < kUpperBin; ++k) {
// Find the maximum of H2[j].
size_t peak = 0;
for (size_t j = 0; j < H2.size(); ++j) {
if (H2[j][k] > H2[peak][k]) {
peak = j;
}
}
++delays[peak];
}
return std::distance(delays.begin(),
std::max_element(delays.begin(), delays.end()));
}
float ComputeGainRampupIncrease(const EchoCanceller3Config& config) {
const auto& c = config.echo_removal_control.gain_rampup;
return powf(1.f / c.first_non_zero_gain, 1.f / c.non_zero_gain_blocks);
}
constexpr size_t kBlocksSinceConvergencedFilterInit = 10000;
constexpr size_t kBlocksSinceConsistentEstimateInit = 10000;
} // namespace
int AecState::instance_count_ = 0;
@ -64,27 +43,33 @@ AecState::AecState(const EchoCanceller3Config& config)
max_render_(config_.filter.main.length_blocks, 0.f),
reverb_decay_(fabsf(config_.ep_strength.default_len)),
gain_rampup_increase_(ComputeGainRampupIncrease(config_)),
suppression_gain_limiter_(config_) {}
suppression_gain_limiter_(config_),
filter_analyzer_(config_),
blocks_since_converged_filter_(kBlocksSinceConvergencedFilterInit),
active_blocks_since_consistent_filter_estimate_(
kBlocksSinceConsistentEstimateInit) {}
AecState::~AecState() = default;
void AecState::HandleEchoPathChange(
const EchoPathVariability& echo_path_variability) {
const auto full_reset = [&]() {
filter_analyzer_.Reset();
blocks_since_last_saturation_ = 0;
usable_linear_estimate_ = false;
echo_leakage_detected_ = false;
capture_signal_saturation_ = false;
echo_saturation_ = false;
previous_max_sample_ = 0.f;
std::fill(max_render_.begin(), max_render_.end(), 0.f);
blocks_with_proper_filter_adaptation_ = 0;
capture_block_counter_ = 0;
blocks_since_reset_ = 0;
filter_has_had_time_to_converge_ = false;
render_received_ = false;
blocks_with_active_render_ = 0;
initial_state_ = true;
suppression_gain_limiter_.Reset();
blocks_since_converged_filter_ = kBlocksSinceConvergencedFilterInit;
diverged_blocks_ = 0;
};
// TODO(peah): Refine the reset scheme according to the type of gain and
@ -106,30 +91,38 @@ void AecState::HandleEchoPathChange(
EchoPathVariability::DelayAdjustment::kNewDetectedDelay) {
full_reset();
} else if (echo_path_variability.gain_change) {
capture_block_counter_ = kNumBlocksPerSecond;
blocks_since_reset_ = kNumBlocksPerSecond;
}
}
void AecState::Update(
const rtc::Optional<DelayEstimate>& delay_estimate,
const rtc::Optional<DelayEstimate>& external_delay,
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
adaptive_filter_frequency_response,
const std::vector<float>& adaptive_filter_impulse_response,
bool converged_filter,
bool diverged_filter,
const RenderBuffer& render_buffer,
const std::array<float, kFftLengthBy2Plus1>& E2_main,
const std::array<float, kFftLengthBy2Plus1>& Y2,
const std::array<float, kBlockSize>& s,
bool echo_leakage_detected) {
// Store input parameters.
echo_leakage_detected_ = echo_leakage_detected;
const std::array<float, kBlockSize>& s) {
// Analyze the filter and compute the delays.
filter_analyzer_.Update(adaptive_filter_impulse_response, render_buffer);
filter_delay_blocks_ = filter_analyzer_.DelayBlocks();
// Estimate the filter delay.
filter_delay_ = EstimateFilterDelay(adaptive_filter_frequency_response);
const std::vector<float>& x = render_buffer.Block(-filter_delay_)[0];
if (filter_analyzer_.Consistent()) {
internal_delay_ = filter_analyzer_.DelayBlocks();
} else {
internal_delay_ = rtc::nullopt;
}
external_delay_seen_ = external_delay_seen_ || external_delay;
const std::vector<float>& x = render_buffer.Block(-filter_delay_blocks_)[0];
// Update counters.
++capture_block_counter_;
++blocks_since_reset_;
const bool active_render_block = DetectActiveRender(x);
blocks_with_active_render_ += active_render_block ? 1 : 0;
blocks_with_proper_filter_adaptation_ +=
@ -137,18 +130,16 @@ void AecState::Update(
// Update the limit on the echo suppression after an echo path change to avoid
// an initial echo burst.
suppression_gain_limiter_.Update(render_buffer.GetRenderActivity());
suppression_gain_limiter_.Update(render_buffer.GetRenderActivity(),
transparent_mode_);
// Update the ERL and ERLE measures.
if (converged_filter && capture_block_counter_ >= 2 * kNumBlocksPerSecond) {
const auto& X2 = render_buffer.Spectrum(filter_delay_);
if (converged_filter && blocks_since_reset_ >= 2 * kNumBlocksPerSecond) {
const auto& X2 = render_buffer.Spectrum(filter_delay_blocks_);
erle_estimator_.Update(X2, Y2, E2_main);
erl_estimator_.Update(X2, Y2);
}
// Update the echo audibility evaluator.
echo_audibility_.Update(x, s, converged_filter);
// Detect and flag echo saturation.
// TODO(peah): Add the delay in this computation to ensure that the render and
// capture signals are properly aligned.
@ -156,26 +147,99 @@ void AecState::Update(
echo_saturation_ = DetectEchoSaturation(x);
}
// TODO(peah): Move?
filter_has_had_time_to_converge_ =
bool filter_has_had_time_to_converge =
blocks_with_proper_filter_adaptation_ >= 1.5f * kNumBlocksPerSecond;
if (!filter_should_have_converged_) {
filter_should_have_converged_ =
blocks_with_proper_filter_adaptation_ > 6 * kNumBlocksPerSecond;
}
// Flag whether the initial state is still active.
initial_state_ =
blocks_with_proper_filter_adaptation_ < 5 * kNumBlocksPerSecond;
// Flag whether the linear filter estimate is usable.
usable_linear_estimate_ =
!echo_saturation_ &&
(converged_filter && filter_has_had_time_to_converge_) &&
capture_block_counter_ >= 1.f * kNumBlocksPerSecond && !TransparentMode();
// Update counters for the filter divergence and convergence.
diverged_blocks_ = diverged_filter ? diverged_blocks_ + 1 : 0;
if (diverged_blocks_ >= 60) {
blocks_since_converged_filter_ = kBlocksSinceConvergencedFilterInit;
} else {
blocks_since_converged_filter_ =
converged_filter ? 0 : blocks_since_converged_filter_ + 1;
}
bool recently_converged_filter =
blocks_since_converged_filter_ < 60 * kNumBlocksPerSecond;
if (filter_analyzer_.Consistent() && filter_delay_blocks_ < 5) {
consistent_filter_seen_ = true;
active_blocks_since_consistent_filter_estimate_ = 0;
} else if (active_render_block) {
++active_blocks_since_consistent_filter_estimate_;
}
bool consistent_filter_estimate_not_seen;
if (!consistent_filter_seen_) {
consistent_filter_estimate_not_seen =
capture_block_counter_ > 5 * kNumBlocksPerSecond;
} else {
consistent_filter_estimate_not_seen =
active_blocks_since_consistent_filter_estimate_ >
30 * kNumBlocksPerSecond;
}
converged_filter_seen_ = converged_filter_seen_ || converged_filter;
// After an amount of active render samples for which an echo should have been
// detected in the capture signal if the ERL was not infinite, flag that a
// transparent mode should be entered.
transparent_mode_ = !config_.ep_strength.bounded_erl;
transparent_mode_ =
!converged_filter &&
(blocks_with_active_render_ == 0 ||
blocks_with_proper_filter_adaptation_ >= 5 * kNumBlocksPerSecond);
transparent_mode_ &&
(consistent_filter_estimate_not_seen || !converged_filter_seen_);
transparent_mode_ = transparent_mode_ &&
(filter_should_have_converged_ ||
(!external_delay_seen_ &&
capture_block_counter_ > 10 * kNumBlocksPerSecond));
usable_linear_estimate_ = !echo_saturation_;
usable_linear_estimate_ =
usable_linear_estimate_ && filter_has_had_time_to_converge;
usable_linear_estimate_ =
usable_linear_estimate_ && recently_converged_filter;
usable_linear_estimate_ = usable_linear_estimate_ && !diverged_filter;
usable_linear_estimate_ = usable_linear_estimate_ && external_delay;
use_linear_filter_output_ = usable_linear_estimate_ && !TransparentMode();
data_dumper_->DumpRaw("aec3_erle", Erle());
data_dumper_->DumpRaw("aec3_erl", Erl());
data_dumper_->DumpRaw("aec3_erle_time_domain", ErleTimeDomain());
data_dumper_->DumpRaw("aec3_erl_time_domain", ErlTimeDomain());
data_dumper_->DumpRaw("aec3_usable_linear_estimate", UsableLinearEstimate());
data_dumper_->DumpRaw("aec3_transparent_mode", transparent_mode_);
data_dumper_->DumpRaw("aec3_state_internal_delay",
internal_delay_ ? *internal_delay_ : -1);
data_dumper_->DumpRaw("aec3_filter_delay", filter_analyzer_.DelayBlocks());
data_dumper_->DumpRaw("aec3_consistent_filter",
filter_analyzer_.Consistent());
data_dumper_->DumpRaw("aec3_suppression_gain_limit", SuppressionGainLimit());
data_dumper_->DumpRaw("aec3_initial_state", InitialState());
data_dumper_->DumpRaw("aec3_capture_saturation", SaturatedCapture());
data_dumper_->DumpRaw("aec3_echo_saturation", echo_saturation_);
data_dumper_->DumpRaw("aec3_converged_filter", converged_filter);
data_dumper_->DumpRaw("aec3_diverged_filter", diverged_filter);
data_dumper_->DumpRaw("aec3_external_delay_avaliable",
external_delay ? 1 : 0);
data_dumper_->DumpRaw("aec3_consistent_filter_estimate_not_seen",
consistent_filter_estimate_not_seen);
data_dumper_->DumpRaw("aec3_filter_should_have_converged",
filter_should_have_converged_);
data_dumper_->DumpRaw("aec3_filter_has_had_time_to_converge",
filter_has_had_time_to_converge);
data_dumper_->DumpRaw("aec3_recently_converged_filter",
recently_converged_filter);
}
void AecState::UpdateReverb(const std::vector<float>& impulse_response) {
@ -184,8 +248,8 @@ void AecState::UpdateReverb(const std::vector<float>& impulse_response) {
return;
}
if ((!(filter_delay_ && usable_linear_estimate_)) ||
(filter_delay_ >
if ((!(filter_delay_blocks_ && usable_linear_estimate_)) ||
(filter_delay_blocks_ >
static_cast<int>(config_.filter.main.length_blocks) - 4)) {
return;
}
@ -386,52 +450,4 @@ bool AecState::DetectEchoSaturation(rtc::ArrayView<const float> x) {
return blocks_since_last_saturation_ < 20;
}
void AecState::EchoAudibility::Update(rtc::ArrayView<const float> x,
const std::array<float, kBlockSize>& s,
bool converged_filter) {
auto result_x = std::minmax_element(x.begin(), x.end());
auto result_s = std::minmax_element(s.begin(), s.end());
const float x_abs = std::max(fabsf(*result_x.first), fabsf(*result_x.second));
const float s_abs = std::max(fabsf(*result_s.first), fabsf(*result_s.second));
if (converged_filter) {
if (x_abs < 20.f) {
++low_farend_counter_;
} else {
low_farend_counter_ = 0;
}
} else {
if (x_abs < 100.f) {
++low_farend_counter_;
} else {
low_farend_counter_ = 0;
}
}
// The echo is deemed as not audible if the echo estimate is on the level of
// the quantization noise in the FFTs and the nearend level is sufficiently
// strong to mask that by ensuring that the playout and AGC gains do not boost
// any residual echo that is below the quantization noise level. Furthermore,
// cases where the render signal is very close to zero are also identified as
// not producing audible echo.
inaudible_echo_ = (max_nearend_ > 500 && s_abs < 30.f) ||
(!converged_filter && x_abs < 500);
inaudible_echo_ = inaudible_echo_ || low_farend_counter_ > 20;
}
void AecState::EchoAudibility::UpdateWithOutput(rtc::ArrayView<const float> e) {
const float e_max = *std::max_element(e.begin(), e.end());
const float e_min = *std::min_element(e.begin(), e.end());
const float e_abs = std::max(fabsf(e_max), fabsf(e_min));
if (max_nearend_ < e_abs) {
max_nearend_ = e_abs;
max_nearend_counter_ = 0;
} else {
if (++max_nearend_counter_ > 5 * kNumBlocksPerSecond) {
max_nearend_ *= 0.995f;
}
}
}
} // namespace webrtc

View File

@ -25,6 +25,7 @@
#include "modules/audio_processing/aec3/echo_path_variability.h"
#include "modules/audio_processing/aec3/erl_estimator.h"
#include "modules/audio_processing/aec3/erle_estimator.h"
#include "modules/audio_processing/aec3/filter_analyzer.h"
#include "modules/audio_processing/aec3/render_buffer.h"
#include "modules/audio_processing/aec3/suppression_gain_limiter.h"
#include "rtc_base/constructormagic.h"
@ -43,8 +44,11 @@ class AecState {
// echo.
bool UsableLinearEstimate() const { return usable_linear_estimate_; }
// Returns whether there has been echo leakage detected.
bool EchoLeakageDetected() const { return echo_leakage_detected_; }
// Returns whether the echo subtractor output should be used as output.
bool UseLinearFilterOutput() const { return use_linear_filter_output_; }
// Returns the estimated echo path gain.
bool EchoPathGain() const { return filter_analyzer_.Gain(); }
// Returns whether the render signal is currently active.
bool ActiveRender() const { return blocks_with_active_render_ > 200; }
@ -66,7 +70,10 @@ class AecState {
float ErlTimeDomain() const { return erl_estimator_.ErlTimeDomain(); }
// Returns the delay estimate based on the linear filter.
int FilterDelay() const { return filter_delay_; }
int FilterDelayBlocks() const { return filter_delay_blocks_; }
// Returns the internal delay estimate based on the linear filter.
rtc::Optional<int> InternalDelay() const { return internal_delay_; }
// Returns whether the capture signal is saturated.
bool SaturatedCapture() const { return capture_signal_saturation_; }
@ -96,14 +103,6 @@ class AecState {
return suppression_gain_limiter_.Limit();
}
// Returns whether the echo in the capture signal is audible.
bool InaudibleEcho() const { return echo_audibility_.InaudibleEcho(); }
// Updates the aec state with the AEC output signal.
void UpdateWithOutput(rtc::ArrayView<const float> e) {
echo_audibility_.UpdateWithOutput(e);
}
// Returns whether the linear filter should have been able to properly adapt.
bool FilterHasHadTimeToConverge() const {
return filter_has_had_time_to_converge_;
@ -113,33 +112,18 @@ class AecState {
bool InitialState() const { return initial_state_; }
// Updates the aec state.
void Update(const rtc::Optional<DelayEstimate>& delay_estimate,
void Update(const rtc::Optional<DelayEstimate>& external_delay,
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
adaptive_filter_frequency_response,
const std::vector<float>& adaptive_filter_impulse_response,
bool converged_filter,
bool diverged_filter,
const RenderBuffer& render_buffer,
const std::array<float, kFftLengthBy2Plus1>& E2_main,
const std::array<float, kFftLengthBy2Plus1>& Y2,
const std::array<float, kBlockSize>& s_main,
bool echo_leakage_detected);
const std::array<float, kBlockSize>& s);
private:
class EchoAudibility {
public:
void Update(rtc::ArrayView<const float> x,
const std::array<float, kBlockSize>& s,
bool converged_filter);
void UpdateWithOutput(rtc::ArrayView<const float> e);
bool InaudibleEcho() const { return inaudible_echo_; }
private:
float max_nearend_ = 0.f;
size_t max_nearend_counter_ = 0;
size_t low_farend_counter_ = 0;
bool inaudible_echo_ = false;
};
void UpdateReverb(const std::vector<float>& impulse_response);
bool DetectActiveRender(rtc::ArrayView<const float> x) const;
void UpdateSuppressorGainLimit(bool render_activity);
@ -150,16 +134,16 @@ class AecState {
ErlEstimator erl_estimator_;
ErleEstimator erle_estimator_;
size_t capture_block_counter_ = 0;
size_t blocks_since_reset_ = 0;
size_t blocks_with_proper_filter_adaptation_ = 0;
size_t blocks_with_active_render_ = 0;
bool usable_linear_estimate_ = false;
bool echo_leakage_detected_ = false;
bool capture_signal_saturation_ = false;
bool echo_saturation_ = false;
bool transparent_mode_ = false;
float previous_max_sample_ = 0.f;
bool render_received_ = false;
int filter_delay_ = 0;
int filter_delay_blocks_ = 0;
size_t blocks_since_last_saturation_ = 1000;
float tail_energy_ = 0.f;
float accumulated_nz_ = 0.f;
@ -171,7 +155,6 @@ class AecState {
bool found_end_of_reverb_decay_ = false;
bool main_filter_is_adapting_ = true;
std::array<float, kMaxAdaptiveFilterLength> block_energies_;
EchoAudibility echo_audibility_;
const EchoCanceller3Config config_;
std::vector<float> max_render_;
float reverb_decay_ = fabsf(config_.ep_strength.default_len);
@ -180,6 +163,16 @@ class AecState {
bool initial_state_ = true;
const float gain_rampup_increase_;
SuppressionGainUpperLimiter suppression_gain_limiter_;
FilterAnalyzer filter_analyzer_;
bool use_linear_filter_output_ = false;
rtc::Optional<int> internal_delay_;
size_t diverged_blocks_ = 0;
bool filter_should_have_converged_ = false;
size_t blocks_since_converged_filter_;
size_t active_blocks_since_consistent_filter_estimate_;
bool converged_filter_seen_ = false;
bool consistent_filter_seen_ = false;
bool external_delay_seen_ = false;
RTC_DISALLOW_COPY_AND_ASSIGN(AecState);
};

View File

@ -22,7 +22,8 @@ TEST(AecState, NormalUsage) {
ApmDataDumper data_dumper(42);
EchoCanceller3Config config;
AecState state(config);
rtc::Optional<DelayEstimate> delay_estimate;
rtc::Optional<DelayEstimate> delay_estimate =
DelayEstimate(DelayEstimate::Quality::kRefined, 10);
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, 3));
std::array<float, kFftLengthBy2Plus1> E2_main = {};
@ -49,17 +50,17 @@ TEST(AecState, NormalUsage) {
// Verify that linear AEC usability is false when the filter is diverged.
state.Update(delay_estimate, diverged_filter_frequency_response,
impulse_response, true, *render_delay_buffer->GetRenderBuffer(),
E2_main, Y2, s, false);
impulse_response, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
EXPECT_FALSE(state.UsableLinearEstimate());
// Verify that linear AEC usability is true when the filter is converged
std::fill(x[0].begin(), x[0].end(), 101.f);
for (int k = 0; k < 3000; ++k) {
render_delay_buffer->Insert(x);
state.Update(
delay_estimate, converged_filter_frequency_response, impulse_response,
true, *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s, false);
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
}
EXPECT_TRUE(state.UsableLinearEstimate());
@ -68,8 +69,8 @@ TEST(AecState, NormalUsage) {
state.HandleEchoPathChange(EchoPathVariability(
true, EchoPathVariability::DelayAdjustment::kNone, false));
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, *render_delay_buffer->GetRenderBuffer(),
E2_main, Y2, s, false);
impulse_response, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
EXPECT_FALSE(state.UsableLinearEstimate());
// Verify that the active render detection works as intended.
@ -78,29 +79,18 @@ TEST(AecState, NormalUsage) {
state.HandleEchoPathChange(EchoPathVariability(
true, EchoPathVariability::DelayAdjustment::kNewDetectedDelay, false));
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, *render_delay_buffer->GetRenderBuffer(),
E2_main, Y2, s, false);
impulse_response, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
EXPECT_FALSE(state.ActiveRender());
for (int k = 0; k < 1000; ++k) {
render_delay_buffer->Insert(x);
state.Update(
delay_estimate, converged_filter_frequency_response, impulse_response,
true, *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s, false);
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
}
EXPECT_TRUE(state.ActiveRender());
// Verify that echo leakage is properly reported.
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, *render_delay_buffer->GetRenderBuffer(),
E2_main, Y2, s, false);
EXPECT_FALSE(state.EchoLeakageDetected());
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, *render_delay_buffer->GetRenderBuffer(),
E2_main, Y2, s, true);
EXPECT_TRUE(state.EchoLeakageDetected());
// Verify that the ERL is properly estimated
for (auto& x_k : x) {
x_k = std::vector<float>(kBlockSize, 0.f);
@ -118,9 +108,9 @@ TEST(AecState, NormalUsage) {
Y2.fill(10.f * 10000.f * 10000.f);
for (size_t k = 0; k < 1000; ++k) {
state.Update(
delay_estimate, converged_filter_frequency_response, impulse_response,
true, *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s, false);
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
}
ASSERT_TRUE(state.UsableLinearEstimate());
@ -135,9 +125,9 @@ TEST(AecState, NormalUsage) {
E2_main.fill(1.f * 10000.f * 10000.f);
Y2.fill(10.f * E2_main[0]);
for (size_t k = 0; k < 1000; ++k) {
state.Update(
delay_estimate, converged_filter_frequency_response, impulse_response,
true, *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s, false);
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
}
ASSERT_TRUE(state.UsableLinearEstimate());
{
@ -145,7 +135,7 @@ TEST(AecState, NormalUsage) {
EXPECT_EQ(erle[0], erle[1]);
constexpr size_t kLowFrequencyLimit = 32;
for (size_t k = 1; k < kLowFrequencyLimit; ++k) {
EXPECT_NEAR(k % 2 == 0 ? 8.f : 1.f, erle[k], 0.1);
EXPECT_NEAR(k % 2 == 0 ? 4.f : 1.f, erle[k], 0.1);
}
for (size_t k = kLowFrequencyLimit; k < erle.size() - 1; ++k) {
EXPECT_NEAR(k % 2 == 0 ? 1.5f : 1.f, erle[k], 0.1);
@ -156,9 +146,9 @@ TEST(AecState, NormalUsage) {
E2_main.fill(1.f * 10000.f * 10000.f);
Y2.fill(5.f * E2_main[0]);
for (size_t k = 0; k < 1000; ++k) {
state.Update(
delay_estimate, converged_filter_frequency_response, impulse_response,
true, *render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s, false);
state.Update(delay_estimate, converged_filter_frequency_response,
impulse_response, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
}
ASSERT_TRUE(state.UsableLinearEstimate());
@ -167,7 +157,7 @@ TEST(AecState, NormalUsage) {
EXPECT_EQ(erle[0], erle[1]);
constexpr size_t kLowFrequencyLimit = 32;
for (size_t k = 1; k < kLowFrequencyLimit; ++k) {
EXPECT_NEAR(k % 2 == 0 ? 5.f : 1.f, erle[k], 0.1);
EXPECT_NEAR(k % 2 == 0 ? 4.f : 1.f, erle[k], 0.1);
}
for (size_t k = kLowFrequencyLimit; k < erle.size() - 1; ++k) {
EXPECT_NEAR(k % 2 == 0 ? 1.5f : 1.f, erle[k], 0.1);
@ -178,7 +168,7 @@ TEST(AecState, NormalUsage) {
// Verifies the delay for a converged filter is correctly identified.
TEST(AecState, ConvergedFilterDelay) {
constexpr int kFilterLength = 10;
constexpr int kFilterLengthBlocks = 10;
EchoCanceller3Config config;
AecState state(config);
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
@ -194,25 +184,23 @@ TEST(AecState, ConvergedFilterDelay) {
x.fill(0.f);
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
kFilterLength);
kFilterLengthBlocks);
for (auto& v : frequency_response) {
v.fill(0.01f);
}
std::vector<float> impulse_response(
GetTimeDomainLength(config.filter.main.length_blocks), 0.f);
// Verify that the filter delay for a converged filter is properly identified.
for (int k = 0; k < kFilterLength; ++k) {
for (auto& v : frequency_response) {
v.fill(0.01f);
}
frequency_response[k].fill(100.f);
frequency_response[k][0] = 0.f;
for (int k = 0; k < kFilterLengthBlocks; ++k) {
std::fill(impulse_response.begin(), impulse_response.end(), 0.f);
impulse_response[k * kBlockSize + 1] = 1.f;
state.HandleEchoPathChange(echo_path_variability);
state.Update(delay_estimate, frequency_response, impulse_response, true,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s,
false);
if (k != (kFilterLength - 1)) {
EXPECT_EQ(k, state.FilterDelay());
}
false, *render_delay_buffer->GetRenderBuffer(), E2_main, Y2,
s);
}
}

View File

@ -57,6 +57,7 @@ class BlockProcessorImpl final : public BlockProcessor {
RenderDelayBuffer::BufferingEvent render_event_;
size_t capture_call_counter_ = 0;
rtc::Optional<DelayEstimate> estimated_delay_;
rtc::Optional<int> echo_remover_delay_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockProcessorImpl);
};
@ -158,7 +159,8 @@ void BlockProcessorImpl::ProcessCapture(
// Compute and and apply the render delay required to achieve proper signal
// alignment.
estimated_delay_ = delay_controller_->GetDelay(
render_buffer_->GetDownsampledRenderBuffer(), (*capture_block)[0]);
render_buffer_->GetDownsampledRenderBuffer(), render_buffer_->Delay(),
echo_remover_delay_, (*capture_block)[0]);
if (estimated_delay_) {
if (render_buffer_->CausalDelay(estimated_delay_->delay)) {
@ -191,6 +193,10 @@ void BlockProcessorImpl::ProcessCapture(
echo_path_variability, capture_signal_saturation, estimated_delay_,
render_buffer_->GetRenderBuffer(), capture_block);
// Check to see if a refined delay estimate has been obtained from the echo
// remover.
echo_remover_delay_ = echo_remover_->Delay();
// Update the metrics.
metrics_.UpdateCapture(false);

View File

@ -166,7 +166,7 @@ TEST(BlockProcessor, DISABLED_SubmoduleIntegration) {
EXPECT_CALL(*render_delay_buffer_mock, Delay())
.Times(kNumBlocks)
.WillRepeatedly(Return(0));
EXPECT_CALL(*render_delay_controller_mock, GetDelay(_, _))
EXPECT_CALL(*render_delay_controller_mock, GetDelay(_, _, _, _))
.Times(kNumBlocks);
EXPECT_CALL(*echo_remover_mock, ProcessCapture(_, _, _, _, _))
.Times(kNumBlocks);

View File

@ -49,8 +49,7 @@ void LinearEchoPower(const FftData& E,
// Class for removing the echo from the capture signal.
class EchoRemoverImpl final : public EchoRemover {
public:
explicit EchoRemoverImpl(const EchoCanceller3Config& config,
int sample_rate_hz);
EchoRemoverImpl(const EchoCanceller3Config& config, int sample_rate_hz);
~EchoRemoverImpl() override;
void GetMetrics(EchoControl::Metrics* metrics) const override;
@ -60,10 +59,15 @@ class EchoRemoverImpl final : public EchoRemover {
// signal.
void ProcessCapture(const EchoPathVariability& echo_path_variability,
bool capture_signal_saturation,
const rtc::Optional<DelayEstimate>& delay_estimate,
const rtc::Optional<DelayEstimate>& external_delay,
RenderBuffer* render_buffer,
std::vector<std::vector<float>>* capture) override;
// Returns the internal delay estimate in blocks.
rtc::Optional<int> Delay() const override {
return aec_state_.InternalDelay();
}
// Updates the status on whether echo leakage is detected in the output of the
// echo remover.
void UpdateEchoLeakageStatus(bool leakage_detected) override {
@ -124,7 +128,7 @@ void EchoRemoverImpl::GetMetrics(EchoControl::Metrics* metrics) const {
void EchoRemoverImpl::ProcessCapture(
const EchoPathVariability& echo_path_variability,
bool capture_signal_saturation,
const rtc::Optional<DelayEstimate>& delay_estimate,
const rtc::Optional<DelayEstimate>& external_delay,
RenderBuffer* render_buffer,
std::vector<std::vector<float>>* capture) {
const std::vector<std::vector<float>>& x = render_buffer->Block(0);
@ -169,7 +173,8 @@ void EchoRemoverImpl::ProcessCapture(
auto& e_main = subtractor_output.e_main;
// Analyze the render signal.
render_signal_analyzer_.Update(*render_buffer, aec_state_.FilterDelay());
render_signal_analyzer_.Update(*render_buffer,
aec_state_.FilterDelayBlocks());
// Perform linear echo cancellation.
if (initial_state_ && !aec_state_.InitialState()) {
@ -177,27 +182,32 @@ void EchoRemoverImpl::ProcessCapture(
suppression_gain_.SetInitialState(false);
initial_state_ = false;
}
// If the delay is known, use the echo subtractor.
subtractor_.Process(*render_buffer, y0, render_signal_analyzer_, aec_state_,
&subtractor_output);
// Compute spectra.
// fft_.ZeroPaddedFft(y0, Aec3Fft::Window::kHanning, &Y);
fft_.ZeroPaddedFft(y0, Aec3Fft::Window::kRectangular, &Y);
LinearEchoPower(E_main_nonwindowed, Y, &S2_linear);
Y.Spectrum(optimization_, Y2);
// Update the AEC state information.
aec_state_.Update(delay_estimate, subtractor_.FilterFrequencyResponse(),
aec_state_.Update(external_delay, subtractor_.FilterFrequencyResponse(),
subtractor_.FilterImpulseResponse(),
subtractor_.ConvergedFilter(), *render_buffer, E2_main, Y2,
subtractor_output.s_main, echo_leakage_detected_);
subtractor_.ConvergedFilter(), subtractor_.DivergedFilter(),
*render_buffer, E2_main, Y2, subtractor_output.s_main);
// Choose the linear output.
output_selector_.FormLinearOutput(!aec_state_.TransparentMode(), e_main, y0);
data_dumper_->DumpWav("aec3_output_linear2", kBlockSize, &e_main[0],
LowestBandRate(sample_rate_hz_), 1);
output_selector_.FormLinearOutput(aec_state_.UseLinearFilterOutput(), e_main,
y0);
data_dumper_->DumpWav("aec3_output_linear", kBlockSize, &y0[0],
LowestBandRate(sample_rate_hz_), 1);
data_dumper_->DumpRaw("aec3_output_linear", y0);
const auto& E2 = output_selector_.UseSubtractorOutput() ? E2_main : Y2;
const auto& E2 = aec_state_.UseLinearFilterOutput() ? E2_main : Y2;
// Estimate the residual echo power.
residual_echo_estimator_.Estimate(aec_state_, *render_buffer, S2_linear, Y2,
@ -216,9 +226,6 @@ void EchoRemoverImpl::ProcessCapture(
// Update the metrics.
metrics_.Update(aec_state_, cng_.NoiseSpectrum(), G);
// Update the aec state with the aec output characteristics.
aec_state_.UpdateWithOutput(y0);
// Debug outputs for the purpose of development and analysis.
data_dumper_->DumpWav("aec3_echo_estimate", kBlockSize,
&subtractor_output.s_main[0],
@ -232,7 +239,7 @@ void EchoRemoverImpl::ProcessCapture(
rtc::ArrayView<const float>(&y0[0], kBlockSize),
LowestBandRate(sample_rate_hz_), 1);
data_dumper_->DumpRaw("aec3_using_subtractor_output",
output_selector_.UseSubtractorOutput() ? 1 : 0);
aec_state_.UseLinearFilterOutput() ? 1 : 0);
data_dumper_->DumpRaw("aec3_E2", E2);
data_dumper_->DumpRaw("aec3_E2_main", E2_main);
data_dumper_->DumpRaw("aec3_E2_shadow", E2_shadow);
@ -242,9 +249,7 @@ void EchoRemoverImpl::ProcessCapture(
data_dumper_->DumpRaw("aec3_R2", R2);
data_dumper_->DumpRaw("aec3_erle", aec_state_.Erle());
data_dumper_->DumpRaw("aec3_erl", aec_state_.Erl());
data_dumper_->DumpRaw("aec3_usable_linear_estimate",
aec_state_.UsableLinearEstimate());
data_dumper_->DumpRaw("aec3_filter_delay", aec_state_.FilterDelay());
data_dumper_->DumpRaw("aec3_filter_delay", aec_state_.FilterDelayBlocks());
data_dumper_->DumpRaw("aec3_capture_saturation",
aec_state_.SaturatedCapture() ? 1 : 0);
}

View File

@ -38,10 +38,13 @@ class EchoRemover {
virtual void ProcessCapture(
const EchoPathVariability& echo_path_variability,
bool capture_signal_saturation,
const rtc::Optional<DelayEstimate>& delay_estimate,
const rtc::Optional<DelayEstimate>& external_delay,
RenderBuffer* render_buffer,
std::vector<std::vector<float>>* capture) = 0;
// Returns the internal delay estimate in blocks.
virtual rtc::Optional<int> Delay() const = 0;
// Updates the status on whether echo leakage is detected in the output of the
// echo remover.
virtual void UpdateEchoLeakageStatus(bool leakage_detected) = 0;

View File

@ -237,7 +237,7 @@ void EchoRemoverMetrics::Update(
static_cast<int>(
active_render_count_ > kMetricsCollectionBlocksBy2 ? 1 : 0));
RTC_HISTOGRAM_COUNTS_LINEAR("WebRTC.Audio.EchoCanceller.FilterDelay",
aec_state.FilterDelay(), 0, 30, 31);
aec_state.FilterDelayBlocks(), 0, 30, 31);
RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.EchoCanceller.CaptureSaturation",
static_cast<int>(saturated_capture_ ? 1 : 0));
break;

View File

@ -0,0 +1,127 @@
/*
* 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/aec3/filter_analyzer.h"
#include <math.h>
#include <algorithm>
#include <array>
#include <numeric>
#include "rtc_base/checks.h"
namespace webrtc {
namespace {
size_t FindPeakIndex(rtc::ArrayView<const float> 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) {
float tmp = filter_time_domain[k] * filter_time_domain[k];
if (tmp > max_h2) {
peak_index = k;
max_h2 = tmp;
}
}
return peak_index;
}
} // namespace
FilterAnalyzer::FilterAnalyzer(const EchoCanceller3Config& config)
: 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) {
Reset();
}
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_;
}
void FilterAnalyzer::Update(rtc::ArrayView<const float> filter_time_domain,
const RenderBuffer& render_buffer) {
size_t peak_index = FindPeakIndex(filter_time_domain);
delay_blocks_ = peak_index / kBlockSize;
UpdateFilterGain(filter_time_domain, 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_time_domain.size() - 129 ? 0 : peak_index + 128;
for (size_t k = 0; k < limit1; ++k) {
float abs_h = fabsf(filter_time_domain[k]);
filter_floor += abs_h;
filter_secondary_peak = std::max(filter_secondary_peak, abs_h);
}
for (size_t k = limit2; k < filter_time_domain.size(); ++k) {
float abs_h = fabsf(filter_time_domain[k]);
filter_floor += abs_h;
filter_secondary_peak = std::max(filter_secondary_peak, abs_h);
}
filter_floor /= (limit1 + filter_time_domain.size() - limit2);
float abs_peak = fabsf(filter_time_domain[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;
}
void FilterAnalyzer::UpdateFilterGain(
rtc::ArrayView<const float> filter_time_domain,
size_t peak_index) {
bool sufficient_time_to_converge =
++blocks_since_reset_ > 5 * kNumBlocksPerSecond;
if (sufficient_time_to_converge && consistent_estimate_) {
gain_ = fabsf(filter_time_domain[peak_index]);
} else {
if (gain_) {
gain_ = std::max(gain_, fabsf(filter_time_domain[peak_index]));
}
}
if (bounded_erl_ && gain_) {
gain_ = std::max(gain_, 0.01f);
}
}
} // namespace webrtc

View File

@ -0,0 +1,68 @@
/*
* 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_AEC3_FILTER_ANALYZER_H_
#define MODULES_AUDIO_PROCESSING_AEC3_FILTER_ANALYZER_H_
#include <vector>
#include "api/array_view.h"
#include "api/audio/echo_canceller3_config.h"
#include "api/optional.h"
#include "modules/audio_processing/aec3/aec3_common.h"
#include "modules/audio_processing/aec3/render_buffer.h"
#include "rtc_base/constructormagic.h"
namespace webrtc {
// Class for analyzing the properties of an adaptive filter.
class FilterAnalyzer {
public:
explicit FilterAnalyzer(const EchoCanceller3Config& config);
~FilterAnalyzer();
// Resets the analysis.
void Reset();
// Updates the estimates with new input data.
void Update(rtc::ArrayView<const float> filter_time_domain,
const RenderBuffer& render_buffer);
// Returns the delay of the filter in terms of blocks.
int DelayBlocks() const { return delay_blocks_; }
// Returns whether the filter is consistent in the sense that it does not
// change much over time.
bool Consistent() const { return consistent_estimate_; }
// Returns the estimated filter gain.
float Gain() const { return gain_; }
private:
void UpdateFilterGain(rtc::ArrayView<const float> filter_time_domain,
size_t max_index);
const bool bounded_erl_;
const float default_gain_;
const float active_render_threshold_;
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_;
RTC_DISALLOW_COPY_AND_ASSIGN(FilterAnalyzer);
};
} // namespace webrtc
#endif // MODULES_AUDIO_PROCESSING_AEC3_FILTER_ANALYZER_H_

View File

@ -114,7 +114,7 @@ void RunFilterUpdateTest(int num_blocks_to_process,
render_delay_buffer->PrepareCaptureProcessing();
render_signal_analyzer.Update(*render_delay_buffer->GetRenderBuffer(),
aec_state.FilterDelay());
aec_state.FilterDelayBlocks());
// Apply the main filter.
main_filter.Filter(*render_delay_buffer->GetRenderBuffer(), &S);
@ -162,9 +162,8 @@ void RunFilterUpdateTest(int num_blocks_to_process,
aec_state.HandleEchoPathChange(EchoPathVariability(
false, EchoPathVariability::DelayAdjustment::kNone, false));
aec_state.Update(delay_estimate, main_filter.FilterFrequencyResponse(),
main_filter.FilterImpulseResponse(), true,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s,
false);
main_filter.FilterImpulseResponse(), true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
}
std::copy(e_main.begin(), e_main.end(), e_last_block->begin());

View File

@ -70,8 +70,6 @@ rtc::Optional<DelayEstimate> MatchedFilterLagAggregator::Aggregate(
if (histogram_[candidate] > 25) {
significant_candidate_found_ = true;
return DelayEstimate(DelayEstimate::Quality::kRefined, candidate);
} else if (!significant_candidate_found_) {
return DelayEstimate(DelayEstimate::Quality::kCoarse, candidate);
}
}
return rtc::nullopt;

View File

@ -22,7 +22,7 @@
namespace webrtc {
namespace {
constexpr size_t kNumLagsBeforeDetection = 25;
constexpr size_t kNumLagsBeforeDetection = 26;
} // namespace
@ -37,7 +37,7 @@ TEST(MatchedFilterLagAggregator, MostAccurateLagChosen) {
lag_estimates[1] = MatchedFilter::LagEstimate(0.5f, true, kLag2, true);
for (size_t k = 0; k < kNumLagsBeforeDetection; ++k) {
EXPECT_TRUE(aggregator.Aggregate(lag_estimates));
aggregator.Aggregate(lag_estimates);
}
rtc::Optional<DelayEstimate> aggregated_lag =

View File

@ -32,7 +32,7 @@ class MockEchoRemover : public EchoRemover {
const rtc::Optional<DelayEstimate>& delay_estimate,
RenderBuffer* render_buffer,
std::vector<std::vector<float>>* capture));
MOCK_CONST_METHOD0(Delay, rtc::Optional<int>());
MOCK_METHOD1(UpdateEchoLeakageStatus, void(bool leakage_detected));
MOCK_CONST_METHOD1(GetMetrics, void(EchoControl::Metrics* metrics));
};

View File

@ -47,7 +47,7 @@ class MockRenderDelayBuffer : public RenderDelayBuffer {
const std::vector<std::vector<float>>& block));
MOCK_METHOD0(PrepareCaptureProcessing, RenderDelayBuffer::BufferingEvent());
MOCK_METHOD1(SetDelay, bool(size_t delay));
MOCK_CONST_METHOD0(Delay, rtc::Optional<size_t>());
MOCK_CONST_METHOD0(Delay, size_t());
MOCK_CONST_METHOD0(MaxDelay, size_t());
MOCK_METHOD0(GetRenderBuffer, RenderBuffer*());
MOCK_CONST_METHOD0(GetDownsampledRenderBuffer,

View File

@ -26,9 +26,11 @@ class MockRenderDelayController : public RenderDelayController {
MOCK_METHOD0(Reset, void());
MOCK_METHOD0(LogRenderCall, void());
MOCK_METHOD2(
MOCK_METHOD4(
GetDelay,
rtc::Optional<DelayEstimate>(const DownsampledRenderBuffer& render_buffer,
size_t render_delay_buffer_delay,
const rtc::Optional<int>& echo_remover_delay,
rtc::ArrayView<const float> capture));
};

View File

@ -28,9 +28,6 @@ class OutputSelector {
rtc::ArrayView<const float> subtractor_output,
rtc::ArrayView<float> capture);
// Returns true if the linear aec output is the one used.
bool UseSubtractorOutput() const { return use_subtractor_output_; }
private:
bool use_subtractor_output_ = false;
RTC_DISALLOW_COPY_AND_ASSIGN(OutputSelector);

View File

@ -38,7 +38,7 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer {
BufferingEvent Insert(const std::vector<std::vector<float>>& block) override;
BufferingEvent PrepareCaptureProcessing() override;
bool SetDelay(size_t delay) override;
rtc::Optional<size_t> Delay() const override { return delay_; }
size_t Delay() const override { return MapInternalDelayToExternalDelay(); }
size_t MaxDelay() const override {
return blocks_.buffer.size() - 1 - buffer_headroom_;
}
@ -77,7 +77,8 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer {
size_t render_activity_counter_ = 0;
int LowRateBufferOffset() const { return DelayEstimatorOffset(config_) >> 1; }
int MaxExternalDelayToInternalDelay(size_t delay) const;
int MapExternalDelayToInternalDelay(size_t external_delay_blocks) const;
int MapInternalDelayToExternalDelay() const;
void ApplyDelay(int delay);
void InsertBlock(const std::vector<std::vector<float>>& block,
int previous_write);
@ -167,7 +168,7 @@ RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
kBlockSize),
spectra_(blocks_.buffer.size(), kFftLengthBy2Plus1),
ffts_(blocks_.buffer.size()),
delay_(config_.delay.min_echo_path_delay_blocks),
delay_(config_.delay.default_delay),
echo_remover_buffer_(&blocks_, &spectra_, &ffts_),
low_rate_(GetDownSampledBufferSize(config.delay.down_sampling_factor,
config.delay.num_filters)),
@ -310,7 +311,7 @@ bool RenderDelayBufferImpl::SetDelay(size_t delay) {
delay_ = delay;
// Compute the internal delay and limit the delay to the allowed range.
int internal_delay = MaxExternalDelayToInternalDelay(*delay_);
int internal_delay = MapExternalDelayToInternalDelay(*delay_);
internal_delay_ =
std::min(MaxDelay(), static_cast<size_t>(std::max(internal_delay, 0)));
@ -322,7 +323,7 @@ bool RenderDelayBufferImpl::SetDelay(size_t delay) {
// Returns whether the specified delay is causal.
bool RenderDelayBufferImpl::CausalDelay(size_t delay) const {
// Compute the internal delay and limit the delay to the allowed range.
int internal_delay = MaxExternalDelayToInternalDelay(delay);
int internal_delay = MapExternalDelayToInternalDelay(delay);
internal_delay =
std::min(MaxDelay(), static_cast<size_t>(std::max(internal_delay, 0)));
@ -331,7 +332,7 @@ bool RenderDelayBufferImpl::CausalDelay(size_t delay) const {
}
// Maps the externally computed delay to the delay used internally.
int RenderDelayBufferImpl::MaxExternalDelayToInternalDelay(
int RenderDelayBufferImpl::MapExternalDelayToInternalDelay(
size_t external_delay_blocks) const {
const int latency = BufferLatency(low_rate_);
RTC_DCHECK_LT(0, sub_block_size_);
@ -341,6 +342,17 @@ int RenderDelayBufferImpl::MaxExternalDelayToInternalDelay(
DelayEstimatorOffset(config_);
}
// Maps the internally used delay to the delay used externally.
int RenderDelayBufferImpl::MapInternalDelayToExternalDelay() const {
const int latency = BufferLatency(low_rate_);
int latency_blocks = latency / sub_block_size_;
int internal_delay = spectra_.read >= spectra_.write
? spectra_.read - spectra_.write
: spectra_.size + spectra_.read - spectra_.write;
return internal_delay - latency_blocks + DelayEstimatorOffset(config_);
}
// Set the read indices according to the delay.
void RenderDelayBufferImpl::ApplyDelay(int delay) {
blocks_.read = blocks_.OffsetIndex(blocks_.write, -delay);

View File

@ -57,7 +57,7 @@ class RenderDelayBuffer {
virtual bool SetDelay(size_t delay) = 0;
// Gets the buffer delay.
virtual rtc::Optional<size_t> Delay() const = 0;
virtual size_t Delay() const = 0;
// Gets the buffer delay.
virtual size_t MaxDelay() const = 0;

View File

@ -75,12 +75,14 @@ TEST(RenderDelayBuffer, SetDelay) {
EchoCanceller3Config config;
std::unique_ptr<RenderDelayBuffer> delay_buffer(
RenderDelayBuffer::Create(config, 1));
ASSERT_FALSE(delay_buffer->Delay());
for (size_t delay = config.delay.min_echo_path_delay_blocks + 1; delay < 20;
++delay) {
delay_buffer->SetDelay(delay);
ASSERT_TRUE(delay_buffer->Delay());
EXPECT_EQ(delay, *delay_buffer->Delay());
ASSERT_TRUE(delay_buffer->Delay());
delay_buffer->Reset();
size_t initial_internal_delay = config.delay.min_echo_path_delay_blocks +
config.delay.api_call_jitter_blocks;
for (size_t delay = initial_internal_delay;
delay < initial_internal_delay + 20; ++delay) {
ASSERT_TRUE(delay_buffer->SetDelay(delay));
EXPECT_EQ(delay, delay_buffer->Delay());
}
}

View File

@ -40,6 +40,8 @@ class RenderDelayControllerImpl final : public RenderDelayController {
void LogRenderCall() override;
rtc::Optional<DelayEstimate> GetDelay(
const DownsampledRenderBuffer& render_buffer,
size_t render_delay_buffer_delay,
const rtc::Optional<int>& echo_remover_delay,
rtc::ArrayView<const float> capture) override;
private:
@ -146,6 +148,8 @@ void RenderDelayControllerImpl::LogRenderCall() {
rtc::Optional<DelayEstimate> RenderDelayControllerImpl::GetDelay(
const DownsampledRenderBuffer& render_buffer,
size_t render_delay_buffer_delay,
const rtc::Optional<int>& echo_remover_delay,
rtc::ArrayView<const float> capture) {
RTC_DCHECK_EQ(kBlockSize, capture.size());
++capture_call_counter_;
@ -157,6 +161,14 @@ rtc::Optional<DelayEstimate> RenderDelayControllerImpl::GetDelay(
auto delay_samples =
delay_estimator_.EstimateDelay(render_buffer, capture_delayed);
// Overrule the delay estimator delay if the echo remover reports a delay.
if (echo_remover_delay) {
int total_echo_remover_delay_samples =
(render_delay_buffer_delay + *echo_remover_delay) * kBlockSize;
delay_samples = DelayEstimate(DelayEstimate::Quality::kRefined,
total_echo_remover_delay_samples);
}
std::copy(capture.begin(), capture.end(),
delay_buf_.begin() + delay_buf_index_);
delay_buf_index_ = (delay_buf_index_ + kBlockSize) % delay_buf_.size();
@ -165,6 +177,9 @@ rtc::Optional<DelayEstimate> RenderDelayControllerImpl::GetDelay(
rtc::Optional<int> skew = skew_estimator_.GetSkewFromCapture();
if (delay_samples) {
// TODO(peah): Refactor the rest of the code to assume a kRefined estimate
// quality.
RTC_DCHECK(DelayEstimate::Quality::kRefined == delay_samples->quality);
if (!delay_samples_ || delay_samples->delay != delay_samples_->delay) {
delay_change_counter_ = 0;
}

View File

@ -38,6 +38,8 @@ class RenderDelayController {
// Aligns the render buffer content with the capture signal.
virtual rtc::Optional<DelayEstimate> GetDelay(
const DownsampledRenderBuffer& render_buffer,
size_t render_delay_buffer_delay,
const rtc::Optional<int>& echo_remover_delay,
rtc::ArrayView<const float> capture) = 0;
};
} // namespace webrtc

View File

@ -48,6 +48,7 @@ constexpr size_t kDownSamplingFactors[] = {2, 4, 8};
TEST(RenderDelayController, NoRenderSignal) {
std::vector<float> block(kBlockSize, 0.f);
EchoCanceller3Config config;
rtc::Optional<int> echo_remover_delay_;
for (size_t num_matched_filters = 4; num_matched_filters == 10;
num_matched_filters++) {
for (auto down_sampling_factor : kDownSamplingFactors) {
@ -62,7 +63,8 @@ TEST(RenderDelayController, NoRenderSignal) {
config, RenderDelayBuffer::DelayEstimatorOffset(config), rate));
for (size_t k = 0; k < 100; ++k) {
auto delay = delay_controller->GetDelay(
delay_buffer->GetDownsampledRenderBuffer(), block);
delay_buffer->GetDownsampledRenderBuffer(), delay_buffer->Delay(),
echo_remover_delay_, block);
EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay->delay);
}
}
@ -74,6 +76,7 @@ TEST(RenderDelayController, NoRenderSignal) {
TEST(RenderDelayController, BasicApiCalls) {
std::vector<float> capture_block(kBlockSize, 0.f);
rtc::Optional<DelayEstimate> delay_blocks;
rtc::Optional<int> echo_remover_delay;
for (size_t num_matched_filters = 4; num_matched_filters == 10;
num_matched_filters++) {
for (auto down_sampling_factor : kDownSamplingFactors) {
@ -94,7 +97,8 @@ TEST(RenderDelayController, BasicApiCalls) {
render_delay_buffer->PrepareCaptureProcessing();
delay_blocks = delay_controller->GetDelay(
render_delay_buffer->GetDownsampledRenderBuffer(), capture_block);
render_delay_buffer->GetDownsampledRenderBuffer(),
render_delay_buffer->Delay(), echo_remover_delay, capture_block);
}
EXPECT_TRUE(delay_blocks);
EXPECT_EQ(config.delay.min_echo_path_delay_blocks, delay_blocks->delay);
@ -107,6 +111,7 @@ TEST(RenderDelayController, BasicApiCalls) {
// simple timeshifts between the signals.
TEST(RenderDelayController, Alignment) {
Random random_generator(42U);
rtc::Optional<int> echo_remover_delay;
std::vector<float> capture_block(kBlockSize, 0.f);
for (size_t num_matched_filters = 4; num_matched_filters == 10;
num_matched_filters++) {
@ -136,6 +141,7 @@ TEST(RenderDelayController, Alignment) {
render_delay_buffer->PrepareCaptureProcessing();
delay_blocks = delay_controller->GetDelay(
render_delay_buffer->GetDownsampledRenderBuffer(),
render_delay_buffer->Delay(), echo_remover_delay,
capture_block);
}
ASSERT_TRUE(!!delay_blocks);
@ -156,6 +162,7 @@ TEST(RenderDelayController, Alignment) {
// delays.
TEST(RenderDelayController, NonCausalAlignment) {
Random random_generator(42U);
rtc::Optional<int> echo_remover_delay;
for (size_t num_matched_filters = 4; num_matched_filters == 10;
num_matched_filters++) {
for (auto down_sampling_factor : kDownSamplingFactors) {
@ -186,6 +193,7 @@ TEST(RenderDelayController, NonCausalAlignment) {
render_delay_buffer->PrepareCaptureProcessing();
delay_blocks = delay_controller->GetDelay(
render_delay_buffer->GetDownsampledRenderBuffer(),
render_delay_buffer->Delay(), echo_remover_delay,
capture_block[0]);
}
@ -200,6 +208,7 @@ TEST(RenderDelayController, NonCausalAlignment) {
// simple timeshifts between the signals when there is jitter in the API calls.
TEST(RenderDelayController, AlignmentWithJitter) {
Random random_generator(42U);
rtc::Optional<int> echo_remover_delay;
std::vector<float> capture_block(kBlockSize, 0.f);
for (size_t num_matched_filters = 4; num_matched_filters == 10;
num_matched_filters++) {
@ -237,6 +246,7 @@ TEST(RenderDelayController, AlignmentWithJitter) {
render_delay_buffer->PrepareCaptureProcessing();
delay_blocks = delay_controller->GetDelay(
render_delay_buffer->GetDownsampledRenderBuffer(),
render_delay_buffer->Delay(), echo_remover_delay,
capture_block_buffer[k]);
}
}
@ -286,6 +296,7 @@ TEST(RenderDelayController, InitialHeadroom) {
TEST(RenderDelayController, WrongCaptureSize) {
std::vector<float> block(kBlockSize - 1, 0.f);
EchoCanceller3Config config;
rtc::Optional<int> echo_remover_delay;
for (auto rate : {8000, 16000, 32000, 48000}) {
SCOPED_TRACE(ProduceDebugText(rate));
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
@ -296,7 +307,7 @@ TEST(RenderDelayController, WrongCaptureSize) {
EchoCanceller3Config(),
RenderDelayBuffer::DelayEstimatorOffset(config), rate))
->GetDelay(render_delay_buffer->GetDownsampledRenderBuffer(),
block),
render_delay_buffer->Delay(), echo_remover_delay, block),
"");
}
}

View File

@ -1,3 +1,4 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
@ -96,9 +97,10 @@ void ResidualEchoEstimator::Estimate(
// Estimate the residual echo power.
if (aec_state.UsableLinearEstimate()) {
LinearEstimate(S2_linear, aec_state.Erle(), aec_state.FilterDelay(), R2);
AddEchoReverb(S2_linear, aec_state.SaturatedEcho(), aec_state.FilterDelay(),
aec_state.ReverbDecay(), R2);
LinearEstimate(S2_linear, aec_state.Erle(), aec_state.FilterDelayBlocks(),
R2);
AddEchoReverb(S2_linear, aec_state.SaturatedEcho(),
aec_state.FilterDelayBlocks(), aec_state.ReverbDecay(), R2);
// If the echo is saturated, estimate the echo power as the maximum echo
// power with a leakage factor.
@ -110,8 +112,9 @@ void ResidualEchoEstimator::Estimate(
std::array<float, kFftLengthBy2Plus1> X2;
// Computes the spectral power over the blocks surrounding the delay.
EchoGeneratingPower(render_buffer, std::max(0, aec_state.FilterDelay() - 1),
aec_state.FilterDelay() + 10, &X2);
EchoGeneratingPower(render_buffer,
std::max(0, aec_state.FilterDelayBlocks() - 1),
aec_state.FilterDelayBlocks() + 3, &X2);
// Subtract the stationary noise power to avoid stationary noise causing
// excessive echo suppression.
@ -119,10 +122,8 @@ void ResidualEchoEstimator::Estimate(
X2.begin(), X2.end(), X2_noise_floor_.begin(), X2.begin(),
[](float a, float b) { return std::max(0.f, a - 10.f * b); });
NonLinearEstimate(aec_state.FilterHasHadTimeToConverge(),
aec_state.SaturatedEcho(),
config_.ep_strength.bounded_erl,
aec_state.TransparentMode(), X2, Y2, R2);
NonLinearEstimate(aec_state.SaturatedEcho(), aec_state.EchoPathGain(), X2,
Y2, R2);
if (aec_state.SaturatedEcho()) {
// TODO(peah): Modify to make sense theoretically.
@ -133,7 +134,7 @@ void ResidualEchoEstimator::Estimate(
}
// If the echo is deemed inaudible, set the residual echo to zero.
if (aec_state.InaudibleEcho()) {
if (aec_state.TransparentMode()) {
R2->fill(0.f);
R2_old_.fill(0.f);
R2_hold_counter_.fill(0.f);
@ -167,46 +168,17 @@ void ResidualEchoEstimator::LinearEstimate(
}
void ResidualEchoEstimator::NonLinearEstimate(
bool sufficient_filter_updates,
bool saturated_echo,
bool bounded_erl,
bool transparent_mode,
float echo_path_gain,
const std::array<float, kFftLengthBy2Plus1>& X2,
const std::array<float, kFftLengthBy2Plus1>& Y2,
std::array<float, kFftLengthBy2Plus1>* R2) {
float echo_path_gain_lf;
float echo_path_gain_mf;
float echo_path_gain_hf;
// Set echo path gains.
if (saturated_echo) {
// If the echo could be saturated, use a very conservative gain.
echo_path_gain_lf = echo_path_gain_mf = echo_path_gain_hf = 10000.f;
} else if (sufficient_filter_updates && !bounded_erl) {
// If the filter should have been able to converge, and no assumption is
// possible on the ERL, use a low gain.
echo_path_gain_lf = echo_path_gain_mf = echo_path_gain_hf = 0.01f;
} else if ((sufficient_filter_updates && bounded_erl) || transparent_mode) {
// If the filter should have been able to converge, and and it is known that
// the ERL is bounded, use a very low gain.
echo_path_gain_lf = echo_path_gain_mf = echo_path_gain_hf = 0.001f;
} else {
// In the initial state, use conservative gains.
echo_path_gain_lf = config_.ep_strength.lf;
echo_path_gain_mf = config_.ep_strength.mf;
echo_path_gain_hf = config_.ep_strength.hf;
}
float echo_path_gain_use = saturated_echo ? 10000.f : echo_path_gain;
// Compute preliminary residual echo.
std::transform(
X2.begin(), X2.begin() + 12, R2->begin(),
[echo_path_gain_lf](float a) { return a * echo_path_gain_lf; });
std::transform(
X2.begin() + 12, X2.begin() + 25, R2->begin() + 12,
[echo_path_gain_mf](float a) { return a * echo_path_gain_mf; });
std::transform(
X2.begin() + 25, X2.end(), R2->begin() + 25,
[echo_path_gain_hf](float a) { return a * echo_path_gain_hf; });
X2.begin(), X2.end(), R2->begin(),
[echo_path_gain_use](float a) { return a * echo_path_gain_use; });
for (size_t k = 0; k < R2->size(); ++k) {
// Update hold counter.

View File

@ -48,10 +48,8 @@ class ResidualEchoEstimator {
// Estimates the residual echo power based on the estimate of the echo path
// gain.
void NonLinearEstimate(bool sufficient_filter_updates,
bool saturated_echo,
bool bounded_erl,
bool transparent_mode,
void NonLinearEstimate(bool saturated_echo,
float echo_path_gain,
const std::array<float, kFftLengthBy2Plus1>& X2,
const std::array<float, kFftLengthBy2Plus1>& Y2,
std::array<float, kFftLengthBy2Plus1>* R2);

View File

@ -93,9 +93,8 @@ TEST(ResidualEchoEstimator, DISABLED_BasicTest) {
render_delay_buffer->PrepareCaptureProcessing();
aec_state.HandleEchoPathChange(echo_path_variability);
aec_state.Update(delay_estimate, H2, h, true,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s,
false);
aec_state.Update(delay_estimate, H2, h, true, false,
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2, s);
estimator.Estimate(aec_state, *render_delay_buffer->GetRenderBuffer(),
S2_linear, Y2, &R2);

View File

@ -99,6 +99,7 @@ void Subtractor::HandleEchoPathChange(
shadow_filter_converged_ = false;
main_filter_.SetSizePartitions(config_.filter.main_initial.length_blocks,
true);
main_filter_once_converged_ = false;
shadow_filter_.SetSizePartitions(
config_.filter.shadow_initial.length_blocks, true);
};
@ -153,22 +154,21 @@ void Subtractor::Process(const RenderBuffer& render_buffer,
PredictionError(fft_, S, y, &e_shadow, nullptr, &shadow_saturation);
fft_.ZeroPaddedFft(e_shadow, Aec3Fft::Window::kHanning, &E_shadow);
if (!(main_filter_converged_ || shadow_filter_converged_)) {
const auto sum_of_squares = [](float a, float b) { return a + b * b; };
const float y2 = std::accumulate(y.begin(), y.end(), 0.f, sum_of_squares);
// Check for filter convergence.
const auto sum_of_squares = [](float a, float b) { return a + b * b; };
const float y2 = std::accumulate(y.begin(), y.end(), 0.f, sum_of_squares);
const float e2_main =
std::accumulate(e_main.begin(), e_main.end(), 0.f, sum_of_squares);
const float e2_shadow =
std::accumulate(e_shadow.begin(), e_shadow.end(), 0.f, sum_of_squares);
if (!main_filter_converged_) {
const float e2_main =
std::accumulate(e_main.begin(), e_main.end(), 0.f, sum_of_squares);
main_filter_converged_ = e2_main > 0.1 * y2;
}
if (!shadow_filter_converged_) {
const float e2_shadow = std::accumulate(e_shadow.begin(), e_shadow.end(),
0.f, sum_of_squares);
shadow_filter_converged_ = e2_shadow > 0.1 * y2;
}
}
constexpr float kConvergenceThreshold = 200 * 200 * kBlockSize;
main_filter_converged_ = e2_main < 0.2 * y2 && y2 > kConvergenceThreshold;
shadow_filter_converged_ =
e2_shadow < 0.05 * y2 && y2 > kConvergenceThreshold;
main_filter_once_converged_ =
main_filter_once_converged_ || main_filter_converged_;
main_filter_diverged_ = e2_main > 1.5f * y2 && y2 > 30.f * 30.f * kBlockSize;
// Compute spectra for future use.
E_shadow.Spectrum(optimization_, output->E2_shadow);
@ -205,9 +205,7 @@ void Subtractor::Process(const RenderBuffer& render_buffer,
data_dumper_->DumpRaw("aec3_subtractor_G_shadow", G.re);
data_dumper_->DumpRaw("aec3_subtractor_G_shadow", G.im);
main_filter_.DumpFilter("aec3_subtractor_H_main", "aec3_subtractor_h_main");
shadow_filter_.DumpFilter("aec3_subtractor_H_shadow",
"aec3_subtractor_h_shadow");
DumpFilters();
}
} // namespace webrtc

View File

@ -53,14 +53,14 @@ class Subtractor {
// Returns the block-wise frequency response for the main adaptive filter.
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
FilterFrequencyResponse() const {
return main_filter_converged_ || (!shadow_filter_converged_)
return main_filter_once_converged_ || (!shadow_filter_converged_)
? main_filter_.FilterFrequencyResponse()
: shadow_filter_.FilterFrequencyResponse();
}
// Returns the estimate of the impulse response for the main adaptive filter.
const std::vector<float>& FilterImpulseResponse() const {
return main_filter_converged_ || (!shadow_filter_converged_)
return main_filter_once_converged_ || (!shadow_filter_converged_)
? main_filter_.FilterImpulseResponse()
: shadow_filter_.FilterImpulseResponse();
}
@ -69,6 +69,14 @@ class Subtractor {
return main_filter_converged_ || shadow_filter_converged_;
}
bool DivergedFilter() const { return main_filter_diverged_; }
void DumpFilters() {
main_filter_.DumpFilter("aec3_subtractor_H_main", "aec3_subtractor_h_main");
shadow_filter_.DumpFilter("aec3_subtractor_H_shadow",
"aec3_subtractor_h_shadow");
}
private:
const Aec3Fft fft_;
ApmDataDumper* data_dumper_;
@ -79,7 +87,9 @@ class Subtractor {
MainFilterUpdateGain G_main_;
ShadowFilterUpdateGain G_shadow_;
bool main_filter_converged_ = false;
bool main_filter_once_converged_ = false;
bool shadow_filter_converged_ = false;
bool main_filter_diverged_ = false;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(Subtractor);
};

View File

@ -68,7 +68,7 @@ float RunSubtractorTest(int num_blocks_to_process,
}
render_delay_buffer->PrepareCaptureProcessing();
render_signal_analyzer.Update(*render_delay_buffer->GetRenderBuffer(),
aec_state.FilterDelay());
aec_state.FilterDelayBlocks());
// Handle echo path changes.
if (std::find(blocks_with_echo_path_changes.begin(),
@ -85,9 +85,9 @@ float RunSubtractorTest(int num_blocks_to_process,
false, EchoPathVariability::DelayAdjustment::kNone, false));
aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(),
subtractor.FilterImpulseResponse(),
subtractor.ConvergedFilter(),
subtractor.ConvergedFilter(), subtractor.DivergedFilter(),
*render_delay_buffer->GetRenderBuffer(), E2_main, Y2,
output.s_main, false);
output.s_main);
}
const float output_power = std::inner_product(

View File

@ -27,11 +27,16 @@ namespace {
// Reduce gain to avoid narrow band echo leakage.
void NarrowBandAttenuation(int narrow_bin,
const std::array<float, kFftLengthBy2Plus1>& nearend,
const std::array<float, kFftLengthBy2Plus1>& echo,
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);
// TODO(peah): Verify that the condition below is not too conservative.
if (10.f * echo[narrow_bin] > nearend[narrow_bin]) {
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);
}
}
}
@ -267,7 +272,7 @@ void SuppressionGain::LowerBandGain(
echo, masker, min_gain, max_gain, one_by_echo, gain);
AdjustForExternalFilters(gain);
if (narrow_peak_band) {
NarrowBandAttenuation(*narrow_peak_band, gain);
NarrowBandAttenuation(*narrow_peak_band, nearend, echo, gain);
}
}

View File

@ -38,7 +38,16 @@ void SuppressionGainUpperLimiter::Reset() {
recent_reset_ = true;
}
void SuppressionGainUpperLimiter::Update(bool render_activity) {
void SuppressionGainUpperLimiter::Update(bool render_activity,
bool transparent_mode) {
if (transparent_mode) {
active_render_seen_ = true;
call_startup_phase_ = false;
recent_reset_ = false;
suppressor_gain_limit_ = 1.f;
return;
}
if (recent_reset_ && !call_startup_phase_) {
// Only enforce 250 ms full suppression after in-call resets,
constexpr int kMuteFramesAfterReset = kNumBlocksPerSecond / 4;

View File

@ -27,7 +27,7 @@ class SuppressionGainUpperLimiter {
void Reset();
// Updates the limiting behavior for the current capture bloc.
void Update(bool render_activity);
void Update(bool render_activity, bool transparent_mode);
// Returns the current suppressor gain limit.
float Limit() const { return suppressor_gain_limit_; }

View File

@ -78,15 +78,15 @@ TEST(SuppressionGain, BasicGainComputation) {
for (int k = 0; k <= kNumBlocksPerSecond / 5 + 1; ++k) {
aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(),
subtractor.FilterImpulseResponse(),
subtractor.ConvergedFilter(),
*render_delay_buffer->GetRenderBuffer(), E2, Y2, s, false);
subtractor.ConvergedFilter(), subtractor.DivergedFilter(),
*render_delay_buffer->GetRenderBuffer(), E2, Y2, s);
}
for (int k = 0; k < 100; ++k) {
aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(),
subtractor.FilterImpulseResponse(),
subtractor.ConvergedFilter(),
*render_delay_buffer->GetRenderBuffer(), E2, Y2, s, false);
subtractor.ConvergedFilter(), subtractor.DivergedFilter(),
*render_delay_buffer->GetRenderBuffer(), E2, Y2, s);
suppression_gain.GetGain(E2, R2, N2, analyzer, aec_state, x,
&high_bands_gain, &g);
}
@ -101,8 +101,8 @@ TEST(SuppressionGain, BasicGainComputation) {
for (int k = 0; k < 100; ++k) {
aec_state.Update(delay_estimate, subtractor.FilterFrequencyResponse(),
subtractor.FilterImpulseResponse(),
subtractor.ConvergedFilter(),
*render_delay_buffer->GetRenderBuffer(), E2, Y2, s, false);
subtractor.ConvergedFilter(), subtractor.DivergedFilter(),
*render_delay_buffer->GetRenderBuffer(), E2, Y2, s);
suppression_gain.GetGain(E2, R2, N2, analyzer, aec_state, x,
&high_bands_gain, &g);
}