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:
parent
c59a576c86
commit
09a718accd
@ -415,10 +415,10 @@ AdaptiveFirFilter::AdaptiveFirFilter(size_t size_partitions,
|
|||||||
fft_(),
|
fft_(),
|
||||||
optimization_(optimization),
|
optimization_(optimization),
|
||||||
H_(size_partitions),
|
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_);
|
RTC_DCHECK(data_dumper_);
|
||||||
|
|
||||||
h_.fill(0.f);
|
|
||||||
for (auto& H_j : H_) {
|
for (auto& H_j : H_) {
|
||||||
H_j.Clear();
|
H_j.Clear();
|
||||||
}
|
}
|
||||||
@ -431,7 +431,7 @@ AdaptiveFirFilter::AdaptiveFirFilter(size_t size_partitions,
|
|||||||
AdaptiveFirFilter::~AdaptiveFirFilter() = default;
|
AdaptiveFirFilter::~AdaptiveFirFilter() = default;
|
||||||
|
|
||||||
void AdaptiveFirFilter::HandleEchoPathChange() {
|
void AdaptiveFirFilter::HandleEchoPathChange() {
|
||||||
h_.fill(0.f);
|
std::fill(h_.begin(), h_.end(), 0.f);
|
||||||
for (auto& H_j : H_) {
|
for (auto& H_j : H_) {
|
||||||
H_j.Clear();
|
H_j.Clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,10 +120,7 @@ class AdaptiveFirFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns the estimate of the impulse response.
|
// Returns the estimate of the impulse response.
|
||||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
const std::vector<float>& FilterImpulseResponse() const { return h_; }
|
||||||
FilterImpulseResponse() const {
|
|
||||||
return h_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpFilter(const char* name) {
|
void DumpFilter(const char* name) {
|
||||||
for (auto& H : H_) {
|
for (auto& H : H_) {
|
||||||
@ -141,7 +138,7 @@ class AdaptiveFirFilter {
|
|||||||
const Aec3Optimization optimization_;
|
const Aec3Optimization optimization_;
|
||||||
std::vector<FftData> H_;
|
std::vector<FftData> H_;
|
||||||
std::vector<std::array<float, kFftLengthBy2Plus1>> H2_;
|
std::vector<std::array<float, kFftLengthBy2Plus1>> H2_;
|
||||||
std::array<float, kAdaptiveFilterTimeDomainLength> h_;
|
std::vector<float> h_;
|
||||||
std::array<float, kFftLengthBy2Plus1> erl_;
|
std::array<float, kFftLengthBy2Plus1> erl_;
|
||||||
size_t partition_to_constrain_ = 0;
|
size_t partition_to_constrain_ = 0;
|
||||||
|
|
||||||
|
|||||||
@ -308,10 +308,10 @@ TEST(AdaptiveFirFilter, FilterSize) {
|
|||||||
TEST(AdaptiveFirFilter, FilterAndAdapt) {
|
TEST(AdaptiveFirFilter, FilterAndAdapt) {
|
||||||
constexpr size_t kNumBlocksToProcess = 500;
|
constexpr size_t kNumBlocksToProcess = 500;
|
||||||
ApmDataDumper data_dumper(42);
|
ApmDataDumper data_dumper(42);
|
||||||
AdaptiveFirFilter filter(kAdaptiveFilterLength, DetectOptimization(),
|
EchoCanceller3Config config;
|
||||||
|
AdaptiveFirFilter filter(config.filter.length_blocks, DetectOptimization(),
|
||||||
&data_dumper);
|
&data_dumper);
|
||||||
Aec3Fft fft;
|
Aec3Fft fft;
|
||||||
EchoCanceller3Config config;
|
|
||||||
config.delay.min_echo_path_delay_blocks = 0;
|
config.delay.min_echo_path_delay_blocks = 0;
|
||||||
config.delay.default_delay = 1;
|
config.delay.default_delay = 1;
|
||||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
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 kFftLengthBy2Minus1 = kFftLengthBy2 - 1;
|
||||||
constexpr size_t kFftLength = 2 * kFftLengthBy2;
|
constexpr size_t kFftLength = 2 * kFftLengthBy2;
|
||||||
|
|
||||||
constexpr int kAdaptiveFilterLength = 12;
|
constexpr int kMaxAdaptiveFilterLength = 50;
|
||||||
constexpr int kUnknownDelayRenderWindowSize = 12;
|
constexpr int kUnknownDelayRenderWindowSize = 30;
|
||||||
constexpr int kAdaptiveFilterTimeDomainLength =
|
|
||||||
kAdaptiveFilterLength * kFftLengthBy2;
|
|
||||||
constexpr int kRenderTransferQueueSizeFrames = 100;
|
constexpr int kRenderTransferQueueSizeFrames = 100;
|
||||||
|
|
||||||
constexpr size_t kMaxNumBands = 3;
|
constexpr size_t kMaxNumBands = 3;
|
||||||
@ -72,6 +70,10 @@ constexpr bool ValidFullBandRate(int sample_rate_hz) {
|
|||||||
sample_rate_hz == 32000 || sample_rate_hz == 48000;
|
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,
|
constexpr size_t GetDownSampledBufferSize(size_t down_sampling_factor,
|
||||||
size_t num_matched_filters) {
|
size_t num_matched_filters) {
|
||||||
return kBlockSize / down_sampling_factor *
|
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,
|
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) /
|
return GetDownSampledBufferSize(down_sampling_factor, num_matched_filters) /
|
||||||
(kBlockSize / down_sampling_factor) +
|
(kBlockSize / down_sampling_factor) +
|
||||||
kAdaptiveFilterLength + 1;
|
filter_length_blocks + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detects what kind of optimizations to use for the code.
|
// Detects what kind of optimizations to use for the code.
|
||||||
|
|||||||
@ -29,8 +29,8 @@ int EstimateFilterDelay(
|
|||||||
adaptive_filter_frequency_response) {
|
adaptive_filter_frequency_response) {
|
||||||
const auto& H2 = adaptive_filter_frequency_response;
|
const auto& H2 = adaptive_filter_frequency_response;
|
||||||
constexpr size_t kUpperBin = kFftLengthBy2 - 5;
|
constexpr size_t kUpperBin = kFftLengthBy2 - 5;
|
||||||
RTC_DCHECK_GE(kAdaptiveFilterLength, H2.size());
|
RTC_DCHECK_GE(kMaxAdaptiveFilterLength, H2.size());
|
||||||
std::array<int, kAdaptiveFilterLength> delays;
|
std::array<int, kMaxAdaptiveFilterLength> delays;
|
||||||
delays.fill(0);
|
delays.fill(0);
|
||||||
for (size_t k = 1; k < kUpperBin; ++k) {
|
for (size_t k = 1; k < kUpperBin; ++k) {
|
||||||
// Find the maximum of H2[j].
|
// Find the maximum of H2[j].
|
||||||
@ -56,9 +56,8 @@ AecState::AecState(const EchoCanceller3Config& config)
|
|||||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||||
erle_estimator_(config.erle.min, config.erle.max_l, config.erle.max_h),
|
erle_estimator_(config.erle.min, config.erle.max_l, config.erle.max_h),
|
||||||
config_(config),
|
config_(config),
|
||||||
reverb_decay_(config_.ep_strength.default_len) {
|
max_render_(config_.filter.length_blocks, 0.f),
|
||||||
max_render_.fill(0.f);
|
reverb_decay_(config_.ep_strength.default_len) {}
|
||||||
}
|
|
||||||
|
|
||||||
AecState::~AecState() = default;
|
AecState::~AecState() = default;
|
||||||
|
|
||||||
@ -71,7 +70,7 @@ void AecState::HandleEchoPathChange(
|
|||||||
capture_signal_saturation_ = false;
|
capture_signal_saturation_ = false;
|
||||||
echo_saturation_ = false;
|
echo_saturation_ = false;
|
||||||
previous_max_sample_ = 0.f;
|
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;
|
force_zero_gain_counter_ = 0;
|
||||||
blocks_with_filter_adaptation_ = 0;
|
blocks_with_filter_adaptation_ = 0;
|
||||||
blocks_with_strong_render_ = 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,
|
adaptive_filter_frequency_response,
|
||||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
const std::vector<float>& adaptive_filter_impulse_response,
|
||||||
adaptive_filter_impulse_response,
|
|
||||||
bool converged_filter,
|
bool converged_filter,
|
||||||
const rtc::Optional<size_t>& external_delay_samples,
|
const rtc::Optional<size_t>& external_delay_samples,
|
||||||
const RenderBuffer& render_buffer,
|
const RenderBuffer& render_buffer,
|
||||||
@ -208,24 +207,29 @@ void AecState::Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
|||||||
UpdateReverb(adaptive_filter_impulse_response);
|
UpdateReverb(adaptive_filter_impulse_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AecState::UpdateReverb(
|
void AecState::UpdateReverb(const std::vector<float>& impulse_response) {
|
||||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
|
||||||
impulse_response) {
|
|
||||||
if ((!(filter_delay_ && usable_linear_estimate_)) ||
|
if ((!(filter_delay_ && usable_linear_estimate_)) ||
|
||||||
(*filter_delay_ > kAdaptiveFilterLength - 4)) {
|
(*filter_delay_ > config_.filter.length_blocks - 4)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form the data to match against by squaring the impulse response
|
// Form the data to match against by squaring the impulse response
|
||||||
// coefficients.
|
// 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(),
|
std::transform(impulse_response.begin(), impulse_response.end(),
|
||||||
matching_data.begin(), [](float a) { return a * a; });
|
matching_data.begin(), [](float a) { return a * a; });
|
||||||
|
|
||||||
// Avoid matching against noise in the model by subtracting an estimate of the
|
// Avoid matching against noise in the model by subtracting an estimate of the
|
||||||
// model noise power.
|
// model noise power.
|
||||||
constexpr size_t kTailLength = 64;
|
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,
|
const float tail_power = *std::max_element(matching_data.begin() + tail_index,
|
||||||
matching_data.end());
|
matching_data.end());
|
||||||
std::for_each(matching_data.begin(), matching_data.begin() + tail_index,
|
std::for_each(matching_data.begin(), matching_data.begin() + tail_index,
|
||||||
|
|||||||
@ -114,8 +114,7 @@ class AecState {
|
|||||||
// Updates the aec state.
|
// Updates the aec state.
|
||||||
void Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
void Update(const std::vector<std::array<float, kFftLengthBy2Plus1>>&
|
||||||
adaptive_filter_frequency_response,
|
adaptive_filter_frequency_response,
|
||||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
const std::vector<float>& adaptive_filter_impulse_response,
|
||||||
adaptive_filter_impulse_response,
|
|
||||||
bool converged_filter,
|
bool converged_filter,
|
||||||
const rtc::Optional<size_t>& external_delay_samples,
|
const rtc::Optional<size_t>& external_delay_samples,
|
||||||
const RenderBuffer& render_buffer,
|
const RenderBuffer& render_buffer,
|
||||||
@ -141,8 +140,7 @@ class AecState {
|
|||||||
bool inaudible_echo_ = false;
|
bool inaudible_echo_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void UpdateReverb(const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
void UpdateReverb(const std::vector<float>& impulse_response);
|
||||||
impulse_response);
|
|
||||||
|
|
||||||
static int instance_count_;
|
static int instance_count_;
|
||||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||||
@ -157,7 +155,6 @@ class AecState {
|
|||||||
bool echo_saturation_ = false;
|
bool echo_saturation_ = false;
|
||||||
bool transparent_mode_ = false;
|
bool transparent_mode_ = false;
|
||||||
float previous_max_sample_ = 0.f;
|
float previous_max_sample_ = 0.f;
|
||||||
std::array<float, kAdaptiveFilterLength> max_render_;
|
|
||||||
bool force_zero_gain_ = false;
|
bool force_zero_gain_ = false;
|
||||||
bool render_received_ = false;
|
bool render_received_ = false;
|
||||||
size_t force_zero_gain_counter_ = 0;
|
size_t force_zero_gain_counter_ = 0;
|
||||||
@ -169,6 +166,7 @@ class AecState {
|
|||||||
float reverb_decay_candidate_residual_ = -1.f;
|
float reverb_decay_candidate_residual_ = -1.f;
|
||||||
EchoAudibility echo_audibility_;
|
EchoAudibility echo_audibility_;
|
||||||
const EchoCanceller3Config config_;
|
const EchoCanceller3Config config_;
|
||||||
|
std::vector<float> max_render_;
|
||||||
float reverb_decay_;
|
float reverb_decay_;
|
||||||
bool saturating_echo_path_ = false;
|
bool saturating_echo_path_ = false;
|
||||||
bool initial_state_ = true;
|
bool initial_state_ = true;
|
||||||
|
|||||||
@ -43,8 +43,8 @@ TEST(AecState, NormalUsage) {
|
|||||||
converged_filter_frequency_response[2].fill(100.f);
|
converged_filter_frequency_response[2].fill(100.f);
|
||||||
converged_filter_frequency_response[2][0] = 1.f;
|
converged_filter_frequency_response[2][0] = 1.f;
|
||||||
|
|
||||||
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
|
std::vector<float> impulse_response(
|
||||||
impulse_response.fill(0.f);
|
GetTimeDomainLength(config.filter.length_blocks), 0.f);
|
||||||
|
|
||||||
// Verify that linear AEC usability is false when the filter is diverged and
|
// Verify that linear AEC usability is false when the filter is diverged and
|
||||||
// there is no external delay reported.
|
// there is no external delay reported.
|
||||||
@ -193,8 +193,8 @@ TEST(AecState, ConvergedFilterDelay) {
|
|||||||
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
|
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
|
||||||
kFilterLength);
|
kFilterLength);
|
||||||
|
|
||||||
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
|
std::vector<float> impulse_response(
|
||||||
impulse_response.fill(0.f);
|
GetTimeDomainLength(config.filter.length_blocks), 0.f);
|
||||||
|
|
||||||
// Verify that the filter delay for a converged filter is properly identified.
|
// Verify that the filter delay for a converged filter is properly identified.
|
||||||
for (int k = 0; k < kFilterLength; ++k) {
|
for (int k = 0; k < kFilterLength; ++k) {
|
||||||
@ -232,13 +232,13 @@ TEST(AecState, ExternalDelay) {
|
|||||||
x.fill(0.f);
|
x.fill(0.f);
|
||||||
|
|
||||||
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
|
std::vector<std::array<float, kFftLengthBy2Plus1>> frequency_response(
|
||||||
kAdaptiveFilterLength);
|
config.filter.length_blocks);
|
||||||
for (auto& v : frequency_response) {
|
for (auto& v : frequency_response) {
|
||||||
v.fill(0.01f);
|
v.fill(0.01f);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<float, kAdaptiveFilterTimeDomainLength> impulse_response;
|
std::vector<float> impulse_response(
|
||||||
impulse_response.fill(0.f);
|
GetTimeDomainLength(config.filter.length_blocks), 0.f);
|
||||||
|
|
||||||
for (size_t k = 0; k < frequency_response.size() - 1; ++k) {
|
for (size_t k = 0; k < frequency_response.size() - 1; ++k) {
|
||||||
state.HandleEchoPathChange(EchoPathVariability(
|
state.HandleEchoPathChange(EchoPathVariability(
|
||||||
|
|||||||
@ -101,7 +101,7 @@ EchoRemoverImpl::EchoRemoverImpl(const EchoCanceller3Config& config,
|
|||||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||||
optimization_(DetectOptimization()),
|
optimization_(DetectOptimization()),
|
||||||
sample_rate_hz_(sample_rate_hz),
|
sample_rate_hz_(sample_rate_hz),
|
||||||
subtractor_(data_dumper_.get(), optimization_),
|
subtractor_(config, data_dumper_.get(), optimization_),
|
||||||
suppression_gain_(config_, optimization_),
|
suppression_gain_(config_, optimization_),
|
||||||
cng_(optimization_),
|
cng_(optimization_),
|
||||||
suppression_filter_(sample_rate_hz_),
|
suppression_filter_(sample_rate_hz_),
|
||||||
|
|||||||
@ -33,6 +33,7 @@ namespace {
|
|||||||
// gain functionality.
|
// gain functionality.
|
||||||
void RunFilterUpdateTest(int num_blocks_to_process,
|
void RunFilterUpdateTest(int num_blocks_to_process,
|
||||||
size_t delay_samples,
|
size_t delay_samples,
|
||||||
|
int filter_length_blocks,
|
||||||
const std::vector<int>& blocks_with_echo_path_changes,
|
const std::vector<int>& blocks_with_echo_path_changes,
|
||||||
const std::vector<int>& blocks_with_saturation,
|
const std::vector<int>& blocks_with_saturation,
|
||||||
bool use_silent_render_in_second_half,
|
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,
|
std::array<float, kBlockSize>* y_last_block,
|
||||||
FftData* G_last_block) {
|
FftData* G_last_block) {
|
||||||
ApmDataDumper data_dumper(42);
|
ApmDataDumper data_dumper(42);
|
||||||
AdaptiveFirFilter main_filter(12, DetectOptimization(), &data_dumper);
|
EchoCanceller3Config config;
|
||||||
AdaptiveFirFilter shadow_filter(12, DetectOptimization(), &data_dumper);
|
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;
|
Aec3Fft fft;
|
||||||
std::array<float, kBlockSize> x_old;
|
std::array<float, kBlockSize> x_old;
|
||||||
x_old.fill(0.f);
|
x_old.fill(0.f);
|
||||||
@ -50,7 +55,6 @@ void RunFilterUpdateTest(int num_blocks_to_process,
|
|||||||
Random random_generator(42U);
|
Random random_generator(42U);
|
||||||
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
|
std::vector<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
|
||||||
std::vector<float> y(kBlockSize, 0.f);
|
std::vector<float> y(kBlockSize, 0.f);
|
||||||
EchoCanceller3Config config;
|
|
||||||
config.delay.min_echo_path_delay_blocks = 0;
|
config.delay.min_echo_path_delay_blocks = 0;
|
||||||
config.delay.default_delay = 1;
|
config.delay.default_delay = 1;
|
||||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
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::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;
|
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();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,10 +183,11 @@ std::string ProduceDebugText(size_t delay) {
|
|||||||
// Verifies that the check for non-null output gain parameter works.
|
// Verifies that the check for non-null output gain parameter works.
|
||||||
TEST(MainFilterUpdateGain, NullDataOutputGain) {
|
TEST(MainFilterUpdateGain, NullDataOutputGain) {
|
||||||
ApmDataDumper data_dumper(42);
|
ApmDataDumper data_dumper(42);
|
||||||
AdaptiveFirFilter filter(kAdaptiveFilterLength, DetectOptimization(),
|
EchoCanceller3Config config;
|
||||||
|
AdaptiveFirFilter filter(config.filter.length_blocks, DetectOptimization(),
|
||||||
&data_dumper);
|
&data_dumper);
|
||||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||||
RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
|
RenderDelayBuffer::Create(config, 3));
|
||||||
RenderSignalAnalyzer analyzer;
|
RenderSignalAnalyzer analyzer;
|
||||||
SubtractorOutput output;
|
SubtractorOutput output;
|
||||||
MainFilterUpdateGain gain;
|
MainFilterUpdateGain gain;
|
||||||
@ -190,25 +202,30 @@ TEST(MainFilterUpdateGain, NullDataOutputGain) {
|
|||||||
TEST(MainFilterUpdateGain, GainCausesFilterToConverge) {
|
TEST(MainFilterUpdateGain, GainCausesFilterToConverge) {
|
||||||
std::vector<int> blocks_with_echo_path_changes;
|
std::vector<int> blocks_with_echo_path_changes;
|
||||||
std::vector<int> blocks_with_saturation;
|
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}) {
|
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> e;
|
||||||
std::array<float, kBlockSize> y;
|
std::array<float, kBlockSize> y;
|
||||||
FftData G;
|
FftData G;
|
||||||
|
|
||||||
RunFilterUpdateTest(500, delay_samples, blocks_with_echo_path_changes,
|
RunFilterUpdateTest(500, delay_samples, filter_length_blocks,
|
||||||
blocks_with_saturation, false, &e, &y, &G);
|
blocks_with_echo_path_changes, blocks_with_saturation,
|
||||||
|
false, &e, &y, &G);
|
||||||
|
|
||||||
// Verify that the main filter is able to perform well.
|
// Verify that the main filter is able to perform well.
|
||||||
EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
|
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));
|
std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies that the magnitude of the gain on average decreases for a
|
// Verifies that the magnitude of the gain on average decreases for a
|
||||||
// persistently exciting signal.
|
// persistently exciting signal.
|
||||||
TEST(MainFilterUpdateGain, DecreasingGain) {
|
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_echo_path_changes;
|
||||||
std::vector<int> blocks_with_saturation;
|
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_b_power;
|
||||||
std::array<float, kFftLengthBy2Plus1> G_c_power;
|
std::array<float, kFftLengthBy2Plus1> G_c_power;
|
||||||
|
|
||||||
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
|
RunFilterUpdateTest(100, 65, filter_length_blocks,
|
||||||
blocks_with_saturation, false, &e, &y, &G_a);
|
blocks_with_echo_path_changes, blocks_with_saturation,
|
||||||
RunFilterUpdateTest(200, 65, blocks_with_echo_path_changes,
|
false, &e, &y, &G_a);
|
||||||
blocks_with_saturation, false, &e, &y, &G_b);
|
RunFilterUpdateTest(300, 65, filter_length_blocks,
|
||||||
RunFilterUpdateTest(300, 65, blocks_with_echo_path_changes,
|
blocks_with_echo_path_changes, blocks_with_saturation,
|
||||||
blocks_with_saturation, false, &e, &y, &G_c);
|
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_a.Spectrum(Aec3Optimization::kNone, G_a_power);
|
||||||
G_b.Spectrum(Aec3Optimization::kNone, G_b_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.),
|
EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
|
||||||
std::accumulate(G_c_power.begin(), G_c_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
|
// 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.
|
// 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);
|
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> e;
|
||||||
std::array<float, kBlockSize> y;
|
std::array<float, kBlockSize> y;
|
||||||
FftData G_a;
|
FftData G_a;
|
||||||
@ -259,16 +282,19 @@ TEST(MainFilterUpdateGain, SaturationBehavior) {
|
|||||||
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
std::array<float, kFftLengthBy2Plus1> G_a_power;
|
||||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||||
|
|
||||||
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
|
RunFilterUpdateTest(100, 65, filter_length_blocks,
|
||||||
blocks_with_saturation, false, &e, &y, &G_a);
|
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.re, G_a.re);
|
||||||
EXPECT_EQ(G_a_ref.im, G_a.im);
|
EXPECT_EQ(G_a_ref.im, G_a.im);
|
||||||
|
|
||||||
RunFilterUpdateTest(99, 65, blocks_with_echo_path_changes,
|
RunFilterUpdateTest(99, 65, filter_length_blocks,
|
||||||
blocks_with_saturation, false, &e, &y, &G_a);
|
blocks_with_echo_path_changes, blocks_with_saturation,
|
||||||
RunFilterUpdateTest(201, 65, blocks_with_echo_path_changes,
|
false, &e, &y, &G_a);
|
||||||
blocks_with_saturation, false, &e, &y, &G_b);
|
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_a.Spectrum(Aec3Optimization::kNone, G_a_power);
|
||||||
G_b.Spectrum(Aec3Optimization::kNone, G_b_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.),
|
EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies that the gain increases after an echo path change.
|
// Verifies that the gain increases after an echo path change.
|
||||||
TEST(MainFilterUpdateGain, EchoPathChangeBehavior) {
|
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_echo_path_changes;
|
||||||
std::vector<int> blocks_with_saturation;
|
std::vector<int> blocks_with_saturation;
|
||||||
blocks_with_echo_path_changes.push_back(99);
|
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_a_power;
|
||||||
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
std::array<float, kFftLengthBy2Plus1> G_b_power;
|
||||||
|
|
||||||
RunFilterUpdateTest(100, 65, blocks_with_echo_path_changes,
|
RunFilterUpdateTest(100, 65, filter_length_blocks,
|
||||||
blocks_with_saturation, false, &e, &y, &G_a);
|
blocks_with_echo_path_changes, blocks_with_saturation,
|
||||||
RunFilterUpdateTest(101, 65, blocks_with_echo_path_changes,
|
false, &e, &y, &G_a);
|
||||||
blocks_with_saturation, false, &e, &y, &G_b);
|
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_a.Spectrum(Aec3Optimization::kNone, G_a_power);
|
||||||
G_b.Spectrum(Aec3Optimization::kNone, G_b_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.),
|
EXPECT_LT(std::accumulate(G_a_power.begin(), G_a_power.end(), 0.),
|
||||||
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
std::accumulate(G_b_power.begin(), G_b_power.end(), 0.));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -25,15 +25,12 @@ namespace test {
|
|||||||
class MockRenderDelayBuffer : public RenderDelayBuffer {
|
class MockRenderDelayBuffer : public RenderDelayBuffer {
|
||||||
public:
|
public:
|
||||||
explicit MockRenderDelayBuffer(int sample_rate_hz)
|
explicit MockRenderDelayBuffer(int sample_rate_hz)
|
||||||
: block_buffer_(GetRenderDelayBufferSize(4, 4),
|
: block_buffer_(GetRenderDelayBufferSize(4, 4, 12),
|
||||||
NumBandsForRate(sample_rate_hz),
|
NumBandsForRate(sample_rate_hz),
|
||||||
kBlockSize),
|
kBlockSize),
|
||||||
spectrum_buffer_(block_buffer_.buffer.size(), kFftLengthBy2Plus1),
|
spectrum_buffer_(block_buffer_.buffer.size(), kFftLengthBy2Plus1),
|
||||||
fft_buffer_(block_buffer_.buffer.size()),
|
fft_buffer_(block_buffer_.buffer.size()),
|
||||||
render_buffer_(kAdaptiveFilterLength,
|
render_buffer_(12, &block_buffer_, &spectrum_buffer_, &fft_buffer_),
|
||||||
&block_buffer_,
|
|
||||||
&spectrum_buffer_,
|
|
||||||
&fft_buffer_),
|
|
||||||
downsampled_render_buffer_(GetDownSampledBufferSize(4, 4)) {
|
downsampled_render_buffer_(GetDownSampledBufferSize(4, 4)) {
|
||||||
ON_CALL(*this, GetRenderBuffer())
|
ON_CALL(*this, GetRenderBuffer())
|
||||||
.WillByDefault(
|
.WillByDefault(
|
||||||
|
|||||||
@ -28,8 +28,6 @@
|
|||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr int kBufferHeadroom = kAdaptiveFilterLength;
|
|
||||||
|
|
||||||
class RenderDelayBufferImpl final : public RenderDelayBuffer {
|
class RenderDelayBufferImpl final : public RenderDelayBuffer {
|
||||||
public:
|
public:
|
||||||
RenderDelayBufferImpl(const EchoCanceller3Config& config, size_t num_bands);
|
RenderDelayBufferImpl(const EchoCanceller3Config& config, size_t num_bands);
|
||||||
@ -41,7 +39,7 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer {
|
|||||||
bool SetDelay(size_t delay) override;
|
bool SetDelay(size_t delay) override;
|
||||||
rtc::Optional<size_t> Delay() const override { return delay_; }
|
rtc::Optional<size_t> Delay() const override { return delay_; }
|
||||||
size_t MaxDelay() const override {
|
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_; }
|
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 std::vector<std::vector<float>> zero_block_;
|
||||||
const Aec3Fft fft_;
|
const Aec3Fft fft_;
|
||||||
std::vector<float> render_ds_;
|
std::vector<float> render_ds_;
|
||||||
|
const int buffer_headroom_;
|
||||||
|
|
||||||
int LowRateBufferOffset() const { return DelayEstimatorOffset(config_) >> 1; }
|
int LowRateBufferOffset() const { return DelayEstimatorOffset(config_) >> 1; }
|
||||||
int MaxExternalDelayToInternalDelay(size_t delay) const;
|
int MaxExternalDelayToInternalDelay(size_t delay) const;
|
||||||
@ -153,18 +152,24 @@ RenderDelayBufferImpl::RenderDelayBufferImpl(const EchoCanceller3Config& config,
|
|||||||
? kBlockSize / config.delay.down_sampling_factor
|
? kBlockSize / config.delay.down_sampling_factor
|
||||||
: kBlockSize)),
|
: kBlockSize)),
|
||||||
blocks_(GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
blocks_(GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||||
config.delay.num_filters),
|
config.delay.num_filters,
|
||||||
|
config.filter.length_blocks),
|
||||||
num_bands,
|
num_bands,
|
||||||
kBlockSize),
|
kBlockSize),
|
||||||
spectra_(blocks_.buffer.size(), kFftLengthBy2Plus1),
|
spectra_(blocks_.buffer.size(), kFftLengthBy2Plus1),
|
||||||
ffts_(blocks_.buffer.size()),
|
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,
|
low_rate_(GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||||
config.delay.num_filters)),
|
config.delay.num_filters)),
|
||||||
render_decimator_(config.delay.down_sampling_factor),
|
render_decimator_(config.delay.down_sampling_factor),
|
||||||
zero_block_(num_bands, std::vector<float>(kBlockSize, 0.f)),
|
zero_block_(num_bands, std::vector<float>(kBlockSize, 0.f)),
|
||||||
fft_(),
|
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(blocks_.buffer.size(), ffts_.buffer.size());
|
||||||
RTC_DCHECK_EQ(spectra_.buffer.size(), ffts_.buffer.size());
|
RTC_DCHECK_EQ(spectra_.buffer.size(), ffts_.buffer.size());
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,7 @@ void RenderNoisePower(
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ResidualEchoEstimator::ResidualEchoEstimator(const EchoCanceller3Config& config)
|
ResidualEchoEstimator::ResidualEchoEstimator(const EchoCanceller3Config& config)
|
||||||
: config_(config) {
|
: config_(config), S2_old_(config_.filter.length_blocks) {
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,8 +150,8 @@ void ResidualEchoEstimator::Estimate(
|
|||||||
if (aec_state.ExternalDelay() && aec_state.FilterDelay() &&
|
if (aec_state.ExternalDelay() && aec_state.FilterDelay() &&
|
||||||
aec_state.SaturatedEcho()) {
|
aec_state.SaturatedEcho()) {
|
||||||
AddEchoReverb(*R2, aec_state.SaturatedEcho(),
|
AddEchoReverb(*R2, aec_state.SaturatedEcho(),
|
||||||
std::min(static_cast<size_t>(kAdaptiveFilterLength),
|
std::min(static_cast<size_t>(config_.filter.length_blocks),
|
||||||
delay.value_or(kAdaptiveFilterLength)),
|
delay.value_or(config_.filter.length_blocks)),
|
||||||
aec_state.ReverbDecay(), R2);
|
aec_state.ReverbDecay(), R2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,16 +64,14 @@ class ResidualEchoEstimator {
|
|||||||
size_t delay,
|
size_t delay,
|
||||||
float reverb_decay_factor,
|
float reverb_decay_factor,
|
||||||
std::array<float, kFftLengthBy2Plus1>* R2);
|
std::array<float, kFftLengthBy2Plus1>* R2);
|
||||||
|
const EchoCanceller3Config config_;
|
||||||
std::array<float, kFftLengthBy2Plus1> R2_old_;
|
std::array<float, kFftLengthBy2Plus1> R2_old_;
|
||||||
std::array<int, kFftLengthBy2Plus1> R2_hold_counter_;
|
std::array<int, kFftLengthBy2Plus1> R2_hold_counter_;
|
||||||
std::array<float, kFftLengthBy2Plus1> R2_reverb_;
|
std::array<float, kFftLengthBy2Plus1> R2_reverb_;
|
||||||
int S2_old_index_ = 0;
|
int S2_old_index_ = 0;
|
||||||
std::array<std::array<float, kFftLengthBy2Plus1>, kAdaptiveFilterLength>
|
std::vector<std::array<float, kFftLengthBy2Plus1>> S2_old_;
|
||||||
S2_old_;
|
|
||||||
std::array<float, kFftLengthBy2Plus1> X2_noise_floor_;
|
std::array<float, kFftLengthBy2Plus1> X2_noise_floor_;
|
||||||
std::array<int, kFftLengthBy2Plus1> X2_noise_floor_counter_;
|
std::array<int, kFftLengthBy2Plus1> X2_noise_floor_counter_;
|
||||||
const EchoCanceller3Config config_;
|
|
||||||
|
|
||||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ResidualEchoEstimator);
|
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(ResidualEchoEstimator);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -70,8 +70,7 @@ TEST(ResidualEchoEstimator, DISABLED_BasicTest) {
|
|||||||
H2[2].fill(10.f);
|
H2[2].fill(10.f);
|
||||||
H2[2][0] = 0.1f;
|
H2[2][0] = 0.1f;
|
||||||
|
|
||||||
std::array<float, kAdaptiveFilterTimeDomainLength> h;
|
std::vector<float> h(GetTimeDomainLength(config.filter.length_blocks), 0.f);
|
||||||
h.fill(0.f);
|
|
||||||
|
|
||||||
s.fill(100.f);
|
s.fill(100.f);
|
||||||
|
|
||||||
|
|||||||
@ -31,16 +31,20 @@ namespace {
|
|||||||
// gain functionality.
|
// gain functionality.
|
||||||
void RunFilterUpdateTest(int num_blocks_to_process,
|
void RunFilterUpdateTest(int num_blocks_to_process,
|
||||||
size_t delay_samples,
|
size_t delay_samples,
|
||||||
|
int filter_length_blocks,
|
||||||
const std::vector<int>& blocks_with_saturation,
|
const std::vector<int>& blocks_with_saturation,
|
||||||
std::array<float, kBlockSize>* e_last_block,
|
std::array<float, kBlockSize>* e_last_block,
|
||||||
std::array<float, kBlockSize>* y_last_block,
|
std::array<float, kBlockSize>* y_last_block,
|
||||||
FftData* G_last_block) {
|
FftData* G_last_block) {
|
||||||
ApmDataDumper data_dumper(42);
|
ApmDataDumper data_dumper(42);
|
||||||
AdaptiveFirFilter main_filter(12, DetectOptimization(), &data_dumper);
|
EchoCanceller3Config config;
|
||||||
AdaptiveFirFilter shadow_filter(12, DetectOptimization(), &data_dumper);
|
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;
|
Aec3Fft fft;
|
||||||
|
|
||||||
EchoCanceller3Config config;
|
|
||||||
config.delay.min_echo_path_delay_blocks = 0;
|
config.delay.min_echo_path_delay_blocks = 0;
|
||||||
config.delay.default_delay = 1;
|
config.delay.default_delay = 1;
|
||||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
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::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;
|
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();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,24 +144,29 @@ TEST(ShadowFilterUpdateGain, NullDataOutputGain) {
|
|||||||
TEST(ShadowFilterUpdateGain, GainCausesFilterToConverge) {
|
TEST(ShadowFilterUpdateGain, GainCausesFilterToConverge) {
|
||||||
std::vector<int> blocks_with_echo_path_changes;
|
std::vector<int> blocks_with_echo_path_changes;
|
||||||
std::vector<int> blocks_with_saturation;
|
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}) {
|
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> e;
|
||||||
std::array<float, kBlockSize> y;
|
std::array<float, kBlockSize> y;
|
||||||
FftData G;
|
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.
|
// Verify that the main filter is able to perform well.
|
||||||
EXPECT_LT(1000 * std::inner_product(e.begin(), e.end(), e.begin(), 0.f),
|
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));
|
std::inner_product(y.begin(), y.end(), y.begin(), 0.f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies that the magnitude of the gain on average decreases for a
|
// Verifies that the magnitude of the gain on average decreases for a
|
||||||
// persistently exciting signal.
|
// persistently exciting signal.
|
||||||
TEST(ShadowFilterUpdateGain, DecreasingGain) {
|
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_echo_path_changes;
|
||||||
std::vector<int> blocks_with_saturation;
|
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_b_power;
|
||||||
std::array<float, kFftLengthBy2Plus1> G_c_power;
|
std::array<float, kFftLengthBy2Plus1> G_c_power;
|
||||||
|
|
||||||
RunFilterUpdateTest(100, 65, blocks_with_saturation, &e, &y, &G_a);
|
RunFilterUpdateTest(100, 65, filter_length_blocks, blocks_with_saturation,
|
||||||
RunFilterUpdateTest(200, 65, blocks_with_saturation, &e, &y, &G_b);
|
&e, &y, &G_a);
|
||||||
RunFilterUpdateTest(300, 65, blocks_with_saturation, &e, &y, &G_c);
|
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_a.Spectrum(Aec3Optimization::kNone, G_a_power);
|
||||||
G_b.Spectrum(Aec3Optimization::kNone, G_b_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.),
|
EXPECT_GT(std::accumulate(G_b_power.begin(), G_b_power.end(), 0.),
|
||||||
std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
|
std::accumulate(G_c_power.begin(), G_c_power.end(), 0.));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies that the gain is zero when there is saturation.
|
// Verifies that the gain is zero when there is saturation.
|
||||||
TEST(ShadowFilterUpdateGain, SaturationBehavior) {
|
TEST(ShadowFilterUpdateGain, SaturationBehavior) {
|
||||||
@ -185,6 +205,8 @@ TEST(ShadowFilterUpdateGain, SaturationBehavior) {
|
|||||||
for (int k = 99; k < 200; ++k) {
|
for (int k = 99; k < 200; ++k) {
|
||||||
blocks_with_saturation.push_back(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> e;
|
||||||
std::array<float, kBlockSize> y;
|
std::array<float, kBlockSize> y;
|
||||||
@ -193,10 +215,12 @@ TEST(ShadowFilterUpdateGain, SaturationBehavior) {
|
|||||||
G_a_ref.re.fill(0.f);
|
G_a_ref.re.fill(0.f);
|
||||||
G_a_ref.im.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.re, G_a.re);
|
||||||
EXPECT_EQ(G_a_ref.im, G_a.im);
|
EXPECT_EQ(G_a_ref.im, G_a.im);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -45,13 +45,14 @@ void PredictionError(const Aec3Fft& fft,
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Subtractor::Subtractor(ApmDataDumper* data_dumper,
|
Subtractor::Subtractor(const EchoCanceller3Config& config,
|
||||||
|
ApmDataDumper* data_dumper,
|
||||||
Aec3Optimization optimization)
|
Aec3Optimization optimization)
|
||||||
: fft_(),
|
: fft_(),
|
||||||
data_dumper_(data_dumper),
|
data_dumper_(data_dumper),
|
||||||
optimization_(optimization),
|
optimization_(optimization),
|
||||||
main_filter_(kAdaptiveFilterLength, optimization, data_dumper_),
|
main_filter_(config.filter.length_blocks, optimization, data_dumper_),
|
||||||
shadow_filter_(kAdaptiveFilterLength, optimization, data_dumper_) {
|
shadow_filter_(config.filter.length_blocks, optimization, data_dumper_) {
|
||||||
RTC_DCHECK(data_dumper_);
|
RTC_DCHECK(data_dumper_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,9 @@ namespace webrtc {
|
|||||||
// Proves linear echo cancellation functionality
|
// Proves linear echo cancellation functionality
|
||||||
class Subtractor {
|
class Subtractor {
|
||||||
public:
|
public:
|
||||||
Subtractor(ApmDataDumper* data_dumper, Aec3Optimization optimization);
|
Subtractor(const EchoCanceller3Config& config,
|
||||||
|
ApmDataDumper* data_dumper,
|
||||||
|
Aec3Optimization optimization);
|
||||||
~Subtractor();
|
~Subtractor();
|
||||||
|
|
||||||
// Performs the echo subtraction.
|
// Performs the echo subtraction.
|
||||||
@ -52,8 +54,7 @@ class Subtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns the estimate of the impulse response for the main adaptive filter.
|
// Returns the estimate of the impulse response for the main adaptive filter.
|
||||||
const std::array<float, kAdaptiveFilterTimeDomainLength>&
|
const std::vector<float>& FilterImpulseResponse() const {
|
||||||
FilterImpulseResponse() const {
|
|
||||||
return main_filter_.FilterImpulseResponse();
|
return main_filter_.FilterImpulseResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,15 +25,17 @@ namespace {
|
|||||||
|
|
||||||
float RunSubtractorTest(int num_blocks_to_process,
|
float RunSubtractorTest(int num_blocks_to_process,
|
||||||
int delay_samples,
|
int delay_samples,
|
||||||
|
int filter_length_blocks,
|
||||||
bool uncorrelated_inputs,
|
bool uncorrelated_inputs,
|
||||||
const std::vector<int>& blocks_with_echo_path_changes) {
|
const std::vector<int>& blocks_with_echo_path_changes) {
|
||||||
ApmDataDumper data_dumper(42);
|
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<std::vector<float>> x(3, std::vector<float>(kBlockSize, 0.f));
|
||||||
std::vector<float> y(kBlockSize, 0.f);
|
std::vector<float> y(kBlockSize, 0.f);
|
||||||
std::array<float, kBlockSize> x_old;
|
std::array<float, kBlockSize> x_old;
|
||||||
SubtractorOutput output;
|
SubtractorOutput output;
|
||||||
EchoCanceller3Config config;
|
|
||||||
config.delay.min_echo_path_delay_blocks = 0;
|
config.delay.min_echo_path_delay_blocks = 0;
|
||||||
config.delay.default_delay = 1;
|
config.delay.default_delay = 1;
|
||||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
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> Y2;
|
||||||
std::array<float, kFftLengthBy2Plus1> E2_main;
|
std::array<float, kFftLengthBy2Plus1> E2_main;
|
||||||
std::array<float, kFftLengthBy2Plus1> E2_shadow;
|
std::array<float, kFftLengthBy2Plus1> E2_shadow;
|
||||||
AecState aec_state(EchoCanceller3Config{});
|
AecState aec_state(config);
|
||||||
x_old.fill(0.f);
|
x_old.fill(0.f);
|
||||||
Y2.fill(0.f);
|
Y2.fill(0.f);
|
||||||
E2_main.fill(0.f);
|
E2_main.fill(0.f);
|
||||||
@ -98,9 +100,10 @@ float RunSubtractorTest(int num_blocks_to_process,
|
|||||||
return output_power / y_power;
|
return output_power / y_power;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ProduceDebugText(size_t delay) {
|
std::string ProduceDebugText(size_t delay, int filter_length_blocks) {
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
ss << "Delay: " << delay;
|
ss << "Delay: " << delay << ", ";
|
||||||
|
ss << "Length: " << filter_length_blocks;
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +113,8 @@ std::string ProduceDebugText(size_t delay) {
|
|||||||
|
|
||||||
// Verifies that the check for non data dumper works.
|
// Verifies that the check for non data dumper works.
|
||||||
TEST(Subtractor, NullDataDumper) {
|
TEST(Subtractor, NullDataDumper) {
|
||||||
EXPECT_DEATH(Subtractor(nullptr, DetectOptimization()), "");
|
EXPECT_DEATH(
|
||||||
|
Subtractor(EchoCanceller3Config(), nullptr, DetectOptimization()), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifies the check for null subtractor output.
|
// Verifies the check for null subtractor output.
|
||||||
@ -118,31 +122,33 @@ TEST(Subtractor, NullDataDumper) {
|
|||||||
// tests on test bots has been fixed.
|
// tests on test bots has been fixed.
|
||||||
TEST(Subtractor, DISABLED_NullOutput) {
|
TEST(Subtractor, DISABLED_NullOutput) {
|
||||||
ApmDataDumper data_dumper(42);
|
ApmDataDumper data_dumper(42);
|
||||||
Subtractor subtractor(&data_dumper, DetectOptimization());
|
EchoCanceller3Config config;
|
||||||
|
Subtractor subtractor(config, &data_dumper, DetectOptimization());
|
||||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||||
RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
|
RenderDelayBuffer::Create(config, 3));
|
||||||
RenderSignalAnalyzer render_signal_analyzer;
|
RenderSignalAnalyzer render_signal_analyzer;
|
||||||
std::vector<float> y(kBlockSize, 0.f);
|
std::vector<float> y(kBlockSize, 0.f);
|
||||||
|
|
||||||
EXPECT_DEATH(subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
|
EXPECT_DEATH(
|
||||||
render_signal_analyzer,
|
subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
|
||||||
AecState(EchoCanceller3Config{}), nullptr),
|
render_signal_analyzer, AecState(config), nullptr),
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifies the check for the capture signal size.
|
// Verifies the check for the capture signal size.
|
||||||
TEST(Subtractor, WrongCaptureSize) {
|
TEST(Subtractor, WrongCaptureSize) {
|
||||||
ApmDataDumper data_dumper(42);
|
ApmDataDumper data_dumper(42);
|
||||||
Subtractor subtractor(&data_dumper, DetectOptimization());
|
EchoCanceller3Config config;
|
||||||
|
Subtractor subtractor(config, &data_dumper, DetectOptimization());
|
||||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||||
RenderDelayBuffer::Create(EchoCanceller3Config(), 3));
|
RenderDelayBuffer::Create(config, 3));
|
||||||
RenderSignalAnalyzer render_signal_analyzer;
|
RenderSignalAnalyzer render_signal_analyzer;
|
||||||
std::vector<float> y(kBlockSize - 1, 0.f);
|
std::vector<float> y(kBlockSize - 1, 0.f);
|
||||||
SubtractorOutput output;
|
SubtractorOutput output;
|
||||||
|
|
||||||
EXPECT_DEATH(subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
|
EXPECT_DEATH(
|
||||||
render_signal_analyzer,
|
subtractor.Process(*render_delay_buffer->GetRenderBuffer(), y,
|
||||||
AecState(EchoCanceller3Config{}), &output),
|
render_signal_analyzer, AecState(config), &output),
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,39 +157,48 @@ TEST(Subtractor, WrongCaptureSize) {
|
|||||||
// Verifies that the subtractor is able to converge on correlated data.
|
// Verifies that the subtractor is able to converge on correlated data.
|
||||||
TEST(Subtractor, Convergence) {
|
TEST(Subtractor, Convergence) {
|
||||||
std::vector<int> blocks_with_echo_path_changes;
|
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}) {
|
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(
|
float echo_to_nearend_power =
|
||||||
100, delay_samples, false, blocks_with_echo_path_changes);
|
RunSubtractorTest(300, delay_samples, filter_length_blocks, false,
|
||||||
|
blocks_with_echo_path_changes);
|
||||||
EXPECT_GT(0.1f, echo_to_nearend_power);
|
EXPECT_GT(0.1f, echo_to_nearend_power);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies that the subtractor does not converge on uncorrelated signals.
|
// Verifies that the subtractor does not converge on uncorrelated signals.
|
||||||
TEST(Subtractor, NonConvergenceOnUncorrelatedSignals) {
|
TEST(Subtractor, NonConvergenceOnUncorrelatedSignals) {
|
||||||
std::vector<int> blocks_with_echo_path_changes;
|
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}) {
|
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(
|
float echo_to_nearend_power =
|
||||||
100, delay_samples, true, blocks_with_echo_path_changes);
|
RunSubtractorTest(100, delay_samples, filter_length_blocks, true,
|
||||||
|
blocks_with_echo_path_changes);
|
||||||
EXPECT_NEAR(1.f, echo_to_nearend_power, 0.05);
|
EXPECT_NEAR(1.f, echo_to_nearend_power, 0.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifies that the subtractor is properly reset when there is an echo path
|
// Verifies that the subtractor is properly reset when there is an echo path
|
||||||
// change.
|
// change.
|
||||||
TEST(Subtractor, EchoPathChangeReset) {
|
TEST(Subtractor, EchoPathChangeReset) {
|
||||||
std::vector<int> blocks_with_echo_path_changes;
|
std::vector<int> blocks_with_echo_path_changes;
|
||||||
blocks_with_echo_path_changes.push_back(99);
|
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}) {
|
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(
|
float echo_to_nearend_power =
|
||||||
100, delay_samples, false, blocks_with_echo_path_changes);
|
RunSubtractorTest(100, delay_samples, filter_length_blocks, false,
|
||||||
|
blocks_with_echo_path_changes);
|
||||||
EXPECT_NEAR(1.f, echo_to_nearend_power, 0.0000001f);
|
EXPECT_NEAR(1.f, echo_to_nearend_power, 0.0000001f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -60,7 +60,7 @@ TEST(SuppressionGain, BasicGainComputation) {
|
|||||||
EchoCanceller3Config config;
|
EchoCanceller3Config config;
|
||||||
AecState aec_state(config);
|
AecState aec_state(config);
|
||||||
ApmDataDumper data_dumper(42);
|
ApmDataDumper data_dumper(42);
|
||||||
Subtractor subtractor(&data_dumper, DetectOptimization());
|
Subtractor subtractor(config, &data_dumper, DetectOptimization());
|
||||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||||
RenderDelayBuffer::Create(config, 3));
|
RenderDelayBuffer::Create(config, 3));
|
||||||
|
|
||||||
|
|||||||
@ -1158,6 +1158,10 @@ struct EchoCanceller3Config {
|
|||||||
size_t min_echo_path_delay_blocks = 5;
|
size_t min_echo_path_delay_blocks = 5;
|
||||||
} delay;
|
} delay;
|
||||||
|
|
||||||
|
struct Filter {
|
||||||
|
size_t length_blocks = 12;
|
||||||
|
} filter;
|
||||||
|
|
||||||
struct Erle {
|
struct Erle {
|
||||||
float min = 1.f;
|
float min = 1.f;
|
||||||
float max_l = 8.f;
|
float max_l = 8.f;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user