* Copyright (c) 2016 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/echo_canceller3.h"
#include <deque>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "modules/audio_processing/aec3/aec3_common.h"
#include "modules/audio_processing/aec3/block_processor.h"
#include "modules/audio_processing/aec3/frame_blocker.h"
#include "modules/audio_processing/aec3/mock/mock_block_processor.h"
#include "modules/audio_processing/audio_buffer.h"
#include "modules/audio_processing/high_pass_filter.h"
#include "modules/audio_processing/utility/cascaded_biquad_filter.h"
#include "rtc_base/strings/string_builder.h"
#include "test/field_trial.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::_;
using ::testing::StrictMock;
void PopulateInputFrame(size_t frame_length,
size_t num_bands,
size_t frame_index,
float* const* frame,
int offset) {
for (size_t k = 0; k < num_bands; ++k) {
for (size_t i = 0; i < frame_length; ++i) {
float value = static_cast<int>(frame_index * frame_length + i) + offset;
frame[k][i] = (value > 0 ? 5000 * k + value : 0);
}
}
}
void PopulateInputFrame(size_t frame_length,
size_t frame_index,
float* frame,
int offset) {
for (size_t i = 0; i < frame_length; ++i) {
float value = static_cast<int>(frame_index * frame_length + i) + offset;
frame[i] = std::max(value, 0.f);
}
}
bool VerifyOutputFrameBitexactness(size_t frame_length,
size_t num_bands,
size_t frame_index,
const float* const* frame,
int offset) {
float reference_frame_data[kMaxNumBands][2 * kSubFrameLength];
float* reference_frame[kMaxNumBands];
for (size_t k = 0; k < num_bands; ++k) {
reference_frame[k] = &reference_frame_data[k][0];
}
PopulateInputFrame(frame_length, num_bands, frame_index, reference_frame,
offset);
for (size_t k = 0; k < num_bands; ++k) {
for (size_t i = 0; i < frame_length; ++i) {
if (reference_frame[k][i] != frame[k][i]) {
return false;
}
}
}
return true;
}
bool VerifyOutputFrameBitexactness(rtc::ArrayView<const float> reference,
rtc::ArrayView<const float> frame,
int offset) {
for (size_t k = 0; k < frame.size(); ++k) {
int reference_index = static_cast<int>(k) + offset;
if (reference_index >= 0) {
if (reference[reference_index] != frame[k]) {
return false;
}
}
}
return true;
}
class CaptureTransportVerificationProcessor : public BlockProcessor {
public:
explicit CaptureTransportVerificationProcessor(size_t num_bands) {}
CaptureTransportVerificationProcessor() = delete;
CaptureTransportVerificationProcessor(
const CaptureTransportVerificationProcessor&) = delete;
CaptureTransportVerificationProcessor& operator=(
const CaptureTransportVerificationProcessor&) = delete;
~CaptureTransportVerificationProcessor() override = default;
void ProcessCapture(bool level_change,
bool saturated_microphone_signal,
Block* linear_output,
Block* capture_block) override {}
void BufferRender(const Block& block) override {}
void UpdateEchoLeakageStatus(bool leakage_detected) override {}
void GetMetrics(EchoControl::Metrics* metrics) const override {}
void SetAudioBufferDelay(int delay_ms) override {}
void SetCaptureOutputUsage(bool capture_output_used) {}
};
class RenderTransportVerificationProcessor : public BlockProcessor {
public:
explicit RenderTransportVerificationProcessor(size_t num_bands) {}
RenderTransportVerificationProcessor() = delete;
RenderTransportVerificationProcessor(
const RenderTransportVerificationProcessor&) = delete;
RenderTransportVerificationProcessor& operator=(
const RenderTransportVerificationProcessor&) = delete;
~RenderTransportVerificationProcessor() override = default;
void ProcessCapture(bool level_change,
bool saturated_microphone_signal,
Block* linear_output,
Block* capture_block) override {
Block render_block = received_render_blocks_.front();
received_render_blocks_.pop_front();
capture_block->Swap(render_block);
}
void BufferRender(const Block& block) override {
received_render_blocks_.push_back(block);
}
void UpdateEchoLeakageStatus(bool leakage_detected) override {}
void GetMetrics(EchoControl::Metrics* metrics) const override {}
void SetAudioBufferDelay(int delay_ms) override {}
void SetCaptureOutputUsage(bool capture_output_used) {}
private:
std::deque<Block> received_render_blocks_;
};
std::string ProduceDebugText(int sample_rate_hz) {
rtc::StringBuilder ss;
ss << "Sample rate: " << sample_rate_hz;
return ss.Release();
}
std::string ProduceDebugText(int sample_rate_hz, int variant) {
rtc::StringBuilder ss;
ss << "Sample rate: " << sample_rate_hz << ", variant: " << variant;
return ss.Release();
}
void RunAecInStereo(AudioBuffer& buffer,
EchoCanceller3& aec3,
float channel_0_value,
float channel_1_value) {
rtc::ArrayView<float> data_channel_0(&buffer.channels()[0][0],
buffer.num_frames());
std::fill(data_channel_0.begin(), data_channel_0.end(), channel_0_value);
rtc::ArrayView<float> data_channel_1(&buffer.channels()[1][0],
buffer.num_frames());
std::fill(data_channel_1.begin(), data_channel_1.end(), channel_1_value);
aec3.AnalyzeRender(&buffer);
aec3.AnalyzeCapture(&buffer);
aec3.ProcessCapture(&buffer, false);
}
void RunAecInSMono(AudioBuffer& buffer,
EchoCanceller3& aec3,
float channel_0_value) {
rtc::ArrayView<float> data_channel_0(&buffer.channels()[0][0],
buffer.num_frames());
std::fill(data_channel_0.begin(), data_channel_0.end(), channel_0_value);
aec3.AnalyzeRender(&buffer);
aec3.AnalyzeCapture(&buffer);
aec3.ProcessCapture(&buffer, false);
}
}
class EchoCanceller3Tester {
public:
explicit EchoCanceller3Tester(int sample_rate_hz)
: sample_rate_hz_(sample_rate_hz),
num_bands_(NumBandsForRate(sample_rate_hz_)),
frame_length_(160),
fullband_frame_length_(rtc::CheckedDivExact(sample_rate_hz_, 100)),
capture_buffer_(fullband_frame_length_ * 100,
1,
fullband_frame_length_ * 100,
1,
fullband_frame_length_ * 100,
1),
render_buffer_(fullband_frame_length_ * 100,
1,
fullband_frame_length_ * 100,
1,
fullband_frame_length_ * 100,
1) {}
EchoCanceller3Tester() = delete;
EchoCanceller3Tester(const EchoCanceller3Tester&) = delete;
EchoCanceller3Tester& operator=(const EchoCanceller3Tester&) = delete;
void RunCaptureTransportVerificationTest() {
EchoCanceller3 aec3(EchoCanceller3Config(),
absl::nullopt, sample_rate_hz_,
1, 1);
aec3.SetBlockProcessorForTesting(
std::make_unique<CaptureTransportVerificationProcessor>(num_bands_));
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
++frame_index) {
aec3.AnalyzeCapture(&capture_buffer_);
OptionalBandSplit();
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&capture_buffer_.split_bands(0)[0], 0);
PopulateInputFrame(frame_length_, frame_index,
&render_buffer_.channels()[0][0], 0);
aec3.AnalyzeRender(&render_buffer_);
aec3.ProcessCapture(&capture_buffer_, false);
EXPECT_TRUE(VerifyOutputFrameBitexactness(
frame_length_, num_bands_, frame_index,
&capture_buffer_.split_bands(0)[0], -64));
}
}
void RunRenderTransportVerificationTest() {
EchoCanceller3 aec3(EchoCanceller3Config(),
absl::nullopt, sample_rate_hz_,
1, 1);
aec3.SetBlockProcessorForTesting(
std::make_unique<RenderTransportVerificationProcessor>(num_bands_));
std::vector<std::vector<float>> render_input(1);
std::vector<float> capture_output;
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
++frame_index) {
aec3.AnalyzeCapture(&capture_buffer_);
OptionalBandSplit();
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&capture_buffer_.split_bands(0)[0], 100);
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&render_buffer_.split_bands(0)[0], 0);
for (size_t k = 0; k < frame_length_; ++k) {
render_input[0].push_back(render_buffer_.split_bands(0)[0][k]);
}
aec3.AnalyzeRender(&render_buffer_);
aec3.ProcessCapture(&capture_buffer_, false);
for (size_t k = 0; k < frame_length_; ++k) {
capture_output.push_back(capture_buffer_.split_bands(0)[0][k]);
}
}
EXPECT_TRUE(
VerifyOutputFrameBitexactness(render_input[0], capture_output, -64));
}
enum class EchoPathChangeTestVariant { kNone, kOneSticky, kOneNonSticky };
void RunEchoPathChangeVerificationTest(
EchoPathChangeTestVariant echo_path_change_test_variant) {
constexpr size_t kNumFullBlocksPerFrame = 160 / kBlockSize;
constexpr size_t kExpectedNumBlocksToProcess =
(kNumFramesToProcess * 160) / kBlockSize;
std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
block_processor_mock(
new StrictMock<webrtc::test::MockBlockProcessor>());
EXPECT_CALL(*block_processor_mock, BufferRender(_))
.Times(kExpectedNumBlocksToProcess);
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(_)).Times(0);
switch (echo_path_change_test_variant) {
case EchoPathChangeTestVariant::kNone:
EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _, _))
.Times(kExpectedNumBlocksToProcess);
break;
case EchoPathChangeTestVariant::kOneSticky:
EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _, _))
.Times(kExpectedNumBlocksToProcess);
break;
case EchoPathChangeTestVariant::kOneNonSticky:
EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _, _))
.Times(kNumFullBlocksPerFrame);
EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _, _))
.Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
break;
}
EchoCanceller3 aec3(EchoCanceller3Config(),
absl::nullopt, sample_rate_hz_,
1, 1);
aec3.SetBlockProcessorForTesting(std::move(block_processor_mock));
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
++frame_index) {
bool echo_path_change = false;
switch (echo_path_change_test_variant) {
case EchoPathChangeTestVariant::kNone:
break;
case EchoPathChangeTestVariant::kOneSticky:
echo_path_change = true;
break;
case EchoPathChangeTestVariant::kOneNonSticky:
if (frame_index == 0) {
echo_path_change = true;
}
break;
}
aec3.AnalyzeCapture(&capture_buffer_);
OptionalBandSplit();
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&capture_buffer_.split_bands(0)[0], 0);
PopulateInputFrame(frame_length_, frame_index,
&render_buffer_.channels()[0][0], 0);
aec3.AnalyzeRender(&render_buffer_);
aec3.ProcessCapture(&capture_buffer_, echo_path_change);
}
}
enum class EchoLeakageTestVariant {
kNone,
kFalseSticky,
kTrueSticky,
kTrueNonSticky
};
void RunEchoLeakageVerificationTest(
EchoLeakageTestVariant leakage_report_variant) {
constexpr size_t kExpectedNumBlocksToProcess =
(kNumFramesToProcess * 160) / kBlockSize;
std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
block_processor_mock(
new StrictMock<webrtc::test::MockBlockProcessor>());
EXPECT_CALL(*block_processor_mock, BufferRender(_))
.Times(kExpectedNumBlocksToProcess);
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, _, _, _))
.Times(kExpectedNumBlocksToProcess);
switch (leakage_report_variant) {
case EchoLeakageTestVariant::kNone:
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(_)).Times(0);
break;
case EchoLeakageTestVariant::kFalseSticky:
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(false))
.Times(1);
break;
case EchoLeakageTestVariant::kTrueSticky:
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(true))
.Times(1);
break;
case EchoLeakageTestVariant::kTrueNonSticky: {
::testing::InSequence s;
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(true))
.Times(1);
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(false))
.Times(kNumFramesToProcess - 1);
} break;
}
EchoCanceller3 aec3(EchoCanceller3Config(),
absl::nullopt, sample_rate_hz_,
1, 1);
aec3.SetBlockProcessorForTesting(std::move(block_processor_mock));
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
++frame_index) {
switch (leakage_report_variant) {
case EchoLeakageTestVariant::kNone:
break;
case EchoLeakageTestVariant::kFalseSticky:
if (frame_index == 0) {
aec3.UpdateEchoLeakageStatus(false);
}
break;
case EchoLeakageTestVariant::kTrueSticky:
if (frame_index == 0) {
aec3.UpdateEchoLeakageStatus(true);
}
break;
case EchoLeakageTestVariant::kTrueNonSticky:
if (frame_index == 0) {
aec3.UpdateEchoLeakageStatus(true);
} else {
aec3.UpdateEchoLeakageStatus(false);
}
break;
}
aec3.AnalyzeCapture(&capture_buffer_);
OptionalBandSplit();
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&capture_buffer_.split_bands(0)[0], 0);
PopulateInputFrame(frame_length_, frame_index,
&render_buffer_.channels()[0][0], 0);
aec3.AnalyzeRender(&render_buffer_);
aec3.ProcessCapture(&capture_buffer_, false);
}
}
enum class SaturationTestVariant { kNone, kOneNegative, kOnePositive };
void RunCaptureSaturationVerificationTest(
SaturationTestVariant saturation_variant) {
const size_t kNumFullBlocksPerFrame = 160 / kBlockSize;
const size_t kExpectedNumBlocksToProcess =
(kNumFramesToProcess * 160) / kBlockSize;
std::unique_ptr<testing::StrictMock<webrtc::test::MockBlockProcessor>>
block_processor_mock(
new StrictMock<webrtc::test::MockBlockProcessor>());
EXPECT_CALL(*block_processor_mock, BufferRender(_))
.Times(kExpectedNumBlocksToProcess);
EXPECT_CALL(*block_processor_mock, UpdateEchoLeakageStatus(_)).Times(0);
switch (saturation_variant) {
case SaturationTestVariant::kNone:
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _))
.Times(kExpectedNumBlocksToProcess);
break;
case SaturationTestVariant::kOneNegative: {
::testing::InSequence s;
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _, _))
.Times(kNumFullBlocksPerFrame);
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _))
.Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
} break;
case SaturationTestVariant::kOnePositive: {
::testing::InSequence s;
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _, _))
.Times(kNumFullBlocksPerFrame);
EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _))
.Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
} break;
}
EchoCanceller3 aec3(EchoCanceller3Config(),
absl::nullopt, sample_rate_hz_,
1, 1);
aec3.SetBlockProcessorForTesting(std::move(block_processor_mock));
for (size_t frame_index = 0; frame_index < kNumFramesToProcess;
++frame_index) {
for (int k = 0; k < fullband_frame_length_; ++k) {
capture_buffer_.channels()[0][k] = 0.f;
}
switch (saturation_variant) {
case SaturationTestVariant::kNone:
break;
case SaturationTestVariant::kOneNegative:
if (frame_index == 0) {
capture_buffer_.channels()[0][10] = -32768.f;
}
break;
case SaturationTestVariant::kOnePositive:
if (frame_index == 0) {
capture_buffer_.channels()[0][10] = 32767.f;
}
break;
}
aec3.AnalyzeCapture(&capture_buffer_);
OptionalBandSplit();
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&capture_buffer_.split_bands(0)[0], 0);
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&render_buffer_.split_bands(0)[0], 0);
aec3.AnalyzeRender(&render_buffer_);
aec3.ProcessCapture(&capture_buffer_, false);
}
}
void RunRenderSwapQueueVerificationTest() {
const EchoCanceller3Config config;
EchoCanceller3 aec3(config, absl::nullopt,
sample_rate_hz_, 1, 1);
aec3.SetBlockProcessorForTesting(
std::make_unique<RenderTransportVerificationProcessor>(num_bands_));
std::vector<std::vector<float>> render_input(1);
std::vector<float> capture_output;
for (size_t frame_index = 0; frame_index < kRenderTransferQueueSizeFrames;
++frame_index) {
if (sample_rate_hz_ > 16000) {
render_buffer_.SplitIntoFrequencyBands();
}
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&render_buffer_.split_bands(0)[0], 0);
if (sample_rate_hz_ > 16000) {
render_buffer_.SplitIntoFrequencyBands();
}
for (size_t k = 0; k < frame_length_; ++k) {
render_input[0].push_back(render_buffer_.split_bands(0)[0][k]);
}
aec3.AnalyzeRender(&render_buffer_);
}
for (size_t frame_index = 0; frame_index < kRenderTransferQueueSizeFrames;
++frame_index) {
aec3.AnalyzeCapture(&capture_buffer_);
if (sample_rate_hz_ > 16000) {
capture_buffer_.SplitIntoFrequencyBands();
}
PopulateInputFrame(frame_length_, num_bands_, frame_index,
&capture_buffer_.split_bands(0)[0], 0);
aec3.ProcessCapture(&capture_buffer_, false);
for (size_t k = 0; k < frame_length_; ++k) {
capture_output.push_back(capture_buffer_.split_bands(0)[0][k]);
}
}
EXPECT_TRUE(
VerifyOutputFrameBitexactness(render_input[0], capture_output, -64));
}
void RunRenderPipelineSwapQueueOverrunReturnValueTest() {
EchoCanceller3 aec3(EchoCanceller3Config(),
absl::nullopt, sample_rate_hz_,
1, 1);
constexpr size_t kRenderTransferQueueSize = 30;
for (size_t k = 0; k < 2; ++k) {
for (size_t frame_index = 0; frame_index < kRenderTransferQueueSize;
++frame_index) {
if (sample_rate_hz_ > 16000) {
render_buffer_.SplitIntoFrequencyBands();
}
PopulateInputFrame(frame_length_, frame_index,
&render_buffer_.channels()[0][0], 0);
aec3.AnalyzeRender(&render_buffer_);
}
}
}
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
void RunAnalyzeRenderNumBandsCheckVerification() {
const int aec3_sample_rate_hz = sample_rate_hz_ == 48000 ? 32000 : 48000;
EchoCanceller3 aec3(EchoCanceller3Config(),
absl::nullopt,
aec3_sample_rate_hz, 1, 1);
PopulateInputFrame(frame_length_, 0, &render_buffer_.channels_f()[0][0], 0);
EXPECT_DEATH(aec3.AnalyzeRender(&render_buffer_), "");
}
void RunProcessCaptureNumBandsCheckVerification() {
const int aec3_sample_rate_hz = sample_rate_hz_ == 48000 ? 32000 : 48000;
EchoCanceller3 aec3(EchoCanceller3Config(),
absl::nullopt,
aec3_sample_rate_hz, 1, 1);
PopulateInputFrame(frame_length_, num_bands_, 0,
&capture_buffer_.split_bands_f(0)[0], 100);
EXPECT_DEATH(aec3.ProcessCapture(&capture_buffer_, false), "");
}
#endif
private:
void OptionalBandSplit() {
if (sample_rate_hz_ > 16000) {
capture_buffer_.SplitIntoFrequencyBands();
render_buffer_.SplitIntoFrequencyBands();
}
}
static constexpr size_t kNumFramesToProcess = 20;
const int sample_rate_hz_;
const size_t num_bands_;
const size_t frame_length_;
const int fullband_frame_length_;
AudioBuffer capture_buffer_;
AudioBuffer render_buffer_;
};
TEST(EchoCanceller3Buffering, CaptureBitexactness) {
for (auto rate : {16000, 32000, 48000}) {
SCOPED_TRACE(ProduceDebugText(rate));
EchoCanceller3Tester(rate).RunCaptureTransportVerificationTest();
}
}
TEST(EchoCanceller3Buffering, RenderBitexactness) {
for (auto rate : {16000, 32000, 48000}) {
SCOPED_TRACE(ProduceDebugText(rate));
EchoCanceller3Tester(rate).RunRenderTransportVerificationTest();
}
}
TEST(EchoCanceller3Buffering, RenderSwapQueue) {
EchoCanceller3Tester(16000).RunRenderSwapQueueVerificationTest();
}
TEST(EchoCanceller3Buffering, RenderSwapQueueOverrunReturnValue) {
for (auto rate : {16000, 32000, 48000}) {
SCOPED_TRACE(ProduceDebugText(rate));
EchoCanceller3Tester(rate)
.RunRenderPipelineSwapQueueOverrunReturnValueTest();
}
}
TEST(EchoCanceller3Messaging, CaptureSaturation) {
auto variants = {EchoCanceller3Tester::SaturationTestVariant::kNone,
EchoCanceller3Tester::SaturationTestVariant::kOneNegative,
EchoCanceller3Tester::SaturationTestVariant::kOnePositive};
for (auto rate : {16000, 32000, 48000}) {
for (auto variant : variants) {
SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
EchoCanceller3Tester(rate).RunCaptureSaturationVerificationTest(variant);
}
}
}
TEST(EchoCanceller3Messaging, EchoPathChange) {
auto variants = {
EchoCanceller3Tester::EchoPathChangeTestVariant::kNone,
EchoCanceller3Tester::EchoPathChangeTestVariant::kOneSticky,
EchoCanceller3Tester::EchoPathChangeTestVariant::kOneNonSticky};
for (auto rate : {16000, 32000, 48000}) {
for (auto variant : variants) {
SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
EchoCanceller3Tester(rate).RunEchoPathChangeVerificationTest(variant);
}
}
}
TEST(EchoCanceller3Messaging, EchoLeakage) {
auto variants = {
EchoCanceller3Tester::EchoLeakageTestVariant::kNone,
EchoCanceller3Tester::EchoLeakageTestVariant::kFalseSticky,
EchoCanceller3Tester::EchoLeakageTestVariant::kTrueSticky,
EchoCanceller3Tester::EchoLeakageTestVariant::kTrueNonSticky};
for (auto rate : {16000, 32000, 48000}) {
for (auto variant : variants) {
SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(variant)));
EchoCanceller3Tester(rate).RunEchoLeakageVerificationTest(variant);
}
}
}
TEST(EchoCanceller3FieldTrials, Aec3SuppressorAntiHowlingGainOverride) {
EchoCanceller3Config default_config;
EchoCanceller3Config adjusted_config = AdjustConfig(default_config);
ASSERT_EQ(
default_config.suppressor.high_bands_suppression.anti_howling_gain,
adjusted_config.suppressor.high_bands_suppression.anti_howling_gain);
webrtc::test::ScopedFieldTrials field_trials(
"WebRTC-Aec3SuppressorAntiHowlingGainOverride/0.02/");
adjusted_config = AdjustConfig(default_config);
ASSERT_NE(
default_config.suppressor.high_bands_suppression.anti_howling_gain,
adjusted_config.suppressor.high_bands_suppression.anti_howling_gain);
EXPECT_FLOAT_EQ(
0.02f,
adjusted_config.suppressor.high_bands_suppression.anti_howling_gain);
}
TEST(EchoCanceller3FieldTrials, Aec3EnforceLowActiveRenderLimit) {
EchoCanceller3Config default_config;
EchoCanceller3Config adjusted_config = AdjustConfig(default_config);
ASSERT_EQ(default_config.render_levels.active_render_limit,
adjusted_config.render_levels.active_render_limit);
webrtc::test::ScopedFieldTrials field_trials(
"WebRTC-Aec3EnforceLowActiveRenderLimit/Enabled/");
adjusted_config = AdjustConfig(default_config);
ASSERT_NE(default_config.render_levels.active_render_limit,
adjusted_config.render_levels.active_render_limit);
EXPECT_FLOAT_EQ(50.f, adjusted_config.render_levels.active_render_limit);
}
TEST(EchoCanceller3FieldTrials, Aec3SuppressorTuningOverrideAllParams) {
webrtc::test::ScopedFieldTrials field_trials(
"WebRTC-Aec3SuppressorTuningOverride/"
"nearend_tuning_mask_lf_enr_transparent:0.1,nearend_tuning_mask_lf_enr_"
"suppress:0.2,nearend_tuning_mask_hf_enr_transparent:0.3,nearend_tuning_"
"mask_hf_enr_suppress:0.4,nearend_tuning_max_inc_factor:0.5,nearend_"
"tuning_max_dec_factor_lf:0.6,normal_tuning_mask_lf_enr_transparent:0.7,"
"normal_tuning_mask_lf_enr_suppress:0.8,normal_tuning_mask_hf_enr_"
"transparent:0.9,normal_tuning_mask_hf_enr_suppress:1.0,normal_tuning_"
"max_inc_factor:1.1,normal_tuning_max_dec_factor_lf:1.2,dominant_nearend_"
"detection_enr_threshold:1.3,dominant_nearend_detection_enr_exit_"
"threshold:1.4,dominant_nearend_detection_snr_threshold:1.5,dominant_"
"nearend_detection_hold_duration:10,dominant_nearend_detection_trigger_"
"threshold:11/");
EchoCanceller3Config default_config;
EchoCanceller3Config adjusted_config = AdjustConfig(default_config);
ASSERT_NE(adjusted_config.suppressor.nearend_tuning.mask_lf.enr_transparent,
default_config.suppressor.nearend_tuning.mask_lf.enr_transparent);
ASSERT_NE(adjusted_config.suppressor.nearend_tuning.mask_lf.enr_suppress,
default_config.suppressor.nearend_tuning.mask_lf.enr_suppress);
ASSERT_NE(adjusted_config.suppressor.nearend_tuning.mask_hf.enr_transparent,
default_config.suppressor.nearend_tuning.mask_hf.enr_transparent);
ASSERT_NE(adjusted_config.suppressor.nearend_tuning.mask_hf.enr_suppress,
default_config.suppressor.nearend_tuning.mask_hf.enr_suppress);
ASSERT_NE(adjusted_config.suppressor.nearend_tuning.max_inc_factor,
default_config.suppressor.nearend_tuning.max_inc_factor);
ASSERT_NE(adjusted_config.suppressor.nearend_tuning.max_dec_factor_lf,
default_config.suppressor.nearend_tuning.max_dec_factor_lf);
ASSERT_NE(adjusted_config.suppressor.normal_tuning.mask_lf.enr_transparent,
default_config.suppressor.normal_tuning.mask_lf.enr_transparent);
ASSERT_NE(adjusted_config.suppressor.normal_tuning.mask_lf.enr_suppress,
default_config.suppressor.normal_tuning.mask_lf.enr_suppress);
ASSERT_NE(adjusted_config.suppressor.normal_tuning.mask_hf.enr_transparent,
default_config.suppressor.normal_tuning.mask_hf.enr_transparent);
ASSERT_NE(adjusted_config.suppressor.normal_tuning.mask_hf.enr_suppress,
default_config.suppressor.normal_tuning.mask_hf.enr_suppress);
ASSERT_NE(adjusted_config.suppressor.normal_tuning.max_inc_factor,
default_config.suppressor.normal_tuning.max_inc_factor);
ASSERT_NE(adjusted_config.suppressor.normal_tuning.max_dec_factor_lf,
default_config.suppressor.normal_tuning.max_dec_factor_lf);
ASSERT_NE(adjusted_config.suppressor.dominant_nearend_detection.enr_threshold,
default_config.suppressor.dominant_nearend_detection.enr_threshold);
ASSERT_NE(
adjusted_config.suppressor.dominant_nearend_detection.enr_exit_threshold,
default_config.suppressor.dominant_nearend_detection.enr_exit_threshold);
ASSERT_NE(adjusted_config.suppressor.dominant_nearend_detection.snr_threshold,
default_config.suppressor.dominant_nearend_detection.snr_threshold);
ASSERT_NE(adjusted_config.suppressor.dominant_nearend_detection.hold_duration,
default_config.suppressor.dominant_nearend_detection.hold_duration);
ASSERT_NE(
adjusted_config.suppressor.dominant_nearend_detection.trigger_threshold,
default_config.suppressor.dominant_nearend_detection.trigger_threshold);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.nearend_tuning.mask_lf.enr_transparent, 0.1);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.nearend_tuning.mask_lf.enr_suppress, 0.2);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.nearend_tuning.mask_hf.enr_transparent, 0.3);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.nearend_tuning.mask_hf.enr_suppress, 0.4);
EXPECT_FLOAT_EQ(adjusted_config.suppressor.nearend_tuning.max_inc_factor,
0.5);
EXPECT_FLOAT_EQ(adjusted_config.suppressor.nearend_tuning.max_dec_factor_lf,
0.6);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.normal_tuning.mask_lf.enr_transparent, 0.7);
EXPECT_FLOAT_EQ(adjusted_config.suppressor.normal_tuning.mask_lf.enr_suppress,
0.8);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.normal_tuning.mask_hf.enr_transparent, 0.9);
EXPECT_FLOAT_EQ(adjusted_config.suppressor.normal_tuning.mask_hf.enr_suppress,
1.0);
EXPECT_FLOAT_EQ(adjusted_config.suppressor.normal_tuning.max_inc_factor, 1.1);
EXPECT_FLOAT_EQ(adjusted_config.suppressor.normal_tuning.max_dec_factor_lf,
1.2);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.dominant_nearend_detection.enr_threshold, 1.3);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.dominant_nearend_detection.enr_exit_threshold,
1.4);
EXPECT_FLOAT_EQ(
adjusted_config.suppressor.dominant_nearend_detection.snr_threshold, 1.5);
EXPECT_EQ(adjusted_config.suppressor.dominant_nearend_detection.hold_duration,
10);
EXPECT_EQ(
adjusted_config.suppressor.dominant_nearend_detection.trigger_threshold,
11);
}
TEST(EchoCanceller3FieldTrials, Aec3SuppressorTuningOverrideOneParam) {
webrtc::test::ScopedFieldTrials field_trials(
"WebRTC-Aec3SuppressorTuningOverride/nearend_tuning_max_inc_factor:0.5/");
EchoCanceller3Config default_config;
EchoCanceller3Config adjusted_config = AdjustConfig(default_config);
ASSERT_EQ(adjusted_config.suppressor.nearend_tuning.mask_lf.enr_transparent,
default_config.suppressor.nearend_tuning.mask_lf.enr_transparent);
ASSERT_EQ(adjusted_config.suppressor.nearend_tuning.mask_lf.enr_suppress,
default_config.suppressor.nearend_tuning.mask_lf.enr_suppress);
ASSERT_EQ(adjusted_config.suppressor.nearend_tuning.mask_hf.enr_transparent,
default_config.suppressor.nearend_tuning.mask_hf.enr_transparent);
ASSERT_EQ(adjusted_config.suppressor.nearend_tuning.mask_hf.enr_suppress,
default_config.suppressor.nearend_tuning.mask_hf.enr_suppress);
ASSERT_EQ(adjusted_config.suppressor.nearend_tuning.max_dec_factor_lf,
default_config.suppressor.nearend_tuning.max_dec_factor_lf);
ASSERT_EQ(adjusted_config.suppressor.normal_tuning.mask_lf.enr_transparent,
default_config.suppressor.normal_tuning.mask_lf.enr_transparent);
ASSERT_EQ(adjusted_config.suppressor.normal_tuning.mask_lf.enr_suppress,
default_config.suppressor.normal_tuning.mask_lf.enr_suppress);
ASSERT_EQ(adjusted_config.suppressor.normal_tuning.mask_hf.enr_transparent,
default_config.suppressor.normal_tuning.mask_hf.enr_transparent);
ASSERT_EQ(adjusted_config.suppressor.normal_tuning.mask_hf.enr_suppress,
default_config.suppressor.normal_tuning.mask_hf.enr_suppress);
ASSERT_EQ(adjusted_config.suppressor.normal_tuning.max_inc_factor,
default_config.suppressor.normal_tuning.max_inc_factor);
ASSERT_EQ(adjusted_config.suppressor.normal_tuning.max_dec_factor_lf,
default_config.suppressor.normal_tuning.max_dec_factor_lf);
ASSERT_EQ(adjusted_config.suppressor.dominant_nearend_detection.enr_threshold,
default_config.suppressor.dominant_nearend_detection.enr_threshold);
ASSERT_EQ(
adjusted_config.suppressor.dominant_nearend_detection.enr_exit_threshold,
default_config.suppressor.dominant_nearend_detection.enr_exit_threshold);
ASSERT_EQ(adjusted_config.suppressor.dominant_nearend_detection.snr_threshold,
default_config.suppressor.dominant_nearend_detection.snr_threshold);
ASSERT_EQ(adjusted_config.suppressor.dominant_nearend_detection.hold_duration,
default_config.suppressor.dominant_nearend_detection.hold_duration);
ASSERT_EQ(
adjusted_config.suppressor.dominant_nearend_detection.trigger_threshold,
default_config.suppressor.dominant_nearend_detection.trigger_threshold);
ASSERT_NE(adjusted_config.suppressor.nearend_tuning.max_inc_factor,
default_config.suppressor.nearend_tuning.max_inc_factor);
EXPECT_FLOAT_EQ(adjusted_config.suppressor.nearend_tuning.max_inc_factor,
0.5);
}
TEST(EchoCanceller3FieldTrials, Aec3UseNearendReverb) {
webrtc::test::ScopedFieldTrials field_trials(
"WebRTC-Aec3UseNearendReverbLen/default_len:0.9,nearend_len:0.8/");
EchoCanceller3Config default_config;
EchoCanceller3Config adjusted_config = AdjustConfig(default_config);
EXPECT_FLOAT_EQ(adjusted_config.ep_strength.default_len, 0.9);
EXPECT_FLOAT_EQ(adjusted_config.ep_strength.nearend_len, 0.8);
}
TEST(EchoCanceller3, DetectionOfProperStereo) {
constexpr int kSampleRateHz = 16000;
constexpr int kNumChannels = 2;
AudioBuffer buffer(kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels);
constexpr size_t kNumBlocksForMonoConfig = 1;
constexpr size_t kNumBlocksForSurroundConfig = 2;
EchoCanceller3Config mono_config;
absl::optional<EchoCanceller3Config> multichannel_config;
mono_config.multi_channel.detect_stereo_content = true;
mono_config.multi_channel.stereo_detection_threshold = 0.0f;
mono_config.multi_channel.stereo_detection_hysteresis_seconds = 0.0f;
multichannel_config = mono_config;
mono_config.filter.coarse_initial.length_blocks = kNumBlocksForMonoConfig;
multichannel_config->filter.coarse_initial.length_blocks =
kNumBlocksForSurroundConfig;
EchoCanceller3 aec3(mono_config, multichannel_config,
kSampleRateHz,
kNumChannels,
kNumChannels);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
RunAecInStereo(buffer, aec3, 100.0f, 100.0f);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
RunAecInStereo(buffer, aec3, 100.0f, 101.0f);
EXPECT_TRUE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForSurroundConfig);
}
TEST(EchoCanceller3, DetectionOfProperStereoUsingThreshold) {
constexpr int kSampleRateHz = 16000;
constexpr int kNumChannels = 2;
AudioBuffer buffer(kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels);
constexpr size_t kNumBlocksForMonoConfig = 1;
constexpr size_t kNumBlocksForSurroundConfig = 2;
EchoCanceller3Config mono_config;
absl::optional<EchoCanceller3Config> multichannel_config;
constexpr float kStereoDetectionThreshold = 2.0f;
mono_config.multi_channel.detect_stereo_content = true;
mono_config.multi_channel.stereo_detection_threshold =
kStereoDetectionThreshold;
mono_config.multi_channel.stereo_detection_hysteresis_seconds = 0.0f;
multichannel_config = mono_config;
mono_config.filter.coarse_initial.length_blocks = kNumBlocksForMonoConfig;
multichannel_config->filter.coarse_initial.length_blocks =
kNumBlocksForSurroundConfig;
EchoCanceller3 aec3(mono_config, multichannel_config,
kSampleRateHz,
kNumChannels,
kNumChannels);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
RunAecInStereo(buffer, aec3, 100.0f,
100.0f + kStereoDetectionThreshold - 1.0f);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
RunAecInStereo(buffer, aec3, 100.0f,
100.0f + kStereoDetectionThreshold + 10.0f);
EXPECT_TRUE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForSurroundConfig);
}
TEST(EchoCanceller3, DetectionOfProperStereoUsingHysteresis) {
constexpr int kSampleRateHz = 16000;
constexpr int kNumChannels = 2;
AudioBuffer buffer(kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels);
constexpr size_t kNumBlocksForMonoConfig = 1;
constexpr size_t kNumBlocksForSurroundConfig = 2;
EchoCanceller3Config mono_config;
absl::optional<EchoCanceller3Config> surround_config;
mono_config.multi_channel.detect_stereo_content = true;
mono_config.multi_channel.stereo_detection_hysteresis_seconds = 0.5f;
surround_config = mono_config;
mono_config.filter.coarse_initial.length_blocks = kNumBlocksForMonoConfig;
surround_config->filter.coarse_initial.length_blocks =
kNumBlocksForSurroundConfig;
EchoCanceller3 aec3(mono_config, surround_config,
kSampleRateHz,
kNumChannels,
kNumChannels);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
RunAecInStereo(buffer, aec3, 100.0f, 100.0f);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
constexpr int kNumFramesPerSecond = 100;
for (int k = 0;
k < static_cast<int>(
kNumFramesPerSecond *
mono_config.multi_channel.stereo_detection_hysteresis_seconds);
++k) {
RunAecInStereo(buffer, aec3, 100.0f, 101.0f);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
}
RunAecInStereo(buffer, aec3, 100.0f, 101.0f);
EXPECT_TRUE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForSurroundConfig);
}
TEST(EchoCanceller3, StereoContentDetectionForMonoSignals) {
constexpr int kSampleRateHz = 16000;
constexpr int kNumChannels = 2;
AudioBuffer buffer(kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels,
kSampleRateHz,
kNumChannels);
constexpr size_t kNumBlocksForMonoConfig = 1;
constexpr size_t kNumBlocksForSurroundConfig = 2;
EchoCanceller3Config mono_config;
absl::optional<EchoCanceller3Config> multichannel_config;
for (bool detect_stereo_content : {false, true}) {
mono_config.multi_channel.detect_stereo_content = detect_stereo_content;
multichannel_config = mono_config;
mono_config.filter.coarse_initial.length_blocks = kNumBlocksForMonoConfig;
multichannel_config->filter.coarse_initial.length_blocks =
kNumBlocksForSurroundConfig;
AudioBuffer mono_buffer(kSampleRateHz,
1,
kSampleRateHz,
1,
kSampleRateHz,
1);
EchoCanceller3 aec3(mono_config, multichannel_config,
kSampleRateHz,
1,
1);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
RunAecInSMono(mono_buffer, aec3, 100.0f);
EXPECT_FALSE(aec3.StereoRenderProcessingActiveForTesting());
EXPECT_EQ(
aec3.GetActiveConfigForTesting().filter.coarse_initial.length_blocks,
kNumBlocksForMonoConfig);
}
}
#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
TEST(EchoCanceller3InputCheckDeathTest, WrongCaptureNumBandsCheckVerification) {
for (auto rate : {16000, 32000, 48000}) {
SCOPED_TRACE(ProduceDebugText(rate));
EchoCanceller3Tester(rate).RunProcessCaptureNumBandsCheckVerification();
}
}
TEST(EchoCanceller3InputCheckDeathTest, NullCaptureProcessingParameter) {
EXPECT_DEATH(
EchoCanceller3(EchoCanceller3Config(),
absl::nullopt, 16000, 1, 1)
.ProcessCapture(nullptr, false),
"");
}
TEST(EchoCanceller3InputCheckDeathTest, DISABLED_WrongSampleRate) {
ApmDataDumper data_dumper(0);
EXPECT_DEATH(
EchoCanceller3(EchoCanceller3Config(),
absl::nullopt, 8001, 1, 1),
"");
}
#endif
}