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:
parent
f709e56b4a
commit
38fd1758e9
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
52
webrtc/modules/audio_processing/aec3/aec3_constants.h
Normal file
52
webrtc/modules/audio_processing/aec3/aec3_constants.h
Normal 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_
|
||||
59
webrtc/modules/audio_processing/aec3/block_framer.cc
Normal file
59
webrtc/modules/audio_processing/aec3/block_framer.cc
Normal 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
|
||||
47
webrtc/modules/audio_processing/aec3/block_framer.h
Normal file
47
webrtc/modules/audio_processing/aec3/block_framer.h
Normal 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_
|
||||
261
webrtc/modules/audio_processing/aec3/block_framer_unittest.cc
Normal file
261
webrtc/modules/audio_processing/aec3/block_framer_unittest.cc
Normal 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
|
||||
73
webrtc/modules/audio_processing/aec3/block_processor.cc
Normal file
73
webrtc/modules/audio_processing/aec3/block_processor.cc
Normal 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
|
||||
44
webrtc/modules/audio_processing/aec3/block_processor.h
Normal file
44
webrtc/modules/audio_processing/aec3/block_processor.h
Normal 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_
|
||||
141
webrtc/modules/audio_processing/aec3/block_processor_unittest.cc
Normal file
141
webrtc/modules/audio_processing/aec3/block_processor_unittest.cc
Normal 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
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
717
webrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc
Normal file
717
webrtc/modules/audio_processing/aec3/echo_canceller3_unittest.cc
Normal 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
|
||||
72
webrtc/modules/audio_processing/aec3/frame_blocker.cc
Normal file
72
webrtc/modules/audio_processing/aec3/frame_blocker.cc
Normal 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
|
||||
48
webrtc/modules/audio_processing/aec3/frame_blocker.h
Normal file
48
webrtc/modules/audio_processing/aec3/frame_blocker.h
Normal 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_
|
||||
341
webrtc/modules/audio_processing/aec3/frame_blocker_unittest.cc
Normal file
341
webrtc/modules/audio_processing/aec3/frame_blocker_unittest.cc
Normal 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
|
||||
@ -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_
|
||||
Loading…
x
Reference in New Issue
Block a user