AEC3 delay estimator refactoring and introducing ability to customize
This CL refactors the delay estimator in AEC3. Furthermore, it adds: 1. Allow for a customized delay estimator behavior to simplify development. 2. Exposes that behavior to clear configuration settings. 3. Adds logging of the delay range supported by the delay estimator. Bug: webrtc:8519 Change-Id: I1764a090519a78b021b2e7de565c52a6c02c848e Reviewed-on: https://webrtc-review.googlesource.com/21166 Commit-Queue: Per Åhgren <peah@webrtc.org> Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org> Cr-Commit-Position: refs/heads/master@{#20733}
This commit is contained in:
parent
76f2a85027
commit
38e2d95bda
@ -44,8 +44,8 @@ rtc_static_library("audio_processing") {
|
||||
"aec3/cascaded_biquad_filter.h",
|
||||
"aec3/comfort_noise_generator.cc",
|
||||
"aec3/comfort_noise_generator.h",
|
||||
"aec3/decimator_by_4.cc",
|
||||
"aec3/decimator_by_4.h",
|
||||
"aec3/decimator.cc",
|
||||
"aec3/decimator.h",
|
||||
"aec3/downsampled_render_buffer.cc",
|
||||
"aec3/downsampled_render_buffer.h",
|
||||
"aec3/echo_canceller3.cc",
|
||||
@ -610,7 +610,7 @@ if (rtc_include_tests) {
|
||||
"aec3/block_processor_unittest.cc",
|
||||
"aec3/cascaded_biquad_filter_unittest.cc",
|
||||
"aec3/comfort_noise_generator_unittest.cc",
|
||||
"aec3/decimator_by_4_unittest.cc",
|
||||
"aec3/decimator_unittest.cc",
|
||||
"aec3/echo_canceller3_unittest.cc",
|
||||
"aec3/echo_path_delay_estimator_unittest.cc",
|
||||
"aec3/echo_path_variability_unittest.cc",
|
||||
|
||||
@ -48,20 +48,9 @@ constexpr size_t kSubFrameLength = 80;
|
||||
|
||||
constexpr size_t kBlockSize = kFftLengthBy2;
|
||||
constexpr size_t kExtendedBlockSize = 2 * kFftLengthBy2;
|
||||
constexpr size_t kSubBlockSize = 16;
|
||||
|
||||
constexpr size_t kNumMatchedFilters = 4;
|
||||
constexpr size_t kMatchedFilterWindowSizeSubBlocks = 32;
|
||||
constexpr size_t kMatchedFilterAlignmentShiftSizeSubBlocks =
|
||||
kMatchedFilterWindowSizeSubBlocks * 3 / 4;
|
||||
constexpr size_t kDownsampledRenderBufferSize =
|
||||
kSubBlockSize *
|
||||
(kMatchedFilterAlignmentShiftSizeSubBlocks * kNumMatchedFilters +
|
||||
kMatchedFilterWindowSizeSubBlocks +
|
||||
1);
|
||||
|
||||
constexpr size_t kRenderDelayBufferSize =
|
||||
(3 * kDownsampledRenderBufferSize) / (4 * kSubBlockSize);
|
||||
|
||||
constexpr size_t kMinEchoPathDelayBlocks = 5;
|
||||
constexpr size_t kMaxApiCallsJitterBlocks = 26;
|
||||
@ -85,6 +74,20 @@ constexpr bool ValidFullBandRate(int sample_rate_hz) {
|
||||
sample_rate_hz == 32000 || sample_rate_hz == 48000;
|
||||
}
|
||||
|
||||
constexpr size_t GetDownSampledBufferSize(size_t down_sampling_factor,
|
||||
size_t num_matched_filters) {
|
||||
return kBlockSize / down_sampling_factor *
|
||||
(kMatchedFilterAlignmentShiftSizeSubBlocks * num_matched_filters +
|
||||
kMatchedFilterWindowSizeSubBlocks + 1);
|
||||
}
|
||||
|
||||
constexpr size_t GetRenderDelayBufferSize(size_t down_sampling_factor,
|
||||
size_t num_matched_filters) {
|
||||
return (3 *
|
||||
GetDownSampledBufferSize(down_sampling_factor, num_matched_filters)) /
|
||||
(4 * kBlockSize / down_sampling_factor);
|
||||
}
|
||||
|
||||
// Detects what kind of optimizations to use for the code.
|
||||
Aec3Optimization DetectOptimization();
|
||||
|
||||
|
||||
@ -183,8 +183,12 @@ void BlockProcessorImpl::UpdateEchoLeakageStatus(bool leakage_detected) {
|
||||
|
||||
BlockProcessor* BlockProcessor::Create(const EchoCanceller3Config& config,
|
||||
int sample_rate_hz) {
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(sample_rate_hz)));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
|
||||
NumBandsForRate(sample_rate_hz), config.delay.down_sampling_factor,
|
||||
GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters),
|
||||
GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(config, sample_rate_hz));
|
||||
std::unique_ptr<EchoRemover> echo_remover(
|
||||
|
||||
70
modules/audio_processing/aec3/decimator.cc
Normal file
70
modules/audio_processing/aec3/decimator.cc
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#include "modules/audio_processing/aec3/decimator.h"
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// b, a = signal.butter(2, 3400/8000.0, 'lowpass', analog=False) which are the
|
||||
// same as b, a = signal.butter(2, 1700/4000.0, 'lowpass', analog=False).
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients2 = {
|
||||
{0.22711796f, 0.45423593f, 0.22711796f},
|
||||
{-0.27666461f, 0.18513647f}};
|
||||
constexpr int kNumFilters2 = 3;
|
||||
|
||||
// b, a = signal.butter(2, 1500/8000.0, 'lowpass', analog=False) which are the
|
||||
// same as b, a = signal.butter(2, 75/4000.0, 'lowpass', analog=False).
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients4 = {
|
||||
{0.0179f, 0.0357f, 0.0179f},
|
||||
{-1.5879f, 0.6594f}};
|
||||
constexpr int kNumFilters4 = 3;
|
||||
|
||||
// b, a = signal.butter(2, 800/8000.0, 'lowpass', analog=False) which are the
|
||||
// same as b, a = signal.butter(2, 400/4000.0, 'lowpass', analog=False).
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients8 = {
|
||||
{0.02008337f, 0.04016673f, 0.02008337f},
|
||||
{-1.56101808f, 0.64135154f}};
|
||||
constexpr int kNumFilters8 = 4;
|
||||
|
||||
} // namespace
|
||||
|
||||
Decimator::Decimator(size_t down_sampling_factor)
|
||||
: down_sampling_factor_(down_sampling_factor),
|
||||
low_pass_filter_(
|
||||
down_sampling_factor_ == 4
|
||||
? kLowPassFilterCoefficients4
|
||||
: (down_sampling_factor_ == 8 ? kLowPassFilterCoefficients8
|
||||
: kLowPassFilterCoefficients2),
|
||||
down_sampling_factor_ == 4
|
||||
? kNumFilters4
|
||||
: (down_sampling_factor_ == 8 ? kNumFilters8 : kNumFilters2)) {
|
||||
RTC_DCHECK(down_sampling_factor_ == 2 || down_sampling_factor_ == 4 ||
|
||||
down_sampling_factor_ == 8);
|
||||
}
|
||||
|
||||
void Decimator::Decimate(rtc::ArrayView<const float> in,
|
||||
rtc::ArrayView<float> out) {
|
||||
RTC_DCHECK_EQ(kBlockSize, in.size());
|
||||
RTC_DCHECK_EQ(kBlockSize / down_sampling_factor_, out.size());
|
||||
std::array<float, kBlockSize> x;
|
||||
|
||||
// Limit the frequency content of the signal to avoid aliasing.
|
||||
low_pass_filter_.Process(in, x);
|
||||
|
||||
// Downsample the signal.
|
||||
for (size_t j = 0, k = 0; j < out.size(); ++j, k += down_sampling_factor_) {
|
||||
RTC_DCHECK_GT(kBlockSize, k);
|
||||
out[j] = x[k];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -8,8 +8,8 @@
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_
|
||||
#define MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_
|
||||
#ifndef MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_H_
|
||||
#define MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_H_
|
||||
|
||||
#include <array>
|
||||
|
||||
@ -20,19 +20,20 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Provides functionality for decimating a signal by 4.
|
||||
class DecimatorBy4 {
|
||||
// Provides functionality for decimating a signal.
|
||||
class Decimator {
|
||||
public:
|
||||
DecimatorBy4();
|
||||
explicit Decimator(size_t down_sampling_factor);
|
||||
|
||||
// Downsamples the signal.
|
||||
void Decimate(rtc::ArrayView<const float> in, rtc::ArrayView<float> out);
|
||||
|
||||
private:
|
||||
const size_t down_sampling_factor_;
|
||||
CascadedBiQuadFilter low_pass_filter_;
|
||||
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(DecimatorBy4);
|
||||
RTC_DISALLOW_COPY_AND_ASSIGN(Decimator);
|
||||
};
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_BY_4_H_
|
||||
#endif // MODULES_AUDIO_PROCESSING_AEC3_DECIMATOR_H_
|
||||
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#include "modules/audio_processing/aec3/decimator_by_4.h"
|
||||
|
||||
#include "rtc_base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// [B,A] = butter(2,1500/16000) which are the same as [B,A] =
|
||||
// butter(2,750/8000).
|
||||
const CascadedBiQuadFilter::BiQuadCoefficients kLowPassFilterCoefficients = {
|
||||
{0.0179f, 0.0357f, 0.0179f},
|
||||
{-1.5879f, 0.6594f}};
|
||||
|
||||
} // namespace
|
||||
|
||||
DecimatorBy4::DecimatorBy4()
|
||||
: low_pass_filter_(kLowPassFilterCoefficients, 3) {}
|
||||
|
||||
void DecimatorBy4::Decimate(rtc::ArrayView<const float> in,
|
||||
rtc::ArrayView<float> out) {
|
||||
RTC_DCHECK_EQ(kBlockSize, in.size());
|
||||
RTC_DCHECK_EQ(kSubBlockSize, out.size());
|
||||
std::array<float, kBlockSize> x;
|
||||
|
||||
// Limit the frequency content of the signal to avoid aliasing.
|
||||
low_pass_filter_.Process(in, x);
|
||||
|
||||
// Downsample the signal.
|
||||
for (size_t j = 0, k = 0; j < out.size(); ++j, k += 4) {
|
||||
RTC_DCHECK_GT(kBlockSize, k);
|
||||
out[j] = x[k];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -8,7 +8,7 @@
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "modules/audio_processing/aec3/decimator_by_4.h"
|
||||
#include "modules/audio_processing/aec3/decimator.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <algorithm>
|
||||
@ -31,15 +31,18 @@ std::string ProduceDebugText(int sample_rate_hz) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
constexpr size_t kDownSamplingFactors[] = {2, 4, 8};
|
||||
constexpr float kPi = 3.141592f;
|
||||
constexpr size_t kNumStartupBlocks = 50;
|
||||
constexpr size_t kNumBlocks = 1000;
|
||||
|
||||
void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz,
|
||||
size_t down_sampling_factor,
|
||||
float sinusoidal_frequency_hz,
|
||||
float* input_power,
|
||||
float* output_power) {
|
||||
float input[kBlockSize * kNumBlocks];
|
||||
const size_t sub_block_size = kBlockSize / down_sampling_factor;
|
||||
|
||||
// Produce a sinusoid of the specified frequency.
|
||||
for (size_t k = 0; k < kBlockSize * kNumBlocks; ++k) {
|
||||
@ -47,18 +50,18 @@ void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz,
|
||||
32767.f * sin(2.f * kPi * sinusoidal_frequency_hz * k / sample_rate_hz);
|
||||
}
|
||||
|
||||
DecimatorBy4 decimator;
|
||||
std::array<float, kSubBlockSize * kNumBlocks> output;
|
||||
Decimator decimator(down_sampling_factor);
|
||||
std::vector<float> output(sub_block_size * kNumBlocks);
|
||||
|
||||
for (size_t k = 0; k < kNumBlocks; ++k) {
|
||||
std::array<float, kSubBlockSize> sub_block;
|
||||
std::vector<float> sub_block(sub_block_size);
|
||||
|
||||
decimator.Decimate(
|
||||
rtc::ArrayView<const float>(&input[k * kBlockSize], kBlockSize),
|
||||
sub_block);
|
||||
|
||||
std::copy(sub_block.begin(), sub_block.end(),
|
||||
output.begin() + k * kSubBlockSize);
|
||||
output.begin() + k * sub_block_size);
|
||||
}
|
||||
|
||||
ASSERT_GT(kNumBlocks, kNumStartupBlocks);
|
||||
@ -66,8 +69,8 @@ void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz,
|
||||
&input[kNumStartupBlocks * kBlockSize],
|
||||
(kNumBlocks - kNumStartupBlocks) * kBlockSize);
|
||||
rtc::ArrayView<const float> output_to_evaluate(
|
||||
&output[kNumStartupBlocks * kSubBlockSize],
|
||||
(kNumBlocks - kNumStartupBlocks) * kSubBlockSize);
|
||||
&output[kNumStartupBlocks * sub_block_size],
|
||||
(kNumBlocks - kNumStartupBlocks) * sub_block_size);
|
||||
*input_power =
|
||||
std::inner_product(input_to_evaluate.begin(), input_to_evaluate.end(),
|
||||
input_to_evaluate.begin(), 0.f) /
|
||||
@ -82,46 +85,64 @@ void ProduceDecimatedSinusoidalOutputPower(int sample_rate_hz,
|
||||
|
||||
// Verifies that there is little aliasing from upper frequencies in the
|
||||
// downsampling.
|
||||
TEST(DecimatorBy4, NoLeakageFromUpperFrequencies) {
|
||||
TEST(Decimator, NoLeakageFromUpperFrequencies) {
|
||||
float input_power;
|
||||
float output_power;
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
ProduceDebugText(rate);
|
||||
ProduceDecimatedSinusoidalOutputPower(rate, 3.f / 8.f * rate, &input_power,
|
||||
&output_power);
|
||||
EXPECT_GT(0.0001f * input_power, output_power);
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
ProduceDebugText(rate);
|
||||
ProduceDecimatedSinusoidalOutputPower(rate, down_sampling_factor,
|
||||
3.f / 8.f * rate, &input_power,
|
||||
&output_power);
|
||||
EXPECT_GT(0.0001f * input_power, output_power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies that the impact of low-frequency content is small during the
|
||||
// downsampling.
|
||||
TEST(DecimatorBy4, NoImpactOnLowerFrequencies) {
|
||||
TEST(Decimator, NoImpactOnLowerFrequencies) {
|
||||
float input_power;
|
||||
float output_power;
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
ProduceDebugText(rate);
|
||||
ProduceDecimatedSinusoidalOutputPower(rate, 200.f, &input_power,
|
||||
&output_power);
|
||||
EXPECT_LT(0.7f * input_power, output_power);
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
ProduceDebugText(rate);
|
||||
ProduceDecimatedSinusoidalOutputPower(rate, down_sampling_factor, 200.f,
|
||||
&input_power, &output_power);
|
||||
EXPECT_LT(0.7f * input_power, output_power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
|
||||
// Verifies the check for the input size.
|
||||
TEST(DecimatorBy4, WrongInputSize) {
|
||||
DecimatorBy4 decimator;
|
||||
TEST(Decimator, WrongInputSize) {
|
||||
Decimator decimator(4);
|
||||
std::vector<float> x(std::vector<float>(kBlockSize - 1, 0.f));
|
||||
std::array<float, kSubBlockSize> x_downsampled;
|
||||
std::array<float, kBlockSize / 4> x_downsampled;
|
||||
EXPECT_DEATH(decimator.Decimate(x, x_downsampled), "");
|
||||
}
|
||||
|
||||
// Verifies the check for non-null output parameter.
|
||||
TEST(DecimatorBy4, NullOutput) {
|
||||
DecimatorBy4 decimator;
|
||||
TEST(Decimator, NullOutput) {
|
||||
Decimator decimator(4);
|
||||
std::vector<float> x(std::vector<float>(kBlockSize, 0.f));
|
||||
EXPECT_DEATH(decimator.Decimate(x, nullptr), "");
|
||||
}
|
||||
|
||||
// Verifies the check for the output size.
|
||||
TEST(Decimator, WrongOutputSize) {
|
||||
Decimator decimator(4);
|
||||
std::vector<float> x(std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kBlockSize / 4 - 1> x_downsampled;
|
||||
EXPECT_DEATH(decimator.Decimate(x, x_downsampled), "");
|
||||
}
|
||||
|
||||
// Verifies the check for the correct downsampling factor.
|
||||
TEST(Decimator, CorrectDownSamplingFactor) {
|
||||
EXPECT_DEATH(Decimator(3), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace webrtc
|
||||
@ -12,7 +12,8 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
DownsampledRenderBuffer::DownsampledRenderBuffer() = default;
|
||||
DownsampledRenderBuffer::DownsampledRenderBuffer(size_t downsampled_buffer_size)
|
||||
: buffer(downsampled_buffer_size, 0.f) {}
|
||||
|
||||
DownsampledRenderBuffer::~DownsampledRenderBuffer() = default;
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
#ifndef MODULES_AUDIO_PROCESSING_AEC3_DOWNSAMPLED_RENDER_BUFFER_H_
|
||||
#define MODULES_AUDIO_PROCESSING_AEC3_DOWNSAMPLED_RENDER_BUFFER_H_
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "modules/audio_processing/aec3/aec3_common.h"
|
||||
|
||||
@ -19,9 +19,9 @@ namespace webrtc {
|
||||
|
||||
// Holds the circular buffer of the downsampled render data.
|
||||
struct DownsampledRenderBuffer {
|
||||
DownsampledRenderBuffer();
|
||||
explicit DownsampledRenderBuffer(size_t downsampled_buffer_size);
|
||||
~DownsampledRenderBuffer();
|
||||
std::array<float, kDownsampledRenderBufferSize> buffer = {};
|
||||
std::vector<float> buffer;
|
||||
int position = 0;
|
||||
};
|
||||
|
||||
|
||||
@ -19,23 +19,26 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kDownSamplingFactor = 4;
|
||||
} // namespace
|
||||
|
||||
EchoPathDelayEstimator::EchoPathDelayEstimator(
|
||||
ApmDataDumper* data_dumper,
|
||||
const EchoCanceller3Config& config)
|
||||
: data_dumper_(data_dumper),
|
||||
down_sampling_factor_(config.delay.down_sampling_factor),
|
||||
sub_block_size_(down_sampling_factor_ != 0
|
||||
? kBlockSize / down_sampling_factor_
|
||||
: kBlockSize),
|
||||
capture_decimator_(down_sampling_factor_),
|
||||
matched_filter_(data_dumper_,
|
||||
DetectOptimization(),
|
||||
sub_block_size_,
|
||||
kMatchedFilterWindowSizeSubBlocks,
|
||||
kNumMatchedFilters,
|
||||
config.delay.num_filters,
|
||||
kMatchedFilterAlignmentShiftSizeSubBlocks,
|
||||
config.render_levels.poor_excitation_render_limit),
|
||||
matched_filter_lag_aggregator_(data_dumper_) {
|
||||
matched_filter_lag_aggregator_(data_dumper_,
|
||||
matched_filter_.GetMaxFilterLag()) {
|
||||
RTC_DCHECK(data_dumper);
|
||||
RTC_DCHECK(down_sampling_factor_ > 0);
|
||||
}
|
||||
|
||||
EchoPathDelayEstimator::~EchoPathDelayEstimator() = default;
|
||||
@ -50,8 +53,15 @@ rtc::Optional<size_t> EchoPathDelayEstimator::EstimateDelay(
|
||||
rtc::ArrayView<const float> capture) {
|
||||
RTC_DCHECK_EQ(kBlockSize, capture.size());
|
||||
|
||||
std::array<float, kSubBlockSize> downsampled_capture;
|
||||
std::array<float, kBlockSize> downsampled_capture_data;
|
||||
rtc::ArrayView<float> downsampled_capture(downsampled_capture_data.data(),
|
||||
sub_block_size_);
|
||||
data_dumper_->DumpWav("aec3_capture_decimator_input", capture.size(),
|
||||
capture.data(), 16000, 1);
|
||||
capture_decimator_.Decimate(capture, downsampled_capture);
|
||||
data_dumper_->DumpWav("aec3_capture_decimator_output",
|
||||
downsampled_capture.size(), downsampled_capture.data(),
|
||||
16000 / down_sampling_factor_, 1);
|
||||
matched_filter_.Update(render_buffer, downsampled_capture);
|
||||
|
||||
rtc::Optional<size_t> aggregated_matched_filter_lag =
|
||||
@ -63,14 +73,14 @@ rtc::Optional<size_t> EchoPathDelayEstimator::EstimateDelay(
|
||||
data_dumper_->DumpRaw("aec3_echo_path_delay_estimator_delay",
|
||||
aggregated_matched_filter_lag
|
||||
? static_cast<int>(*aggregated_matched_filter_lag *
|
||||
kDownSamplingFactor)
|
||||
down_sampling_factor_)
|
||||
: -1);
|
||||
|
||||
// Return the detected delay in samples as the aggregated matched filter lag
|
||||
// compensated by the down sampling factor for the signal being correlated.
|
||||
return aggregated_matched_filter_lag
|
||||
? rtc::Optional<size_t>(*aggregated_matched_filter_lag *
|
||||
kDownSamplingFactor)
|
||||
down_sampling_factor_)
|
||||
: rtc::Optional<size_t>();
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "api/optional.h"
|
||||
#include "modules/audio_processing/aec3/decimator_by_4.h"
|
||||
#include "modules/audio_processing/aec3/decimator.h"
|
||||
#include "modules/audio_processing/aec3/downsampled_render_buffer.h"
|
||||
#include "modules/audio_processing/aec3/matched_filter.h"
|
||||
#include "modules/audio_processing/aec3/matched_filter_lag_aggregator.h"
|
||||
@ -40,9 +40,17 @@ class EchoPathDelayEstimator {
|
||||
const DownsampledRenderBuffer& render_buffer,
|
||||
rtc::ArrayView<const float> capture);
|
||||
|
||||
// Log delay estimator properties.
|
||||
void LogDelayEstimationProperties(int sample_rate_hz, size_t shift) const {
|
||||
matched_filter_.LogFilterProperties(sample_rate_hz, shift,
|
||||
down_sampling_factor_);
|
||||
}
|
||||
|
||||
private:
|
||||
ApmDataDumper* const data_dumper_;
|
||||
DecimatorBy4 capture_decimator_;
|
||||
const size_t down_sampling_factor_;
|
||||
const size_t sub_block_size_;
|
||||
Decimator capture_decimator_;
|
||||
MatchedFilter matched_filter_;
|
||||
MatchedFilterLagAggregator matched_filter_lag_aggregator_;
|
||||
|
||||
|
||||
@ -35,9 +35,15 @@ std::string ProduceDebugText(size_t delay) {
|
||||
// Verifies that the basic API calls work.
|
||||
TEST(EchoPathDelayEstimator, BasicApiCalls) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoCanceller3Config config;
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config());
|
||||
RenderDelayBuffer::Create(
|
||||
3, config.delay.down_sampling_factor,
|
||||
GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters),
|
||||
GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters)));
|
||||
EchoPathDelayEstimator estimator(&data_dumper, config);
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
@ -54,29 +60,40 @@ TEST(EchoPathDelayEstimator, DelayEstimation) {
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
ApmDataDumper data_dumper(0);
|
||||
for (size_t delay_samples : {15, 64, 150, 200, 800, 4000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
DelayBuffer<float> signal_delay_buffer(delay_samples);
|
||||
EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config());
|
||||
constexpr size_t kDownSamplingFactors[] = {2, 4, 8};
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
EchoCanceller3Config config;
|
||||
config.delay.down_sampling_factor = down_sampling_factor;
|
||||
config.delay.num_filters = 10;
|
||||
for (size_t delay_samples : {30, 64, 150, 200, 800, 4000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
3, config.delay.down_sampling_factor,
|
||||
GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters),
|
||||
GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters)));
|
||||
DelayBuffer<float> signal_delay_buffer(delay_samples);
|
||||
EchoPathDelayEstimator estimator(&data_dumper, config);
|
||||
|
||||
rtc::Optional<size_t> estimated_delay_samples;
|
||||
for (size_t k = 0; k < (150 + delay_samples / kBlockSize); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
signal_delay_buffer.Delay(render[0], capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
estimated_delay_samples = estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture);
|
||||
}
|
||||
if (estimated_delay_samples) {
|
||||
// Due to the internal down-sampling by 4 done inside the delay estimator
|
||||
// the estimated delay cannot be expected to be closer than 4 samples to
|
||||
// the true delay.
|
||||
EXPECT_NEAR(delay_samples, *estimated_delay_samples, 4);
|
||||
} else {
|
||||
ADD_FAILURE();
|
||||
rtc::Optional<size_t> estimated_delay_samples;
|
||||
for (size_t k = 0; k < (300 + delay_samples / kBlockSize); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
signal_delay_buffer.Delay(render[0], capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
estimated_delay_samples = estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture);
|
||||
}
|
||||
if (estimated_delay_samples) {
|
||||
// Due to the internal down-sampling done inside the delay estimator
|
||||
// the estimated delay cannot be expected to be exact to the true delay.
|
||||
EXPECT_NEAR(delay_samples, *estimated_delay_samples,
|
||||
config.delay.down_sampling_factor);
|
||||
} else {
|
||||
ADD_FAILURE();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -85,13 +102,19 @@ TEST(EchoPathDelayEstimator, DelayEstimation) {
|
||||
// quickly.
|
||||
TEST(EchoPathDelayEstimator, NoInitialDelayestimates) {
|
||||
Random random_generator(42U);
|
||||
EchoCanceller3Config config;
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
RenderDelayBuffer::Create(
|
||||
3, config.delay.down_sampling_factor,
|
||||
GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters),
|
||||
GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters)));
|
||||
|
||||
EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config());
|
||||
EchoPathDelayEstimator estimator(&data_dumper, config);
|
||||
for (size_t k = 0; k < 19; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
std::copy(render[0].begin(), render[0].end(), capture.begin());
|
||||
@ -106,12 +129,18 @@ TEST(EchoPathDelayEstimator, NoInitialDelayestimates) {
|
||||
// signals of low level.
|
||||
TEST(EchoPathDelayEstimator, NoDelayEstimatesForLowLevelRenderSignals) {
|
||||
Random random_generator(42U);
|
||||
EchoCanceller3Config config;
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config());
|
||||
EchoPathDelayEstimator estimator(&data_dumper, config);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
RenderDelayBuffer::Create(
|
||||
3, config.delay.down_sampling_factor,
|
||||
GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters),
|
||||
GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters)));
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
for (auto& render_k : render[0]) {
|
||||
@ -129,12 +158,18 @@ TEST(EchoPathDelayEstimator, NoDelayEstimatesForLowLevelRenderSignals) {
|
||||
// uncorrelated signals.
|
||||
TEST(EchoPathDelayEstimator, NoDelayEstimatesForUncorrelatedSignals) {
|
||||
Random random_generator(42U);
|
||||
EchoCanceller3Config config;
|
||||
std::vector<std::vector<float>> render(3, std::vector<float>(kBlockSize));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config());
|
||||
EchoPathDelayEstimator estimator(&data_dumper, config);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
RenderDelayBuffer::Create(
|
||||
3, config.delay.down_sampling_factor,
|
||||
GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters),
|
||||
GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters)));
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
RandomizeSampleVector(&random_generator, capture);
|
||||
@ -152,9 +187,15 @@ TEST(EchoPathDelayEstimator, NoDelayEstimatesForUncorrelatedSignals) {
|
||||
// tests on test bots has been fixed.
|
||||
TEST(EchoPathDelayEstimator, DISABLED_WrongRenderBlockSize) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config());
|
||||
EchoCanceller3Config config;
|
||||
EchoPathDelayEstimator estimator(&data_dumper, config);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
RenderDelayBuffer::Create(
|
||||
3, config.delay.down_sampling_factor,
|
||||
GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters),
|
||||
GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters)));
|
||||
std::vector<float> capture(kBlockSize);
|
||||
EXPECT_DEATH(estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture),
|
||||
@ -166,9 +207,15 @@ TEST(EchoPathDelayEstimator, DISABLED_WrongRenderBlockSize) {
|
||||
// tests on test bots has been fixed.
|
||||
TEST(EchoPathDelayEstimator, WrongCaptureBlockSize) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EchoPathDelayEstimator estimator(&data_dumper, EchoCanceller3Config());
|
||||
EchoCanceller3Config config;
|
||||
EchoPathDelayEstimator estimator(&data_dumper, config);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
RenderDelayBuffer::Create(
|
||||
3, config.delay.down_sampling_factor,
|
||||
GetDownSampledBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters),
|
||||
GetRenderDelayBufferSize(config.delay.down_sampling_factor,
|
||||
config.delay.num_filters)));
|
||||
std::vector<float> capture(std::vector<float>(kBlockSize - 1));
|
||||
EXPECT_DEATH(estimator.EstimateDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture),
|
||||
|
||||
@ -39,6 +39,9 @@ std::string ProduceDebugText(int sample_rate_hz, int delay) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
constexpr size_t kDownSamplingFactor = 4;
|
||||
constexpr size_t kNumMatchedFilters = 4;
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies the basic API call sequence
|
||||
@ -47,8 +50,10 @@ TEST(EchoRemover, BasicApiCalls) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(EchoCanceller3Config(), rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
|
||||
std::vector<std::vector<float>> render(NumBandsForRate(rate),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
@ -86,8 +91,10 @@ TEST(EchoRemover, WrongCaptureBlockSize) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(EchoCanceller3Config(), rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
std::vector<std::vector<float>> capture(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize - 1, 0.f));
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
@ -107,8 +114,10 @@ TEST(EchoRemover, DISABLED_WrongCaptureNumBands) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(EchoCanceller3Config(), rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
std::vector<std::vector<float>> capture(
|
||||
NumBandsForRate(rate == 48000 ? 16000 : rate + 16000),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
@ -125,8 +134,10 @@ TEST(EchoRemover, DISABLED_WrongCaptureNumBands) {
|
||||
TEST(EchoRemover, NullCapture) {
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(EchoCanceller3Config(), 8000));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(RenderDelayBuffer::Create(
|
||||
3, kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
EchoPathVariability echo_path_variability(false, false);
|
||||
rtc::Optional<size_t> echo_path_delay_samples;
|
||||
EXPECT_DEATH(
|
||||
@ -153,7 +164,11 @@ TEST(EchoRemover, BasicEchoRemoval) {
|
||||
std::unique_ptr<EchoRemover> remover(
|
||||
EchoRemover::Create(EchoCanceller3Config(), rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor,
|
||||
kNumMatchedFilters)));
|
||||
std::vector<std::unique_ptr<DelayBuffer<float>>> delay_buffers(x.size());
|
||||
for (size_t j = 0; j < x.size(); ++j) {
|
||||
delay_buffers[j].reset(new DelayBuffer<float>(delay_samples));
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
|
||||
#include "modules/audio_processing/include/audio_processing.h"
|
||||
#include "modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace aec3 {
|
||||
@ -39,7 +40,7 @@ void MatchedFilterCore_NEON(size_t x_start_index,
|
||||
RTC_DCHECK_EQ(0, h_size % 4);
|
||||
|
||||
// Process for all samples in the sub-block.
|
||||
for (size_t i = 0; i < kSubBlockSize; ++i) {
|
||||
for (size_t i = 0; i < y.size(); ++i) {
|
||||
// Apply the matched filter as filter * x, and compute x * x.
|
||||
|
||||
RTC_DCHECK_GT(x_size, x_start_index);
|
||||
@ -147,7 +148,7 @@ void MatchedFilterCore_SSE2(size_t x_start_index,
|
||||
RTC_DCHECK_EQ(0, h_size % 4);
|
||||
|
||||
// Process for all samples in the sub-block.
|
||||
for (size_t i = 0; i < kSubBlockSize; ++i) {
|
||||
for (size_t i = 0; i < y.size(); ++i) {
|
||||
// Apply the matched filter as filter * x, and compute x * x.
|
||||
|
||||
RTC_DCHECK_GT(x_size, x_start_index);
|
||||
@ -252,7 +253,7 @@ void MatchedFilterCore(size_t x_start_index,
|
||||
bool* filters_updated,
|
||||
float* error_sum) {
|
||||
// Process for all samples in the sub-block.
|
||||
for (size_t i = 0; i < kSubBlockSize; ++i) {
|
||||
for (size_t i = 0; i < y.size(); ++i) {
|
||||
// Apply the matched filter as filter * x, and compute x * x.
|
||||
float x2_sum = 0.f;
|
||||
float s = 0;
|
||||
@ -289,19 +290,25 @@ void MatchedFilterCore(size_t x_start_index,
|
||||
|
||||
MatchedFilter::MatchedFilter(ApmDataDumper* data_dumper,
|
||||
Aec3Optimization optimization,
|
||||
size_t sub_block_size,
|
||||
size_t window_size_sub_blocks,
|
||||
int num_matched_filters,
|
||||
size_t alignment_shift_sub_blocks,
|
||||
float excitation_limit)
|
||||
: data_dumper_(data_dumper),
|
||||
optimization_(optimization),
|
||||
filter_intra_lag_shift_(alignment_shift_sub_blocks * kSubBlockSize),
|
||||
filters_(num_matched_filters,
|
||||
std::vector<float>(window_size_sub_blocks * kSubBlockSize, 0.f)),
|
||||
sub_block_size_(sub_block_size),
|
||||
filter_intra_lag_shift_(alignment_shift_sub_blocks * sub_block_size_),
|
||||
filters_(
|
||||
num_matched_filters,
|
||||
std::vector<float>(window_size_sub_blocks * sub_block_size_, 0.f)),
|
||||
lag_estimates_(num_matched_filters),
|
||||
filters_offsets_(num_matched_filters, 0),
|
||||
excitation_limit_(excitation_limit) {
|
||||
RTC_DCHECK(data_dumper);
|
||||
RTC_DCHECK_LT(0, window_size_sub_blocks);
|
||||
RTC_DCHECK((kBlockSize % sub_block_size) == 0);
|
||||
RTC_DCHECK((sub_block_size % 4) == 0);
|
||||
}
|
||||
|
||||
MatchedFilter::~MatchedFilter() = default;
|
||||
@ -317,8 +324,9 @@ void MatchedFilter::Reset() {
|
||||
}
|
||||
|
||||
void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer,
|
||||
const std::array<float, kSubBlockSize>& capture) {
|
||||
const std::array<float, kSubBlockSize>& y = capture;
|
||||
rtc::ArrayView<const float> capture) {
|
||||
RTC_DCHECK_EQ(sub_block_size_, capture.size());
|
||||
auto& y = capture;
|
||||
|
||||
const float x2_sum_threshold =
|
||||
filters_[0].size() * excitation_limit_ * excitation_limit_;
|
||||
@ -330,7 +338,7 @@ void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer,
|
||||
bool filters_updated = false;
|
||||
|
||||
size_t x_start_index =
|
||||
(render_buffer.position + alignment_shift + kSubBlockSize - 1) %
|
||||
(render_buffer.position + alignment_shift + sub_block_size_ - 1) %
|
||||
render_buffer.buffer.size();
|
||||
|
||||
switch (optimization_) {
|
||||
@ -375,8 +383,7 @@ void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer,
|
||||
error_sum < kMatchingFilterThreshold * error_sum_anchor),
|
||||
lag_estimate + alignment_shift, filters_updated);
|
||||
|
||||
// TODO(peah): Remove once development of EchoCanceller3 is fully done.
|
||||
RTC_DCHECK_EQ(4, filters_.size());
|
||||
RTC_DCHECK_GE(10, filters_.size());
|
||||
switch (n) {
|
||||
case 0:
|
||||
data_dumper_->DumpRaw("aec3_correlator_0_h", filters_[0]);
|
||||
@ -390,12 +397,47 @@ void MatchedFilter::Update(const DownsampledRenderBuffer& render_buffer,
|
||||
case 3:
|
||||
data_dumper_->DumpRaw("aec3_correlator_3_h", filters_[3]);
|
||||
break;
|
||||
case 4:
|
||||
data_dumper_->DumpRaw("aec3_correlator_4_h", filters_[4]);
|
||||
break;
|
||||
case 5:
|
||||
data_dumper_->DumpRaw("aec3_correlator_5_h", filters_[5]);
|
||||
break;
|
||||
case 6:
|
||||
data_dumper_->DumpRaw("aec3_correlator_6_h", filters_[6]);
|
||||
break;
|
||||
case 7:
|
||||
data_dumper_->DumpRaw("aec3_correlator_7_h", filters_[7]);
|
||||
break;
|
||||
case 8:
|
||||
data_dumper_->DumpRaw("aec3_correlator_8_h", filters_[8]);
|
||||
break;
|
||||
case 9:
|
||||
data_dumper_->DumpRaw("aec3_correlator_9_h", filters_[9]);
|
||||
break;
|
||||
default:
|
||||
RTC_DCHECK(false);
|
||||
RTC_NOTREACHED();
|
||||
}
|
||||
|
||||
alignment_shift += filter_intra_lag_shift_;
|
||||
}
|
||||
}
|
||||
|
||||
void MatchedFilter::LogFilterProperties(int sample_rate_hz,
|
||||
size_t shift,
|
||||
size_t downsampling_factor) const {
|
||||
size_t alignment_shift = 0;
|
||||
const int fs_by_1000 = LowestBandRate(sample_rate_hz) / 1000;
|
||||
for (size_t k = 0; k < filters_.size(); ++k) {
|
||||
int start = static_cast<int>(alignment_shift * downsampling_factor);
|
||||
int end = static_cast<int>((alignment_shift + filters_[k].size()) *
|
||||
downsampling_factor);
|
||||
RTC_LOG(LS_INFO) << "Filter " << k << ": start: "
|
||||
<< (start - static_cast<int>(shift)) / fs_by_1000
|
||||
<< " ms, end: "
|
||||
<< (end - static_cast<int>(shift)) / fs_by_1000 << " ms.";
|
||||
alignment_shift += filter_intra_lag_shift_;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -81,6 +81,7 @@ class MatchedFilter {
|
||||
|
||||
MatchedFilter(ApmDataDumper* data_dumper,
|
||||
Aec3Optimization optimization,
|
||||
size_t sub_block_size,
|
||||
size_t window_size_sub_blocks,
|
||||
int num_matched_filters,
|
||||
size_t alignment_shift_sub_blocks,
|
||||
@ -90,7 +91,7 @@ class MatchedFilter {
|
||||
|
||||
// Updates the correlation with the values in the capture buffer.
|
||||
void Update(const DownsampledRenderBuffer& render_buffer,
|
||||
const std::array<float, kSubBlockSize>& capture);
|
||||
rtc::ArrayView<const float> capture);
|
||||
|
||||
// Resets the matched filter.
|
||||
void Reset();
|
||||
@ -100,15 +101,24 @@ class MatchedFilter {
|
||||
return lag_estimates_;
|
||||
}
|
||||
|
||||
// Returns the number of lag estimates produced using the shifted signals.
|
||||
size_t NumLagEstimates() const { return filters_.size(); }
|
||||
// Returns the maximum filter lag.
|
||||
size_t GetMaxFilterLag() const {
|
||||
return filters_.size() * filter_intra_lag_shift_ + filters_[0].size();
|
||||
}
|
||||
|
||||
// Log matched filter properties.
|
||||
void LogFilterProperties(int sample_rate_hz,
|
||||
size_t shift,
|
||||
size_t downsampling_factor) const;
|
||||
|
||||
private:
|
||||
ApmDataDumper* const data_dumper_;
|
||||
const Aec3Optimization optimization_;
|
||||
const size_t sub_block_size_;
|
||||
const size_t filter_intra_lag_shift_;
|
||||
std::vector<std::vector<float>> filters_;
|
||||
std::vector<LagEstimate> lag_estimates_;
|
||||
std::vector<size_t> filters_offsets_;
|
||||
const float excitation_limit_;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(MatchedFilter);
|
||||
|
||||
@ -14,17 +14,17 @@
|
||||
namespace webrtc {
|
||||
|
||||
MatchedFilterLagAggregator::MatchedFilterLagAggregator(
|
||||
ApmDataDumper* data_dumper)
|
||||
: data_dumper_(data_dumper) {
|
||||
ApmDataDumper* data_dumper,
|
||||
size_t max_filter_lag)
|
||||
: data_dumper_(data_dumper), histogram_(max_filter_lag + 1, 0) {
|
||||
RTC_DCHECK(data_dumper);
|
||||
histogram_.fill(0);
|
||||
histogram_data_.fill(0);
|
||||
}
|
||||
|
||||
MatchedFilterLagAggregator::~MatchedFilterLagAggregator() = default;
|
||||
|
||||
void MatchedFilterLagAggregator::Reset() {
|
||||
histogram_.fill(0);
|
||||
std::fill(histogram_.begin(), histogram_.end(), 0);
|
||||
histogram_data_.fill(0);
|
||||
histogram_data_index_ = 0;
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ class ApmDataDumper;
|
||||
// reliable combined lag estimate.
|
||||
class MatchedFilterLagAggregator {
|
||||
public:
|
||||
explicit MatchedFilterLagAggregator(ApmDataDumper* data_dumper);
|
||||
MatchedFilterLagAggregator(ApmDataDumper* data_dumper, size_t max_filter_lag);
|
||||
~MatchedFilterLagAggregator();
|
||||
|
||||
// Resets the aggregator.
|
||||
@ -37,7 +37,7 @@ class MatchedFilterLagAggregator {
|
||||
|
||||
private:
|
||||
ApmDataDumper* const data_dumper_;
|
||||
std::array<int, 1664> histogram_;
|
||||
std::vector<int> histogram_;
|
||||
std::array<int, 250> histogram_data_;
|
||||
int histogram_data_index_ = 0;
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ TEST(MatchedFilterLagAggregator, MostAccurateLagChosen) {
|
||||
constexpr size_t kLag2 = 10;
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(2);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, std::max(kLag1, kLag2));
|
||||
lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, kLag1, true);
|
||||
lag_estimates[1] = MatchedFilter::LagEstimate(0.5f, true, kLag2, true);
|
||||
|
||||
@ -65,7 +65,7 @@ TEST(MatchedFilterLagAggregator,
|
||||
LagEstimateInvarianceRequiredForAggregatedLag) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(1);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, 100);
|
||||
for (size_t k = 0; k < kNumLagsBeforeDetection * 100; ++k) {
|
||||
lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, k % 100, true);
|
||||
rtc::Optional<size_t> aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
@ -80,7 +80,7 @@ TEST(MatchedFilterLagAggregator,
|
||||
constexpr size_t kLag = 5;
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(1);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, kLag);
|
||||
for (size_t k = 0; k < kNumLagsBeforeDetection * 10; ++k) {
|
||||
lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, kLag, false);
|
||||
rtc::Optional<size_t> aggregated_lag = aggregator.Aggregate(lag_estimates);
|
||||
@ -97,7 +97,7 @@ TEST(MatchedFilterLagAggregator, DISABLED_PersistentAggregatedLag) {
|
||||
constexpr size_t kLag2 = 10;
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::vector<MatchedFilter::LagEstimate> lag_estimates(1);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper);
|
||||
MatchedFilterLagAggregator aggregator(&data_dumper, std::max(kLag1, kLag2));
|
||||
rtc::Optional<size_t> aggregated_lag;
|
||||
for (size_t k = 0; k < kNumLagsBeforeDetection; ++k) {
|
||||
lag_estimates[0] = MatchedFilter::LagEstimate(1.f, true, kLag1, true);
|
||||
@ -118,7 +118,7 @@ TEST(MatchedFilterLagAggregator, DISABLED_PersistentAggregatedLag) {
|
||||
|
||||
// Verifies the check for non-null data dumper.
|
||||
TEST(MatchedFilterLagAggregator, NullDataDumper) {
|
||||
EXPECT_DEATH(MatchedFilterLagAggregator(nullptr), "");
|
||||
EXPECT_DEATH(MatchedFilterLagAggregator(nullptr, 10), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "modules/audio_processing/aec3/decimator_by_4.h"
|
||||
#include "modules/audio_processing/aec3/decimator.h"
|
||||
#include "modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "modules/audio_processing/test/echo_canceller_test_tools.h"
|
||||
@ -31,15 +31,17 @@ namespace webrtc {
|
||||
namespace aec3 {
|
||||
namespace {
|
||||
|
||||
std::string ProduceDebugText(size_t delay) {
|
||||
std::string ProduceDebugText(size_t delay, size_t down_sampling_factor) {
|
||||
std::ostringstream ss;
|
||||
ss << "Delay: " << delay;
|
||||
ss << ", Down sampling factor: " << down_sampling_factor;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
constexpr size_t kNumMatchedFilters = 10;
|
||||
constexpr size_t kDownSamplingFactors[] = {2, 4, 8};
|
||||
constexpr size_t kWindowSizeSubBlocks = 32;
|
||||
constexpr size_t kAlignmentShiftSubBlocks = kWindowSizeSubBlocks * 3 / 4;
|
||||
constexpr size_t kNumMatchedFilters = 4;
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -48,34 +50,38 @@ constexpr size_t kNumMatchedFilters = 4;
|
||||
// counterparts.
|
||||
TEST(MatchedFilter, TestNeonOptimizations) {
|
||||
Random random_generator(42U);
|
||||
std::vector<float> x(2000);
|
||||
RandomizeSampleVector(&random_generator, x);
|
||||
std::vector<float> y(kSubBlockSize);
|
||||
std::vector<float> h_NEON(512);
|
||||
std::vector<float> h(512);
|
||||
int x_index = 0;
|
||||
for (int k = 0; k < 1000; ++k) {
|
||||
RandomizeSampleVector(&random_generator, y);
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
const size_t sub_block_size = kBlockSize / down_sampling_factor;
|
||||
|
||||
bool filters_updated = false;
|
||||
float error_sum = 0.f;
|
||||
bool filters_updated_NEON = false;
|
||||
float error_sum_NEON = 0.f;
|
||||
std::vector<float> x(2000);
|
||||
RandomizeSampleVector(&random_generator, x);
|
||||
std::vector<float> y(sub_block_size);
|
||||
std::vector<float> h_NEON(512);
|
||||
std::vector<float> h(512);
|
||||
int x_index = 0;
|
||||
for (int k = 0; k < 1000; ++k) {
|
||||
RandomizeSampleVector(&random_generator, y);
|
||||
|
||||
MatchedFilterCore_NEON(x_index, h.size() * 150.f * 150.f, x, y, h_NEON,
|
||||
&filters_updated_NEON, &error_sum_NEON);
|
||||
bool filters_updated = false;
|
||||
float error_sum = 0.f;
|
||||
bool filters_updated_NEON = false;
|
||||
float error_sum_NEON = 0.f;
|
||||
|
||||
MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h,
|
||||
&filters_updated, &error_sum);
|
||||
MatchedFilterCore_NEON(x_index, h.size() * 150.f * 150.f, x, y, h_NEON,
|
||||
&filters_updated_NEON, &error_sum_NEON);
|
||||
|
||||
EXPECT_EQ(filters_updated, filters_updated_NEON);
|
||||
EXPECT_NEAR(error_sum, error_sum_NEON, error_sum / 100000.f);
|
||||
MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h,
|
||||
&filters_updated, &error_sum);
|
||||
|
||||
for (size_t j = 0; j < h.size(); ++j) {
|
||||
EXPECT_NEAR(h[j], h_NEON[j], 0.00001f);
|
||||
EXPECT_EQ(filters_updated, filters_updated_NEON);
|
||||
EXPECT_NEAR(error_sum, error_sum_NEON, error_sum / 100000.f);
|
||||
|
||||
for (size_t j = 0; j < h.size(); ++j) {
|
||||
EXPECT_NEAR(h[j], h_NEON[j], 0.00001f);
|
||||
}
|
||||
|
||||
x_index = (x_index + sub_block_size) % x.size();
|
||||
}
|
||||
|
||||
x_index = (x_index + kSubBlockSize) % x.size();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -87,34 +93,37 @@ TEST(MatchedFilter, TestSse2Optimizations) {
|
||||
bool use_sse2 = (WebRtc_GetCPUInfo(kSSE2) != 0);
|
||||
if (use_sse2) {
|
||||
Random random_generator(42U);
|
||||
std::vector<float> x(2000);
|
||||
RandomizeSampleVector(&random_generator, x);
|
||||
std::vector<float> y(kSubBlockSize);
|
||||
std::vector<float> h_SSE2(512);
|
||||
std::vector<float> h(512);
|
||||
int x_index = 0;
|
||||
for (int k = 0; k < 1000; ++k) {
|
||||
RandomizeSampleVector(&random_generator, y);
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
const size_t sub_block_size = kBlockSize / down_sampling_factor;
|
||||
std::vector<float> x(2000);
|
||||
RandomizeSampleVector(&random_generator, x);
|
||||
std::vector<float> y(sub_block_size);
|
||||
std::vector<float> h_SSE2(512);
|
||||
std::vector<float> h(512);
|
||||
int x_index = 0;
|
||||
for (int k = 0; k < 1000; ++k) {
|
||||
RandomizeSampleVector(&random_generator, y);
|
||||
|
||||
bool filters_updated = false;
|
||||
float error_sum = 0.f;
|
||||
bool filters_updated_SSE2 = false;
|
||||
float error_sum_SSE2 = 0.f;
|
||||
bool filters_updated = false;
|
||||
float error_sum = 0.f;
|
||||
bool filters_updated_SSE2 = false;
|
||||
float error_sum_SSE2 = 0.f;
|
||||
|
||||
MatchedFilterCore_SSE2(x_index, h.size() * 150.f * 150.f, x, y, h_SSE2,
|
||||
&filters_updated_SSE2, &error_sum_SSE2);
|
||||
MatchedFilterCore_SSE2(x_index, h.size() * 150.f * 150.f, x, y, h_SSE2,
|
||||
&filters_updated_SSE2, &error_sum_SSE2);
|
||||
|
||||
MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h,
|
||||
&filters_updated, &error_sum);
|
||||
MatchedFilterCore(x_index, h.size() * 150.f * 150.f, x, y, h,
|
||||
&filters_updated, &error_sum);
|
||||
|
||||
EXPECT_EQ(filters_updated, filters_updated_SSE2);
|
||||
EXPECT_NEAR(error_sum, error_sum_SSE2, error_sum / 100000.f);
|
||||
EXPECT_EQ(filters_updated, filters_updated_SSE2);
|
||||
EXPECT_NEAR(error_sum, error_sum_SSE2, error_sum / 100000.f);
|
||||
|
||||
for (size_t j = 0; j < h.size(); ++j) {
|
||||
EXPECT_NEAR(h[j], h_SSE2[j], 0.00001f);
|
||||
for (size_t j = 0; j < h.size(); ++j) {
|
||||
EXPECT_NEAR(h[j], h_SSE2[j], 0.00001f);
|
||||
}
|
||||
|
||||
x_index = (x_index + sub_block_size) % x.size();
|
||||
}
|
||||
|
||||
x_index = (x_index + kSubBlockSize) % x.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,71 +135,100 @@ TEST(MatchedFilter, TestSse2Optimizations) {
|
||||
// delayed signals.
|
||||
TEST(MatchedFilter, LagEstimation) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kBlockSize> capture;
|
||||
capture.fill(0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
for (size_t delay_samples : {5, 64, 150, 200, 800, 1000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples));
|
||||
DecimatorBy4 capture_decimator;
|
||||
DelayBuffer<float> signal_delay_buffer(4 * delay_samples);
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(),
|
||||
kWindowSizeSubBlocks, kNumMatchedFilters,
|
||||
kAlignmentShiftSubBlocks, 150);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
const size_t sub_block_size = kBlockSize / down_sampling_factor;
|
||||
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < (150 + delay_samples / kSubBlockSize); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
signal_delay_buffer.Delay(render[0], capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
std::array<float, kSubBlockSize> downsampled_capture;
|
||||
capture_decimator.Decimate(capture, downsampled_capture);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
downsampled_capture);
|
||||
}
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kBlockSize> capture;
|
||||
capture.fill(0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
for (size_t delay_samples : {5, 64, 150, 200, 800, 1000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(delay_samples, down_sampling_factor));
|
||||
Decimator capture_decimator(down_sampling_factor);
|
||||
DelayBuffer<float> signal_delay_buffer(down_sampling_factor *
|
||||
delay_samples);
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size,
|
||||
kWindowSizeSubBlocks, kNumMatchedFilters,
|
||||
kAlignmentShiftSubBlocks, 150);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
3, down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor,
|
||||
kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
kNumMatchedFilters)));
|
||||
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
|
||||
// Find which lag estimate should be the most accurate.
|
||||
rtc::Optional<size_t> expected_most_accurate_lag_estimate;
|
||||
size_t alignment_shift_sub_blocks = 0;
|
||||
for (size_t k = 0; k < kNumMatchedFilters; ++k) {
|
||||
if ((alignment_shift_sub_blocks + kWindowSizeSubBlocks / 2) *
|
||||
kSubBlockSize >
|
||||
delay_samples) {
|
||||
expected_most_accurate_lag_estimate = rtc::Optional<size_t>(k);
|
||||
break;
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < (300 + delay_samples / sub_block_size); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
signal_delay_buffer.Delay(render[0], capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
std::array<float, kBlockSize> downsampled_capture_data;
|
||||
rtc::ArrayView<float> downsampled_capture(
|
||||
downsampled_capture_data.data(), sub_block_size);
|
||||
capture_decimator.Decimate(capture, downsampled_capture);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
downsampled_capture);
|
||||
}
|
||||
alignment_shift_sub_blocks += kAlignmentShiftSubBlocks;
|
||||
}
|
||||
ASSERT_TRUE(expected_most_accurate_lag_estimate);
|
||||
|
||||
// Verify that the expected most accurate lag estimate is the most accurate
|
||||
// estimate.
|
||||
for (size_t k = 0; k < kNumMatchedFilters; ++k) {
|
||||
if (k != *expected_most_accurate_lag_estimate) {
|
||||
EXPECT_GT(lag_estimates[*expected_most_accurate_lag_estimate].accuracy,
|
||||
lag_estimates[k].accuracy);
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
|
||||
// Find which lag estimate should be the most accurate.
|
||||
rtc::Optional<size_t> expected_most_accurate_lag_estimate;
|
||||
size_t alignment_shift_sub_blocks = 0;
|
||||
for (size_t k = 0; k < kNumMatchedFilters; ++k) {
|
||||
if ((alignment_shift_sub_blocks + 3 * kWindowSizeSubBlocks / 4) *
|
||||
sub_block_size >
|
||||
delay_samples) {
|
||||
expected_most_accurate_lag_estimate =
|
||||
rtc::Optional<size_t>(k > 0 ? k - 1 : 0);
|
||||
break;
|
||||
}
|
||||
alignment_shift_sub_blocks += kAlignmentShiftSubBlocks;
|
||||
}
|
||||
ASSERT_TRUE(expected_most_accurate_lag_estimate);
|
||||
|
||||
// Verify that the expected most accurate lag estimate is the most
|
||||
// accurate estimate.
|
||||
for (size_t k = 0; k < kNumMatchedFilters; ++k) {
|
||||
if (k != *expected_most_accurate_lag_estimate &&
|
||||
k != (*expected_most_accurate_lag_estimate + 1)) {
|
||||
EXPECT_TRUE(
|
||||
lag_estimates[*expected_most_accurate_lag_estimate].accuracy >
|
||||
lag_estimates[k].accuracy ||
|
||||
!lag_estimates[k].reliable ||
|
||||
!lag_estimates[*expected_most_accurate_lag_estimate].reliable);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that all lag estimates are updated as expected for signals
|
||||
// containing strong noise.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_TRUE(le.updated);
|
||||
}
|
||||
|
||||
// Verify that the expected most accurate lag estimate is reliable.
|
||||
EXPECT_TRUE(
|
||||
lag_estimates[*expected_most_accurate_lag_estimate].reliable ||
|
||||
lag_estimates[std::min(*expected_most_accurate_lag_estimate + 1,
|
||||
lag_estimates.size() - 1)]
|
||||
.reliable);
|
||||
|
||||
// Verify that the expected most accurate lag estimate is correct.
|
||||
if (lag_estimates[*expected_most_accurate_lag_estimate].reliable) {
|
||||
EXPECT_TRUE(delay_samples ==
|
||||
lag_estimates[*expected_most_accurate_lag_estimate].lag);
|
||||
} else {
|
||||
EXPECT_TRUE(
|
||||
delay_samples ==
|
||||
lag_estimates[std::min(*expected_most_accurate_lag_estimate + 1,
|
||||
lag_estimates.size() - 1)]
|
||||
.lag);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that all lag estimates are updated as expected for signals
|
||||
// containing strong noise.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_TRUE(le.updated);
|
||||
}
|
||||
|
||||
// Verify that the expected most accurate lag estimate is reliable.
|
||||
EXPECT_TRUE(lag_estimates[*expected_most_accurate_lag_estimate].reliable);
|
||||
|
||||
// Verify that the expected most accurate lag estimate is correct.
|
||||
EXPECT_EQ(delay_samples,
|
||||
lag_estimates[*expected_most_accurate_lag_estimate].lag);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,31 +236,41 @@ TEST(MatchedFilter, LagEstimation) {
|
||||
// estimates for uncorrelated render and capture signals.
|
||||
TEST(MatchedFilter, LagNotReliableForUncorrelatedRenderAndCapture) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kSubBlockSize> capture;
|
||||
capture.fill(0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), kWindowSizeSubBlocks,
|
||||
kNumMatchedFilters, kAlignmentShiftSubBlocks, 150);
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
const size_t sub_block_size = kBlockSize / down_sampling_factor;
|
||||
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
RandomizeSampleVector(&random_generator, capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), capture);
|
||||
}
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kBlockSize> capture_data;
|
||||
rtc::ArrayView<float> capture(capture_data.data(), sub_block_size);
|
||||
std::fill(capture.begin(), capture.end(), 0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
3, down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
kNumMatchedFilters)));
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size,
|
||||
kWindowSizeSubBlocks, kNumMatchedFilters,
|
||||
kAlignmentShiftSubBlocks, 150);
|
||||
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
EXPECT_EQ(kNumMatchedFilters, lag_estimates.size());
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
RandomizeSampleVector(&random_generator, capture);
|
||||
render_delay_buffer->Insert(render);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(), capture);
|
||||
}
|
||||
|
||||
// Verify that no lag estimates are reliable.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_FALSE(le.reliable);
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
EXPECT_EQ(kNumMatchedFilters, lag_estimates.size());
|
||||
|
||||
// Verify that no lag estimates are reliable.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_FALSE(le.reliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,39 +278,50 @@ TEST(MatchedFilter, LagNotReliableForUncorrelatedRenderAndCapture) {
|
||||
// render signals of low level.
|
||||
TEST(MatchedFilter, LagNotUpdatedForLowLevelRender) {
|
||||
Random random_generator(42U);
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kBlockSize> capture;
|
||||
capture.fill(0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), kWindowSizeSubBlocks,
|
||||
kNumMatchedFilters, kAlignmentShiftSubBlocks, 150);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
DecimatorBy4 capture_decimator;
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
const size_t sub_block_size = kBlockSize / down_sampling_factor;
|
||||
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
for (auto& render_k : render[0]) {
|
||||
render_k *= 149.f / 32767.f;
|
||||
std::vector<std::vector<float>> render(3,
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
std::array<float, kBlockSize> capture;
|
||||
capture.fill(0.f);
|
||||
ApmDataDumper data_dumper(0);
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size,
|
||||
kWindowSizeSubBlocks, kNumMatchedFilters,
|
||||
kAlignmentShiftSubBlocks, 150);
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
3, down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
kNumMatchedFilters)));
|
||||
Decimator capture_decimator(down_sampling_factor);
|
||||
|
||||
// Analyze the correlation between render and capture.
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
RandomizeSampleVector(&random_generator, render[0]);
|
||||
for (auto& render_k : render[0]) {
|
||||
render_k *= 149.f / 32767.f;
|
||||
}
|
||||
std::copy(render[0].begin(), render[0].end(), capture.begin());
|
||||
std::array<float, kBlockSize> downsampled_capture_data;
|
||||
rtc::ArrayView<float> downsampled_capture(downsampled_capture_data.data(),
|
||||
sub_block_size);
|
||||
capture_decimator.Decimate(capture, downsampled_capture);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
downsampled_capture);
|
||||
}
|
||||
std::copy(render[0].begin(), render[0].end(), capture.begin());
|
||||
std::array<float, kSubBlockSize> downsampled_capture;
|
||||
capture_decimator.Decimate(capture, downsampled_capture);
|
||||
filter.Update(render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
downsampled_capture);
|
||||
}
|
||||
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
EXPECT_EQ(kNumMatchedFilters, lag_estimates.size());
|
||||
// Obtain the lag estimates.
|
||||
auto lag_estimates = filter.GetLagEstimates();
|
||||
EXPECT_EQ(kNumMatchedFilters, lag_estimates.size());
|
||||
|
||||
// Verify that no lag estimates are updated and that no lag estimates are
|
||||
// reliable.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_FALSE(le.updated);
|
||||
EXPECT_FALSE(le.reliable);
|
||||
// Verify that no lag estimates are updated and that no lag estimates are
|
||||
// reliable.
|
||||
for (auto& le : lag_estimates) {
|
||||
EXPECT_FALSE(le.updated);
|
||||
EXPECT_FALSE(le.reliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,11 +329,14 @@ TEST(MatchedFilter, LagNotUpdatedForLowLevelRender) {
|
||||
// number of alignment shifts.
|
||||
TEST(MatchedFilter, NumberOfLagEstimates) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
for (size_t num_matched_filters = 0; num_matched_filters < 10;
|
||||
++num_matched_filters) {
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), 32,
|
||||
num_matched_filters, 1, 150);
|
||||
EXPECT_EQ(num_matched_filters, filter.GetLagEstimates().size());
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
const size_t sub_block_size = kBlockSize / down_sampling_factor;
|
||||
for (size_t num_matched_filters = 0; num_matched_filters < 10;
|
||||
++num_matched_filters) {
|
||||
MatchedFilter filter(&data_dumper, DetectOptimization(), sub_block_size,
|
||||
32, num_matched_filters, 1, 150);
|
||||
EXPECT_EQ(num_matched_filters, filter.GetLagEstimates().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,13 +345,31 @@ TEST(MatchedFilter, NumberOfLagEstimates) {
|
||||
// Verifies the check for non-zero windows size.
|
||||
TEST(MatchedFilter, ZeroWindowSize) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EXPECT_DEATH(MatchedFilter(&data_dumper, DetectOptimization(), 0, 1, 1, 150),
|
||||
"");
|
||||
EXPECT_DEATH(
|
||||
MatchedFilter(&data_dumper, DetectOptimization(), 16, 0, 1, 1, 150), "");
|
||||
}
|
||||
|
||||
// Verifies the check for non-null data dumper.
|
||||
TEST(MatchedFilter, NullDataDumper) {
|
||||
EXPECT_DEATH(MatchedFilter(nullptr, DetectOptimization(), 1, 1, 1, 150), "");
|
||||
EXPECT_DEATH(MatchedFilter(nullptr, DetectOptimization(), 16, 1, 1, 1, 150),
|
||||
"");
|
||||
}
|
||||
|
||||
// Verifies the check for that the sub block size is a multiple of 4.
|
||||
// TODO(peah): Activate the unittest once the required code has been landed.
|
||||
TEST(MatchedFilter, DISABLED_BlockSizeMultipleOf4) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EXPECT_DEATH(
|
||||
MatchedFilter(&data_dumper, DetectOptimization(), 15, 1, 1, 1, 150), "");
|
||||
}
|
||||
|
||||
// Verifies the check for that there is an integer number of sub blocks that add
|
||||
// up to a block size.
|
||||
// TODO(peah): Activate the unittest once the required code has been landed.
|
||||
TEST(MatchedFilter, DISABLED_SubBlockSizeAddsUpToBlockSize) {
|
||||
ApmDataDumper data_dumper(0);
|
||||
EXPECT_DEATH(
|
||||
MatchedFilter(&data_dumper, DetectOptimization(), 12, 1, 1, 1, 150), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -27,8 +27,9 @@ class MockRenderDelayBuffer : public RenderDelayBuffer {
|
||||
explicit MockRenderDelayBuffer(int sample_rate_hz)
|
||||
: render_buffer_(Aec3Optimization::kNone,
|
||||
NumBandsForRate(sample_rate_hz),
|
||||
kRenderDelayBufferSize,
|
||||
std::vector<size_t>(1, kAdaptiveFilterLength)) {
|
||||
GetRenderDelayBufferSize(4, 4),
|
||||
std::vector<size_t>(1, kAdaptiveFilterLength)),
|
||||
downsampled_render_buffer_(GetDownSampledBufferSize(4, 4)) {
|
||||
ON_CALL(*this, GetRenderBuffer())
|
||||
.WillByDefault(
|
||||
testing::Invoke(this, &MockRenderDelayBuffer::FakeGetRenderBuffer));
|
||||
|
||||
@ -15,8 +15,9 @@
|
||||
|
||||
#include "modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "modules/audio_processing/aec3/block_processor.h"
|
||||
#include "modules/audio_processing/aec3/decimator_by_4.h"
|
||||
#include "modules/audio_processing/aec3/decimator.h"
|
||||
#include "modules/audio_processing/aec3/fft_data.h"
|
||||
#include "rtc_base/atomicops.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/constructormagic.h"
|
||||
#include "rtc_base/logging.h"
|
||||
@ -73,7 +74,10 @@ class ApiCallJitterBuffer {
|
||||
|
||||
class RenderDelayBufferImpl final : public RenderDelayBuffer {
|
||||
public:
|
||||
explicit RenderDelayBufferImpl(size_t num_bands);
|
||||
RenderDelayBufferImpl(size_t num_bands,
|
||||
size_t down_sampling_factor,
|
||||
size_t downsampled_render_buffer_size,
|
||||
size_t render_delay_buffer_size);
|
||||
~RenderDelayBufferImpl() override;
|
||||
|
||||
void Reset() override;
|
||||
@ -89,30 +93,49 @@ class RenderDelayBufferImpl final : public RenderDelayBuffer {
|
||||
}
|
||||
|
||||
private:
|
||||
static int instance_count_;
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
const Aec3Optimization optimization_;
|
||||
std::array<std::vector<std::vector<float>>, kRenderDelayBufferSize> buffer_;
|
||||
const size_t down_sampling_factor_;
|
||||
const size_t sub_block_size_;
|
||||
std::vector<std::vector<std::vector<float>>> buffer_;
|
||||
size_t delay_ = 0;
|
||||
size_t last_insert_index_ = 0;
|
||||
RenderBuffer fft_buffer_;
|
||||
DownsampledRenderBuffer downsampled_render_buffer_;
|
||||
DecimatorBy4 render_decimator_;
|
||||
Decimator render_decimator_;
|
||||
ApiCallJitterBuffer api_call_jitter_buffer_;
|
||||
const std::vector<std::vector<float>> zero_block_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayBufferImpl);
|
||||
};
|
||||
|
||||
RenderDelayBufferImpl::RenderDelayBufferImpl(size_t num_bands)
|
||||
: optimization_(DetectOptimization()),
|
||||
int RenderDelayBufferImpl::instance_count_ = 0;
|
||||
|
||||
RenderDelayBufferImpl::RenderDelayBufferImpl(
|
||||
size_t num_bands,
|
||||
size_t down_sampling_factor,
|
||||
size_t downsampled_render_buffer_size,
|
||||
size_t render_delay_buffer_size)
|
||||
: data_dumper_(
|
||||
new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))),
|
||||
optimization_(DetectOptimization()),
|
||||
down_sampling_factor_(down_sampling_factor),
|
||||
sub_block_size_(down_sampling_factor_ > 0
|
||||
? kBlockSize / down_sampling_factor
|
||||
: kBlockSize),
|
||||
buffer_(
|
||||
render_delay_buffer_size,
|
||||
std::vector<std::vector<float>>(num_bands,
|
||||
std::vector<float>(kBlockSize, 0.f))),
|
||||
fft_buffer_(
|
||||
optimization_,
|
||||
num_bands,
|
||||
std::max(kUnknownDelayRenderWindowSize, kAdaptiveFilterLength),
|
||||
std::vector<size_t>(1, kAdaptiveFilterLength)),
|
||||
downsampled_render_buffer_(downsampled_render_buffer_size),
|
||||
render_decimator_(down_sampling_factor_),
|
||||
api_call_jitter_buffer_(num_bands),
|
||||
zero_block_(num_bands, std::vector<float>(kBlockSize, 0.f)) {
|
||||
buffer_.fill(std::vector<std::vector<float>>(
|
||||
num_bands, std::vector<float>(kBlockSize, 0.f)));
|
||||
|
||||
RTC_DCHECK_LT(buffer_.size(), downsampled_render_buffer_.buffer.size());
|
||||
}
|
||||
|
||||
@ -123,7 +146,8 @@ void RenderDelayBufferImpl::Reset() {
|
||||
delay_ = 0;
|
||||
last_insert_index_ = 0;
|
||||
downsampled_render_buffer_.position = 0;
|
||||
downsampled_render_buffer_.buffer.fill(0.f);
|
||||
std::fill(downsampled_render_buffer_.buffer.begin(),
|
||||
downsampled_render_buffer_.buffer.end(), 0.f);
|
||||
fft_buffer_.Clear();
|
||||
api_call_jitter_buffer_.Reset();
|
||||
for (auto& c : buffer_) {
|
||||
@ -158,20 +182,26 @@ bool RenderDelayBufferImpl::UpdateBuffers() {
|
||||
}
|
||||
|
||||
downsampled_render_buffer_.position =
|
||||
(downsampled_render_buffer_.position - kSubBlockSize +
|
||||
(downsampled_render_buffer_.position - sub_block_size_ +
|
||||
downsampled_render_buffer_.buffer.size()) %
|
||||
downsampled_render_buffer_.buffer.size();
|
||||
|
||||
std::array<float, kSubBlockSize> render_downsampled;
|
||||
if (underrun) {
|
||||
render_decimator_.Decimate(zero_block_[0], render_downsampled);
|
||||
} else {
|
||||
render_decimator_.Decimate(buffer_[last_insert_index_][0],
|
||||
render_downsampled);
|
||||
rtc::ArrayView<const float> input(
|
||||
underrun ? zero_block_[0].data() : buffer_[last_insert_index_][0].data(),
|
||||
kBlockSize);
|
||||
rtc::ArrayView<float> output(downsampled_render_buffer_.buffer.data() +
|
||||
downsampled_render_buffer_.position,
|
||||
sub_block_size_);
|
||||
data_dumper_->DumpWav("aec3_render_decimator_input", input.size(),
|
||||
input.data(), 16000, 1);
|
||||
render_decimator_.Decimate(input, output);
|
||||
data_dumper_->DumpWav("aec3_render_decimator_output", output.size(),
|
||||
output.data(), 16000 / down_sampling_factor_, 1);
|
||||
for (size_t k = 0; k < output.size() / 2; ++k) {
|
||||
float tmp = output[k];
|
||||
output[k] = output[output.size() - 1 - k];
|
||||
output[output.size() - 1 - k] = tmp;
|
||||
}
|
||||
std::copy(render_downsampled.rbegin(), render_downsampled.rend(),
|
||||
downsampled_render_buffer_.buffer.begin() +
|
||||
downsampled_render_buffer_.position);
|
||||
|
||||
if (underrun) {
|
||||
fft_buffer_.Insert(zero_block_);
|
||||
@ -196,7 +226,7 @@ void RenderDelayBufferImpl::SetDelay(size_t delay) {
|
||||
// size.
|
||||
downsampled_render_buffer_.position =
|
||||
(downsampled_render_buffer_.position +
|
||||
kSubBlockSize * (delay - (buffer_.size() - 1))) %
|
||||
sub_block_size_ * (delay - (buffer_.size() - 1))) %
|
||||
downsampled_render_buffer_.buffer.size();
|
||||
|
||||
last_insert_index_ =
|
||||
@ -210,8 +240,14 @@ void RenderDelayBufferImpl::SetDelay(size_t delay) {
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderDelayBuffer* RenderDelayBuffer::Create(size_t num_bands) {
|
||||
return new RenderDelayBufferImpl(num_bands);
|
||||
RenderDelayBuffer* RenderDelayBuffer::Create(
|
||||
size_t num_bands,
|
||||
size_t down_sampling_factor,
|
||||
size_t downsampled_render_buffer_size,
|
||||
size_t render_delay_buffer_size) {
|
||||
return new RenderDelayBufferImpl(num_bands, down_sampling_factor,
|
||||
downsampled_render_buffer_size,
|
||||
render_delay_buffer_size);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -27,7 +27,10 @@ namespace webrtc {
|
||||
// extracted with a specified delay.
|
||||
class RenderDelayBuffer {
|
||||
public:
|
||||
static RenderDelayBuffer* Create(size_t num_bands);
|
||||
static RenderDelayBuffer* Create(size_t num_bands,
|
||||
size_t down_sampling_factor,
|
||||
size_t downsampled_render_buffer_size,
|
||||
size_t render_delay_buffer_size);
|
||||
virtual ~RenderDelayBuffer() = default;
|
||||
|
||||
// Resets the buffer data.
|
||||
|
||||
@ -30,14 +30,19 @@ std::string ProduceDebugText(int sample_rate_hz) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
constexpr size_t kDownSamplingFactor = 4;
|
||||
constexpr size_t kNumMatchedFilters = 4;
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that the buffer overflow is correctly reported.
|
||||
TEST(RenderDelayBuffer, BufferOverflow) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
std::vector<std::vector<float>> block_to_insert(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
for (size_t k = 0; k < kMaxApiCallsJitterBlocks; ++k) {
|
||||
@ -50,8 +55,10 @@ TEST(RenderDelayBuffer, BufferOverflow) {
|
||||
// Verifies that the check for available block works.
|
||||
TEST(RenderDelayBuffer, AvailableBlock) {
|
||||
constexpr size_t kNumBands = 1;
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(kNumBands));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
|
||||
kNumBands, kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
std::vector<std::vector<float>> input_block(
|
||||
kNumBands, std::vector<float>(kBlockSize, 1.f));
|
||||
EXPECT_TRUE(delay_buffer->Insert(input_block));
|
||||
@ -60,7 +67,10 @@ TEST(RenderDelayBuffer, AvailableBlock) {
|
||||
|
||||
// Verifies the SetDelay method.
|
||||
TEST(RenderDelayBuffer, SetDelay) {
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(1));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
|
||||
1, kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
EXPECT_EQ(0u, delay_buffer->Delay());
|
||||
for (size_t delay = 0; delay < 20; ++delay) {
|
||||
delay_buffer->SetDelay(delay);
|
||||
@ -74,7 +84,10 @@ TEST(RenderDelayBuffer, SetDelay) {
|
||||
// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
|
||||
// tests on test bots has been fixed.
|
||||
TEST(RenderDelayBuffer, DISABLED_WrongDelay) {
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(3));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
|
||||
3, kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
EXPECT_DEATH(delay_buffer->SetDelay(21), "");
|
||||
}
|
||||
|
||||
@ -82,8 +95,10 @@ TEST(RenderDelayBuffer, DISABLED_WrongDelay) {
|
||||
TEST(RenderDelayBuffer, WrongNumberOfBands) {
|
||||
for (auto rate : {16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
std::vector<std::vector<float>> block_to_insert(
|
||||
NumBandsForRate(rate < 48000 ? rate + 16000 : 16000),
|
||||
std::vector<float>(kBlockSize, 0.f));
|
||||
@ -95,8 +110,10 @@ TEST(RenderDelayBuffer, WrongNumberOfBands) {
|
||||
TEST(RenderDelayBuffer, WrongBlockLength) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(3));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(RenderDelayBuffer::Create(
|
||||
3, kDownSamplingFactor,
|
||||
GetDownSampledBufferSize(kDownSamplingFactor, kNumMatchedFilters),
|
||||
GetRenderDelayBufferSize(kDownSamplingFactor, kNumMatchedFilters)));
|
||||
std::vector<std::vector<float>> block_to_insert(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize - 1, 0.f));
|
||||
EXPECT_DEATH(delay_buffer->Insert(block_to_insert), "");
|
||||
|
||||
@ -43,7 +43,6 @@ class RenderDelayControllerImpl final : public RenderDelayController {
|
||||
std::unique_ptr<ApmDataDumper> data_dumper_;
|
||||
const size_t default_delay_;
|
||||
size_t delay_;
|
||||
EchoPathDelayEstimator delay_estimator_;
|
||||
size_t blocks_since_last_delay_estimate_ = 300000;
|
||||
int echo_path_delay_samples_;
|
||||
size_t align_call_counter_ = 0;
|
||||
@ -51,6 +50,7 @@ class RenderDelayControllerImpl final : public RenderDelayController {
|
||||
std::vector<float> capture_delay_buffer_;
|
||||
int capture_delay_buffer_index_ = 0;
|
||||
RenderDelayControllerMetrics metrics_;
|
||||
EchoPathDelayEstimator delay_estimator_;
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl);
|
||||
};
|
||||
|
||||
@ -81,10 +81,12 @@ RenderDelayControllerImpl::RenderDelayControllerImpl(
|
||||
default_delay_(
|
||||
std::max(config.delay.default_delay, kMinEchoPathDelayBlocks)),
|
||||
delay_(default_delay_),
|
||||
delay_estimator_(data_dumper_.get(), config),
|
||||
echo_path_delay_samples_(default_delay_ * kBlockSize),
|
||||
capture_delay_buffer_(kBlockSize * (kMaxApiCallsJitterBlocks + 2), 0.f) {
|
||||
capture_delay_buffer_(kBlockSize * (kMaxApiCallsJitterBlocks + 2), 0.f),
|
||||
delay_estimator_(data_dumper_.get(), config) {
|
||||
RTC_DCHECK(ValidFullBandRate(sample_rate_hz));
|
||||
delay_estimator_.LogDelayEstimationProperties(sample_rate_hz,
|
||||
capture_delay_buffer_.size());
|
||||
}
|
||||
|
||||
RenderDelayControllerImpl::~RenderDelayControllerImpl() = default;
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
#include "modules/audio_processing/aec3/aec3_common.h"
|
||||
#include "modules/audio_processing/aec3/block_processor.h"
|
||||
#include "modules/audio_processing/aec3/decimator_by_4.h"
|
||||
#include "modules/audio_processing/aec3/decimator.h"
|
||||
#include "modules/audio_processing/aec3/render_delay_buffer.h"
|
||||
#include "modules/audio_processing/logging/apm_data_dumper.h"
|
||||
#include "modules/audio_processing/test/echo_canceller_test_tools.h"
|
||||
@ -40,21 +40,33 @@ std::string ProduceDebugText(int sample_rate_hz, size_t delay) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
constexpr size_t kDownSamplingFactors[] = {2, 4, 8};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies the output of GetDelay when there are no AnalyzeRender calls.
|
||||
TEST(RenderDelayController, NoRenderSignal) {
|
||||
std::vector<float> block(kBlockSize, 0.f);
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
EXPECT_EQ(kMinEchoPathDelayBlocks,
|
||||
delay_controller->GetDelay(
|
||||
delay_buffer->GetDownsampledRenderBuffer(), block));
|
||||
for (size_t num_matched_filters = 4; num_matched_filters == 10;
|
||||
num_matched_filters++) {
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor,
|
||||
num_matched_filters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
num_matched_filters)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
for (size_t k = 0; k < 100; ++k) {
|
||||
EXPECT_EQ(kMinEchoPathDelayBlocks,
|
||||
delay_controller->GetDelay(
|
||||
delay_buffer->GetDownsampledRenderBuffer(), block));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,21 +75,31 @@ TEST(RenderDelayController, NoRenderSignal) {
|
||||
TEST(RenderDelayController, BasicApiCalls) {
|
||||
std::vector<float> capture_block(kBlockSize, 0.f);
|
||||
size_t delay_blocks = 0;
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
for (size_t k = 0; k < 10; ++k) {
|
||||
render_delay_buffer->Insert(render_block);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
delay_blocks = delay_controller->GetDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture_block);
|
||||
for (size_t num_matched_filters = 4; num_matched_filters == 10;
|
||||
num_matched_filters++) {
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor,
|
||||
num_matched_filters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
num_matched_filters)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
for (size_t k = 0; k < 10; ++k) {
|
||||
render_delay_buffer->Insert(render_block);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
delay_blocks = delay_controller->GetDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture_block);
|
||||
}
|
||||
EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples());
|
||||
EXPECT_EQ(kMinEchoPathDelayBlocks, delay_blocks);
|
||||
}
|
||||
}
|
||||
EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples());
|
||||
EXPECT_EQ(kMinEchoPathDelayBlocks, delay_blocks);
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,38 +109,49 @@ TEST(RenderDelayController, Alignment) {
|
||||
Random random_generator(42U);
|
||||
std::vector<float> capture_block(kBlockSize, 0.f);
|
||||
size_t delay_blocks = 0;
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
for (size_t num_matched_filters = 4; num_matched_filters == 10;
|
||||
num_matched_filters++) {
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
|
||||
for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
DelayBuffer<float> signal_delay_buffer(delay_samples);
|
||||
for (size_t k = 0; k < (400 + delay_samples / kBlockSize); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render_block[0]);
|
||||
signal_delay_buffer.Delay(render_block[0], capture_block);
|
||||
render_delay_buffer->Insert(render_block);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
delay_blocks = delay_controller->GetDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(), capture_block);
|
||||
for (size_t delay_samples : {15, 50, 150, 200, 800, 4000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor,
|
||||
num_matched_filters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
num_matched_filters)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
DelayBuffer<float> signal_delay_buffer(delay_samples);
|
||||
for (size_t k = 0; k < (400 + delay_samples / kBlockSize); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render_block[0]);
|
||||
signal_delay_buffer.Delay(render_block[0], capture_block);
|
||||
render_delay_buffer->Insert(render_block);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
delay_blocks = delay_controller->GetDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
capture_block);
|
||||
}
|
||||
|
||||
constexpr int kDelayHeadroomBlocks = 1;
|
||||
size_t expected_delay_blocks =
|
||||
std::max(0, static_cast<int>(delay_samples / kBlockSize) -
|
||||
kDelayHeadroomBlocks);
|
||||
|
||||
EXPECT_EQ(expected_delay_blocks, delay_blocks);
|
||||
|
||||
const rtc::Optional<size_t> headroom_samples =
|
||||
delay_controller->AlignmentHeadroomSamples();
|
||||
ASSERT_TRUE(headroom_samples);
|
||||
EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize,
|
||||
*headroom_samples, 4);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int kDelayHeadroomBlocks = 1;
|
||||
size_t expected_delay_blocks =
|
||||
std::max(0, static_cast<int>(delay_samples / kBlockSize) -
|
||||
kDelayHeadroomBlocks);
|
||||
|
||||
EXPECT_EQ(expected_delay_blocks, delay_blocks);
|
||||
|
||||
const rtc::Optional<size_t> headroom_samples =
|
||||
delay_controller->AlignmentHeadroomSamples();
|
||||
ASSERT_TRUE(headroom_samples);
|
||||
EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize, *headroom_samples,
|
||||
4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,35 +161,45 @@ TEST(RenderDelayController, Alignment) {
|
||||
TEST(RenderDelayController, NonCausalAlignment) {
|
||||
Random random_generator(42U);
|
||||
size_t delay_blocks = 0;
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> capture_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
for (size_t num_matched_filters = 4; num_matched_filters == 10;
|
||||
num_matched_filters++) {
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
std::vector<std::vector<float>> capture_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
|
||||
for (int delay_samples : {-15, -50, -150, -200}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate, -delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
DelayBuffer<float> signal_delay_buffer(-delay_samples);
|
||||
for (int k = 0; k < (400 - delay_samples / static_cast<int>(kBlockSize));
|
||||
++k) {
|
||||
RandomizeSampleVector(&random_generator, capture_block[0]);
|
||||
signal_delay_buffer.Delay(capture_block[0], render_block[0]);
|
||||
render_delay_buffer->Insert(render_block);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
delay_blocks = delay_controller->GetDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
capture_block[0]);
|
||||
for (int delay_samples : {-15, -50, -150, -200}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate, -delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor,
|
||||
num_matched_filters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
num_matched_filters)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
DelayBuffer<float> signal_delay_buffer(-delay_samples);
|
||||
for (int k = 0;
|
||||
k < (400 - delay_samples / static_cast<int>(kBlockSize)); ++k) {
|
||||
RandomizeSampleVector(&random_generator, capture_block[0]);
|
||||
signal_delay_buffer.Delay(capture_block[0], render_block[0]);
|
||||
render_delay_buffer->Insert(render_block);
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
delay_blocks = delay_controller->GetDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
capture_block[0]);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0u, delay_blocks);
|
||||
|
||||
const rtc::Optional<size_t> headroom_samples =
|
||||
delay_controller->AlignmentHeadroomSamples();
|
||||
ASSERT_FALSE(headroom_samples);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(0u, delay_blocks);
|
||||
|
||||
const rtc::Optional<size_t> headroom_samples =
|
||||
delay_controller->AlignmentHeadroomSamples();
|
||||
ASSERT_FALSE(headroom_samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,66 +209,86 @@ TEST(RenderDelayController, NonCausalAlignment) {
|
||||
TEST(RenderDelayController, AlignmentWithJitter) {
|
||||
Random random_generator(42U);
|
||||
std::vector<float> capture_block(kBlockSize, 0.f);
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
for (size_t delay_samples : {15, 50, 300, 800}) {
|
||||
size_t delay_blocks = 0;
|
||||
SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
DelayBuffer<float> signal_delay_buffer(delay_samples);
|
||||
for (size_t j = 0;
|
||||
j <
|
||||
(1000 + delay_samples / kBlockSize) / kMaxApiCallsJitterBlocks + 1;
|
||||
++j) {
|
||||
std::vector<std::vector<float>> capture_block_buffer;
|
||||
for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 1); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render_block[0]);
|
||||
signal_delay_buffer.Delay(render_block[0], capture_block);
|
||||
capture_block_buffer.push_back(capture_block);
|
||||
render_delay_buffer->Insert(render_block);
|
||||
for (size_t num_matched_filters = 4; num_matched_filters == 10;
|
||||
num_matched_filters++) {
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
std::vector<std::vector<float>> render_block(
|
||||
NumBandsForRate(rate), std::vector<float>(kBlockSize, 0.f));
|
||||
for (size_t delay_samples : {15, 50, 300, 800}) {
|
||||
size_t delay_blocks = 0;
|
||||
SCOPED_TRACE(ProduceDebugText(rate, delay_samples));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor,
|
||||
num_matched_filters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
num_matched_filters)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
DelayBuffer<float> signal_delay_buffer(delay_samples);
|
||||
for (size_t j = 0; j < (1000 + delay_samples / kBlockSize) /
|
||||
kMaxApiCallsJitterBlocks +
|
||||
1;
|
||||
++j) {
|
||||
std::vector<std::vector<float>> capture_block_buffer;
|
||||
for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 1); ++k) {
|
||||
RandomizeSampleVector(&random_generator, render_block[0]);
|
||||
signal_delay_buffer.Delay(render_block[0], capture_block);
|
||||
capture_block_buffer.push_back(capture_block);
|
||||
render_delay_buffer->Insert(render_block);
|
||||
}
|
||||
for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 1); ++k) {
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
delay_blocks = delay_controller->GetDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
capture_block_buffer[k]);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int kDelayHeadroomBlocks = 1;
|
||||
size_t expected_delay_blocks =
|
||||
std::max(0, static_cast<int>(delay_samples / kBlockSize) -
|
||||
kDelayHeadroomBlocks);
|
||||
if (expected_delay_blocks < 2) {
|
||||
expected_delay_blocks = 0;
|
||||
}
|
||||
|
||||
EXPECT_EQ(expected_delay_blocks, delay_blocks);
|
||||
|
||||
const rtc::Optional<size_t> headroom_samples =
|
||||
delay_controller->AlignmentHeadroomSamples();
|
||||
ASSERT_TRUE(headroom_samples);
|
||||
EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize,
|
||||
*headroom_samples, 4);
|
||||
}
|
||||
for (size_t k = 0; k < (kMaxApiCallsJitterBlocks - 1); ++k) {
|
||||
render_delay_buffer->UpdateBuffers();
|
||||
delay_blocks = delay_controller->GetDelay(
|
||||
render_delay_buffer->GetDownsampledRenderBuffer(),
|
||||
capture_block_buffer[k]);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int kDelayHeadroomBlocks = 1;
|
||||
size_t expected_delay_blocks =
|
||||
std::max(0, static_cast<int>(delay_samples / kBlockSize) -
|
||||
kDelayHeadroomBlocks);
|
||||
if (expected_delay_blocks < 2) {
|
||||
expected_delay_blocks = 0;
|
||||
}
|
||||
|
||||
EXPECT_EQ(expected_delay_blocks, delay_blocks);
|
||||
|
||||
const rtc::Optional<size_t> headroom_samples =
|
||||
delay_controller->AlignmentHeadroomSamples();
|
||||
ASSERT_TRUE(headroom_samples);
|
||||
EXPECT_NEAR(delay_samples - delay_blocks * kBlockSize, *headroom_samples,
|
||||
4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verifies the initial value for the AlignmentHeadroomSamples.
|
||||
TEST(RenderDelayController, InitialHeadroom) {
|
||||
std::vector<float> render_block(kBlockSize, 0.f);
|
||||
std::vector<float> capture_block(kBlockSize, 0.f);
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples());
|
||||
for (size_t num_matched_filters = 4; num_matched_filters == 10;
|
||||
num_matched_filters++) {
|
||||
for (auto down_sampling_factor : kDownSamplingFactors) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(
|
||||
NumBandsForRate(rate), down_sampling_factor,
|
||||
GetDownSampledBufferSize(down_sampling_factor,
|
||||
num_matched_filters),
|
||||
GetRenderDelayBufferSize(down_sampling_factor,
|
||||
num_matched_filters)));
|
||||
std::unique_ptr<RenderDelayController> delay_controller(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate));
|
||||
EXPECT_FALSE(delay_controller->AlignmentHeadroomSamples());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +300,9 @@ TEST(RenderDelayController, WrongCaptureSize) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate), 4,
|
||||
GetDownSampledBufferSize(4, 4),
|
||||
GetRenderDelayBufferSize(4, 4)));
|
||||
EXPECT_DEATH(
|
||||
std::unique_ptr<RenderDelayController>(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate))
|
||||
@ -254,7 +319,9 @@ TEST(RenderDelayController, DISABLED_WrongSampleRate) {
|
||||
for (auto rate : {-1, 0, 8001, 16001}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
std::unique_ptr<RenderDelayBuffer> render_delay_buffer(
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate)));
|
||||
RenderDelayBuffer::Create(NumBandsForRate(rate), 4,
|
||||
GetDownSampledBufferSize(4, 4),
|
||||
GetRenderDelayBufferSize(4, 4)));
|
||||
EXPECT_DEATH(
|
||||
std::unique_ptr<RenderDelayController>(
|
||||
RenderDelayController::Create(EchoCanceller3Config(), rate)),
|
||||
|
||||
@ -93,7 +93,7 @@ float UpperBandsGain(
|
||||
// or if the power in upper frequencies is low, do not bound the gain in the
|
||||
// upper bands.
|
||||
float anti_howling_gain;
|
||||
constexpr float kThreshold = kSubBlockSize * 10.f * 10.f;
|
||||
constexpr float kThreshold = kBlockSize * 10.f * 10.f / 4.f;
|
||||
if (high_band_energy < std::max(low_band_energy, kThreshold)) {
|
||||
anti_howling_gain = 1.f;
|
||||
} else {
|
||||
|
||||
@ -1137,6 +1137,8 @@ class VoiceDetection {
|
||||
struct EchoCanceller3Config {
|
||||
struct Delay {
|
||||
size_t default_delay = 5;
|
||||
size_t down_sampling_factor = 4;
|
||||
size_t num_filters = 4;
|
||||
} delay;
|
||||
|
||||
struct Erle {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user