Reland of Added first layer of the echo canceller 3 functionality.
Reason for reland:
Added disabling of the BlockProcessor DEATH tests due to false alarms on the trybots.
These errors cannot be reproduced locally and they occur only in the DEATH test code,
which means that the memory leaks do not occur under normal conditions.
Original issue's description:
> Reason for revert:
> Memcheck buildbot detected memory leaks in the death tests. The code looks fine
> to me, but please investigate what causes the error and reland.
> > Original issue's description:
> > 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}
> > Committed:
> 38fd1758e9
>
> TBR=ivoc@webrtc.org,aleloi@webrtc.org,henrik.lundin@webrtc.org,peah@webrtc.org
> # Skipping CQ checks because original CL landed less than 1 days ago.
> NOPRESUBMIT=true
> NOTREECHECKS=true
> NOTRY=true
> BUG=webrtc:6018
BUG=webrtc:6018
TBR=ivoc@webrtc.org,aleloi@webrtc.org,henrik.lundin@webrtc.org
Review-Url: https://codereview.webrtc.org/2608233002
Cr-Commit-Position: refs/heads/master@{#15884}
This commit is contained in:
parent
338b606b94
commit
d026354410
@ -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_
|
||||
143
webrtc/modules/audio_processing/aec3/block_processor_unittest.cc
Normal file
143
webrtc/modules/audio_processing/aec3/block_processor_unittest.cc
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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)
|
||||
// TODO(peah): Enable all DEATH tests below, or add suppressions, once clarity
|
||||
// has been reached on why they fail on the trybots.
|
||||
TEST(BlockProcessor, DISABLED_VerifyRenderBlockSizeCheck) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunRenderBlockSizeVerificationTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockProcessor, DISABLED_VerifyCaptureBlockSizeCheck) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunCaptureBlockSizeVerificationTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockProcessor, DISABLED_VerifyRenderNumBandsCheck) {
|
||||
for (auto rate : {8000, 16000, 32000, 48000}) {
|
||||
SCOPED_TRACE(ProduceDebugText(rate));
|
||||
RunRenderNumBandsVerificationTest(rate);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlockProcessor, DISABLED_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, DISABLED_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, DISABLED_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