Add algorithm for Residual Echo Detector.
This algorithm calculates an estimate of the Pearson product-moment correlation coefficient between the power of 10ms audio buffers taken from the render and capture sides, for various different delay values. BUG=webrtc:6525 Review-Url: https://codereview.webrtc.org/2419563003 Cr-Commit-Position: refs/heads/master@{#14824}
This commit is contained in:
parent
5f1b05129e
commit
af27ed01d7
@ -589,11 +589,15 @@ if (rtc_include_tests) {
|
||||
"audio_processing/audio_processing_unittest.cc",
|
||||
"audio_processing/echo_cancellation_bit_exact_unittest.cc",
|
||||
"audio_processing/echo_control_mobile_unittest.cc",
|
||||
"audio_processing/echo_detector/circular_buffer_unittest.cc",
|
||||
"audio_processing/echo_detector/mean_variance_estimator_unittest.cc",
|
||||
"audio_processing/echo_detector/normalized_covariance_estimator_unittest.cc",
|
||||
"audio_processing/gain_control_unittest.cc",
|
||||
"audio_processing/high_pass_filter_unittest.cc",
|
||||
"audio_processing/level_controller/level_controller_unittest.cc",
|
||||
"audio_processing/level_estimator_unittest.cc",
|
||||
"audio_processing/noise_suppression_unittest.cc",
|
||||
"audio_processing/residual_echo_detector_unittest.cc",
|
||||
"audio_processing/test/bitexactness_tools.cc",
|
||||
"audio_processing/test/bitexactness_tools.h",
|
||||
"audio_processing/test/debug_dump_replayer.cc",
|
||||
|
||||
@ -61,6 +61,12 @@ rtc_static_library("audio_processing") {
|
||||
"echo_cancellation_impl.h",
|
||||
"echo_control_mobile_impl.cc",
|
||||
"echo_control_mobile_impl.h",
|
||||
"echo_detector/circular_buffer.cc",
|
||||
"echo_detector/circular_buffer.h",
|
||||
"echo_detector/mean_variance_estimator.cc",
|
||||
"echo_detector/mean_variance_estimator.h",
|
||||
"echo_detector/normalized_covariance_estimator.cc",
|
||||
"echo_detector/normalized_covariance_estimator.h",
|
||||
"gain_control_for_experimental_agc.cc",
|
||||
"gain_control_for_experimental_agc.h",
|
||||
"gain_control_impl.cc",
|
||||
|
||||
@ -73,6 +73,12 @@
|
||||
'echo_cancellation_impl.h',
|
||||
'echo_control_mobile_impl.cc',
|
||||
'echo_control_mobile_impl.h',
|
||||
'echo_detector/circular_buffer.cc',
|
||||
'echo_detector/circular_buffer.h',
|
||||
'echo_detector/mean_variance_estimator.cc',
|
||||
'echo_detector/mean_variance_estimator.h',
|
||||
'echo_detector/normalized_covariance_estimator.cc',
|
||||
'echo_detector/normalized_covariance_estimator.h',
|
||||
'gain_control_for_experimental_agc.cc',
|
||||
'gain_control_for_experimental_agc.h',
|
||||
'gain_control_impl.cc',
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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/echo_detector/circular_buffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
CircularBuffer::CircularBuffer(size_t size) : buffer_(size) {}
|
||||
CircularBuffer::~CircularBuffer() = default;
|
||||
|
||||
void CircularBuffer::Push(float value) {
|
||||
buffer_[next_insertion_index_] = value;
|
||||
++next_insertion_index_;
|
||||
next_insertion_index_ %= buffer_.size();
|
||||
RTC_DCHECK_LT(next_insertion_index_, buffer_.size());
|
||||
nr_elements_in_buffer_ = std::min(nr_elements_in_buffer_ + 1, buffer_.size());
|
||||
RTC_DCHECK_LE(nr_elements_in_buffer_, buffer_.size());
|
||||
}
|
||||
|
||||
rtc::Optional<float> CircularBuffer::Pop() {
|
||||
if (nr_elements_in_buffer_ == 0) {
|
||||
return rtc::Optional<float>();
|
||||
}
|
||||
const size_t index =
|
||||
(buffer_.size() + next_insertion_index_ - nr_elements_in_buffer_) %
|
||||
buffer_.size();
|
||||
RTC_DCHECK_LT(index, buffer_.size());
|
||||
--nr_elements_in_buffer_;
|
||||
return rtc::Optional<float>(buffer_[index]);
|
||||
}
|
||||
|
||||
void CircularBuffer::Clear() {
|
||||
std::fill(buffer_.begin(), buffer_.end(), 0.f);
|
||||
next_insertion_index_ = 0;
|
||||
nr_elements_in_buffer_ = 0;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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_ECHO_DETECTOR_CIRCULAR_BUFFER_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_CIRCULAR_BUFFER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/base/optional.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Ring buffer containing floating point values.
|
||||
struct CircularBuffer {
|
||||
public:
|
||||
explicit CircularBuffer(size_t size);
|
||||
~CircularBuffer();
|
||||
|
||||
void Push(float value);
|
||||
rtc::Optional<float> Pop();
|
||||
size_t Size() const { return nr_elements_in_buffer_; }
|
||||
// This function fills the buffer with zeros, but does not change its size.
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
std::vector<float> buffer_;
|
||||
size_t next_insertion_index_ = 0;
|
||||
// This is the number of elements that have been pushed into the circular
|
||||
// buffer, not the allocated buffer size.
|
||||
size_t nr_elements_in_buffer_ = 0;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_CIRCULAR_BUFFER_H_
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/audio_processing/echo_detector/circular_buffer.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST(CircularBufferTests, LessThanMaxTest) {
|
||||
CircularBuffer test_buffer(3);
|
||||
test_buffer.Push(1.f);
|
||||
test_buffer.Push(2.f);
|
||||
EXPECT_EQ(rtc::Optional<float>(1.f), test_buffer.Pop());
|
||||
EXPECT_EQ(rtc::Optional<float>(2.f), test_buffer.Pop());
|
||||
}
|
||||
|
||||
TEST(CircularBufferTests, FillTest) {
|
||||
CircularBuffer test_buffer(3);
|
||||
test_buffer.Push(1.f);
|
||||
test_buffer.Push(2.f);
|
||||
test_buffer.Push(3.f);
|
||||
EXPECT_EQ(rtc::Optional<float>(1.f), test_buffer.Pop());
|
||||
EXPECT_EQ(rtc::Optional<float>(2.f), test_buffer.Pop());
|
||||
EXPECT_EQ(rtc::Optional<float>(3.f), test_buffer.Pop());
|
||||
}
|
||||
|
||||
TEST(CircularBufferTests, OverflowTest) {
|
||||
CircularBuffer test_buffer(3);
|
||||
test_buffer.Push(1.f);
|
||||
test_buffer.Push(2.f);
|
||||
test_buffer.Push(3.f);
|
||||
test_buffer.Push(4.f);
|
||||
// Because the circular buffer has a size of 3, the first insert should have
|
||||
// been forgotten.
|
||||
EXPECT_EQ(rtc::Optional<float>(2.f), test_buffer.Pop());
|
||||
EXPECT_EQ(rtc::Optional<float>(3.f), test_buffer.Pop());
|
||||
EXPECT_EQ(rtc::Optional<float>(4.f), test_buffer.Pop());
|
||||
}
|
||||
|
||||
TEST(CircularBufferTests, ReadFromEmpty) {
|
||||
CircularBuffer test_buffer(3);
|
||||
EXPECT_EQ(rtc::Optional<float>(), test_buffer.Pop());
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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/echo_detector/mean_variance_estimator.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// Parameter controlling the adaptation speed.
|
||||
constexpr float kAlpha = 0.01f;
|
||||
|
||||
} // namespace
|
||||
|
||||
void MeanVarianceEstimator::Update(float value) {
|
||||
mean_ = (1.f - kAlpha) * mean_ + kAlpha * value;
|
||||
variance_ =
|
||||
(1.f - kAlpha) * variance_ + kAlpha * (value - mean_) * (value - mean_);
|
||||
}
|
||||
|
||||
float MeanVarianceEstimator::std_deviation() const {
|
||||
RTC_DCHECK_GE(variance_, 0.f);
|
||||
return sqrtf(variance_);
|
||||
}
|
||||
|
||||
float MeanVarianceEstimator::mean() const {
|
||||
return mean_;
|
||||
}
|
||||
|
||||
void MeanVarianceEstimator::Clear() {
|
||||
mean_ = 0.f;
|
||||
variance_ = 0.f;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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_ECHO_DETECTOR_MEAN_VARIANCE_ESTIMATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_MEAN_VARIANCE_ESTIMATOR_H_
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// This class iteratively estimates the mean and variance of a signal.
|
||||
class MeanVarianceEstimator {
|
||||
public:
|
||||
void Update(float value);
|
||||
float std_deviation() const;
|
||||
float mean() const;
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
// Estimate of the expected value of the input values.
|
||||
float mean_ = 0.f;
|
||||
// Estimate of the variance of the input values.
|
||||
float variance_ = 0.f;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_MEAN_VARIANCE_ESTIMATOR_H_
|
||||
@ -0,0 +1,64 @@
|
||||
|
||||
/*
|
||||
* 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/echo_detector/mean_variance_estimator.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST(MeanVarianceEstimatorTests, InsertTwoValues) {
|
||||
MeanVarianceEstimator test_estimator;
|
||||
// Insert two values.
|
||||
test_estimator.Update(3.f);
|
||||
test_estimator.Update(5.f);
|
||||
|
||||
EXPECT_GT(test_estimator.mean(), 0.f);
|
||||
EXPECT_GT(test_estimator.std_deviation(), 0.f);
|
||||
// Test Clear method
|
||||
test_estimator.Clear();
|
||||
EXPECT_EQ(test_estimator.mean(), 0.f);
|
||||
EXPECT_EQ(test_estimator.std_deviation(), 0.f);
|
||||
}
|
||||
|
||||
TEST(MeanVarianceEstimatorTests, InsertZeroes) {
|
||||
MeanVarianceEstimator test_estimator;
|
||||
// Insert the same value many times.
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
test_estimator.Update(0.f);
|
||||
}
|
||||
EXPECT_EQ(test_estimator.mean(), 0.f);
|
||||
EXPECT_EQ(test_estimator.std_deviation(), 0.f);
|
||||
}
|
||||
|
||||
TEST(MeanVarianceEstimatorTests, ConstantValueTest) {
|
||||
MeanVarianceEstimator test_estimator;
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
test_estimator.Update(3.f);
|
||||
}
|
||||
// The mean should be close to three, and the standard deviation should be
|
||||
// close to zero.
|
||||
EXPECT_NEAR(3.0f, test_estimator.mean(), 0.01f);
|
||||
EXPECT_NEAR(0.0f, test_estimator.std_deviation(), 0.01f);
|
||||
}
|
||||
|
||||
TEST(MeanVarianceEstimatorTests, AlternatingValueTest) {
|
||||
MeanVarianceEstimator test_estimator;
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
test_estimator.Update(1.f);
|
||||
test_estimator.Update(-1.f);
|
||||
}
|
||||
// The mean should be close to zero, and the standard deviation should be
|
||||
// close to one.
|
||||
EXPECT_NEAR(0.0f, test_estimator.mean(), 0.01f);
|
||||
EXPECT_NEAR(1.0f, test_estimator.std_deviation(), 0.01f);
|
||||
}
|
||||
|
||||
} // 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.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/audio_processing/echo_detector/normalized_covariance_estimator.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
// Parameter controlling the adaptation speed.
|
||||
constexpr float kAlpha = 0.01f;
|
||||
|
||||
} // namespace
|
||||
|
||||
void NormalizedCovarianceEstimator::Update(float x,
|
||||
float x_mean,
|
||||
float x_sigma,
|
||||
float y,
|
||||
float y_mean,
|
||||
float y_sigma) {
|
||||
covariance_ =
|
||||
(1.f - kAlpha) * covariance_ + kAlpha * (x - x_mean) * (y - y_mean);
|
||||
normalized_cross_correlation_ = covariance_ / (x_sigma * y_sigma + .0001f);
|
||||
}
|
||||
|
||||
void NormalizedCovarianceEstimator::Clear() {
|
||||
covariance_ = 0.f;
|
||||
normalized_cross_correlation_ = 0.f;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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_ECHO_DETECTOR_NORMALIZED_COVARIANCE_ESTIMATOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_NORMALIZED_COVARIANCE_ESTIMATOR_H_
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// This class iteratively estimates the normalized covariance between two
|
||||
// signals.
|
||||
class NormalizedCovarianceEstimator {
|
||||
public:
|
||||
void Update(float x,
|
||||
float x_mean,
|
||||
float x_var,
|
||||
float y,
|
||||
float y_mean,
|
||||
float y_var);
|
||||
// This function returns an estimate of the Pearson product-moment correlation
|
||||
// coefficient of the two signals.
|
||||
float normalized_cross_correlation() const {
|
||||
return normalized_cross_correlation_;
|
||||
}
|
||||
// This function resets the estimated values to zero.
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
float normalized_cross_correlation_ = 0.f;
|
||||
// Estimate of the covariance value.
|
||||
float covariance_ = 0.f;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_DETECTOR_NORMALIZED_COVARIANCE_ESTIMATOR_H_
|
||||
@ -0,0 +1,40 @@
|
||||
|
||||
/*
|
||||
* 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/echo_detector/normalized_covariance_estimator.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST(NormalizedCovarianceEstimatorTests, IdenticalSignalTest) {
|
||||
NormalizedCovarianceEstimator test_estimator;
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
test_estimator.Update(1.f, 0.f, 1.f, 1.f, 0.f, 1.f);
|
||||
test_estimator.Update(-1.f, 0.f, 1.f, -1.f, 0.f, 1.f);
|
||||
}
|
||||
// A normalized covariance value close to 1 is expected.
|
||||
EXPECT_NEAR(1.f, test_estimator.normalized_cross_correlation(), 0.01f);
|
||||
test_estimator.Clear();
|
||||
EXPECT_EQ(0.f, test_estimator.normalized_cross_correlation());
|
||||
}
|
||||
|
||||
TEST(NormalizedCovarianceEstimatorTests, OppositeSignalTest) {
|
||||
NormalizedCovarianceEstimator test_estimator;
|
||||
// Insert the same value many times.
|
||||
for (size_t i = 0; i < 10000; i++) {
|
||||
test_estimator.Update(1.f, 0.f, 1.f, -1.f, 0.f, 1.f);
|
||||
test_estimator.Update(-1.f, 0.f, 1.f, 1.f, 0.f, 1.f);
|
||||
}
|
||||
// A normalized covariance value close to -1 is expected.
|
||||
EXPECT_NEAR(-1.f, test_estimator.normalized_cross_correlation(), 0.01f);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -10,26 +10,113 @@
|
||||
|
||||
#include "webrtc/modules/audio_processing/residual_echo_detector.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "webrtc/modules/audio_processing/audio_buffer.h"
|
||||
|
||||
namespace {
|
||||
|
||||
float Power(rtc::ArrayView<const float> input) {
|
||||
return std::inner_product(input.begin(), input.end(), input.begin(), 0.f);
|
||||
}
|
||||
|
||||
constexpr size_t kLookbackFrames = 650;
|
||||
// TODO(ivoc): Verify the size of this buffer.
|
||||
constexpr size_t kRenderBufferSize = 30;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
ResidualEchoDetector::ResidualEchoDetector() = default;
|
||||
ResidualEchoDetector::ResidualEchoDetector()
|
||||
: render_buffer_(kRenderBufferSize),
|
||||
render_power_(kLookbackFrames),
|
||||
render_power_mean_(kLookbackFrames),
|
||||
render_power_std_dev_(kLookbackFrames),
|
||||
covariances_(kLookbackFrames){};
|
||||
|
||||
ResidualEchoDetector::~ResidualEchoDetector() = default;
|
||||
|
||||
void ResidualEchoDetector::AnalyzeRenderAudio(
|
||||
rtc::ArrayView<const float> render_audio) const {
|
||||
// TODO(ivoc): Add implementation.
|
||||
rtc::ArrayView<const float> render_audio) {
|
||||
if (render_buffer_.Size() == 0) {
|
||||
frames_since_zero_buffer_size_ = 0;
|
||||
} else if (frames_since_zero_buffer_size_ >= kRenderBufferSize) {
|
||||
// This can happen in a few cases: at the start of a call, due to a glitch
|
||||
// or due to clock drift. The excess capture value will be ignored.
|
||||
// TODO(ivoc): Include how often this happens in APM stats.
|
||||
render_buffer_.Pop();
|
||||
frames_since_zero_buffer_size_ = 0;
|
||||
}
|
||||
++frames_since_zero_buffer_size_;
|
||||
float power = Power(render_audio);
|
||||
render_buffer_.Push(power);
|
||||
}
|
||||
|
||||
void ResidualEchoDetector::AnalyzeCaptureAudio(
|
||||
rtc::ArrayView<const float> render_audio) {
|
||||
// TODO(ivoc): Add implementation.
|
||||
rtc::ArrayView<const float> capture_audio) {
|
||||
if (first_process_call_) {
|
||||
// On the first process call (so the start of a call), we must flush the
|
||||
// render buffer, otherwise the render data will be delayed.
|
||||
render_buffer_.Clear();
|
||||
first_process_call_ = false;
|
||||
}
|
||||
|
||||
// Get the next render value.
|
||||
const rtc::Optional<float> buffered_render_power = render_buffer_.Pop();
|
||||
if (!buffered_render_power) {
|
||||
// This can happen in a few cases: at the start of a call, due to a glitch
|
||||
// or due to clock drift. The excess capture value will be ignored.
|
||||
// TODO(ivoc): Include how often this happens in APM stats.
|
||||
return;
|
||||
}
|
||||
// Update the render statistics, and store the statistics in circular buffers.
|
||||
render_statistics_.Update(*buffered_render_power);
|
||||
RTC_DCHECK_LT(next_insertion_index_, kLookbackFrames);
|
||||
render_power_[next_insertion_index_] = *buffered_render_power;
|
||||
render_power_mean_[next_insertion_index_] = render_statistics_.mean();
|
||||
render_power_std_dev_[next_insertion_index_] =
|
||||
render_statistics_.std_deviation();
|
||||
|
||||
// Get the next capture value, update capture statistics and add the relevant
|
||||
// values to the buffers.
|
||||
const float capture_power = Power(capture_audio);
|
||||
capture_statistics_.Update(capture_power);
|
||||
const float capture_mean = capture_statistics_.mean();
|
||||
const float capture_std_deviation = capture_statistics_.std_deviation();
|
||||
|
||||
// Update the covariance values and determine the new echo likelihood.
|
||||
echo_likelihood_ = 0.f;
|
||||
for (size_t delay = 0; delay < covariances_.size(); ++delay) {
|
||||
const size_t read_index =
|
||||
(kLookbackFrames + next_insertion_index_ - delay) % kLookbackFrames;
|
||||
RTC_DCHECK_LT(read_index, render_power_.size());
|
||||
covariances_[delay].Update(capture_power, capture_mean,
|
||||
capture_std_deviation, render_power_[read_index],
|
||||
render_power_mean_[read_index],
|
||||
render_power_std_dev_[read_index]);
|
||||
echo_likelihood_ = std::max(
|
||||
echo_likelihood_, covariances_[delay].normalized_cross_correlation());
|
||||
}
|
||||
|
||||
// Update the next insertion index.
|
||||
++next_insertion_index_;
|
||||
next_insertion_index_ %= kLookbackFrames;
|
||||
}
|
||||
|
||||
void ResidualEchoDetector::Initialize() {
|
||||
// TODO(ivoc): Add implementation.
|
||||
render_buffer_.Clear();
|
||||
std::fill(render_power_.begin(), render_power_.end(), 0.f);
|
||||
std::fill(render_power_mean_.begin(), render_power_mean_.end(), 0.f);
|
||||
std::fill(render_power_std_dev_.begin(), render_power_std_dev_.end(), 0.f);
|
||||
render_statistics_.Clear();
|
||||
capture_statistics_.Clear();
|
||||
for (auto& cov : covariances_) {
|
||||
cov.Clear();
|
||||
}
|
||||
echo_likelihood_ = 0.f;
|
||||
next_insertion_index_ = 0;
|
||||
}
|
||||
|
||||
void ResidualEchoDetector::PackRenderAudioBuffer(
|
||||
@ -44,9 +131,4 @@ void ResidualEchoDetector::PackRenderAudioBuffer(
|
||||
audio->num_frames_per_band()));
|
||||
}
|
||||
|
||||
float ResidualEchoDetector::get_echo_likelihood() const {
|
||||
// TODO(ivoc): Add implementation.
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -11,10 +11,12 @@
|
||||
#ifndef WEBRTC_MODULES_AUDIO_PROCESSING_RESIDUAL_ECHO_DETECTOR_H_
|
||||
#define WEBRTC_MODULES_AUDIO_PROCESSING_RESIDUAL_ECHO_DETECTOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/base/array_view.h"
|
||||
#include "webrtc/modules/audio_processing/echo_detector/circular_buffer.h"
|
||||
#include "webrtc/modules/audio_processing/echo_detector/mean_variance_estimator.h"
|
||||
#include "webrtc/modules/audio_processing/echo_detector/normalized_covariance_estimator.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
@ -27,7 +29,7 @@ class ResidualEchoDetector {
|
||||
~ResidualEchoDetector();
|
||||
|
||||
// This function should be called while holding the render lock.
|
||||
void AnalyzeRenderAudio(rtc::ArrayView<const float> render_audio) const;
|
||||
void AnalyzeRenderAudio(rtc::ArrayView<const float> render_audio);
|
||||
|
||||
// This function should be called while holding the capture lock.
|
||||
void AnalyzeCaptureAudio(rtc::ArrayView<const float> capture_audio);
|
||||
@ -39,7 +41,36 @@ class ResidualEchoDetector {
|
||||
std::vector<float>* packed_buffer);
|
||||
|
||||
// This function should be called while holding the capture lock.
|
||||
float get_echo_likelihood() const;
|
||||
float echo_likelihood() const { return echo_likelihood_; }
|
||||
|
||||
private:
|
||||
// Keep track if the |Process| function has been previously called.
|
||||
bool first_process_call_ = true;
|
||||
// Buffer for storing the power of incoming farend buffers. This is needed for
|
||||
// cases where calls to BufferFarend and Process are jittery.
|
||||
CircularBuffer render_buffer_;
|
||||
// Count how long ago it was that the size of |render_buffer_| was zero. This
|
||||
// value is also reset to zero when clock drift is detected and a value from
|
||||
// the renderbuffer is discarded, even though the buffer is not actually zero
|
||||
// at that point. This is done to avoid repeatedly removing elements in this
|
||||
// situation.
|
||||
size_t frames_since_zero_buffer_size_ = 0;
|
||||
|
||||
// Circular buffers containing delayed versions of the power, mean and
|
||||
// standard deviation, for calculating the delayed covariance values.
|
||||
std::vector<float> render_power_;
|
||||
std::vector<float> render_power_mean_;
|
||||
std::vector<float> render_power_std_dev_;
|
||||
// Covariance estimates for different delay values.
|
||||
std::vector<NormalizedCovarianceEstimator> covariances_;
|
||||
// Index where next element should be inserted in all of the above circular
|
||||
// buffers.
|
||||
size_t next_insertion_index_ = 0;
|
||||
|
||||
MeanVarianceEstimator render_statistics_;
|
||||
MeanVarianceEstimator capture_statistics_;
|
||||
// Current echo likelihood.
|
||||
float echo_likelihood_ = 0.f;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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 <vector>
|
||||
|
||||
#include "webrtc/modules/audio_processing/residual_echo_detector.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST(ResidualEchoDetectorTests, Echo) {
|
||||
ResidualEchoDetector echo_detector;
|
||||
std::vector<float> ones(160, 1.f);
|
||||
std::vector<float> zeros(160, 0.f);
|
||||
|
||||
// In this test the capture signal has a delay of 10 frames w.r.t. the render
|
||||
// signal, but is otherwise identical. Both signals are periodic with a 20
|
||||
// frame interval.
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if (i % 20 == 0) {
|
||||
echo_detector.AnalyzeRenderAudio(ones);
|
||||
echo_detector.AnalyzeCaptureAudio(zeros);
|
||||
} else if (i % 20 == 10) {
|
||||
echo_detector.AnalyzeRenderAudio(zeros);
|
||||
echo_detector.AnalyzeCaptureAudio(ones);
|
||||
} else {
|
||||
echo_detector.AnalyzeRenderAudio(zeros);
|
||||
echo_detector.AnalyzeCaptureAudio(zeros);
|
||||
}
|
||||
}
|
||||
// We expect to detect echo with near certain likelihood.
|
||||
EXPECT_NEAR(1.f, echo_detector.echo_likelihood(), 0.01f);
|
||||
}
|
||||
|
||||
TEST(ResidualEchoDetectorTests, NoEcho) {
|
||||
ResidualEchoDetector echo_detector;
|
||||
std::vector<float> ones(160, 1.f);
|
||||
std::vector<float> zeros(160, 0.f);
|
||||
|
||||
// In this test the capture signal is always zero, so no echo should be
|
||||
// detected.
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if (i % 20 == 0) {
|
||||
echo_detector.AnalyzeRenderAudio(ones);
|
||||
} else {
|
||||
echo_detector.AnalyzeRenderAudio(zeros);
|
||||
}
|
||||
echo_detector.AnalyzeCaptureAudio(zeros);
|
||||
}
|
||||
// We expect to not detect any echo.
|
||||
EXPECT_NEAR(0.f, echo_detector.echo_likelihood(), 0.01f);
|
||||
}
|
||||
|
||||
TEST(ResidualEchoDetectorTests, EchoWithRenderClockDrift) {
|
||||
ResidualEchoDetector echo_detector;
|
||||
std::vector<float> ones(160, 1.f);
|
||||
std::vector<float> zeros(160, 0.f);
|
||||
|
||||
// In this test the capture signal has a delay of 10 frames w.r.t. the render
|
||||
// signal, but is otherwise identical. Both signals are periodic with a 20
|
||||
// frame interval. There is a simulated clock drift of 1% in this test, with
|
||||
// the render side producing data slightly faster.
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if (i % 20 == 0) {
|
||||
echo_detector.AnalyzeRenderAudio(ones);
|
||||
echo_detector.AnalyzeCaptureAudio(zeros);
|
||||
} else if (i % 20 == 10) {
|
||||
echo_detector.AnalyzeRenderAudio(zeros);
|
||||
echo_detector.AnalyzeCaptureAudio(ones);
|
||||
} else {
|
||||
echo_detector.AnalyzeRenderAudio(zeros);
|
||||
echo_detector.AnalyzeCaptureAudio(zeros);
|
||||
}
|
||||
if (i % 100 == 0) {
|
||||
// This is causing the simulated clock drift.
|
||||
echo_detector.AnalyzeRenderAudio(zeros);
|
||||
}
|
||||
}
|
||||
// We expect to detect echo with high likelihood. Clock drift is harder to
|
||||
// correct on the render side than on the capture side. This is due to the
|
||||
// render buffer, clock drift can only be discovered after a certain delay.
|
||||
// A growing buffer can be caused by jitter or clock drift and it's not
|
||||
// possible to make this decision right away. For this reason we only expect
|
||||
// an echo likelihood of 80% in this test.
|
||||
EXPECT_GT(echo_detector.echo_likelihood(), 0.8f);
|
||||
}
|
||||
|
||||
TEST(ResidualEchoDetectorTests, EchoWithCaptureClockDrift) {
|
||||
ResidualEchoDetector echo_detector;
|
||||
std::vector<float> ones(160, 1.f);
|
||||
std::vector<float> zeros(160, 0.f);
|
||||
|
||||
// In this test the capture signal has a delay of 10 frames w.r.t. the render
|
||||
// signal, but is otherwise identical. Both signals are periodic with a 20
|
||||
// frame interval. There is a simulated clock drift of 1% in this test, with
|
||||
// the capture side producing data slightly faster.
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if (i % 20 == 0) {
|
||||
echo_detector.AnalyzeRenderAudio(ones);
|
||||
echo_detector.AnalyzeCaptureAudio(zeros);
|
||||
} else if (i % 20 == 10) {
|
||||
echo_detector.AnalyzeRenderAudio(zeros);
|
||||
echo_detector.AnalyzeCaptureAudio(ones);
|
||||
} else {
|
||||
echo_detector.AnalyzeRenderAudio(zeros);
|
||||
echo_detector.AnalyzeCaptureAudio(zeros);
|
||||
}
|
||||
if (i % 100 == 0) {
|
||||
// This is causing the simulated clock drift.
|
||||
echo_detector.AnalyzeCaptureAudio(zeros);
|
||||
}
|
||||
}
|
||||
// We expect to detect echo with near certain likelihood.
|
||||
EXPECT_NEAR(1.f, echo_detector.echo_likelihood(), 0.01f);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
Loading…
x
Reference in New Issue
Block a user