Added first layer of the echo canceller 3 functionality.

This CL adds the first layer of the echo canceller 3.
All of the code is as it should, apart from
block_processor.* which only contains placeholder
functionality. (Upcoming CLs will add proper
functionality into those files.)

BUG=webrtc:6018

Review-Url: https://codereview.webrtc.org/2584493002
Cr-Commit-Position: refs/heads/master@{#15861}
This commit is contained in:
peah 2017-01-02 03:13:38 -08:00 committed by Commit bot
parent f709e56b4a
commit 38fd1758e9
19 changed files with 2522 additions and 15 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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 <stddef.h>
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<size_t>(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_

View File

@ -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 <algorithm>
#include "webrtc/base/checks.h"
namespace webrtc {
BlockFramer::BlockFramer(size_t num_bands)
: num_bands_(num_bands),
buffer_(num_bands_, std::vector<float>(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<std::vector<float>>& 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<std::vector<float>>& block,
std::vector<rtc::ArrayView<float>>* 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

View File

@ -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 <vector>
#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<std::vector<float>>& block);
// Adds a 64 sample block and extracts an 80 sample subframe.
void InsertBlockAndExtractSubFrame(
const std::vector<std::vector<float>>& block,
std::vector<rtc::ArrayView<float>>* sub_frame);
private:
const size_t num_bands_;
std::vector<std::vector<float>> buffer_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockFramer);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_FRAMER_H_

View File

@ -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 <sstream>
#include <string>
#include <vector>
#include "webrtc/modules/audio_processing/aec3/aec3_constants.h"
#include "webrtc/test/gtest.h"
namespace webrtc {
namespace {
void SetupSubFrameView(std::vector<std::vector<float>>* sub_frame,
std::vector<rtc::ArrayView<float>>* sub_frame_view) {
for (size_t k = 0; k < sub_frame_view->size(); ++k) {
(*sub_frame_view)[k] =
rtc::ArrayView<float>((*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<int>(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<rtc::ArrayView<float>>& 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<std::vector<float>>* 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<std::vector<float>> block(num_bands,
std::vector<float>(kBlockSize, 0.f));
std::vector<std::vector<float>> output_sub_frame(
num_bands, std::vector<float>(kSubFrameLength, 0.f));
std::vector<rtc::ArrayView<float>> 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<std::vector<float>> block(num_block_bands,
std::vector<float>(block_length, 0.f));
std::vector<std::vector<float>> output_sub_frame(
num_sub_frame_bands, std::vector<float>(sub_frame_length, 0.f));
std::vector<rtc::ArrayView<float>> 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<std::vector<float>> correct_block(
correct_num_bands, std::vector<float>(kBlockSize, 0.f));
std::vector<std::vector<float>> wrong_block(
num_block_bands, std::vector<float>(block_length, 0.f));
std::vector<std::vector<float>> output_sub_frame(
correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
std::vector<rtc::ArrayView<float>> 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<std::vector<float>> block(correct_num_bands,
std::vector<float>(kBlockSize, 0.f));
std::vector<std::vector<float>> output_sub_frame(
correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
std::vector<rtc::ArrayView<float>> 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<std::vector<float>>(
1, std::vector<float>(kBlockSize, 0.f)),
nullptr),
"");
}
#endif
TEST(BlockFramer, FrameBitexactness) {
for (auto rate : {8000, 16000, 32000, 48000}) {
SCOPED_TRACE(ProduceDebugText(rate));
RunFramerTest(rate);
}
}
} // namespace webrtc

View File

@ -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<std::vector<float>>* capture_block) override;
bool BufferRender(std::vector<std::vector<float>>* block) override;
void ReportEchoLeakage(bool leakage_detected) override;
private:
const size_t sample_rate_hz_;
static int instance_count_;
std::unique_ptr<ApmDataDumper> 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<std::vector<float>>* 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<std::vector<float>>* 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

View File

@ -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 <memory>
#include <vector>
#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<std::vector<float>>* capture_block) = 0;
// Buffers a block of render data supplied by a FrameBlocker object.
virtual bool BufferRender(std::vector<std::vector<float>>* 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_

View File

@ -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 <memory>
#include <sstream>
#include <string>
#include <vector>
#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<BlockProcessor> block_processor(
BlockProcessor::Create(sample_rate_hz));
std::vector<std::vector<float>> block(NumBandsForRate(sample_rate_hz),
std::vector<float>(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<BlockProcessor> block_processor(
BlockProcessor::Create(sample_rate_hz));
std::vector<std::vector<float>> block(
NumBandsForRate(sample_rate_hz), std::vector<float>(kBlockSize - 1, 0.f));
EXPECT_DEATH(block_processor->BufferRender(&block), "");
}
void RunCaptureBlockSizeVerificationTest(int sample_rate_hz) {
std::unique_ptr<BlockProcessor> block_processor(
BlockProcessor::Create(sample_rate_hz));
std::vector<std::vector<float>> block(
NumBandsForRate(sample_rate_hz), std::vector<float>(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<BlockProcessor> block_processor(
BlockProcessor::Create(sample_rate_hz));
std::vector<std::vector<float>> block(wrong_num_bands,
std::vector<float>(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<BlockProcessor> block_processor(
BlockProcessor::Create(sample_rate_hz));
std::vector<std::vector<float>> block(wrong_num_bands,
std::vector<float>(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>(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>(BlockProcessor::Create(8000))
->BufferRender(nullptr),
"");
}
#endif
} // namespace webrtc

View File

@ -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<const float> x,
rtc::ArrayView<float> 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<float> y) {
for (auto& biquad : biquad_states_) {
ApplyBiQuad(y, y, &biquad);
}
}
void CascadedBiQuadFilter::ApplyBiQuad(
rtc::ArrayView<const float> x,
rtc::ArrayView<float> 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

View File

@ -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 <vector>
#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<const float> x, rtc::ArrayView<float> y);
// Applies the biquads on the values in y in an in-place manner.
void Process(rtc::ArrayView<float> y);
private:
void ApplyBiQuad(rtc::ArrayView<const float> x,
rtc::ArrayView<float> y,
CascadedBiQuadFilter::BiQuadState* biquad_state);
std::vector<BiQuadState> biquad_states_;
const BiQuadCoefficients coefficients_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(CascadedBiQuadFilter);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_CASCADED_BIQUAD_FILTER_H_

View File

@ -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 <vector>
#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<float> CreateInputWithIncreasingValues(size_t vector_length) {
std::vector<float> 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<float> values = CreateInputWithIncreasingValues(1000);
CascadedBiQuadFilter filter(kBlockingCoefficients, 1);
filter.Process(values);
EXPECT_EQ(std::vector<float>(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<float> 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<float> input = CreateInputWithIncreasingValues(1000);
std::vector<float> 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<float> input = CreateInputWithIncreasingValues(10);
std::vector<float> output(input.size() - 1);
CascadedBiQuadFilter filter(kTransparentCoefficients, 1);
EXPECT_DEATH(filter.Process(input, output), "");
}
#endif
} // namespace webrtc

View File

@ -9,36 +9,312 @@
*/
#include "webrtc/modules/audio_processing/aec3/echo_canceller3.h"
#include <sstream>
#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<const float> 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<rtc::ArrayView<float>>* 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<float>(
&frame->split_bands_f(0)[k][sub_frame_index * kSubFrameLength],
kSubFrameLength);
}
}
void FillSubFrameView(std::vector<std::vector<float>>* frame,
size_t sub_frame_index,
std::vector<rtc::ArrayView<float>>* 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<float>(
&(*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<std::vector<float>>* block,
std::vector<rtc::ArrayView<float>>* 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<std::vector<float>>* 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<std::vector<float>>* render_frame,
size_t sub_frame_index,
FrameBlocker* render_blocker,
BlockProcessor* block_processor,
std::vector<std::vector<float>>* block,
std::vector<rtc::ArrayView<float>>* 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<std::vector<float>>* 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<std::vector<float>>* frame) {
RTC_DCHECK_EQ(num_bands, frame->size());
for (size_t i = 0; i < num_bands; ++i) {
rtc::ArrayView<float> 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<std::vector<std::vector<float>>,
Aec3RenderQueueItemVerifier>* render_transfer_queue,
std::unique_ptr<CascadedBiQuadFilter> 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<CascadedBiQuadFilter> render_highpass_filter_;
std::vector<std::vector<float>> render_queue_input_frame_;
SwapQueue<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>*
render_transfer_queue_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWriter);
};
EchoCanceller3::RenderWriter::RenderWriter(
ApmDataDumper* data_dumper,
SwapQueue<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>*
render_transfer_queue,
std::unique_ptr<CascadedBiQuadFilter> 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<float>(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>(
BlockProcessor::Create(sample_rate_hz))) {}
EchoCanceller3::EchoCanceller3(int sample_rate_hz,
bool use_highpass_filter,
std::unique_ptr<BlockProcessor> 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<std::vector<float>>(
num_bands_,
std::vector<float>(frame_length_, 0.f)),
Aec3RenderQueueItemVerifier(num_bands_, frame_length_)),
block_processor_(std::move(block_processor)),
render_queue_output_frame_(num_bands_,
std::vector<float>(frame_length_, 0.f)),
block_(num_bands_, std::vector<float>(kBlockSize, 0.f)),
sub_frame_view_(num_bands_) {
std::unique_ptr<CascadedBiQuadFilter> 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<const float>(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<float> capture_lower_band =
rtc::ArrayView<float>(&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

View File

@ -11,16 +11,63 @@
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_CANCELLER3_H_
#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_CANCELLER3_H_
#include <string>
#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<std::vector<float>>& 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<BlockProcessor> 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<RenderWriter> 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<ApmDataDumper> 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<std::vector<std::vector<float>>, Aec3RenderQueueItemVerifier>
render_transfer_queue_;
std::unique_ptr<BlockProcessor> block_processor_
GUARDED_BY(capture_race_checker_);
std::vector<std::vector<float>> render_queue_output_frame_
GUARDED_BY(capture_race_checker_);
std::unique_ptr<CascadedBiQuadFilter> capture_highpass_filter_
GUARDED_BY(capture_race_checker_);
bool saturated_microphone_signal_ GUARDED_BY(capture_race_checker_) = false;
std::vector<std::vector<float>> block_ GUARDED_BY(capture_race_checker_);
std::vector<rtc::ArrayView<float>> sub_frame_view_
GUARDED_BY(capture_race_checker_);
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(EchoCanceller3);
};

View File

@ -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 <deque>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#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<int>(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<std::vector<float>>* capture_block) override {
}
bool BufferRender(std::vector<std::vector<float>>* 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<std::vector<float>>* capture_block) override {
std::vector<std::vector<float>> render_block =
received_render_blocks_.front();
received_render_blocks_.pop_front();
capture_block->swap(render_block);
}
bool BufferRender(std::vector<std::vector<float>>* block) override {
received_render_blocks_.push_back(*block);
return false;
}
void ReportEchoLeakage(bool leakage_detected) override {}
private:
std::deque<std::vector<std::vector<float>>> 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<BlockProcessor>(
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<BlockProcessor>(
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<testing::StrictMock<webrtc::test::MockBlockProcessor>>
block_processor_mock(
new StrictMock<webrtc::test::MockBlockProcessor>());
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<testing::StrictMock<webrtc::test::MockBlockProcessor>>
block_processor_mock(
new StrictMock<webrtc::test::MockBlockProcessor>());
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<testing::StrictMock<webrtc::test::MockBlockProcessor>>
block_processor_mock(
new StrictMock<webrtc::test::MockBlockProcessor>());
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<BlockProcessor>(
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<int>(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<int>(variant)));
EchoCanceller3Tester(rate).RunEchoPathChangeVerificationTest(variant);
}
}
}
TEST(EchoCanceller3Messaging, EchoLeakage) {
auto variants = {
EchoCanceller3Tester::EchoLeakageTestVariant::kNone,
EchoCanceller3Tester::EchoLeakageTestVariant::kFalseSticky,
EchoCanceller3Tester::EchoLeakageTestVariant::kTrueSticky,
EchoCanceller3Tester::EchoLeakageTestVariant::kTrueNonSticky};
for (auto rate : {8000, 16000, 32000, 48000}) {
for (auto variant : variants) {
SCOPED_TRACE(ProduceDebugText(rate, static_cast<int>(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

View File

@ -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 <algorithm>
#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<rtc::ArrayView<float>>& sub_frame,
std::vector<std::vector<float>>* 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<std::vector<float>>* 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

View File

@ -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 <stddef.h>
#include <vector>
#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<rtc::ArrayView<float>>& sub_frame,
std::vector<std::vector<float>>* 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<std::vector<float>>* block);
private:
const size_t num_bands_;
std::vector<std::vector<float>> buffer_;
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(FrameBlocker);
};
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_FRAME_BLOCKER_H_

View File

@ -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 <sstream>
#include <string>
#include <vector>
#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<int>(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<std::vector<float>>* 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<std::vector<float>>* sub_frame,
std::vector<rtc::ArrayView<float>>* 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<float>(&(*sub_frame)[k][0], (*sub_frame)[k].size());
}
}
bool VerifySubFrame(size_t sub_frame_counter,
int offset,
const std::vector<rtc::ArrayView<float>>& sub_frame_view) {
std::vector<std::vector<float>> reference_sub_frame(
sub_frame_view.size(), std::vector<float>(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<std::vector<float>>& 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<std::vector<float>> block(num_bands,
std::vector<float>(kBlockSize, 0.f));
std::vector<std::vector<float>> input_sub_frame(
num_bands, std::vector<float>(kSubFrameLength, 0.f));
std::vector<rtc::ArrayView<float>> 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<std::vector<float>> block(num_bands,
std::vector<float>(kBlockSize, 0.f));
std::vector<std::vector<float>> input_sub_frame(
num_bands, std::vector<float>(kSubFrameLength, 0.f));
std::vector<std::vector<float>> output_sub_frame(
num_bands, std::vector<float>(kSubFrameLength, 0.f));
std::vector<rtc::ArrayView<float>> output_sub_frame_view(num_bands);
std::vector<rtc::ArrayView<float>> 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<std::vector<float>> block(num_block_bands,
std::vector<float>(block_length, 0.f));
std::vector<std::vector<float>> input_sub_frame(
num_sub_frame_bands, std::vector<float>(sub_frame_length, 0.f));
std::vector<rtc::ArrayView<float>> 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<std::vector<float>> correct_block(
correct_num_bands, std::vector<float>(kBlockSize, 0.f));
std::vector<std::vector<float>> wrong_block(
num_block_bands, std::vector<float>(block_length, 0.f));
std::vector<std::vector<float>> input_sub_frame(
correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
std::vector<rtc::ArrayView<float>> 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<std::vector<float>> block(correct_num_bands,
std::vector<float>(kBlockSize, 0.f));
std::vector<std::vector<float>> input_sub_frame(
correct_num_bands, std::vector<float>(kSubFrameLength, 0.f));
std::vector<rtc::ArrayView<float>> 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<std::vector<float>> sub_frame(
1, std::vector<float>(kSubFrameLength, 0.f));
std::vector<rtc::ArrayView<float>> 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

View File

@ -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 <vector>
#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<std::vector<float>>* capture_block));
MOCK_METHOD1(BufferRender, bool(std::vector<std::vector<float>>* block));
MOCK_METHOD1(ReportEchoLeakage, void(bool leakage_detected));
};
} // namespace test
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_MOCK_MOCK_BLOCK_PROCESSOR_H_