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:
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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,18 +106,18 @@ void AecState::HandleEchoPathChange(
|
||||
}
|
||||
}
|
||||
|
||||
void AecState::Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||
adaptive_filter_frequency_response,
|
||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
||||
adaptive_filter_impulse_response,
|
||||
bool converged_filter,
|
||||
const rtc::Optional<size_t>& external_delay_samples,
|
||||
const RenderBuffer& render_buffer,
|
||||
const std::array<float, kFftLengthBy2Plus1>& E2_main,
|
||||
const std::array<float, kFftLengthBy2Plus1>& Y2,
|
||||
rtc::ArrayView<const float> x,
|
||||
const std::array<float, kBlockSize>& s,
|
||||
bool echo_leakage_detected) {
|
||||
void AecState::Update(
|
||||
const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||
adaptive_filter_frequency_response,
|
||||
const std::vector<float>& adaptive_filter_impulse_response,
|
||||
bool converged_filter,
|
||||
const rtc::Optional<size_t>& external_delay_samples,
|
||||
const RenderBuffer& render_buffer,
|
||||
const std::array<float, kFftLengthBy2Plus1>& E2_main,
|
||||
const std::array<float, kFftLengthBy2Plus1>& Y2,
|
||||
rtc::ArrayView<const float> x,
|
||||
const std::array<float, kBlockSize>& s,
|
||||
bool echo_leakage_detected) {
|
||||
// Store input parameters.
|
||||
echo_leakage_detected_ = echo_leakage_detected;
|
||||
|
||||
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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_),
|
||||
|
@ -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,53 +202,62 @@ TEST(MainFilterUpdateGain, NullDataOutputGain) {
|
||||
TEST(MainFilterUpdateGain, GainCausesFilterToConverge) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
for (size_t filter_length_blocks : {12, 20, 30}) {
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G;
|
||||
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));
|
||||
// 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) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
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;
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
FftData G_c;
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_c_power;
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
FftData G_c;
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
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);
|
||||
G_c.Spectrum(Aec3Optimization::kNone, G_c_power);
|
||||
G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
|
||||
G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
|
||||
G_c.Spectrum(Aec3Optimization::kNone, G_c_power);
|
||||
|
||||
EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
|
||||
EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
|
||||
std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
|
||||
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
|
||||
@ -248,58 +269,69 @@ TEST(MainFilterUpdateGain, SaturationBehavior) {
|
||||
blocks_with_saturation.push_back(k);
|
||||
}
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
FftData G_a_ref;
|
||||
G_a_ref.re.fill(0.f);
|
||||
G_a_ref.im.fill(0.f);
|
||||
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;
|
||||
FftData G_b;
|
||||
FftData G_a_ref;
|
||||
G_a_ref.re.fill(0.f);
|
||||
G_a_ref.im.fill(0.f);
|
||||
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||
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);
|
||||
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);
|
||||
G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
|
||||
G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
|
||||
|
||||
EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
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) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
blocks_with_echo_path_changes.push_back(99);
|
||||
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);
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
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);
|
||||
G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
|
||||
G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
|
||||
|
||||
EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
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
|
||||
|
@ -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(
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,49 +144,58 @@ TEST(ShadowFilterUpdateGain, NullDataOutputGain) {
|
||||
TEST(ShadowFilterUpdateGain, GainCausesFilterToConverge) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
for (size_t filter_length_blocks : {12, 20, 30}) {
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G;
|
||||
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));
|
||||
// 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) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
std::vector<int> blocks_with_saturation;
|
||||
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;
|
||||
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
FftData G_c;
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||
std::array<float, kFftLengthBy2Plus1> G_c_power;
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_b;
|
||||
FftData G_c;
|
||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||
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);
|
||||
G_c.Spectrum(Aec3Optimization::kNone, G_c_power);
|
||||
G_a.Spectrum(Aec3Optimization::kNone, G_a_power);
|
||||
G_b.Spectrum(Aec3Optimization::kNone, G_b_power);
|
||||
G_c.Spectrum(Aec3Optimization::kNone, G_c_power);
|
||||
|
||||
EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
EXPECT_GT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||
|
||||
EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
|
||||
std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
|
||||
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.
|
||||
@ -185,18 +205,22 @@ 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;
|
||||
FftData G_a;
|
||||
FftData G_a_ref;
|
||||
G_a_ref.re.fill(0.f);
|
||||
G_a_ref.im.fill(0.f);
|
||||
std::array<float, kBlockSize> e;
|
||||
std::array<float, kBlockSize> y;
|
||||
FftData G_a;
|
||||
FftData G_a_ref;
|
||||
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);
|
||||
EXPECT_EQ(G_a_ref.re, G_a.re);
|
||||
EXPECT_EQ(G_a_ref.im, G_a.im);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -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_);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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,32 +122,34 @@ 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),
|
||||
"");
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -151,24 +157,30 @@ 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 delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
for (size_t filter_length_blocks : {12, 20, 30}) {
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
|
||||
|
||||
float echo_to_nearend_power = RunSubtractorTest(
|
||||
100, delay_samples, false, blocks_with_echo_path_changes);
|
||||
EXPECT_GT(0.1f, echo_to_nearend_power);
|
||||
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 delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
for (size_t filter_length_blocks : {12, 20, 30}) {
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
|
||||
|
||||
float echo_to_nearend_power = RunSubtractorTest(
|
||||
100, delay_samples, true, blocks_with_echo_path_changes);
|
||||
EXPECT_NEAR(1.f, echo_to_nearend_power, 0.05);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,12 +189,15 @@ TEST(Subtractor, NonConvergenceOnUncorrelatedSignals) {
|
||||
TEST(Subtractor, EchoPathChangeReset) {
|
||||
std::vector<int> blocks_with_echo_path_changes;
|
||||
blocks_with_echo_path_changes.push_back(99);
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
for (size_t filter_length_blocks : {12, 20, 30}) {
|
||||
for (size_t delay_samples : {0, 64, 150, 200, 301}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples, filter_length_blocks));
|
||||
|
||||
float echo_to_nearend_power = RunSubtractorTest(
|
||||
100, delay_samples, false, blocks_with_echo_path_changes);
|
||||
EXPECT_NEAR(1.f, echo_to_nearend_power, 0.0000001f);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user