diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn index 40cbdf62c1..cfe12845bf 100644 --- a/webrtc/modules/BUILD.gn +++ b/webrtc/modules/BUILD.gn @@ -597,6 +597,12 @@ if (rtc_include_tests) { "audio_processing:audioproc_unittest_proto", ] sources += [ + "audio_processing/aec3/block_framer_unittest.cc", + "audio_processing/aec3/block_processor_unittest.cc", + "audio_processing/aec3/cascaded_biquad_filter_unittest.cc", + "audio_processing/aec3/echo_canceller3_unittest.cc", + "audio_processing/aec3/frame_blocker_unittest.cc", + "audio_processing/aec3/mock/mock_block_processor.h", "audio_processing/audio_processing_impl_locking_unittest.cc", "audio_processing/audio_processing_impl_unittest.cc", "audio_processing/audio_processing_unittest.cc", diff --git a/webrtc/modules/audio_processing/BUILD.gn b/webrtc/modules/audio_processing/BUILD.gn index 0aea170673..049a4e1d04 100644 --- a/webrtc/modules/audio_processing/BUILD.gn +++ b/webrtc/modules/audio_processing/BUILD.gn @@ -26,8 +26,17 @@ rtc_static_library("audio_processing") { "aec/aec_resampler.h", "aec/echo_cancellation.cc", "aec/echo_cancellation.h", + "aec3/aec3_constants.h", + "aec3/block_framer.cc", + "aec3/block_framer.h", + "aec3/block_processor.cc", + "aec3/block_processor.h", + "aec3/cascaded_biquad_filter.cc", + "aec3/cascaded_biquad_filter.h", "aec3/echo_canceller3.cc", "aec3/echo_canceller3.h", + "aec3/frame_blocker.cc", + "aec3/frame_blocker.h", "aecm/aecm_core.cc", "aecm/aecm_core.h", "aecm/echo_control_mobile.cc", diff --git a/webrtc/modules/audio_processing/aec3/aec3_constants.h b/webrtc/modules/audio_processing/aec3/aec3_constants.h new file mode 100644 index 0000000000..946e50c786 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/aec3_constants.h @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_CONSTANTS_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_CONSTANTS_H_ + +#include + +namespace webrtc { + +constexpr size_t kFftLengthBy2 = 64; +constexpr size_t kFftLengthBy2Plus1 = kFftLengthBy2 + 1; +constexpr size_t kFftLength = 2 * kFftLengthBy2; + +constexpr size_t kMaxNumBands = 3; +constexpr size_t kSubFrameLength = 80; + +constexpr size_t kBlockSize = kFftLengthBy2; +constexpr size_t kExtendedBlockSize = 2 * kFftLengthBy2; +constexpr size_t kSubBlockSize = 16; + +constexpr size_t NumBandsForRate(int sample_rate_hz) { + return static_cast(sample_rate_hz == 8000 ? 1 + : sample_rate_hz / 16000); +} +constexpr int LowestBandRate(int sample_rate_hz) { + return sample_rate_hz == 8000 ? sample_rate_hz : 16000; +} + +static_assert(1 == NumBandsForRate(8000), "Number of bands for 8 kHz"); +static_assert(1 == NumBandsForRate(16000), "Number of bands for 16 kHz"); +static_assert(2 == NumBandsForRate(32000), "Number of bands for 32 kHz"); +static_assert(3 == NumBandsForRate(48000), "Number of bands for 48 kHz"); + +static_assert(8000 == LowestBandRate(8000), "Sample rate of band 0 for 8 kHz"); +static_assert(16000 == LowestBandRate(16000), + "Sample rate of band 0 for 16 kHz"); +static_assert(16000 == LowestBandRate(32000), + "Sample rate of band 0 for 32 kHz"); +static_assert(16000 == LowestBandRate(48000), + "Sample rate of band 0 for 48 kHz"); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_AEC3_CONSTANTS_H_ diff --git a/webrtc/modules/audio_processing/aec3/block_framer.cc b/webrtc/modules/audio_processing/aec3/block_framer.cc new file mode 100644 index 0000000000..6425dae8c8 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_framer.cc @@ -0,0 +1,59 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/block_framer.h" + +#include + +#include "webrtc/base/checks.h" + +namespace webrtc { + +BlockFramer::BlockFramer(size_t num_bands) + : num_bands_(num_bands), + buffer_(num_bands_, std::vector(kBlockSize, 0.f)) {} + +BlockFramer::~BlockFramer() = default; + +// All the constants are chosen so that the buffer is either empty or has enough +// samples for InsertBlockAndExtractSubFrame to produce a frame. In order to +// achieve this, the InsertBlockAndExtractSubFrame and InsertBlock methods need +// to be called in the correct order. +void BlockFramer::InsertBlock(const std::vector>& block) { + RTC_DCHECK_EQ(num_bands_, block.size()); + for (size_t i = 0; i < num_bands_; ++i) { + RTC_DCHECK_EQ(kBlockSize, block[i].size()); + RTC_DCHECK_EQ(0, buffer_[i].size()); + buffer_[i].insert(buffer_[i].begin(), block[i].begin(), block[i].end()); + } +} + +void BlockFramer::InsertBlockAndExtractSubFrame( + const std::vector>& block, + std::vector>* sub_frame) { + RTC_DCHECK(sub_frame); + RTC_DCHECK_EQ(num_bands_, block.size()); + RTC_DCHECK_EQ(num_bands_, sub_frame->size()); + for (size_t i = 0; i < num_bands_; ++i) { + RTC_DCHECK_LE(kSubFrameLength, buffer_[i].size() + kBlockSize); + RTC_DCHECK_EQ(kBlockSize, block[i].size()); + RTC_DCHECK_GE(kBlockSize, buffer_[i].size()); + RTC_DCHECK_EQ(kSubFrameLength, (*sub_frame)[i].size()); + const int samples_to_frame = kSubFrameLength - buffer_[i].size(); + std::copy(buffer_[i].begin(), buffer_[i].end(), (*sub_frame)[i].begin()); + std::copy(block[i].begin(), block[i].begin() + samples_to_frame, + (*sub_frame)[i].begin() + buffer_[i].size()); + buffer_[i].clear(); + buffer_[i].insert(buffer_[i].begin(), block[i].begin() + samples_to_frame, + block[i].end()); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/block_framer.h b/webrtc/modules/audio_processing/aec3/block_framer.h new file mode 100644 index 0000000000..8a90300f6c --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_framer.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_FRAMER_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_FRAMER_H_ + +#include + +#include "webrtc/base/array_view.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_processing/aec3/aec3_constants.h" + +namespace webrtc { + +// Class for producing frames consisting of 1 or 2 subframes of 80 samples each +// from 64 sample blocks. The class is designed to work together with the +// FrameBlocker class which performs the reverse conversion. Used together with +// that, this class produces output frames are the same rate as frames are +// received by the FrameBlocker class. Note that the internal buffers will +// overrun if any other rate of packets insertion is used. +class BlockFramer { + public: + explicit BlockFramer(size_t num_bands); + ~BlockFramer(); + // Adds a 64 sample block into the data that will form the next output frame. + void InsertBlock(const std::vector>& block); + // Adds a 64 sample block and extracts an 80 sample subframe. + void InsertBlockAndExtractSubFrame( + const std::vector>& block, + std::vector>* sub_frame); + + private: + const size_t num_bands_; + std::vector> buffer_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockFramer); +}; +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_FRAMER_H_ diff --git a/webrtc/modules/audio_processing/aec3/block_framer_unittest.cc b/webrtc/modules/audio_processing/aec3/block_framer_unittest.cc new file mode 100644 index 0000000000..38112392bb --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_framer_unittest.cc @@ -0,0 +1,261 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/block_framer.h" + +#include +#include +#include + +#include "webrtc/modules/audio_processing/aec3/aec3_constants.h" +#include "webrtc/test/gtest.h" + +namespace webrtc { +namespace { + +void SetupSubFrameView(std::vector>* sub_frame, + std::vector>* sub_frame_view) { + for (size_t k = 0; k < sub_frame_view->size(); ++k) { + (*sub_frame_view)[k] = + rtc::ArrayView((*sub_frame)[k].data(), (*sub_frame)[k].size()); + } +} + +float ComputeSampleValue(size_t chunk_counter, + size_t chunk_size, + size_t band, + size_t sample_index, + int offset) { + float value = + static_cast(chunk_counter * chunk_size + sample_index) + offset; + return value > 0 ? 5000 * band + value : 0; +} + +bool VerifySubFrame(size_t sub_frame_counter, + int offset, + const std::vector>& sub_frame_view) { + for (size_t k = 0; k < sub_frame_view.size(); ++k) { + for (size_t i = 0; i < sub_frame_view[k].size(); ++i) { + const float reference_value = + ComputeSampleValue(sub_frame_counter, kSubFrameLength, k, i, offset); + if (reference_value != sub_frame_view[k][i]) { + return false; + } + } + } + return true; +} + +void FillBlock(size_t block_counter, std::vector>* block) { + for (size_t k = 0; k < block->size(); ++k) { + for (size_t i = 0; i < (*block)[0].size(); ++i) { + (*block)[k][i] = ComputeSampleValue(block_counter, kBlockSize, k, i, 0); + } + } +} + +// Verifies that the BlockFramer is able to produce the expected frame content. +void RunFramerTest(int sample_rate_hz) { + constexpr size_t kNumSubFramesToProcess = 2; + const size_t num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> block(num_bands, + std::vector(kBlockSize, 0.f)); + std::vector> output_sub_frame( + num_bands, std::vector(kSubFrameLength, 0.f)); + std::vector> output_sub_frame_view(num_bands); + SetupSubFrameView(&output_sub_frame, &output_sub_frame_view); + BlockFramer framer(num_bands); + + size_t block_index = 0; + for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess; + ++sub_frame_index) { + FillBlock(block_index++, &block); + framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view); + EXPECT_TRUE(VerifySubFrame(sub_frame_index, -64, output_sub_frame_view)); + + if ((sub_frame_index + 1) % 4 == 0) { + FillBlock(block_index++, &block); + framer.InsertBlock(block); + } + } +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +// Verifies that the BlockFramer crashes if the InsertBlockAndExtractSubFrame +// method is called for inputs with the wrong number of bands or band lengths. +void RunWronglySizedInsertAndExtractParametersTest(int sample_rate_hz, + size_t num_block_bands, + size_t block_length, + size_t num_sub_frame_bands, + size_t sub_frame_length) { + const size_t correct_num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> block(num_block_bands, + std::vector(block_length, 0.f)); + std::vector> output_sub_frame( + num_sub_frame_bands, std::vector(sub_frame_length, 0.f)); + std::vector> output_sub_frame_view( + output_sub_frame.size()); + SetupSubFrameView(&output_sub_frame, &output_sub_frame_view); + BlockFramer framer(correct_num_bands); + EXPECT_DEATH( + framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view), ""); +} + +// Verifies that the BlockFramer crashes if the InsertBlock method is called for +// inputs with the wrong number of bands or band lengths. +void RunWronglySizedInsertParameterTest(int sample_rate_hz, + size_t num_block_bands, + size_t block_length) { + const size_t correct_num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> correct_block( + correct_num_bands, std::vector(kBlockSize, 0.f)); + std::vector> wrong_block( + num_block_bands, std::vector(block_length, 0.f)); + std::vector> output_sub_frame( + correct_num_bands, std::vector(kSubFrameLength, 0.f)); + std::vector> output_sub_frame_view( + output_sub_frame.size()); + SetupSubFrameView(&output_sub_frame, &output_sub_frame_view); + BlockFramer framer(correct_num_bands); + framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view); + framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view); + framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view); + framer.InsertBlockAndExtractSubFrame(correct_block, &output_sub_frame_view); + + EXPECT_DEATH(framer.InsertBlock(wrong_block), ""); +} + +// Verifies that the BlockFramer crashes if the InsertBlock method is called +// after a wrong number of previous InsertBlockAndExtractSubFrame method calls +// have been made. +void RunWronglyInsertOrderTest(int sample_rate_hz, + size_t num_preceeding_api_calls) { + const size_t correct_num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> block(correct_num_bands, + std::vector(kBlockSize, 0.f)); + std::vector> output_sub_frame( + correct_num_bands, std::vector(kSubFrameLength, 0.f)); + std::vector> output_sub_frame_view( + output_sub_frame.size()); + SetupSubFrameView(&output_sub_frame, &output_sub_frame_view); + BlockFramer framer(correct_num_bands); + for (size_t k = 0; k < num_preceeding_api_calls; ++k) { + framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view); + } + + EXPECT_DEATH(framer.InsertBlock(block), ""); +} +#endif + +std::string ProduceDebugText(int sample_rate_hz) { + std::ostringstream ss; + ss << "Sample rate: " << sample_rate_hz; + return ss.str(); +} + +} // namespace + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST(BlockFramer, WrongNumberOfBandsInBlockForInsertBlockAndExtractSubFrame) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + const size_t wrong_num_bands = (correct_num_bands % 3) + 1; + RunWronglySizedInsertAndExtractParametersTest( + rate, wrong_num_bands, kBlockSize, correct_num_bands, kSubFrameLength); + } +} + +TEST(BlockFramer, + WrongNumberOfBandsInSubFrameForInsertBlockAndExtractSubFrame) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + const size_t wrong_num_bands = (correct_num_bands % 3) + 1; + RunWronglySizedInsertAndExtractParametersTest( + rate, correct_num_bands, kBlockSize, wrong_num_bands, kSubFrameLength); + } +} + +TEST(BlockFramer, WrongNumberOfSamplesInBlockForInsertBlockAndExtractSubFrame) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + RunWronglySizedInsertAndExtractParametersTest( + rate, correct_num_bands, kBlockSize - 1, correct_num_bands, + kSubFrameLength); + } +} + +TEST(BlockFramer, + WrongNumberOfSamplesInSubFrameForInsertBlockAndExtractSubFrame) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + RunWronglySizedInsertAndExtractParametersTest(rate, correct_num_bands, + kBlockSize, correct_num_bands, + kSubFrameLength - 1); + } +} + +TEST(BlockFramer, WrongNumberOfBandsInBlockForInsertBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + const size_t wrong_num_bands = (correct_num_bands % 3) + 1; + RunWronglySizedInsertParameterTest(rate, wrong_num_bands, kBlockSize); + } +} + +TEST(BlockFramer, WrongNumberOfSamplesInBlockForInsertBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + RunWronglySizedInsertParameterTest(rate, correct_num_bands, kBlockSize - 1); + } +} + +TEST(BlockFramer, WrongNumberOfPreceedingApiCallsForInsertBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + for (size_t num_calls = 0; num_calls < 4; ++num_calls) { + std::ostringstream ss; + ss << "Sample rate: " << rate; + ss << ", Num preceeding InsertBlockAndExtractSubFrame calls: " + << num_calls; + + SCOPED_TRACE(ss.str()); + RunWronglyInsertOrderTest(rate, num_calls); + } + } +} + +// Verifiers that the verification for null sub_frame pointer works. +TEST(BlockFramer, NullSubFrameParameter) { + EXPECT_DEATH(BlockFramer(1).InsertBlockAndExtractSubFrame( + std::vector>( + 1, std::vector(kBlockSize, 0.f)), + nullptr), + ""); +} + +#endif + +TEST(BlockFramer, FrameBitexactness) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + RunFramerTest(rate); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/block_processor.cc b/webrtc/modules/audio_processing/aec3/block_processor.cc new file mode 100644 index 0000000000..066e2f71ac --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_processor.cc @@ -0,0 +1,73 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/block_processor.h" + +#include "webrtc/base/atomicops.h" +#include "webrtc/base/optional.h" +#include "webrtc/modules/audio_processing/aec3/aec3_constants.h" + +namespace webrtc { +namespace { + +class BlockProcessorImpl final : public BlockProcessor { + public: + explicit BlockProcessorImpl(int sample_rate_hz); + ~BlockProcessorImpl() override; + + void ProcessCapture(bool known_echo_path_change, + bool saturated_microphone_signal, + std::vector>* capture_block) override; + + bool BufferRender(std::vector>* block) override; + + void ReportEchoLeakage(bool leakage_detected) override; + + private: + const size_t sample_rate_hz_; + static int instance_count_; + std::unique_ptr data_dumper_; + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockProcessorImpl); +}; + +int BlockProcessorImpl::instance_count_ = 0; + +BlockProcessorImpl::BlockProcessorImpl(int sample_rate_hz) + : sample_rate_hz_(sample_rate_hz), + data_dumper_( + new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))) {} + +BlockProcessorImpl::~BlockProcessorImpl() = default; + +void BlockProcessorImpl::ProcessCapture( + bool known_echo_path_change, + bool saturated_microphone_signal, + std::vector>* capture_block) { + RTC_DCHECK(capture_block); + RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), capture_block->size()); + RTC_DCHECK_EQ(kBlockSize, (*capture_block)[0].size()); +} + +bool BlockProcessorImpl::BufferRender( + std::vector>* render_block) { + RTC_DCHECK(render_block); + RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), render_block->size()); + RTC_DCHECK_EQ(kBlockSize, (*render_block)[0].size()); + return false; +} + +void BlockProcessorImpl::ReportEchoLeakage(bool leakage_detected) {} + +} // namespace + +BlockProcessor* BlockProcessor::Create(int sample_rate_hz) { + return new BlockProcessorImpl(sample_rate_hz); +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/block_processor.h b/webrtc/modules/audio_processing/aec3/block_processor.h new file mode 100644 index 0000000000..b84b18df81 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_processor.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_H_ + +#include +#include + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h" + +namespace webrtc { + +// Class for performing echo cancellation on 64 sample blocks of audio data. +class BlockProcessor { + public: + static BlockProcessor* Create(int sample_rate_hz); + virtual ~BlockProcessor() = default; + + // Processes a block of capture data. + virtual void ProcessCapture( + bool known_echo_path_change, + bool saturated_microphone_signal, + std::vector>* capture_block) = 0; + + // Buffers a block of render data supplied by a FrameBlocker object. + virtual bool BufferRender(std::vector>* render_block) = 0; + + // Reports whether echo leakage has been detected in the echo canceller + // output. + virtual void ReportEchoLeakage(bool leakage_detected) = 0; +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_H_ diff --git a/webrtc/modules/audio_processing/aec3/block_processor_unittest.cc b/webrtc/modules/audio_processing/aec3/block_processor_unittest.cc new file mode 100644 index 0000000000..9628306404 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_processor_unittest.cc @@ -0,0 +1,141 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/block_processor.h" + +#include +#include +#include +#include + +#include "webrtc/modules/audio_processing/aec3/aec3_constants.h" +#include "webrtc/test/gtest.h" + +namespace webrtc { +namespace { + +// Verifies that the basic BlockProcessor functionality works and that the API +// methods are callable. +void RunBasicSetupAndApiCallTest(int sample_rate_hz) { + std::unique_ptr block_processor( + BlockProcessor::Create(sample_rate_hz)); + std::vector> block(NumBandsForRate(sample_rate_hz), + std::vector(kBlockSize, 0.f)); + + EXPECT_FALSE(block_processor->BufferRender(&block)); + block_processor->ProcessCapture(false, false, &block); + block_processor->ReportEchoLeakage(false); +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +void RunRenderBlockSizeVerificationTest(int sample_rate_hz) { + std::unique_ptr block_processor( + BlockProcessor::Create(sample_rate_hz)); + std::vector> block( + NumBandsForRate(sample_rate_hz), std::vector(kBlockSize - 1, 0.f)); + + EXPECT_DEATH(block_processor->BufferRender(&block), ""); +} + +void RunCaptureBlockSizeVerificationTest(int sample_rate_hz) { + std::unique_ptr block_processor( + BlockProcessor::Create(sample_rate_hz)); + std::vector> block( + NumBandsForRate(sample_rate_hz), std::vector(kBlockSize - 1, 0.f)); + + EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), ""); +} + +void RunRenderNumBandsVerificationTest(int sample_rate_hz) { + const size_t wrong_num_bands = NumBandsForRate(sample_rate_hz) < 3 + ? NumBandsForRate(sample_rate_hz) + 1 + : 1; + std::unique_ptr block_processor( + BlockProcessor::Create(sample_rate_hz)); + std::vector> block(wrong_num_bands, + std::vector(kBlockSize, 0.f)); + + EXPECT_DEATH(block_processor->BufferRender(&block), ""); +} + +void RunCaptureNumBandsVerificationTest(int sample_rate_hz) { + const size_t wrong_num_bands = NumBandsForRate(sample_rate_hz) < 3 + ? NumBandsForRate(sample_rate_hz) + 1 + : 1; + std::unique_ptr block_processor( + BlockProcessor::Create(sample_rate_hz)); + std::vector> block(wrong_num_bands, + std::vector(kBlockSize, 0.f)); + + EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), ""); +} +#endif + +std::string ProduceDebugText(int sample_rate_hz) { + std::ostringstream ss; + ss << "Sample rate: " << sample_rate_hz; + return ss.str(); +} + +} // namespace + +TEST(BlockProcessor, BasicSetupAndApiCalls) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + RunBasicSetupAndApiCallTest(rate); + } +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST(BlockProcessor, VerifyRenderBlockSizeCheck) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + RunRenderBlockSizeVerificationTest(rate); + } +} + +TEST(BlockProcessor, VerifyCaptureBlockSizeCheck) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + RunCaptureBlockSizeVerificationTest(rate); + } +} + +TEST(BlockProcessor, VerifyRenderNumBandsCheck) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + RunRenderNumBandsVerificationTest(rate); + } +} + +TEST(BlockProcessor, VerifyCaptureNumBandsCheck) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + RunCaptureNumBandsVerificationTest(rate); + } +} + +// Verifiers that the verification for null ProcessCapture input works. +TEST(BlockProcessor, NullProcessCaptureParameter) { + EXPECT_DEATH(std::unique_ptr(BlockProcessor::Create(8000)) + ->ProcessCapture(false, false, nullptr), + ""); +} + +// Verifiers that the verification for null BufferRender input works. +TEST(BlockProcessor, NullBufferRenderParameter) { + EXPECT_DEATH(std::unique_ptr(BlockProcessor::Create(8000)) + ->BufferRender(nullptr), + ""); +} + +#endif + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.cc b/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.cc new file mode 100644 index 0000000000..da50c7d193 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.cc @@ -0,0 +1,58 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h" + +#include "webrtc/base/checks.h" + +namespace webrtc { + +CascadedBiQuadFilter::CascadedBiQuadFilter( + const CascadedBiQuadFilter::BiQuadCoefficients& coefficients, + size_t num_biquads) + : biquad_states_(num_biquads), coefficients_(coefficients) {} + +CascadedBiQuadFilter::~CascadedBiQuadFilter() = default; + +void CascadedBiQuadFilter::Process(rtc::ArrayView x, + rtc::ArrayView y) { + ApplyBiQuad(x, y, &biquad_states_[0]); + for (size_t k = 1; k < biquad_states_.size(); ++k) { + ApplyBiQuad(y, y, &biquad_states_[k]); + } +} + +void CascadedBiQuadFilter::Process(rtc::ArrayView y) { + for (auto& biquad : biquad_states_) { + ApplyBiQuad(y, y, &biquad); + } +} + +void CascadedBiQuadFilter::ApplyBiQuad( + rtc::ArrayView x, + rtc::ArrayView y, + CascadedBiQuadFilter::BiQuadState* biquad_state) { + RTC_DCHECK_EQ(x.size(), y.size()); + RTC_DCHECK(biquad_state); + const auto c_b = coefficients_.b; + const auto c_a = coefficients_.a; + auto m_x = biquad_state->x; + auto m_y = biquad_state->y; + for (size_t k = 0; k < x.size(); ++k) { + const float tmp = x[k]; + y[k] = c_b[0] * tmp + c_b[1] * m_x[0] + c_b[2] * m_x[1] - c_a[0] * m_y[0] - + c_a[1] * m_y[1]; + m_x[1] = m_x[0]; + m_x[0] = tmp; + m_y[1] = m_y[0]; + m_y[0] = y[k]; + } +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h b/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h new file mode 100644 index 0000000000..438a166eae --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_CASCADED_BIQUAD_FILTER_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_CASCADED_BIQUAD_FILTER_H_ + +#include + +#include "webrtc/base/array_view.h" +#include "webrtc/base/constructormagic.h" + +namespace webrtc { + +// Applies a number of identical biquads in a cascaded manner. The filter +// implementation is direct form 1. +class CascadedBiQuadFilter { + public: + struct BiQuadState { + BiQuadState() : x(), y() {} + float x[2]; + float y[2]; + }; + + struct BiQuadCoefficients { + float b[3]; + float a[2]; + }; + + CascadedBiQuadFilter( + const CascadedBiQuadFilter::BiQuadCoefficients& coefficients, + size_t num_biquads); + ~CascadedBiQuadFilter(); + // Applies the biquads on the values in x in order to form the output in y. + void Process(rtc::ArrayView x, rtc::ArrayView y); + // Applies the biquads on the values in y in an in-place manner. + void Process(rtc::ArrayView y); + + private: + void ApplyBiQuad(rtc::ArrayView x, + rtc::ArrayView y, + CascadedBiQuadFilter::BiQuadState* biquad_state); + + std::vector biquad_states_; + const BiQuadCoefficients coefficients_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(CascadedBiQuadFilter); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_CASCADED_BIQUAD_FILTER_H_ diff --git a/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter_unittest.cc b/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter_unittest.cc new file mode 100644 index 0000000000..7628a8814e --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/cascaded_biquad_filter_unittest.cc @@ -0,0 +1,98 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h" + +#include + +#include "webrtc/test/gtest.h" + +namespace webrtc { + +namespace { + +// Coefficients for a second order Butterworth high-pass filter with cutoff +// frequency 100 Hz. +const CascadedBiQuadFilter::BiQuadCoefficients kHighPassFilterCoefficients = { + {0.97261f, -1.94523f, 0.97261f}, + {-1.94448f, 0.94598f}}; + +const CascadedBiQuadFilter::BiQuadCoefficients kTransparentCoefficients = { + {1.f, 0.f, 0.f}, + {0.f, 0.f}}; + +const CascadedBiQuadFilter::BiQuadCoefficients kBlockingCoefficients = { + {0.f, 0.f, 0.f}, + {0.f, 0.f}}; + +std::vector CreateInputWithIncreasingValues(size_t vector_length) { + std::vector v(vector_length); + for (size_t k = 0; k < v.size(); ++k) { + v[k] = k; + } + return v; +} + +} // namespace + +// Verifies that the filter applies an effect which removes the input signal. +// The test also verifies that the in-place Process API call works as intended. +TEST(CascadedBiquadFilter, BlockingConfiguration) { + std::vector values = CreateInputWithIncreasingValues(1000); + + CascadedBiQuadFilter filter(kBlockingCoefficients, 1); + filter.Process(values); + + EXPECT_EQ(std::vector(1000, 0.f), values); +} + +// Verifies that the filter is able to form a zero-mean output from a +// non-zeromean input signal when coefficients for a high-pass filter are +// applied. The test also verifies that the filter works with multiple biquads. +TEST(CascadedBiquadFilter, HighPassConfiguration) { + std::vector values(1000); + for (size_t k = 0; k < values.size(); ++k) { + values[k] = 1.f; + } + + CascadedBiQuadFilter filter(kHighPassFilterCoefficients, 2); + filter.Process(values); + + for (size_t k = values.size() / 2; k < values.size(); ++k) { + EXPECT_NEAR(0.f, values[k], 1e-4); + } +} + +// Verifies that the filter is able to produce a transparent effect with no +// impact on the data when the proper coefficients are applied. The test also +// verifies that the non-in-place Process API call works as intended. +TEST(CascadedBiquadFilter, TransparentConfiguration) { + const std::vector input = CreateInputWithIncreasingValues(1000); + std::vector output(input.size()); + + CascadedBiQuadFilter filter(kTransparentCoefficients, 1); + filter.Process(input, output); + + EXPECT_EQ(input, output); +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +// Verifies that the check of the lengths for the input and output works for the +// non-in-place call. +TEST(CascadedBiquadFilter, InputSizeCheckVerification) { + const std::vector input = CreateInputWithIncreasingValues(10); + std::vector output(input.size() - 1); + + CascadedBiQuadFilter filter(kTransparentCoefficients, 1); + EXPECT_DEATH(filter.Process(input, output), ""); +} +#endif + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/echo_canceller3.cc b/webrtc/modules/audio_processing/aec3/echo_canceller3.cc index e69ccdcbc6..b409e60350 100644 --- a/webrtc/modules/audio_processing/aec3/echo_canceller3.cc +++ b/webrtc/modules/audio_processing/aec3/echo_canceller3.cc @@ -9,36 +9,312 @@ */ #include "webrtc/modules/audio_processing/aec3/echo_canceller3.h" +#include + #include "webrtc/base/atomicops.h" -#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h" namespace webrtc { +namespace { + +bool DetectSaturation(rtc::ArrayView y) { + for (auto y_k : y) { + if (y_k >= 32767.0f || y_k <= -32768.0f) { + return true; + } + } + return false; +} + +void FillSubFrameView(AudioBuffer* frame, + size_t sub_frame_index, + std::vector>* sub_frame_view) { + RTC_DCHECK_GE(1, sub_frame_index); + RTC_DCHECK_LE(0, sub_frame_index); + RTC_DCHECK_EQ(frame->num_bands(), sub_frame_view->size()); + for (size_t k = 0; k < sub_frame_view->size(); ++k) { + (*sub_frame_view)[k] = rtc::ArrayView( + &frame->split_bands_f(0)[k][sub_frame_index * kSubFrameLength], + kSubFrameLength); + } +} + +void FillSubFrameView(std::vector>* frame, + size_t sub_frame_index, + std::vector>* sub_frame_view) { + RTC_DCHECK_GE(1, sub_frame_index); + RTC_DCHECK_EQ(frame->size(), sub_frame_view->size()); + for (size_t k = 0; k < frame->size(); ++k) { + (*sub_frame_view)[k] = rtc::ArrayView( + &(*frame)[k][sub_frame_index * kSubFrameLength], kSubFrameLength); + } +} + +void ProcessCaptureFrameContent( + AudioBuffer* capture, + bool known_echo_path_change, + bool saturated_microphone_signal, + size_t sub_frame_index, + FrameBlocker* capture_blocker, + BlockFramer* output_framer, + BlockProcessor* block_processor, + std::vector>* block, + std::vector>* sub_frame_view) { + FillSubFrameView(capture, sub_frame_index, sub_frame_view); + capture_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block); + block_processor->ProcessCapture(known_echo_path_change, + saturated_microphone_signal, block); + output_framer->InsertBlockAndExtractSubFrame(*block, sub_frame_view); +} + +void ProcessRemainingCaptureFrameContent( + bool known_echo_path_change, + bool saturated_microphone_signal, + FrameBlocker* capture_blocker, + BlockFramer* output_framer, + BlockProcessor* block_processor, + std::vector>* block) { + if (!capture_blocker->IsBlockAvailable()) { + return; + } + + capture_blocker->ExtractBlock(block); + block_processor->ProcessCapture(known_echo_path_change, + saturated_microphone_signal, block); + output_framer->InsertBlock(*block); +} + +bool BufferRenderFrameContent( + std::vector>* render_frame, + size_t sub_frame_index, + FrameBlocker* render_blocker, + BlockProcessor* block_processor, + std::vector>* block, + std::vector>* sub_frame_view) { + FillSubFrameView(render_frame, sub_frame_index, sub_frame_view); + render_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block); + return block_processor->BufferRender(block); +} + +bool BufferRemainingRenderFrameContent(FrameBlocker* render_blocker, + BlockProcessor* block_processor, + std::vector>* block) { + if (!render_blocker->IsBlockAvailable()) { + return false; + } + render_blocker->ExtractBlock(block); + return block_processor->BufferRender(block); +} + +void CopyAudioBufferIntoFrame(AudioBuffer* buffer, + size_t num_bands, + size_t frame_length, + std::vector>* frame) { + RTC_DCHECK_EQ(num_bands, frame->size()); + for (size_t i = 0; i < num_bands; ++i) { + rtc::ArrayView buffer_view(&buffer->split_bands_f(0)[i][0], + frame_length); + std::copy(buffer_view.begin(), buffer_view.end(), (*frame)[i].begin()); + } +} + +// [B,A] = butter(2,100/4000,'high') +const CascadedBiQuadFilter::BiQuadCoefficients + kHighPassFilterCoefficients_8kHz = {{0.94598f, -1.89195f, 0.94598f}, + {-1.88903f, 0.89487f}}; +const int kNumberOfHighPassBiQuads_8kHz = 1; + +// [B,A] = butter(2,100/8000,'high') +const CascadedBiQuadFilter::BiQuadCoefficients + kHighPassFilterCoefficients_16kHz = {{0.97261f, -1.94523f, 0.97261f}, + {-1.94448f, 0.94598f}}; +const int kNumberOfHighPassBiQuads_16kHz = 1; + +static constexpr size_t kRenderTransferQueueSize = 30; + +} // namespace + +class EchoCanceller3::RenderWriter { + public: + RenderWriter(ApmDataDumper* data_dumper, + SwapQueue>, + Aec3RenderQueueItemVerifier>* render_transfer_queue, + std::unique_ptr render_highpass_filter, + int sample_rate_hz, + int frame_length, + int num_bands); + ~RenderWriter(); + bool Insert(AudioBuffer* render); + + private: + ApmDataDumper* data_dumper_; + const int sample_rate_hz_; + const size_t frame_length_; + const int num_bands_; + std::unique_ptr render_highpass_filter_; + std::vector> render_queue_input_frame_; + SwapQueue>, Aec3RenderQueueItemVerifier>* + render_transfer_queue_; + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWriter); +}; + +EchoCanceller3::RenderWriter::RenderWriter( + ApmDataDumper* data_dumper, + SwapQueue>, Aec3RenderQueueItemVerifier>* + render_transfer_queue, + std::unique_ptr render_highpass_filter, + int sample_rate_hz, + int frame_length, + int num_bands) + : data_dumper_(data_dumper), + sample_rate_hz_(sample_rate_hz), + frame_length_(frame_length), + num_bands_(num_bands), + render_highpass_filter_(std::move(render_highpass_filter)), + render_queue_input_frame_(num_bands_, + std::vector(frame_length_, 0.f)), + render_transfer_queue_(render_transfer_queue) { + RTC_DCHECK(data_dumper); +} + +EchoCanceller3::RenderWriter::~RenderWriter() = default; + +bool EchoCanceller3::RenderWriter::Insert(AudioBuffer* input) { + RTC_DCHECK_EQ(1, input->num_channels()); + RTC_DCHECK_EQ(num_bands_, input->num_bands()); + RTC_DCHECK_EQ(frame_length_, input->num_frames_per_band()); + data_dumper_->DumpWav("aec3_render_input", frame_length_, + &input->split_bands_f(0)[0][0], + LowestBandRate(sample_rate_hz_), 1); + + CopyAudioBufferIntoFrame(input, num_bands_, frame_length_, + &render_queue_input_frame_); + + if (render_highpass_filter_) { + render_highpass_filter_->Process(render_queue_input_frame_[0]); + } + + return render_transfer_queue_->Insert(&render_queue_input_frame_); +} + int EchoCanceller3::instance_count_ = 0; -EchoCanceller3::EchoCanceller3(int sample_rate_hz, bool use_anti_hum_filter) { - int band_sample_rate_hz = (sample_rate_hz == 8000 ? sample_rate_hz : 16000); - frame_length_ = rtc::CheckedDivExact(band_sample_rate_hz, 100); +EchoCanceller3::EchoCanceller3(int sample_rate_hz, bool use_highpass_filter) + : EchoCanceller3(sample_rate_hz, + use_highpass_filter, + std::unique_ptr( + BlockProcessor::Create(sample_rate_hz))) {} +EchoCanceller3::EchoCanceller3(int sample_rate_hz, + bool use_highpass_filter, + std::unique_ptr block_processor) + : data_dumper_( + new ApmDataDumper(rtc::AtomicOps::Increment(&instance_count_))), + sample_rate_hz_(sample_rate_hz), + num_bands_(NumBandsForRate(sample_rate_hz_)), + frame_length_(rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)), + output_framer_(num_bands_), + capture_blocker_(num_bands_), + render_blocker_(num_bands_), + render_transfer_queue_( + kRenderTransferQueueSize, + std::vector>( + num_bands_, + std::vector(frame_length_, 0.f)), + Aec3RenderQueueItemVerifier(num_bands_, frame_length_)), + block_processor_(std::move(block_processor)), + render_queue_output_frame_(num_bands_, + std::vector(frame_length_, 0.f)), + block_(num_bands_, std::vector(kBlockSize, 0.f)), + sub_frame_view_(num_bands_) { + std::unique_ptr render_highpass_filter; + if (use_highpass_filter) { + render_highpass_filter.reset(new CascadedBiQuadFilter( + sample_rate_hz_ == 8000 ? kHighPassFilterCoefficients_8kHz + : kHighPassFilterCoefficients_16kHz, + sample_rate_hz_ == 8000 ? kNumberOfHighPassBiQuads_8kHz + : kNumberOfHighPassBiQuads_16kHz)); + capture_highpass_filter_.reset(new CascadedBiQuadFilter( + sample_rate_hz_ == 8000 ? kHighPassFilterCoefficients_8kHz + : kHighPassFilterCoefficients_16kHz, + sample_rate_hz_ == 8000 ? kNumberOfHighPassBiQuads_8kHz + : kNumberOfHighPassBiQuads_16kHz)); + } - LOG(LS_INFO) << "AEC3 created : " - << "{ instance_count: " << instance_count_ << "}"; - instance_count_ = rtc::AtomicOps::Increment(&instance_count_); + render_writer_.reset( + new RenderWriter(data_dumper_.get(), &render_transfer_queue_, + std::move(render_highpass_filter), sample_rate_hz_, + frame_length_, num_bands_)); + + RTC_DCHECK_EQ(num_bands_, std::max(sample_rate_hz_, 16000) / 16000); + RTC_DCHECK_GE(kMaxNumBands, num_bands_); } EchoCanceller3::~EchoCanceller3() = default; bool EchoCanceller3::AnalyzeRender(AudioBuffer* render) { - RTC_DCHECK_EQ(1u, render->num_channels()); - RTC_DCHECK_EQ(frame_length_, render->num_frames_per_band()); - return true; + RTC_DCHECK_RUNS_SERIALIZED(&render_race_checker_); + RTC_DCHECK(render); + return render_writer_->Insert(render); } -void EchoCanceller3::AnalyzeCapture(AudioBuffer* capture) {} +void EchoCanceller3::AnalyzeCapture(AudioBuffer* capture) { + RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_); + RTC_DCHECK(capture); + data_dumper_->DumpWav("aec3_capture_analyze_input", frame_length_, + capture->channels_f()[0], sample_rate_hz_, 1); + + saturated_microphone_signal_ = false; + for (size_t k = 0; k < capture->num_channels(); ++k) { + saturated_microphone_signal_ |= + DetectSaturation(rtc::ArrayView(capture->channels_f()[k], + capture->num_frames())); + if (saturated_microphone_signal_) { + break; + } + } +} void EchoCanceller3::ProcessCapture(AudioBuffer* capture, bool known_echo_path_change) { + RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_); + RTC_DCHECK(capture); RTC_DCHECK_EQ(1u, capture->num_channels()); + RTC_DCHECK_EQ(num_bands_, capture->num_bands()); RTC_DCHECK_EQ(frame_length_, capture->num_frames_per_band()); + + rtc::ArrayView capture_lower_band = + rtc::ArrayView(&capture->split_bands_f(0)[0][0], frame_length_); + + data_dumper_->DumpWav("aec3_capture_input", capture_lower_band, + LowestBandRate(sample_rate_hz_), 1); + + const bool render_buffer_overrun = EmptyRenderQueue(); + RTC_DCHECK(!render_buffer_overrun); + + if (capture_highpass_filter_) { + capture_highpass_filter_->Process(capture_lower_band); + } + + ProcessCaptureFrameContent(capture, known_echo_path_change, + saturated_microphone_signal_, 0, &capture_blocker_, + &output_framer_, block_processor_.get(), &block_, + &sub_frame_view_); + + if (sample_rate_hz_ != 8000) { + ProcessCaptureFrameContent( + capture, known_echo_path_change, saturated_microphone_signal_, 1, + &capture_blocker_, &output_framer_, block_processor_.get(), &block_, + &sub_frame_view_); + } + + ProcessRemainingCaptureFrameContent( + known_echo_path_change, saturated_microphone_signal_, &capture_blocker_, + &output_framer_, block_processor_.get(), &block_); + + data_dumper_->DumpWav("aec3_capture_output", frame_length_, + &capture->split_bands_f(0)[0][0], + LowestBandRate(sample_rate_hz_), 1); } std::string EchoCanceller3::ToString( @@ -54,4 +330,29 @@ bool EchoCanceller3::Validate( return true; } +bool EchoCanceller3::EmptyRenderQueue() { + RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_); + bool render_buffer_overrun = false; + bool frame_to_buffer = + render_transfer_queue_.Remove(&render_queue_output_frame_); + while (frame_to_buffer) { + render_buffer_overrun |= BufferRenderFrameContent( + &render_queue_output_frame_, 0, &render_blocker_, + block_processor_.get(), &block_, &sub_frame_view_); + + if (sample_rate_hz_ != 8000) { + render_buffer_overrun |= BufferRenderFrameContent( + &render_queue_output_frame_, 1, &render_blocker_, + block_processor_.get(), &block_, &sub_frame_view_); + } + + render_buffer_overrun |= BufferRemainingRenderFrameContent( + &render_blocker_, block_processor_.get(), &block_); + + frame_to_buffer = + render_transfer_queue_.Remove(&render_queue_output_frame_); + } + return render_buffer_overrun; +} + } // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/echo_canceller3.h b/webrtc/modules/audio_processing/aec3/echo_canceller3.h index c65ecdea75..57714b2b06 100644 --- a/webrtc/modules/audio_processing/aec3/echo_canceller3.h +++ b/webrtc/modules/audio_processing/aec3/echo_canceller3.h @@ -11,16 +11,63 @@ #ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_CANCELLER3_H_ #define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_CANCELLER3_H_ -#include - #include "webrtc/base/constructormagic.h" +#include "webrtc/base/race_checker.h" +#include "webrtc/base/swap_queue.h" +#include "webrtc/modules/audio_processing/aec3/block_framer.h" +#include "webrtc/modules/audio_processing/aec3/block_processor.h" +#include "webrtc/modules/audio_processing/aec3/cascaded_biquad_filter.h" +#include "webrtc/modules/audio_processing/aec3/frame_blocker.h" #include "webrtc/modules/audio_processing/audio_buffer.h" +#include "webrtc/modules/audio_processing/include/audio_processing.h" +#include "webrtc/modules/audio_processing/logging/apm_data_dumper.h" namespace webrtc { +// Functor for verifying the invariance of the frames being put into the render +// queue. +class Aec3RenderQueueItemVerifier { + public: + explicit Aec3RenderQueueItemVerifier(size_t num_bands, size_t frame_length) + : num_bands_(num_bands), frame_length_(frame_length) {} + + bool operator()(const std::vector>& v) const { + if (v.size() != num_bands_) { + return false; + } + for (const auto& v_k : v) { + if (v_k.size() != frame_length_) { + return false; + } + } + return true; + } + + private: + const size_t num_bands_; + const size_t frame_length_; +}; + +// Main class for the echo canceller3. +// It does 4 things: +// -Receives 10 ms frames of band-split audio. +// -Optionally applies an anti-hum (high-pass) filter on the +// received signals. +// -Provides the lower level echo canceller functionality with +// blocks of 64 samples of audio data. +// -Partially handles the jitter in the render and capture API +// call sequence. +// +// The class is supposed to be used in a non-concurrent manner apart from the +// AnalyzeRender call which can be called concurrently with the other methods. class EchoCanceller3 { public: - EchoCanceller3(int sample_rate_hz, bool use_anti_hum_filter); + // Normal c-tor to use. + EchoCanceller3(int sample_rate_hz, bool use_highpass_filter); + // Testing c-tor that is used only for testing purposes. + EchoCanceller3(int sample_rate_hz, + bool use_highpass_filter, + std::unique_ptr block_processor); ~EchoCanceller3(); // Analyzes and stores an internal copy of the split-band domain render // signal. @@ -31,6 +78,15 @@ class EchoCanceller3 { // present in the signal. void ProcessCapture(AudioBuffer* capture, bool known_echo_path_change); + // Signals whether an external detector has detected echo leakage from the + // echo canceller. + // Note that in the case echo leakage has been flagged, it should be unflagged + // once it is no longer occurring. + void ReportEchoLeakage(bool leakage_detected) { + RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_); + block_processor_->ReportEchoLeakage(leakage_detected); + } + // Validates a config. static bool Validate(const AudioProcessing::Config::EchoCanceller3& config); // Dumps a config to a string. @@ -38,8 +94,37 @@ class EchoCanceller3 { const AudioProcessing::Config::EchoCanceller3& config); private: + class RenderWriter; + + bool EmptyRenderQueue(); + + rtc::RaceChecker capture_race_checker_; + rtc::RaceChecker render_race_checker_; + + // State that is accessed by the AnalyzeRender call. + std::unique_ptr render_writer_ GUARDED_BY(render_race_checker_); + + // State that may be accessed by the capture thread. static int instance_count_; - size_t frame_length_; + std::unique_ptr data_dumper_; + const int sample_rate_hz_; + const int num_bands_; + const size_t frame_length_; + BlockFramer output_framer_ GUARDED_BY(capture_race_checker_); + FrameBlocker capture_blocker_ GUARDED_BY(capture_race_checker_); + FrameBlocker render_blocker_ GUARDED_BY(capture_race_checker_); + SwapQueue>, Aec3RenderQueueItemVerifier> + render_transfer_queue_; + std::unique_ptr block_processor_ + GUARDED_BY(capture_race_checker_); + std::vector> render_queue_output_frame_ + GUARDED_BY(capture_race_checker_); + std::unique_ptr capture_highpass_filter_ + GUARDED_BY(capture_race_checker_); + bool saturated_microphone_signal_ GUARDED_BY(capture_race_checker_) = false; + std::vector> block_ GUARDED_BY(capture_race_checker_); + std::vector> sub_frame_view_ + GUARDED_BY(capture_race_checker_); RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(EchoCanceller3); }; diff --git a/webrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc b/webrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc new file mode 100644 index 0000000000..8baa45606f --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc @@ -0,0 +1,717 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/echo_canceller3.h" + +#include +#include +#include +#include +#include +#include + +#include "webrtc/modules/audio_processing/aec3/aec3_constants.h" +#include "webrtc/modules/audio_processing/aec3/block_processor.h" +#include "webrtc/modules/audio_processing/aec3/frame_blocker.h" +#include "webrtc/modules/audio_processing/aec3/mock/mock_block_processor.h" +#include "webrtc/modules/audio_processing/audio_buffer.h" +#include "webrtc/test/gmock.h" +#include "webrtc/test/gtest.h" + +namespace webrtc { +namespace { + +using testing::StrictMock; +using testing::_; + +// 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(frame_index * frame_length + i) + offset; + frame[k][i] = (value > 0 ? 5000 * k + value : 0); + } + } +} + +// 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; +} + +// 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() override = default; + + void ProcessCapture(bool known_echo_path_change, + bool saturated_microphone_signal, + std::vector>* capture_block) override { + } + + bool BufferRender(std::vector>* block) override { + return false; + } + + void ReportEchoLeakage(bool leakage_detected) override {} + + private: + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTransportVerificationProcessor); +}; + +// 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() override = default; + + void ProcessCapture(bool known_echo_path_change, + bool saturated_microphone_signal, + std::vector>* capture_block) override { + std::vector> render_block = + received_render_blocks_.front(); + received_render_blocks_.pop_front(); + capture_block->swap(render_block); + } + + bool BufferRender(std::vector>* block) override { + received_render_blocks_.push_back(*block); + return false; + } + + void ReportEchoLeakage(bool leakage_detected) override {} + + private: + std::deque>> received_render_blocks_; + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderTransportVerificationProcessor); +}; + +class EchoCanceller3Tester { + public: + explicit EchoCanceller3Tester(int sample_rate_hz) + : sample_rate_hz_(sample_rate_hz), + num_bands_(NumBandsForRate(sample_rate_hz_)), + frame_length_(sample_rate_hz_ == 8000 ? 80 : 160), + fullband_frame_length_(rtc::CheckedDivExact(sample_rate_hz_, 100)), + capture_buffer_(fullband_frame_length_, + 1, + fullband_frame_length_, + 1, + fullband_frame_length_), + render_buffer_(fullband_frame_length_, + 1, + fullband_frame_length_, + 1, + fullband_frame_length_) {} + + // 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( + sample_rate_hz_, false, + std::unique_ptr( + new 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_f(0)[0], 0); + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &render_buffer_.split_bands_f(0)[0], 100); + + EXPECT_TRUE(aec3.AnalyzeRender(&render_buffer_)); + aec3.ProcessCapture(&capture_buffer_, false); + EXPECT_TRUE(VerifyOutputFrameBitexactness( + frame_length_, num_bands_, frame_index, + &capture_buffer_.split_bands_f(0)[0], -64)); + } + } + + // Test method for testing that the render data is properly received by the + // block processor. + void RunRenderTransportVerificationTest() { + EchoCanceller3 aec3( + sample_rate_hz_, false, + std::unique_ptr( + new RenderTransportVerificationProcessor(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_f(0)[0], 100); + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &render_buffer_.split_bands_f(0)[0], 0); + + EXPECT_TRUE(aec3.AnalyzeRender(&render_buffer_)); + aec3.ProcessCapture(&capture_buffer_, false); + EXPECT_TRUE(VerifyOutputFrameBitexactness( + frame_length_, num_bands_, frame_index, + &capture_buffer_.split_bands_f(0)[0], -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) { + const size_t num_full_blocks_per_frame = + rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100) / kBlockSize; + const size_t expected_num_block_to_process = + (kNumFramesToProcess * + rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) / + kBlockSize; + std::unique_ptr> + block_processor_mock( + new StrictMock()); + EXPECT_CALL(*block_processor_mock, BufferRender(_)) + .Times(expected_num_block_to_process); + EXPECT_CALL(*block_processor_mock, ReportEchoLeakage(_)).Times(0); + + switch (echo_path_change_test_variant) { + case EchoPathChangeTestVariant::kNone: + EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _)) + .Times(expected_num_block_to_process); + break; + case EchoPathChangeTestVariant::kOneSticky: + EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _)) + .Times(expected_num_block_to_process); + break; + case EchoPathChangeTestVariant::kOneNonSticky: + EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _)) + .Times(num_full_blocks_per_frame); + EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _)) + .Times(expected_num_block_to_process - num_full_blocks_per_frame); + break; + } + + EchoCanceller3 aec3(sample_rate_hz_, false, + 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_f(0)[0], 0); + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &render_buffer_.split_bands_f(0)[0], 0); + + EXPECT_TRUE(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) { + const size_t expected_num_block_to_process = + (kNumFramesToProcess * + rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) / + kBlockSize; + std::unique_ptr> + block_processor_mock( + new StrictMock()); + EXPECT_CALL(*block_processor_mock, BufferRender(_)) + .Times(expected_num_block_to_process); + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, _, _)) + .Times(expected_num_block_to_process); + + switch (leakage_report_variant) { + case EchoLeakageTestVariant::kNone: + EXPECT_CALL(*block_processor_mock, ReportEchoLeakage(_)).Times(0); + break; + case EchoLeakageTestVariant::kFalseSticky: + EXPECT_CALL(*block_processor_mock, ReportEchoLeakage(false)).Times(1); + break; + case EchoLeakageTestVariant::kTrueSticky: + EXPECT_CALL(*block_processor_mock, ReportEchoLeakage(true)).Times(1); + break; + case EchoLeakageTestVariant::kTrueNonSticky: { + testing::InSequence s; + EXPECT_CALL(*block_processor_mock, ReportEchoLeakage(true)).Times(1); + EXPECT_CALL(*block_processor_mock, ReportEchoLeakage(false)) + .Times(kNumFramesToProcess - 1); + } break; + } + + EchoCanceller3 aec3(sample_rate_hz_, false, + 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.ReportEchoLeakage(false); + } + break; + case EchoLeakageTestVariant::kTrueSticky: + if (frame_index == 0) { + aec3.ReportEchoLeakage(true); + } + break; + case EchoLeakageTestVariant::kTrueNonSticky: + if (frame_index == 0) { + aec3.ReportEchoLeakage(true); + } else { + aec3.ReportEchoLeakage(false); + } + break; + } + + aec3.AnalyzeCapture(&capture_buffer_); + OptionalBandSplit(); + + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &capture_buffer_.split_bands_f(0)[0], 0); + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &render_buffer_.split_bands_f(0)[0], 0); + + EXPECT_TRUE(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 num_full_blocks_per_frame = + rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100) / kBlockSize; + const size_t expected_num_block_to_process = + (kNumFramesToProcess * + rtc::CheckedDivExact(LowestBandRate(sample_rate_hz_), 100)) / + kBlockSize; + std::unique_ptr> + block_processor_mock( + new StrictMock()); + EXPECT_CALL(*block_processor_mock, BufferRender(_)) + .Times(expected_num_block_to_process); + EXPECT_CALL(*block_processor_mock, ReportEchoLeakage(_)).Times(0); + + switch (saturation_variant) { + case SaturationTestVariant::kNone: + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _)) + .Times(expected_num_block_to_process); + break; + case SaturationTestVariant::kOneNegative: { + testing::InSequence s; + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _)) + .Times(num_full_blocks_per_frame); + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _)) + .Times(expected_num_block_to_process - num_full_blocks_per_frame); + } break; + case SaturationTestVariant::kOnePositive: { + testing::InSequence s; + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _)) + .Times(num_full_blocks_per_frame); + EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _)) + .Times(expected_num_block_to_process - num_full_blocks_per_frame); + } break; + } + + EchoCanceller3 aec3(sample_rate_hz_, false, + 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_f()[0][k] = 0.f; + } + switch (saturation_variant) { + case SaturationTestVariant::kNone: + break; + case SaturationTestVariant::kOneNegative: + if (frame_index == 0) { + capture_buffer_.channels_f()[0][10] = -32768.f; + } + break; + case SaturationTestVariant::kOnePositive: + if (frame_index == 0) { + capture_buffer_.channels_f()[0][10] = 32767.f; + } + break; + } + + aec3.AnalyzeCapture(&capture_buffer_); + OptionalBandSplit(); + + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &capture_buffer_.split_bands_f(0)[0], 0); + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &render_buffer_.split_bands_f(0)[0], 0); + + EXPECT_TRUE(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() { + EchoCanceller3 aec3( + sample_rate_hz_, false, + std::unique_ptr( + new RenderTransportVerificationProcessor(num_bands_))); + + constexpr size_t kSwapQueueLength = 30; + for (size_t frame_index = 0; frame_index < kSwapQueueLength; + ++frame_index) { + if (sample_rate_hz_ > 16000) { + render_buffer_.SplitIntoFrequencyBands(); + } + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &render_buffer_.split_bands_f(0)[0], 0); + + EXPECT_TRUE(aec3.AnalyzeRender(&render_buffer_)); + } + + for (size_t frame_index = 0; frame_index < kSwapQueueLength; + ++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_f(0)[0], 0); + + aec3.ProcessCapture(&capture_buffer_, false); + EXPECT_TRUE(VerifyOutputFrameBitexactness( + frame_length_, num_bands_, frame_index, + &capture_buffer_.split_bands_f(0)[0], -64)); + } + } + + // This test verifies that a buffer overrun in the render swapqueue is + // properly reported. + void RunRenderPipelineSwapQueueOverrunReturnValueTest() { + EchoCanceller3 aec3(sample_rate_hz_, false); + + constexpr size_t kSwapQueueLength = 30; + for (size_t k = 0; k < 2; ++k) { + for (size_t frame_index = 0; frame_index < kSwapQueueLength; + ++frame_index) { + if (sample_rate_hz_ > 16000) { + render_buffer_.SplitIntoFrequencyBands(); + } + PopulateInputFrame(frame_length_, num_bands_, frame_index, + &render_buffer_.split_bands_f(0)[0], 0); + + if (k == 0) { + EXPECT_TRUE(aec3.AnalyzeRender(&render_buffer_)); + } else { + EXPECT_FALSE(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(aec3_sample_rate_hz, false); + PopulateInputFrame(frame_length_, num_bands_, 0, + &render_buffer_.split_bands_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(aec3_sample_rate_hz, false); + PopulateInputFrame(frame_length_, num_bands_, 0, + &capture_buffer_.split_bands_f(0)[0], 100); + EXPECT_DEATH(aec3.ProcessCapture(&capture_buffer_, false), ""); + } + + // Verifies the that the check for the frame length in the AnalyzeRender input + // is correct by adjusting the sample rates of EchoCanceller3 and the input + // AudioBuffer to have a different frame lengths. + void RunAnalyzeRenderFrameLengthCheckVerification() { + // Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a + // way that the band frame lengths are different. + const int aec3_sample_rate_hz = sample_rate_hz_ == 8000 ? 16000 : 8000; + EchoCanceller3 aec3(aec3_sample_rate_hz, false); + + OptionalBandSplit(); + PopulateInputFrame(frame_length_, num_bands_, 0, + &render_buffer_.split_bands_f(0)[0], 0); + + EXPECT_DEATH(aec3.AnalyzeRender(&render_buffer_), ""); + } + + // Verifies the that the check for the frame length in the AnalyzeRender input + // is correct by adjusting the sample rates of EchoCanceller3 and the input + // AudioBuffer to have a different frame lengths. + void RunProcessCaptureFrameLengthCheckVerification() { + // Set aec3_sample_rate_hz to be different from sample_rate_hz_ in such a + // way that the band frame lengths are different. + const int aec3_sample_rate_hz = sample_rate_hz_ == 8000 ? 16000 : 8000; + EchoCanceller3 aec3(aec3_sample_rate_hz, false); + + OptionalBandSplit(); + 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_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(EchoCanceller3Tester); +}; + +std::string ProduceDebugText(int sample_rate_hz) { + std::ostringstream ss; + ss << "Sample rate: " << sample_rate_hz; + return ss.str(); +} + +std::string ProduceDebugText(int sample_rate_hz, int variant) { + std::ostringstream ss; + ss << "Sample rate: " << sample_rate_hz << ", variant: " << variant; + return ss.str(); +} + +} // namespace + +TEST(EchoCanceller3Buffering, CaptureBitexactness) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + EchoCanceller3Tester(rate).RunCaptureTransportVerificationTest(); + } +} + +TEST(EchoCanceller3Buffering, RenderBitexactness) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + EchoCanceller3Tester(rate).RunRenderTransportVerificationTest(); + } +} + +TEST(EchoCanceller3Buffering, RenderSwapQueue) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + EchoCanceller3Tester(rate).RunRenderSwapQueueVerificationTest(); + } +} + +TEST(EchoCanceller3Buffering, RenderSwapQueueOverrunReturnValue) { + for (auto rate : {8000, 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 : {8000, 16000, 32000, 48000}) { + for (auto variant : variants) { + SCOPED_TRACE(ProduceDebugText(rate, static_cast(variant))); + EchoCanceller3Tester(rate).RunCaptureSaturationVerificationTest(variant); + } + } +} + +TEST(EchoCanceller3Messaging, EchoPathChange) { + auto variants = { + EchoCanceller3Tester::EchoPathChangeTestVariant::kNone, + EchoCanceller3Tester::EchoPathChangeTestVariant::kOneSticky, + EchoCanceller3Tester::EchoPathChangeTestVariant::kOneNonSticky}; + for (auto rate : {8000, 16000, 32000, 48000}) { + for (auto variant : variants) { + SCOPED_TRACE(ProduceDebugText(rate, static_cast(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 : {8000, 16000, 32000, 48000}) { + for (auto variant : variants) { + SCOPED_TRACE(ProduceDebugText(rate, static_cast(variant))); + EchoCanceller3Tester(rate).RunEchoLeakageVerificationTest(variant); + } + } +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST(EchoCanceller3InputCheck, WrongRenderNumBandsCheckVerification) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + EchoCanceller3Tester(rate).RunAnalyzeRenderNumBandsCheckVerification(); + } +} + +TEST(EchoCanceller3InputCheck, WrongCaptureNumBandsCheckVerification) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + EchoCanceller3Tester(rate).RunProcessCaptureNumBandsCheckVerification(); + } +} + +TEST(EchoCanceller3InputCheck, WrongRenderFrameLengthCheckVerification) { + for (auto rate : {8000, 16000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + EchoCanceller3Tester(rate).RunAnalyzeRenderFrameLengthCheckVerification(); + } +} + +TEST(EchoCanceller3InputCheck, WrongCaptureFrameLengthCheckVerification) { + for (auto rate : {8000, 16000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + EchoCanceller3Tester(rate).RunProcessCaptureFrameLengthCheckVerification(); + } +} + +// Verifiers that the verification for null input to the render analysis api +// call works. +TEST(EchoCanceller3InputCheck, NullRenderAnalysisParameter) { + EXPECT_DEATH(EchoCanceller3(8000, false).AnalyzeRender(nullptr), ""); +} + +// Verifiers that the verification for null input to the capture analysis api +// call works. +TEST(EchoCanceller3InputCheck, NullCaptureAnalysisParameter) { + EXPECT_DEATH(EchoCanceller3(8000, false).AnalyzeCapture(nullptr), ""); +} + +// Verifiers that the verification for null input to the capture processing api +// call works. +TEST(EchoCanceller3InputCheck, NullCaptureProcessingParameter) { + EXPECT_DEATH(EchoCanceller3(8000, false).ProcessCapture(nullptr, false), ""); +} + +#endif + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/frame_blocker.cc b/webrtc/modules/audio_processing/aec3/frame_blocker.cc new file mode 100644 index 0000000000..b15b454384 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/frame_blocker.cc @@ -0,0 +1,72 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/frame_blocker.h" + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/modules/audio_processing/aec3/aec3_constants.h" + +namespace webrtc { + +FrameBlocker::FrameBlocker(size_t num_bands) + : num_bands_(num_bands), buffer_(num_bands_) { + for (auto& b : buffer_) { + b.reserve(kBlockSize); + RTC_DCHECK(b.empty()); + } +} + +FrameBlocker::~FrameBlocker() = default; + +void FrameBlocker::InsertSubFrameAndExtractBlock( + const std::vector>& sub_frame, + std::vector>* block) { + RTC_DCHECK(block); + RTC_DCHECK_EQ(num_bands_, block->size()); + RTC_DCHECK_EQ(num_bands_, sub_frame.size()); + for (size_t i = 0; i < num_bands_; ++i) { + RTC_DCHECK_GE(kBlockSize - 16, buffer_[i].size()); + RTC_DCHECK_EQ(kBlockSize, (*block)[i].size()); + RTC_DCHECK_EQ(kSubFrameLength, sub_frame[i].size()); + const int samples_to_block = kBlockSize - buffer_[i].size(); + (*block)[i].clear(); + (*block)[i].insert((*block)[i].begin(), buffer_[i].begin(), + buffer_[i].end()); + (*block)[i].insert((*block)[i].begin() + buffer_[i].size(), + sub_frame[i].begin(), + sub_frame[i].begin() + samples_to_block); + buffer_[i].clear(); + buffer_[i].insert(buffer_[i].begin(), + sub_frame[i].begin() + samples_to_block, + sub_frame[i].end()); + } +} + +bool FrameBlocker::IsBlockAvailable() const { + return kBlockSize == buffer_[0].size(); +} + +void FrameBlocker::ExtractBlock(std::vector>* block) { + RTC_DCHECK(block); + RTC_DCHECK_EQ(num_bands_, block->size()); + RTC_DCHECK(IsBlockAvailable()); + for (size_t i = 0; i < num_bands_; ++i) { + RTC_DCHECK_EQ(kBlockSize, buffer_[i].size()); + RTC_DCHECK_EQ(kBlockSize, (*block)[i].size()); + (*block)[i].clear(); + (*block)[i].insert((*block)[i].begin(), buffer_[i].begin(), + buffer_[i].end()); + buffer_[i].clear(); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/frame_blocker.h b/webrtc/modules/audio_processing/aec3/frame_blocker.h new file mode 100644 index 0000000000..958d5f2c0c --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/frame_blocker.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FRAME_BLOCKER_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FRAME_BLOCKER_H_ + +#include +#include + +#include "webrtc/base/array_view.h" +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_processing/aec3/aec3_constants.h" + +namespace webrtc { + +// Class for producing 64 sample multiband blocks from frames consisting of 1 or +// 2 subframes of 80 samples. +class FrameBlocker { + public: + explicit FrameBlocker(size_t num_bands); + ~FrameBlocker(); + // Inserts one 80 sample multiband subframe from the multiband frame and + // extracts one 64 sample multiband block. + void InsertSubFrameAndExtractBlock( + const std::vector>& sub_frame, + std::vector>* block); + // Reports whether a multiband block of 64 samples is available for + // extraction. + bool IsBlockAvailable() const; + // Extracts a multiband block of 64 samples. + void ExtractBlock(std::vector>* block); + + private: + const size_t num_bands_; + std::vector> buffer_; + + RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(FrameBlocker); +}; +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FRAME_BLOCKER_H_ diff --git a/webrtc/modules/audio_processing/aec3/frame_blocker_unittest.cc b/webrtc/modules/audio_processing/aec3/frame_blocker_unittest.cc new file mode 100644 index 0000000000..498fa8eabc --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/frame_blocker_unittest.cc @@ -0,0 +1,341 @@ +/* + * 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 "webrtc/modules/audio_processing/aec3/frame_blocker.h" + +#include +#include +#include + +#include "webrtc/modules/audio_processing/aec3/aec3_constants.h" +#include "webrtc/modules/audio_processing/aec3/block_framer.h" +#include "webrtc/test/gtest.h" + +namespace webrtc { +namespace { + +float ComputeSampleValue(size_t chunk_counter, + size_t chunk_size, + size_t band, + size_t sample_index, + int offset) { + float value = + static_cast(chunk_counter * chunk_size + sample_index) + offset; + return value > 0 ? 5000 * band + value : 0; +} + +void FillSubFrame(size_t sub_frame_counter, + int offset, + std::vector>* sub_frame) { + for (size_t k = 0; k < sub_frame->size(); ++k) { + for (size_t i = 0; i < (*sub_frame)[0].size(); ++i) { + (*sub_frame)[k][i] = + ComputeSampleValue(sub_frame_counter, kSubFrameLength, k, i, offset); + } + } +} + +void FillSubFrameView(size_t sub_frame_counter, + int offset, + std::vector>* sub_frame, + std::vector>* sub_frame_view) { + FillSubFrame(sub_frame_counter, offset, sub_frame); + for (size_t k = 0; k < sub_frame_view->size(); ++k) { + (*sub_frame_view)[k] = + rtc::ArrayView(&(*sub_frame)[k][0], (*sub_frame)[k].size()); + } +} + +bool VerifySubFrame(size_t sub_frame_counter, + int offset, + const std::vector>& sub_frame_view) { + std::vector> reference_sub_frame( + sub_frame_view.size(), std::vector(sub_frame_view[0].size(), 0.f)); + FillSubFrame(sub_frame_counter, offset, &reference_sub_frame); + for (size_t k = 0; k < sub_frame_view.size(); ++k) { + for (size_t i = 0; i < sub_frame_view[k].size(); ++i) { + if (reference_sub_frame[k][i] != sub_frame_view[k][i]) { + return false; + } + } + } + return true; +} + +bool VerifyBlock(size_t block_counter, + int offset, + const std::vector>& block) { + for (size_t k = 0; k < block.size(); ++k) { + for (size_t i = 0; i < block[k].size(); ++i) { + const float reference_value = + ComputeSampleValue(block_counter, kBlockSize, k, i, offset); + if (reference_value != block[k][i]) { + return false; + } + } + } + return true; +} + +// Verifies that the FrameBlocker properly forms blocks out of the frames. +void RunBlockerTest(int sample_rate_hz) { + constexpr size_t kNumSubFramesToProcess = 20; + const size_t num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> block(num_bands, + std::vector(kBlockSize, 0.f)); + std::vector> input_sub_frame( + num_bands, std::vector(kSubFrameLength, 0.f)); + std::vector> input_sub_frame_view(num_bands); + FrameBlocker blocker(num_bands); + + size_t block_counter = 0; + for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess; + ++sub_frame_index) { + FillSubFrameView(sub_frame_index, 0, &input_sub_frame, + &input_sub_frame_view); + + blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block); + VerifyBlock(block_counter++, 0, block); + + if ((sub_frame_index + 1) % 4 == 0) { + EXPECT_TRUE(blocker.IsBlockAvailable()); + } else { + EXPECT_FALSE(blocker.IsBlockAvailable()); + } + if (blocker.IsBlockAvailable()) { + blocker.ExtractBlock(&block); + VerifyBlock(block_counter++, 0, block); + } + } +} + +// Verifies that the FrameBlocker and BlockFramer work well together and produce +// the expected output. +void RunBlockerAndFramerTest(int sample_rate_hz) { + const size_t kNumSubFramesToProcess = 20; + const size_t num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> block(num_bands, + std::vector(kBlockSize, 0.f)); + std::vector> input_sub_frame( + num_bands, std::vector(kSubFrameLength, 0.f)); + std::vector> output_sub_frame( + num_bands, std::vector(kSubFrameLength, 0.f)); + std::vector> output_sub_frame_view(num_bands); + std::vector> input_sub_frame_view(num_bands); + FrameBlocker blocker(num_bands); + BlockFramer framer(num_bands); + + for (size_t sub_frame_index = 0; sub_frame_index < kNumSubFramesToProcess; + ++sub_frame_index) { + FillSubFrameView(sub_frame_index, 0, &input_sub_frame, + &input_sub_frame_view); + FillSubFrameView(sub_frame_index, 0, &output_sub_frame, + &output_sub_frame_view); + + blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block); + framer.InsertBlockAndExtractSubFrame(block, &output_sub_frame_view); + + if ((sub_frame_index + 1) % 4 == 0) { + EXPECT_TRUE(blocker.IsBlockAvailable()); + } else { + EXPECT_FALSE(blocker.IsBlockAvailable()); + } + if (blocker.IsBlockAvailable()) { + blocker.ExtractBlock(&block); + framer.InsertBlock(block); + } + EXPECT_TRUE(VerifySubFrame(sub_frame_index, -64, output_sub_frame_view)); + } +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +// Verifies that the FrameBlocker crashes if the InsertSubFrameAndExtractBlock +// method is called for inputs with the wrong number of bands or band lengths. +void RunWronglySizedInsertAndExtractParametersTest(int sample_rate_hz, + size_t num_block_bands, + size_t block_length, + size_t num_sub_frame_bands, + size_t sub_frame_length) { + const size_t correct_num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> block(num_block_bands, + std::vector(block_length, 0.f)); + std::vector> input_sub_frame( + num_sub_frame_bands, std::vector(sub_frame_length, 0.f)); + std::vector> input_sub_frame_view( + input_sub_frame.size()); + FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view); + FrameBlocker blocker(correct_num_bands); + EXPECT_DEATH( + blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block), ""); +} + +// Verifies that the FrameBlocker crashes if the ExtractBlock method is called +// for inputs with the wrong number of bands or band lengths. +void RunWronglySizedExtractParameterTest(int sample_rate_hz, + size_t num_block_bands, + size_t block_length) { + const size_t correct_num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> correct_block( + correct_num_bands, std::vector(kBlockSize, 0.f)); + std::vector> wrong_block( + num_block_bands, std::vector(block_length, 0.f)); + std::vector> input_sub_frame( + correct_num_bands, std::vector(kSubFrameLength, 0.f)); + std::vector> input_sub_frame_view( + input_sub_frame.size()); + FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view); + FrameBlocker blocker(correct_num_bands); + blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block); + blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block); + blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block); + blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &correct_block); + + EXPECT_DEATH(blocker.ExtractBlock(&wrong_block), ""); +} + +// Verifies that the FrameBlocker crashes if the ExtractBlock method is called +// after a wrong number of previous InsertSubFrameAndExtractBlock method calls +// have been made. +void RunWrongExtractOrderTest(int sample_rate_hz, + size_t num_preceeding_api_calls) { + const size_t correct_num_bands = NumBandsForRate(sample_rate_hz); + + std::vector> block(correct_num_bands, + std::vector(kBlockSize, 0.f)); + std::vector> input_sub_frame( + correct_num_bands, std::vector(kSubFrameLength, 0.f)); + std::vector> input_sub_frame_view( + input_sub_frame.size()); + FillSubFrameView(0, 0, &input_sub_frame, &input_sub_frame_view); + FrameBlocker blocker(correct_num_bands); + for (size_t k = 0; k < num_preceeding_api_calls; ++k) { + blocker.InsertSubFrameAndExtractBlock(input_sub_frame_view, &block); + } + + EXPECT_DEATH(blocker.ExtractBlock(&block), ""); +} +#endif + +std::string ProduceDebugText(int sample_rate_hz) { + std::ostringstream ss; + ss << "Sample rate: " << sample_rate_hz; + return ss.str(); +} + +} // namespace + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST(FrameBlocker, WrongNumberOfBandsInBlockForInsertSubFrameAndExtractBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + const size_t wrong_num_bands = (correct_num_bands % 3) + 1; + RunWronglySizedInsertAndExtractParametersTest( + rate, wrong_num_bands, kBlockSize, correct_num_bands, kSubFrameLength); + } +} + +TEST(FrameBlocker, + WrongNumberOfBandsInSubFrameForInsertSubFrameAndExtractBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + const size_t wrong_num_bands = (correct_num_bands % 3) + 1; + RunWronglySizedInsertAndExtractParametersTest( + rate, correct_num_bands, kBlockSize, wrong_num_bands, kSubFrameLength); + } +} + +TEST(FrameBlocker, + WrongNumberOfSamplesInBlockForInsertSubFrameAndExtractBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + RunWronglySizedInsertAndExtractParametersTest( + rate, correct_num_bands, kBlockSize - 1, correct_num_bands, + kSubFrameLength); + } +} + +TEST(FrameBlocker, + WrongNumberOfSamplesInSubFrameForInsertSubFrameAndExtractBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + RunWronglySizedInsertAndExtractParametersTest(rate, correct_num_bands, + kBlockSize, correct_num_bands, + kSubFrameLength - 1); + } +} + +TEST(FrameBlocker, WrongNumberOfBandsInBlockForExtractBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + const size_t wrong_num_bands = (correct_num_bands % 3) + 1; + RunWronglySizedExtractParameterTest(rate, wrong_num_bands, kBlockSize); + } +} + +TEST(FrameBlocker, WrongNumberOfSamplesInBlockForExtractBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + const size_t correct_num_bands = NumBandsForRate(rate); + RunWronglySizedExtractParameterTest(rate, correct_num_bands, + kBlockSize - 1); + } +} + +TEST(FrameBlocker, WrongNumberOfPreceedingApiCallsForExtractBlock) { + for (auto rate : {8000, 16000, 32000, 48000}) { + for (size_t num_calls = 0; num_calls < 4; ++num_calls) { + std::ostringstream ss; + ss << "Sample rate: " << rate; + ss << ", Num preceeding InsertSubFrameAndExtractBlock calls: " + << num_calls; + + SCOPED_TRACE(ss.str()); + RunWrongExtractOrderTest(rate, num_calls); + } + } +} + +// Verifiers that the verification for null sub_frame pointer works. +TEST(FrameBlocker, NullBlockParameter) { + std::vector> sub_frame( + 1, std::vector(kSubFrameLength, 0.f)); + std::vector> sub_frame_view(sub_frame.size()); + FillSubFrameView(0, 0, &sub_frame, &sub_frame_view); + EXPECT_DEATH( + FrameBlocker(1).InsertSubFrameAndExtractBlock(sub_frame_view, nullptr), + ""); +} + +#endif + +TEST(FrameBlocker, BlockBitexactness) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + RunBlockerTest(rate); + } +} + +TEST(FrameBlocker, BlockerAndFramer) { + for (auto rate : {8000, 16000, 32000, 48000}) { + SCOPED_TRACE(ProduceDebugText(rate)); + RunBlockerAndFramerTest(rate); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/mock/mock_block_processor.h b/webrtc/modules/audio_processing/aec3/mock/mock_block_processor.h new file mode 100644 index 0000000000..d73b095e26 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/mock/mock_block_processor.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_BLOCK_PROCESSOR_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_BLOCK_PROCESSOR_H_ + +#include + +#include "webrtc/modules/audio_processing/aec3/block_processor.h" +#include "webrtc/test/gmock.h" + +namespace webrtc { +namespace test { + +class MockBlockProcessor : public BlockProcessor { + public: + virtual ~MockBlockProcessor() {} + + MOCK_METHOD3(ProcessCapture, + void(bool known_echo_path_change, + bool saturated_microphone_signal, + std::vector>* capture_block)); + MOCK_METHOD1(BufferRender, bool(std::vector>* block)); + MOCK_METHOD1(ReportEchoLeakage, void(bool leakage_detected)); +}; + +} // namespace test +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_BLOCK_PROCESSOR_H_