summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc')
-rw-r--r--third_party/libwebrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc1160
1 files changed, 1160 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc b/third_party/libwebrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc
new file mode 100644
index 0000000000..ad126af4d3
--- /dev/null
+++ b/third_party/libwebrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc
@@ -0,0 +1,1160 @@
+/*
+ * 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;
+
+// Populates the frame with linearly increasing sample values for each band,
+// with a band-specific offset, in order to allow simple bitexactness
+// verification for each band.
+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);
+ }
+ }
+}
+
+// Populates the frame with linearly increasing sample values.
+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);
+ }
+}
+
+// Verifies the that samples in the output frame are identical to the samples
+// that were produced for the input frame, with an offset in order to compensate
+// for buffering delays.
+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 for testing that the capture data is properly received by the block
+// processor and that the processor data is properly passed to the
+// EchoCanceller3 output.
+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 for testing that the render data is properly received by the block
+// processor.
+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, /*level_change=*/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, /*level_change=*/false);
+}
+
+} // namespace
+
+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;
+
+ // Verifies that the capture data is properly received by the block processor
+ // and that the processor data is properly passed to the EchoCanceller3
+ // output.
+ void RunCaptureTransportVerificationTest() {
+ EchoCanceller3 aec3(EchoCanceller3Config(),
+ /*multichannel_config=*/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));
+ }
+ }
+
+ // Test method for testing that the render data is properly received by the
+ // block processor.
+ void RunRenderTransportVerificationTest() {
+ EchoCanceller3 aec3(EchoCanceller3Config(),
+ /*multichannel_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 < 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));
+ }
+
+ // Verifies that information about echo path changes are properly propagated
+ // to the block processor.
+ // The cases tested are:
+ // -That no set echo path change flags are received when there is no echo path
+ // change.
+ // -That set echo path change flags are received and continues to be received
+ // as long as echo path changes are flagged.
+ // -That set echo path change flags are no longer received when echo path
+ // change events stop being flagged.
+ 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(),
+ /*multichannel_config=*/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);
+ }
+ }
+
+ // Test for verifying that echo leakage information is being properly passed
+ // to the processor.
+ // The cases tested are:
+ // -That no method calls are received when they should not.
+ // -That false values are received each time they are flagged.
+ // -That true values are received each time they are flagged.
+ // -That a false value is received when flagged after a true value has been
+ // flagged.
+ 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(),
+ /*multichannel_config=*/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);
+ }
+ }
+
+ // This verifies that saturation information is properly passed to the
+ // BlockProcessor.
+ // The cases tested are:
+ // -That no saturation event is passed to the processor if there is no
+ // saturation.
+ // -That one frame with one negative saturated sample value is reported to be
+ // saturated and that following non-saturated frames are properly reported as
+ // not being saturated.
+ // -That one frame with one positive saturated sample value is reported to be
+ // saturated and that following non-saturated frames are properly reported as
+ // not being saturated.
+ 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(),
+ /*multichannel_config=*/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);
+ }
+ }
+
+ // This test verifies that the swapqueue is able to handle jitter in the
+ // capture and render API calls.
+ void RunRenderSwapQueueVerificationTest() {
+ const EchoCanceller3Config config;
+ EchoCanceller3 aec3(config, /*multichannel_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));
+ }
+
+ // This test verifies that a buffer overrun in the render swapqueue is
+ // properly reported.
+ void RunRenderPipelineSwapQueueOverrunReturnValueTest() {
+ EchoCanceller3 aec3(EchoCanceller3Config(),
+ /*multichannel_config=*/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)
+ // Verifies the that the check for the number of bands in the AnalyzeRender
+ // input is correct by adjusting the sample rates of EchoCanceller3 and the
+ // input AudioBuffer to have a different number of bands.
+ void RunAnalyzeRenderNumBandsCheckVerification() {
+ // Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
+ // way that the number of bands for the rates are different.
+ const int aec3_sample_rate_hz = sample_rate_hz_ == 48000 ? 32000 : 48000;
+ EchoCanceller3 aec3(EchoCanceller3Config(),
+ /*multichannel_config=*/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_), "");
+ }
+
+ // Verifies the that the check for the number of bands in the ProcessCapture
+ // input is correct by adjusting the sample rates of EchoCanceller3 and the
+ // input AudioBuffer to have a different number of bands.
+ void RunProcessCaptureNumBandsCheckVerification() {
+ // Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a
+ // way that the number of bands for the rates are different.
+ const int aec3_sample_rate_hz = sample_rate_hz_ == 48000 ? 32000 : 48000;
+ EchoCanceller3 aec3(EchoCanceller3Config(),
+ /*multichannel_config=*/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);
+ }
+ }
+}
+
+// Tests the parameter functionality for the field trial override for the
+// anti-howling gain.
+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);
+}
+
+// Tests the field trial override for the enforcement of a low active render
+// limit.
+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);
+}
+
+// Testing the field trial-based override of the suppressor parameters for a
+// joint passing of all parameters.
+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);
+}
+
+// Testing the field trial-based override of the suppressor parameters for
+// passing one parameter.
+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);
+}
+
+// Testing the field trial-based that override the exponential decay parameters.
+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(/*input_rate=*/kSampleRateHz,
+ /*input_num_channels=*/kNumChannels,
+ /*input_rate=*/kSampleRateHz,
+ /*buffer_num_channels=*/kNumChannels,
+ /*output_rate=*/kSampleRateHz,
+ /*output_num_channels=*/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,
+ /*sample_rate_hz=*/kSampleRateHz,
+ /*num_render_channels=*/kNumChannels,
+ /*num_capture_input_channels=*/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(/*input_rate=*/kSampleRateHz,
+ /*input_num_channels=*/kNumChannels,
+ /*input_rate=*/kSampleRateHz,
+ /*buffer_num_channels=*/kNumChannels,
+ /*output_rate=*/kSampleRateHz,
+ /*output_num_channels=*/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,
+ /*sample_rate_hz=*/kSampleRateHz,
+ /*num_render_channels=*/kNumChannels,
+ /*num_capture_input_channels=*/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(/*input_rate=*/kSampleRateHz,
+ /*input_num_channels=*/kNumChannels,
+ /*input_rate=*/kSampleRateHz,
+ /*buffer_num_channels=*/kNumChannels,
+ /*output_rate=*/kSampleRateHz,
+ /*output_num_channels=*/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,
+ /*sample_rate_hz=*/kSampleRateHz,
+ /*num_render_channels=*/kNumChannels,
+ /*num_capture_input_channels=*/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(/*input_rate=*/kSampleRateHz,
+ /*input_num_channels=*/kNumChannels,
+ /*input_rate=*/kSampleRateHz,
+ /*buffer_num_channels=*/kNumChannels,
+ /*output_rate=*/kSampleRateHz,
+ /*output_num_channels=*/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(/*input_rate=*/kSampleRateHz,
+ /*input_num_channels=*/1,
+ /*input_rate=*/kSampleRateHz,
+ /*buffer_num_channels=*/1,
+ /*output_rate=*/kSampleRateHz,
+ /*output_num_channels=*/1);
+
+ EchoCanceller3 aec3(mono_config, multichannel_config,
+ /*sample_rate_hz=*/kSampleRateHz,
+ /*num_render_channels=*/1,
+ /*num_capture_input_channels=*/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();
+ }
+}
+
+// Verifiers that the verification for null input to the capture processing api
+// call works.
+TEST(EchoCanceller3InputCheckDeathTest, NullCaptureProcessingParameter) {
+ EXPECT_DEATH(
+ EchoCanceller3(EchoCanceller3Config(),
+ /*multichannel_config_=*/absl::nullopt, 16000, 1, 1)
+ .ProcessCapture(nullptr, false),
+ "");
+}
+
+// Verifies the check for correct sample rate.
+// TODO(peah): Re-enable the test once the issue with memory leaks during DEATH
+// tests on test bots has been fixed.
+TEST(EchoCanceller3InputCheckDeathTest, DISABLED_WrongSampleRate) {
+ ApmDataDumper data_dumper(0);
+ EXPECT_DEATH(
+ EchoCanceller3(EchoCanceller3Config(),
+ /*multichannel_config_=*/absl::nullopt, 8001, 1, 1),
+ "");
+}
+
+#endif
+
+} // namespace webrtc