Added the ability to more easily adjust the filter length in AEC3

Bug: webrtc:8609
Change-Id: If060b332993c2c98d7a12608ab31f4da858b8016
Reviewed-on: https://webrtc-review.googlesource.com/28620
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21216}
This commit is contained in:
Per Åhgren
2017-12-11 22:28:45 +01:00
committed by Commit Bot
parent c59a576c86
commit 09a718accd
20 changed files with 312 additions and 234 deletions

View File

@ -415,10 +415,10 @@ AdaptiveFirFilter::AdaptiveFirFilter(size_t size_partitions,
fft_(),
optimization_(optimization),
H_(size_partitions),
H2_(size_partitions, std::array<float, kFftLengthBy2Plus1>()) {
H2_(size_partitions, std::array<float, kFftLengthBy2Plus1>()),
h_(GetTimeDomainLength(size_partitions), 0.f) {
RTC_DCHECK(data_dumper_);
h_.fill(0.f);
for (auto& H_j : H_) {
H_j.Clear();
}
@ -431,7 +431,7 @@ AdaptiveFirFilter::AdaptiveFirFilter(size_t size_partitions,
AdaptiveFirFilter::~AdaptiveFirFilter() = default;
void AdaptiveFirFilter::HandleEchoPathChange() {
h_.fill(0.f);
std::fill(h_.begin(), h_.end(), 0.f);
for (auto& H_j : H_) {
H_j.Clear();
}

View File

@ -120,10 +120,7 @@ class AdaptiveFirFilter {
}
// Returns the estimate of the impulse response.
const std::array<float, kAdaptiveFilterTimeDomainLength>&
FilterImpulseResponse() const {
return h_;
}
const std::vector<float>& FilterImpulseResponse() const { return h_; }
void DumpFilter(const char* name) {
for (auto& H : H_) {
@ -141,7 +138,7 @@ class AdaptiveFirFilter {
const Aec3Optimization optimization_;
std::vector<FftData> H_;
std::vector<std::array<float, kFftLengthBy2Plus1>> H2_;
std::array<float, kAdaptiveFilterTimeDomainLength> h_;
std::vector<float> h_;
std::array<float, kFftLengthBy2Plus1> erl_;
size_t partition_to_constrain_ = 0;

View File

@ -308,10 +308,10 @@ TEST(AdaptiveFirFilter, FilterSize) {
TEST(AdaptiveFirFilter, FilterAndAdapt) {
constexpr size_t kNumBlocksToProcess = 500;
ApmDataDumper data_dumper(42);
AdaptiveFirFilter filter(kAdaptiveFilterLength, DetectOptimization(),
EchoCanceller3Config config;
AdaptiveFirFilter filter(config.filter.length_blocks, DetectOptimization(),
&data_dumper);
Aec3Fft fft;
EchoCanceller3Config config;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.default_delay = 1;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(

View File

@ -38,10 +38,8 @@ constexpr size_t kFftLengthBy2Plus1 = kFftLengthBy2 + 1;
constexpr size_t kFftLengthBy2Minus1 = kFftLengthBy2 - 1;
constexpr size_t kFftLength = 2 * kFftLengthBy2;
constexpr int kAdaptiveFilterLength = 12;
constexpr int kUnknownDelayRenderWindowSize = 12;
constexpr int kAdaptiveFilterTimeDomainLength =
kAdaptiveFilterLength * kFftLengthBy2;
constexpr int kMaxAdaptiveFilterLength = 50;
constexpr int kUnknownDelayRenderWindowSize = 30;
constexpr int kRenderTransferQueueSizeFrames = 100;
constexpr size_t kMaxNumBands = 3;
@ -72,6 +70,10 @@ constexpr bool ValidFullBandRate(int sample_rate_hz) {
sample_rate_hz == 32000 || sample_rate_hz == 48000;
}
constexpr int GetTimeDomainLength(int filter_length_blocks) {
return filter_length_blocks * kFftLengthBy2;
}
constexpr size_t GetDownSampledBufferSize(size_t down_sampling_factor,
size_t num_matched_filters) {
return kBlockSize / down_sampling_factor *
@ -80,10 +82,11 @@ constexpr size_t GetDownSampledBufferSize(size_t down_sampling_factor,
}
constexpr size_t GetRenderDelayBufferSize(size_t down_sampling_factor,
size_t num_matched_filters) {
size_t num_matched_filters,
size_t filter_length_blocks) {
return GetDownSampledBufferSize(down_sampling_factor, num_matched_filters) /
(kBlockSize / down_sampling_factor) +
kAdaptiveFilterLength + 1;
filter_length_blocks + 1;
}
// Detects what kind of optimizations to use for the code.

View File

@ -29,8 +29,8 @@ int EstimateFilterDelay(
adaptive_filter_frequency_response) {
const auto& H2 = adaptive_filter_frequency_response;
constexpr size_t kUpperBin = kFftLengthBy2 - 5;
RTC_DCHECK_GE(kAdaptiveFilterLength, H2.size());
std::array<int, kAdaptiveFilterLength> delays;
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].
@ -56,9 +56,8 @@ AecState::AecState(const EchoCanceller3Config& config)
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
erle_estimator_(config.erle.min, config.erle.max_l, config.erle.max_h),
config_(config),
reverb_decay_(config_.ep_strength.default_len) {
max_render_.fill(0.f);
}
max_render_(config_.filter.length_blocks, 0.f),
reverb_decay_(config_.ep_strength.default_len) {}
AecState::~AecState() = default;
@ -71,7 +70,7 @@ void AecState::HandleEchoPathChange(
capture_signal_saturation_ = false;
echo_saturation_ = false;
previous_max_sample_ = 0.f;
max_render_.fill(0.f);
std::fill(max_render_.begin(), max_render_.end(), 0.f);
force_zero_gain_counter_ = 0;
blocks_with_filter_adaptation_ = 0;
blocks_with_strong_render_ = 0;
@ -107,10 +106,10 @@ void AecState::HandleEchoPathChange(
}
}
void AecState::Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
void AecState::Update(
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
adaptive_filter_frequency_response,
const std::array<float, kAdaptiveFilterTimeDomainLength>&
adaptive_filter_impulse_response,
const std::vector<float>& adaptive_filter_impulse_response,
bool converged_filter,
const rtc::Optional<size_t>& external_delay_samples,
const RenderBuffer& render_buffer,
@ -208,24 +207,29 @@ void AecState::Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
UpdateReverb(adaptive_filter_impulse_response);
}
void AecState::UpdateReverb(
const std::array<float, kAdaptiveFilterTimeDomainLength>&
impulse_response) {
void AecState::UpdateReverb(const std::vector<float>& impulse_response) {
if ((!(filter_delay_ && usable_linear_estimate_)) ||
(*filter_delay_ > kAdaptiveFilterLength - 4)) {
(*filter_delay_ > config_.filter.length_blocks - 4)) {
return;
}
// Form the data to match against by squaring the impulse response
// coefficients.
std::array<float, kAdaptiveFilterTimeDomainLength> matching_data;
std::array<float, GetTimeDomainLength(kMaxAdaptiveFilterLength)>
matching_data_data;
RTC_DCHECK_LE(GetTimeDomainLength(config_.filter.length_blocks),
matching_data_data.size());
rtc::ArrayView<float> matching_data(
matching_data_data.data(),
GetTimeDomainLength(config_.filter.length_blocks));
std::transform(impulse_response.begin(), impulse_response.end(),
matching_data.begin(), [](float a) { return a * a; });
// Avoid matching against noise in the model by subtracting an estimate of the
// model noise power.
constexpr size_t kTailLength = 64;
constexpr size_t tail_index = kAdaptiveFilterTimeDomainLength - kTailLength;
const size_t tail_index =
GetTimeDomainLength(config_.filter.length_blocks) - kTailLength;
const float tail_power = *std::max_element(matching_data.begin() + tail_index,
matching_data.end());
std::for_each(matching_data.begin(), matching_data.begin() + tail_index,

View File

@ -114,8 +114,7 @@ class AecState {
// Updates the aec state.
void Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
adaptive_filter_frequency_response,
const std::array<float, kAdaptiveFilterTimeDomainLength>&
adaptive_filter_impulse_response,
const std::vector<float>& adaptive_filter_impulse_response,
bool converged_filter,
const rtc::Optional<size_t>& external_delay_samples,
const RenderBuffer& render_buffer,
@ -141,8 +140,7 @@ class AecState {
bool inaudible_echo_ = false;
};
void UpdateReverb(const std::array<float, kAdaptiveFilterTimeDomainLength>&
impulse_response);
void UpdateReverb(const std::vector<float>& impulse_response);
static int instance_count_;
std::unique_ptr<ApmDataDumper> data_dumper_;
@ -157,7 +155,6 @@ class AecState {
bool echo_saturation_ = false;
bool transparent_mode_ = false;
float previous_max_sample_ = 0.f;
std::array<float, kAdaptiveFilterLength> max_render_;
bool force_zero_gain_ = false;
bool render_received_ = false;
size_t force_zero_gain_counter_ = 0;
@ -169,6 +166,7 @@ class AecState {
float reverb_decay_candidate_residual_ = -1.f;
EchoAudibility echo_audibility_;
const EchoCanceller3Config config_;
std::vector<float> max_render_;
float reverb_decay_;
bool saturating_echo_path_ = false;
bool initial_state_ = true;

View File

@ -43,8 +43,8 @@ TEST(AecState, NormalUsage) {
converged_filter_frequency_response[2].fill(100.f);
converged_filter_frequency_response[2][0] = 1.f;
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
impulse_response.fill(0.f);
std::vector<float> impulse_response(
GetTimeDomainLength(config.filter.length_blocks), 0.f);
// Verify that linear AEC usability is false when the filter is diverged and
// there is no external delay reported.
@ -193,8 +193,8 @@ TEST(AecState, ConvergedFilterDelay) {
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
kFilterLength);
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
impulse_response.fill(0.f);
std::vector<float> impulse_response(
GetTimeDomainLength(config.filter.length_blocks), 0.f);
// Verify that the filter delay for a converged filter is properly identified.
for (int k = 0; k < kFilterLength; ++k) {
@ -232,13 +232,13 @@ TEST(AecState, ExternalDelay) {
x.fill(0.f);
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
kAdaptiveFilterLength);
config.filter.length_blocks);
for (auto& v : frequency_response) {
v.fill(0.01f);
}
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
impulse_response.fill(0.f);
std::vector<float> impulse_response(
GetTimeDomainLength(config.filter.length_blocks), 0.f);
for (size_t k = 0; k < frequency_response.size() - 1; ++k) {
state.HandleEchoPathChange(EchoPathVariability(

View File

@ -101,7 +101,7 @@ EchoRemoverImpl::EchoRemoverImpl(const EchoCanceller3Config& config,
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
optimization_(DetectOptimization()),
sample_rate_hz_(sample_rate_hz),
subtractor_(data_dumper_.get(), optimization_),
subtractor_(config, data_dumper_.get(), optimization_),
suppression_gain_(config_, optimization_),
cng_(optimization_),
suppression_filter_(sample_rate_hz_),

View File

@ -33,6 +33,7 @@ namespace {
// gain functionality.
void RunFilterUpdateTest(int num_blocks_to_process,
size_t delay_samples,
int filter_length_blocks,
const std::vector<int>& blocks_with_echo_path_changes,
const std::vector<int>& blocks_with_saturation,
bool use_silent_render_in_second_half,
@ -40,8 +41,12 @@ void RunFilterUpdateTest(int num_blocks_to_process,
std::array<float, kBlockSize>* y_last_block,
FftData* G_last_block) {
ApmDataDumper data_dumper(42);
AdaptiveFirFilter main_filter(12, DetectOptimization(), &data_dumper);
AdaptiveFirFilter shadow_filter(12, DetectOptimization(), &data_dumper);
EchoCanceller3Config config;
config.filter.length_blocks = filter_length_blocks;
AdaptiveFirFilter main_filter(config.filter.length_blocks,
DetectOptimization(), &data_dumper);
AdaptiveFirFilter shadow_filter(config.filter.length_blocks,
DetectOptimization(), &data_dumper);
Aec3Fft fft;
std::array<float, kBlockSize> x_old;
x_old.fill(0.f);
@ -50,7 +55,6 @@ void RunFilterUpdateTest(int num_blocks_to_process,
Random random_generator(42U);
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
std::vector<float> y(kBlockSize, 0.f);
EchoCanceller3Config config;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.default_delay = 1;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
@ -159,9 +163,16 @@ void RunFilterUpdateTest(int num_blocks_to_process,
std::copy(G.im.begin(), G.im.end(), G_last_block->im.begin());
}
std::string ProduceDebugText(size_t delay) {
std::string ProduceDebugText(int filter_length_blocks) {
std::ostringstream ss;
ss << "Delay: " << delay;
ss << "Length: " << filter_length_blocks;
return ss.str();
}
std::string ProduceDebugText(size_t delay, int filter_length_blocks) {
std::ostringstream ss;
ss << "Delay: " << delay << ", ";
ss << ProduceDebugText(filter_length_blocks);
return ss.str();
}
@ -172,10 +183,11 @@ std::string ProduceDebugText(size_t delay) {
// Verifies that the check for non-null output gain parameter works.
TEST(MainFilterUpdateGain, NullDataOutputGain) {
ApmDataDumper data_dumper(42);
AdaptiveFirFilter filter(kAdaptiveFilterLength, DetectOptimization(),
EchoCanceller3Config config;
AdaptiveFirFilter filter(config.filter.length_blocks, DetectOptimization(),
&data_dumper);
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
RenderDelayBuffer::Create(config, 3));
RenderSignalAnalyzer analyzer;
SubtractorOutput output;
MainFilterUpdateGain gain;
@ -190,25 +202,30 @@ TEST(MainFilterUpdateGain, NullDataOutputGain) {
TEST(MainFilterUpdateGain, GainCausesFilterToConverge) {
std::vector<int> blocks_with_echo_path_changes;
std::vector<int> blocks_with_saturation;
for (size_t filter_length_blocks : {12, 20, 30}) {
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
SCOPED_TRACE(ProduceDebugText(delay_samples));
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
std::array<float, kBlockSize> e;
std::array<float, kBlockSize> y;
FftData G;
RunFilterUpdateTest(500, delay_samples, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G);
RunFilterUpdateTest(500, delay_samples, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G);
// Verify that the main 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));
}
}
}
// Verifies that the magnitude of the gain on average decreases for a
// persistently exciting signal.
TEST(MainFilterUpdateGain, DecreasingGain) {
for (size_t filter_length_blocks : {12, 20, 30}) {
SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
std::vector<int> blocks_with_echo_path_changes;
std::vector<int> blocks_with_saturation;
@ -221,12 +238,15 @@ TEST(MainFilterUpdateGain, DecreasingGain) {
std::array<float, kFftLengthBy2Plus1> G_b_power;
std::array<float, kFftLengthBy2Plus1> G_c_power;
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G_a);
RunFilterUpdateTest(200, 65, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G_b);
RunFilterUpdateTest(300, 65, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G_c);
RunFilterUpdateTest(100, 65, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G_a);
RunFilterUpdateTest(300, 65, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G_b);
RunFilterUpdateTest(600, 65, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G_c);
G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
@ -238,6 +258,7 @@ TEST(MainFilterUpdateGain, DecreasingGain) {
EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
}
}
// Verifies that the gain is zero when there is saturation and that the internal
// error estimates cause the gain to increase after a period of saturation.
@ -248,6 +269,8 @@ TEST(MainFilterUpdateGain, SaturationBehavior) {
blocks_with_saturation.push_back(k);
}
for (size_t filter_length_blocks : {12, 20, 30}) {
SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
std::array<float, kBlockSize> e;
std::array<float, kBlockSize> y;
FftData G_a;
@ -259,16 +282,19 @@ TEST(MainFilterUpdateGain, SaturationBehavior) {
std::array<float, kFftLengthBy2Plus1> G_a_power;
std::array<float, kFftLengthBy2Plus1> G_b_power;
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G_a);
RunFilterUpdateTest(100, 65, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G_a);
EXPECT_EQ(G_a_ref.re, G_a.re);
EXPECT_EQ(G_a_ref.im, G_a.im);
RunFilterUpdateTest(99, 65, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G_a);
RunFilterUpdateTest(201, 65, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G_b);
RunFilterUpdateTest(99, 65, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G_a);
RunFilterUpdateTest(201, 65, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G_b);
G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
@ -276,9 +302,12 @@ TEST(MainFilterUpdateGain, SaturationBehavior) {
EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
}
}
// Verifies that the gain increases after an echo path change.
TEST(MainFilterUpdateGain, EchoPathChangeBehavior) {
for (size_t filter_length_blocks : {12, 20, 30}) {
SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
std::vector<int> blocks_with_echo_path_changes;
std::vector<int> blocks_with_saturation;
blocks_with_echo_path_changes.push_back(99);
@ -290,10 +319,12 @@ TEST(MainFilterUpdateGain, EchoPathChangeBehavior) {
std::array<float, kFftLengthBy2Plus1> G_a_power;
std::array<float, kFftLengthBy2Plus1> G_b_power;
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G_a);
RunFilterUpdateTest(101, 65, blocks_with_echo_path_changes,
blocks_with_saturation, false, &e, &y, &G_b);
RunFilterUpdateTest(100, 65, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G_a);
RunFilterUpdateTest(101, 65, filter_length_blocks,
blocks_with_echo_path_changes, blocks_with_saturation,
false, &e, &y, &G_b);
G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
@ -301,5 +332,6 @@ TEST(MainFilterUpdateGain, EchoPathChangeBehavior) {
EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
}
}
} // namespace webrtc

View File

@ -25,15 +25,12 @@ namespace test {
class MockRenderDelayBuffer : public RenderDelayBuffer {
public:
explicit MockRenderDelayBuffer(int sample_rate_hz)
: block_buffer_(GetRenderDelayBufferSize(4, 4),
: block_buffer_(GetRenderDelayBufferSize(4, 4, 12),
NumBandsForRate(sample_rate_hz),
kBlockSize),
spectrum_buffer_(block_buffer_.buffer.size(), kFftLengthBy2Plus1),
fft_buffer_(block_buffer_.buffer.size()),
render_buffer_(kAdaptiveFilterLength,
&block_buffer_,
&spectrum_buffer_,
&fft_buffer_),
render_buffer_(12, &block_buffer_, &spectrum_buffer_, &fft_buffer_),
downsampled_render_buffer_(GetDownSampledBufferSize(4, 4)) {
ON_CALL(*this, GetRenderBuffer())
.WillByDefault(

View File

@ -28,8 +28,6 @@
namespace webrtc {
namespace {
constexpr int kBufferHeadroom = kAdaptiveFilterLength;
class RenderDelayBufferImpl final : public RenderDelayBuffer {
public:
RenderDelayBufferImpl(const EchoCanceller3Config& config, size_t num_bands);
@ -41,7 +39,7 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer {
bool SetDelay(size_t delay) override;
rtc::Optional<size_t> Delay() const override { return delay_; }
size_t MaxDelay() const override {
return blocks_.buffer.size() - 1 - kBufferHeadroom;
return blocks_.buffer.size() - 1 - buffer_headroom_;
}
RenderBuffer* GetRenderBuffer() override { return &echo_remover_buffer_; }
@ -68,6 +66,7 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer {
const std::vector<std::vector<float>> zero_block_;
const Aec3Fft fft_;
std::vector<float> render_ds_;
const int buffer_headroom_;
int LowRateBufferOffset() const { return DelayEstimatorOffset(config_) >> 1; }
int MaxExternalDelayToInternalDelay(size_t delay) const;
@ -153,18 +152,24 @@ RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
? kBlockSize / config.delay.down_sampling_factor
: kBlockSize)),
blocks_(GetRenderDelayBufferSize(config.delay.down_sampling_factor,
config.delay.num_filters),
config.delay.num_filters,
config.filter.length_blocks),
num_bands,
kBlockSize),
spectra_(blocks_.buffer.size(), kFftLengthBy2Plus1),
ffts_(blocks_.buffer.size()),
echo_remover_buffer_(kAdaptiveFilterLength, &blocks_, &spectra_, &ffts_),
delay_(config_.delay.min_echo_path_delay_blocks),
echo_remover_buffer_(config.filter.length_blocks,
&blocks_,
&spectra_,
&ffts_),
low_rate_(GetDownSampledBufferSize(config.delay.down_sampling_factor,
config.delay.num_filters)),
render_decimator_(config.delay.down_sampling_factor),
zero_block_(num_bands, std::vector<float>(kBlockSize, 0.f)),
fft_(),
render_ds_(sub_block_size_, 0.f) {
render_ds_(sub_block_size_, 0.f),
buffer_headroom_(config.filter.length_blocks) {
RTC_DCHECK_EQ(blocks_.buffer.size(), ffts_.buffer.size());
RTC_DCHECK_EQ(spectra_.buffer.size(), ffts_.buffer.size());

View File

@ -77,7 +77,7 @@ void RenderNoisePower(
} // namespace
ResidualEchoEstimator::ResidualEchoEstimator(const EchoCanceller3Config& config)
: config_(config) {
: config_(config), S2_old_(config_.filter.length_blocks) {
Reset();
}
@ -150,8 +150,8 @@ void ResidualEchoEstimator::Estimate(
if (aec_state.ExternalDelay() && aec_state.FilterDelay() &&
aec_state.SaturatedEcho()) {
AddEchoReverb(*R2, aec_state.SaturatedEcho(),
std::min(static_cast<size_t>(kAdaptiveFilterLength),
delay.value_or(kAdaptiveFilterLength)),
std::min(static_cast<size_t>(config_.filter.length_blocks),
delay.value_or(config_.filter.length_blocks)),
aec_state.ReverbDecay(), R2);
}
}

View File

@ -64,16 +64,14 @@ class ResidualEchoEstimator {
size_t delay,
float reverb_decay_factor,
std::array<float, kFftLengthBy2Plus1>* R2);
const EchoCanceller3Config config_;
std::array<float, kFftLengthBy2Plus1> R2_old_;
std::array<int, kFftLengthBy2Plus1> R2_hold_counter_;
std::array<float, kFftLengthBy2Plus1> R2_reverb_;
int S2_old_index_ = 0;
std::array<std::array<float, kFftLengthBy2Plus1>, kAdaptiveFilterLength>
S2_old_;
std::vector<std::array<float, kFftLengthBy2Plus1>> S2_old_;
std::array<float, kFftLengthBy2Plus1> X2_noise_floor_;
std::array<int, kFftLengthBy2Plus1> X2_noise_floor_counter_;
const EchoCanceller3Config config_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ResidualEchoEstimator);
};

View File

@ -70,8 +70,7 @@ TEST(ResidualEchoEstimator, DISABLED_BasicTest) {
H2[2].fill(10.f);
H2[2][0] = 0.1f;
std::array<float, kAdaptiveFilterTimeDomainLength> h;
h.fill(0.f);
std::vector<float> h(GetTimeDomainLength(config.filter.length_blocks), 0.f);
s.fill(100.f);

View File

@ -31,16 +31,20 @@ namespace {
// gain functionality.
void RunFilterUpdateTest(int num_blocks_to_process,
size_t delay_samples,
int filter_length_blocks,
const std::vector<int>& blocks_with_saturation,
std::array<float, kBlockSize>* e_last_block,
std::array<float, kBlockSize>* y_last_block,
FftData* G_last_block) {
ApmDataDumper data_dumper(42);
AdaptiveFirFilter main_filter(12, DetectOptimization(), &data_dumper);
AdaptiveFirFilter shadow_filter(12, DetectOptimization(), &data_dumper);
EchoCanceller3Config config;
config.filter.length_blocks = filter_length_blocks;
AdaptiveFirFilter main_filter(config.filter.length_blocks,
DetectOptimization(), &data_dumper);
AdaptiveFirFilter shadow_filter(config.filter.length_blocks,
DetectOptimization(), &data_dumper);
Aec3Fft fft;
EchoCanceller3Config config;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.default_delay = 1;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
@ -104,9 +108,16 @@ void RunFilterUpdateTest(int num_blocks_to_process,
std::copy(G.im.begin(), G.im.end(), G_last_block->im.begin());
}
std::string ProduceDebugText(size_t delay) {
std::string ProduceDebugText(int filter_length_blocks) {
std::ostringstream ss;
ss << ", Delay: " << delay;
ss << "Length: " << filter_length_blocks;
return ss.str();
}
std::string ProduceDebugText(size_t delay, int filter_length_blocks) {
std::ostringstream ss;
ss << "Delay: " << delay << ", ";
ss << ProduceDebugText(filter_length_blocks);
return ss.str();
}
@ -133,24 +144,29 @@ TEST(ShadowFilterUpdateGain, NullDataOutputGain) {
TEST(ShadowFilterUpdateGain, GainCausesFilterToConverge) {
std::vector<int> blocks_with_echo_path_changes;
std::vector<int> blocks_with_saturation;
for (size_t filter_length_blocks : {12, 20, 30}) {
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
SCOPED_TRACE(ProduceDebugText(delay_samples));
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
std::array<float, kBlockSize> e;
std::array<float, kBlockSize> y;
FftData G;
RunFilterUpdateTest(500, delay_samples, blocks_with_saturation, &e, &y, &G);
RunFilterUpdateTest(1000, delay_samples, filter_length_blocks,
blocks_with_saturation, &e, &y, &G);
// Verify that the main 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));
}
}
}
// Verifies that the magnitude of the gain on average decreases for a
// persistently exciting signal.
TEST(ShadowFilterUpdateGain, DecreasingGain) {
for (size_t filter_length_blocks : {12, 20, 30}) {
SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
std::vector<int> blocks_with_echo_path_changes;
std::vector<int> blocks_with_saturation;
@ -163,9 +179,12 @@ TEST(ShadowFilterUpdateGain, DecreasingGain) {
std::array<float, kFftLengthBy2Plus1> G_b_power;
std::array<float, kFftLengthBy2Plus1> G_c_power;
RunFilterUpdateTest(100, 65, blocks_with_saturation, &e, &y, &G_a);
RunFilterUpdateTest(200, 65, blocks_with_saturation, &e, &y, &G_b);
RunFilterUpdateTest(300, 65, blocks_with_saturation, &e, &y, &G_c);
RunFilterUpdateTest(100, 65, filter_length_blocks, blocks_with_saturation,
&e, &y, &G_a);
RunFilterUpdateTest(200, 65, filter_length_blocks, blocks_with_saturation,
&e, &y, &G_b);
RunFilterUpdateTest(300, 65, filter_length_blocks, blocks_with_saturation,
&e, &y, &G_c);
G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
@ -177,6 +196,7 @@ TEST(ShadowFilterUpdateGain, DecreasingGain) {
EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
}
}
// Verifies that the gain is zero when there is saturation.
TEST(ShadowFilterUpdateGain, SaturationBehavior) {
@ -185,6 +205,8 @@ TEST(ShadowFilterUpdateGain, SaturationBehavior) {
for (int k = 99; k < 200; ++k) {
blocks_with_saturation.push_back(k);
}
for (size_t filter_length_blocks : {12, 20, 30}) {
SCOPED_TRACE(ProduceDebugText(filter_length_blocks));
std::array<float, kBlockSize> e;
std::array<float, kBlockSize> y;
@ -193,10 +215,12 @@ TEST(ShadowFilterUpdateGain, SaturationBehavior) {
G_a_ref.re.fill(0.f);
G_a_ref.im.fill(0.f);
RunFilterUpdateTest(100, 65, blocks_with_saturation, &e, &y, &G_a);
RunFilterUpdateTest(100, 65, filter_length_blocks, blocks_with_saturation,
&e, &y, &G_a);
EXPECT_EQ(G_a_ref.re, G_a.re);
EXPECT_EQ(G_a_ref.im, G_a.im);
}
}
} // namespace webrtc

View File

@ -45,13 +45,14 @@ void PredictionError(const Aec3Fft& fft,
}
} // namespace
Subtractor::Subtractor(ApmDataDumper* data_dumper,
Subtractor::Subtractor(const EchoCanceller3Config& config,
ApmDataDumper* data_dumper,
Aec3Optimization optimization)
: fft_(),
data_dumper_(data_dumper),
optimization_(optimization),
main_filter_(kAdaptiveFilterLength, optimization, data_dumper_),
shadow_filter_(kAdaptiveFilterLength, optimization, data_dumper_) {
main_filter_(config.filter.length_blocks, optimization, data_dumper_),
shadow_filter_(config.filter.length_blocks, optimization, data_dumper_) {
RTC_DCHECK(data_dumper_);
}

View File

@ -33,7 +33,9 @@ namespace webrtc {
// Proves linear echo cancellation functionality
class Subtractor {
public:
Subtractor(ApmDataDumper* data_dumper, Aec3Optimization optimization);
Subtractor(const EchoCanceller3Config& config,
ApmDataDumper* data_dumper,
Aec3Optimization optimization);
~Subtractor();
// Performs the echo subtraction.
@ -52,8 +54,7 @@ class Subtractor {
}
// Returns the estimate of the impulse response for the main adaptive filter.
const std::array<float, kAdaptiveFilterTimeDomainLength>&
FilterImpulseResponse() const {
const std::vector<float>& FilterImpulseResponse() const {
return main_filter_.FilterImpulseResponse();
}

View File

@ -25,15 +25,17 @@ namespace {
float RunSubtractorTest(int num_blocks_to_process,
int delay_samples,
int filter_length_blocks,
bool uncorrelated_inputs,
const std::vector<int>& blocks_with_echo_path_changes) {
ApmDataDumper data_dumper(42);
Subtractor subtractor(&data_dumper, DetectOptimization());
EchoCanceller3Config config;
config.filter.length_blocks = filter_length_blocks;
Subtractor subtractor(config, &data_dumper, DetectOptimization());
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
std::vector<float> y(kBlockSize, 0.f);
std::array<float, kBlockSize> x_old;
SubtractorOutput output;
EchoCanceller3Config config;
config.delay.min_echo_path_delay_blocks = 0;
config.delay.default_delay = 1;
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
@ -44,7 +46,7 @@ float RunSubtractorTest(int num_blocks_to_process,
std::array<float, kFftLengthBy2Plus1> Y2;
std::array<float, kFftLengthBy2Plus1> E2_main;
std::array<float, kFftLengthBy2Plus1> E2_shadow;
AecState aec_state(EchoCanceller3Config{});
AecState aec_state(config);
x_old.fill(0.f);
Y2.fill(0.f);
E2_main.fill(0.f);
@ -98,9 +100,10 @@ float RunSubtractorTest(int num_blocks_to_process,
return output_power / y_power;
}
std::string ProduceDebugText(size_t delay) {
std::string ProduceDebugText(size_t delay, int filter_length_blocks) {
std::ostringstream ss;
ss << "Delay: " << delay;
ss << "Delay: " << delay << ", ";
ss << "Length: " << filter_length_blocks;
return ss.str();
}
@ -110,7 +113,8 @@ std::string ProduceDebugText(size_t delay) {
// Verifies that the check for non data dumper works.
TEST(Subtractor, NullDataDumper) {
EXPECT_DEATH(Subtractor(nullptr, DetectOptimization()), "");
EXPECT_DEATH(
Subtractor(EchoCanceller3Config(), nullptr, DetectOptimization()), "");
}
// Verifies the check for null subtractor output.
@ -118,31 +122,33 @@ TEST(Subtractor, NullDataDumper) {
// tests on test bots has been fixed.
TEST(Subtractor, DISABLED_NullOutput) {
ApmDataDumper data_dumper(42);
Subtractor subtractor(&data_dumper, DetectOptimization());
EchoCanceller3Config config;
Subtractor subtractor(config, &data_dumper, DetectOptimization());
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
RenderDelayBuffer::Create(config, 3));
RenderSignalAnalyzer render_signal_analyzer;
std::vector<float> y(kBlockSize, 0.f);
EXPECT_DEATH(subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
render_signal_analyzer,
AecState(EchoCanceller3Config{}), nullptr),
EXPECT_DEATH(
subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
render_signal_analyzer, AecState(config), nullptr),
"");
}
// Verifies the check for the capture signal size.
TEST(Subtractor, WrongCaptureSize) {
ApmDataDumper data_dumper(42);
Subtractor subtractor(&data_dumper, DetectOptimization());
EchoCanceller3Config config;
Subtractor subtractor(config, &data_dumper, DetectOptimization());
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
RenderDelayBuffer::Create(config, 3));
RenderSignalAnalyzer render_signal_analyzer;
std::vector<float> y(kBlockSize - 1, 0.f);
SubtractorOutput output;
EXPECT_DEATH(subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
render_signal_analyzer,
AecState(EchoCanceller3Config{}), &output),
EXPECT_DEATH(
subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
render_signal_analyzer, AecState(config), &output),
"");
}
@ -151,39 +157,48 @@ TEST(Subtractor, WrongCaptureSize) {
// Verifies that the subtractor is able to converge on correlated data.
TEST(Subtractor, Convergence) {
std::vector<int> blocks_with_echo_path_changes;
for (size_t filter_length_blocks : {12, 20, 30}) {
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
SCOPED_TRACE(ProduceDebugText(delay_samples));
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
float echo_to_nearend_power = RunSubtractorTest(
100, delay_samples, false, blocks_with_echo_path_changes);
float echo_to_nearend_power =
RunSubtractorTest(300, delay_samples, filter_length_blocks, false,
blocks_with_echo_path_changes);
EXPECT_GT(0.1f, echo_to_nearend_power);
}
}
}
// Verifies that the subtractor does not converge on uncorrelated signals.
TEST(Subtractor, NonConvergenceOnUncorrelatedSignals) {
std::vector<int> blocks_with_echo_path_changes;
for (size_t filter_length_blocks : {12, 20, 30}) {
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
SCOPED_TRACE(ProduceDebugText(delay_samples));
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
float echo_to_nearend_power = RunSubtractorTest(
100, delay_samples, true, blocks_with_echo_path_changes);
float echo_to_nearend_power =
RunSubtractorTest(100, delay_samples, filter_length_blocks, true,
blocks_with_echo_path_changes);
EXPECT_NEAR(1.f, echo_to_nearend_power, 0.05);
}
}
}
// Verifies that the subtractor is properly reset when there is an echo path
// change.
TEST(Subtractor, EchoPathChangeReset) {
std::vector<int> blocks_with_echo_path_changes;
blocks_with_echo_path_changes.push_back(99);
for (size_t filter_length_blocks : {12, 20, 30}) {
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
SCOPED_TRACE(ProduceDebugText(delay_samples));
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
float echo_to_nearend_power = RunSubtractorTest(
100, delay_samples, false, blocks_with_echo_path_changes);
float echo_to_nearend_power =
RunSubtractorTest(100, delay_samples, filter_length_blocks, false,
blocks_with_echo_path_changes);
EXPECT_NEAR(1.f, echo_to_nearend_power, 0.0000001f);
}
}
}
} // namespace webrtc

View File

@ -60,7 +60,7 @@ TEST(SuppressionGain, BasicGainComputation) {
EchoCanceller3Config config;
AecState aec_state(config);
ApmDataDumper data_dumper(42);
Subtractor subtractor(&data_dumper, DetectOptimization());
Subtractor subtractor(config, &data_dumper, DetectOptimization());
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
RenderDelayBuffer::Create(config, 3));

View File

@ -1158,6 +1158,10 @@ struct EchoCanceller3Config {
size_t min_echo_path_delay_blocks = 5;
} delay;
struct Filter {
size_t length_blocks = 12;
} filter;
struct Erle {
float min = 1.f;
float max_l = 8.f;