webrtc_m130/webrtc/modules/audio_processing/aec/system_delay_unittest.cc

483 lines
19 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2012 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 "testing/gtest/include/gtest/gtest.h"
extern "C" {
#include "webrtc/modules/audio_processing/aec/aec_core.h"
}
#include "webrtc/modules/audio_processing/aec/echo_cancellation_internal.h"
#include "webrtc/modules/audio_processing/aec/include/echo_cancellation.h"
#include "webrtc/typedefs.h"
namespace {
class SystemDelayTest : public ::testing::Test {
protected:
SystemDelayTest();
virtual void SetUp();
virtual void TearDown();
// Initialization of AEC handle with respect to |sample_rate_hz|. Since the
// device sample rate is unimportant we set that value to 48000 Hz.
void Init(int sample_rate_hz);
// Makes one render call and one capture call in that specific order.
void RenderAndCapture(int device_buffer_ms);
// Fills up the far-end buffer with respect to the default device buffer size.
int BufferFillUp();
// Runs and verifies the behavior in a stable startup procedure.
void RunStableStartup();
// Maps buffer size in ms into samples, taking the unprocessed frame into
// account.
int MapBufferSizeToSamples(int size_in_ms);
void* handle_;
aecpc_t* self_;
int samples_per_frame_;
// Dummy input/output speech data.
int16_t far_[160];
int16_t near_[160];
int16_t out_[160];
};
SystemDelayTest::SystemDelayTest()
: handle_(NULL), self_(NULL), samples_per_frame_(0) {
// Dummy input data are set with more or less arbitrary non-zero values.
memset(far_, 1, sizeof(far_));
memset(near_, 2, sizeof(near_));
memset(out_, 0, sizeof(out_));
}
void SystemDelayTest::SetUp() {
ASSERT_EQ(0, WebRtcAec_Create(&handle_));
self_ = reinterpret_cast<aecpc_t*>(handle_);
}
void SystemDelayTest::TearDown() {
// Free AEC
ASSERT_EQ(0, WebRtcAec_Free(handle_));
handle_ = NULL;
}
// In SWB mode nothing is added to the buffer handling with respect to
// functionality compared to WB. We therefore only verify behavior in NB and WB.
static const int kSampleRateHz[] = {8000, 16000};
static const size_t kNumSampleRates =
sizeof(kSampleRateHz) / sizeof(*kSampleRateHz);
// Default audio device buffer size used.
static const int kDeviceBufMs = 100;
// Requirement for a stable device convergence time in ms. Should converge in
// less than |kStableConvergenceMs|.
static const int kStableConvergenceMs = 100;
// Maximum convergence time in ms. This means that we should leave the startup
// phase after |kMaxConvergenceMs| independent of device buffer stability
// conditions.
static const int kMaxConvergenceMs = 500;
void SystemDelayTest::Init(int sample_rate_hz) {
// Initialize AEC
EXPECT_EQ(0, WebRtcAec_Init(handle_, sample_rate_hz, 48000));
// One frame equals 10 ms of data.
samples_per_frame_ = sample_rate_hz / 100;
}
void SystemDelayTest::RenderAndCapture(int device_buffer_ms) {
EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
EXPECT_EQ(0,
WebRtcAec_Process(handle_,
near_,
NULL,
out_,
NULL,
samples_per_frame_,
device_buffer_ms,
0));
}
int SystemDelayTest::BufferFillUp() {
// To make sure we have a full buffer when we verify stability we first fill
// up the far-end buffer with the same amount as we will report in through
// Process().
int buffer_size = 0;
for (int i = 0; i < kDeviceBufMs / 10; i++) {
EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
buffer_size += samples_per_frame_;
EXPECT_EQ(buffer_size, WebRtcAec_system_delay(self_->aec));
}
return buffer_size;
}
void SystemDelayTest::RunStableStartup() {
// To make sure we have a full buffer when we verify stability we first fill
// up the far-end buffer with the same amount as we will report in through
// Process().
int buffer_size = BufferFillUp();
// A stable device should be accepted and put in a regular process mode within
// |kStableConvergenceMs|.
int process_time_ms = 0;
for (; process_time_ms < kStableConvergenceMs; process_time_ms += 10) {
RenderAndCapture(kDeviceBufMs);
buffer_size += samples_per_frame_;
Add an extended filter mode to AEC. Re-land: http://review.webrtc.org/2151007/ TBR=bjornv@webrtc.org Original change description: This mode extends the filter length from the current 48 ms to 128 ms. It is runtime selectable which allows it to be enabled through experiment. We reuse the DelayCorrection infrastructure to avoid having to replumb everything up to libjingle. Increases AEC complexity by ~50% on modern x86 CPUs. Measurements (in percent of usage on one core): Machine/CPU Normal Extended MacBook Retina (Early 2013), Core i7 Ivy Bridge (2.7 GHz, hyperthreaded) 0.6% 0.9% MacBook Air (Late 2010), Core 2 Duo (2.13 GHz) 1.4% 2.7% Chromebook Pixel, Core i5 Ivy Bridge (1.8 GHz) 0.6% 1.0% Samsung ARM Chromebook, Samsung Exynos 5 Dual (1.7 GHz) 3.2% 5.6% The relative value is large of course but the absolute should be acceptable in order to have a working AEC on some platforms. Detailed changes to the algorithm: - The filter length is changed from 48 to 128 ms. This comes with tuning of several parameters: i) filter adaptation stepsize and error threshold; ii) non-linear processing smoothing and overdrive. - Option to ignore the reported delays on platforms which we deem sufficiently unreliable. Currently this will be enabled in Chromium for Mac. - Faster startup times by removing the excessive "startup phase" processing of reported delays. - Much more conservative adjustments to the far-end read pointer. We smooth the delay difference more heavily, and back off from the difference more. Adjustments force a readaptation of the filter, so they should be avoided except when really necessary. Corresponds to these changes: https://chromereviews.googleplex.com/9412014 https://chromereviews.googleplex.com/9514013 https://chromereviews.googleplex.com/9960013 BUG=454,827,1261 Review URL: https://webrtc-codereview.appspot.com/2295006 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4848 4adac7df-926f-26a2-2b94-8c16560cd09d
2013-09-25 23:17:38 +00:00
if (self_->startup_phase == 0) {
// We have left the startup phase.
break;
}
}
// Verify convergence time.
EXPECT_GT(kStableConvergenceMs, process_time_ms);
// Verify that the buffer has been flushed.
EXPECT_GE(buffer_size, WebRtcAec_system_delay(self_->aec));
}
int SystemDelayTest::MapBufferSizeToSamples(int size_in_ms) {
// The extra 10 ms corresponds to the unprocessed frame.
return (size_in_ms + 10) * samples_per_frame_ / 10;
}
// The tests should meet basic requirements and not be adjusted to what is
// actually implemented. If we don't get good code coverage this way we either
// lack in tests or have unnecessary code.
// General requirements:
// 1) If we add far-end data the system delay should be increased with the same
// amount we add.
// 2) If the far-end buffer is full we should flush the oldest data to make room
// for the new. In this case the system delay is unaffected.
// 3) There should exist a startup phase in which the buffer size is to be
// determined. In this phase no cancellation should be performed.
// 4) Under stable conditions (small variations in device buffer sizes) the AEC
// should determine an appropriate local buffer size within
// |kStableConvergenceMs| ms.
// 5) Under unstable conditions the AEC should make a decision within
// |kMaxConvergenceMs| ms.
// 6) If the local buffer runs out of data we should stuff the buffer with older
// frames.
// 7) The system delay should within |kMaxConvergenceMs| ms heal from
// disturbances like drift, data glitches, toggling events and outliers.
// 8) The system delay should never become negative.
TEST_F(SystemDelayTest, CorrectIncreaseWhenBufferFarend) {
// When we add data to the AEC buffer the internal system delay should be
// incremented with the same amount as the size of data.
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
// Loop through a couple of calls to make sure the system delay increments
// correctly.
for (int j = 1; j <= 5; j++) {
EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
EXPECT_EQ(j * samples_per_frame_, WebRtcAec_system_delay(self_->aec));
}
}
}
// TODO(bjornv): Add a test to verify behavior if the far-end buffer is full
// when adding new data.
TEST_F(SystemDelayTest, CorrectDelayAfterStableStartup) {
// We run the system in a stable startup. After that we verify that the system
// delay meets the requirements.
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
RunStableStartup();
// Verify system delay with respect to requirements, i.e., the
// |system_delay| is in the interval [75%, 100%] of what's reported on the
// average.
int average_reported_delay = kDeviceBufMs * samples_per_frame_ / 10;
EXPECT_GE(average_reported_delay, WebRtcAec_system_delay(self_->aec));
EXPECT_LE(average_reported_delay * 3 / 4,
WebRtcAec_system_delay(self_->aec));
}
}
TEST_F(SystemDelayTest, CorrectDelayAfterUnstableStartup) {
// In an unstable system we would start processing after |kMaxConvergenceMs|.
// On the last frame the AEC buffer is adjusted to 60% of the last reported
// device buffer size.
// We construct an unstable system by altering the device buffer size between
// two values |kDeviceBufMs| +- 25 ms.
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
// To make sure we have a full buffer when we verify stability we first fill
// up the far-end buffer with the same amount as we will report in on the
// average through Process().
int buffer_size = BufferFillUp();
int buffer_offset_ms = 25;
int reported_delay_ms = 0;
int process_time_ms = 0;
for (; process_time_ms <= kMaxConvergenceMs; process_time_ms += 10) {
reported_delay_ms = kDeviceBufMs + buffer_offset_ms;
RenderAndCapture(reported_delay_ms);
buffer_size += samples_per_frame_;
buffer_offset_ms = -buffer_offset_ms;
Add an extended filter mode to AEC. Re-land: http://review.webrtc.org/2151007/ TBR=bjornv@webrtc.org Original change description: This mode extends the filter length from the current 48 ms to 128 ms. It is runtime selectable which allows it to be enabled through experiment. We reuse the DelayCorrection infrastructure to avoid having to replumb everything up to libjingle. Increases AEC complexity by ~50% on modern x86 CPUs. Measurements (in percent of usage on one core): Machine/CPU Normal Extended MacBook Retina (Early 2013), Core i7 Ivy Bridge (2.7 GHz, hyperthreaded) 0.6% 0.9% MacBook Air (Late 2010), Core 2 Duo (2.13 GHz) 1.4% 2.7% Chromebook Pixel, Core i5 Ivy Bridge (1.8 GHz) 0.6% 1.0% Samsung ARM Chromebook, Samsung Exynos 5 Dual (1.7 GHz) 3.2% 5.6% The relative value is large of course but the absolute should be acceptable in order to have a working AEC on some platforms. Detailed changes to the algorithm: - The filter length is changed from 48 to 128 ms. This comes with tuning of several parameters: i) filter adaptation stepsize and error threshold; ii) non-linear processing smoothing and overdrive. - Option to ignore the reported delays on platforms which we deem sufficiently unreliable. Currently this will be enabled in Chromium for Mac. - Faster startup times by removing the excessive "startup phase" processing of reported delays. - Much more conservative adjustments to the far-end read pointer. We smooth the delay difference more heavily, and back off from the difference more. Adjustments force a readaptation of the filter, so they should be avoided except when really necessary. Corresponds to these changes: https://chromereviews.googleplex.com/9412014 https://chromereviews.googleplex.com/9514013 https://chromereviews.googleplex.com/9960013 BUG=454,827,1261 Review URL: https://webrtc-codereview.appspot.com/2295006 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4848 4adac7df-926f-26a2-2b94-8c16560cd09d
2013-09-25 23:17:38 +00:00
if (self_->startup_phase == 0) {
// We have left the startup phase.
break;
}
}
// Verify convergence time.
EXPECT_GE(kMaxConvergenceMs, process_time_ms);
// Verify that the buffer has been flushed.
EXPECT_GE(buffer_size, WebRtcAec_system_delay(self_->aec));
// Verify system delay with respect to requirements, i.e., the
// |system_delay| is in the interval [60%, 100%] of what's last reported.
EXPECT_GE(reported_delay_ms * samples_per_frame_ / 10,
WebRtcAec_system_delay(self_->aec));
EXPECT_LE(reported_delay_ms * samples_per_frame_ / 10 * 3 / 5,
WebRtcAec_system_delay(self_->aec));
}
}
TEST_F(SystemDelayTest, CorrectDelayAfterStableBufferBuildUp) {
// In this test we start by establishing the device buffer size during stable
// conditions, but with an empty internal far-end buffer. Once that is done we
// verify that the system delay is increased correctly until we have reach an
// internal buffer size of 75% of what's been reported.
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
// We assume that running |kStableConvergenceMs| calls will put the
// algorithm in a state where the device buffer size has been determined. We
// can make that assumption since we have a separate stability test.
int process_time_ms = 0;
for (; process_time_ms < kStableConvergenceMs; process_time_ms += 10) {
EXPECT_EQ(0,
WebRtcAec_Process(handle_,
near_,
NULL,
out_,
NULL,
samples_per_frame_,
kDeviceBufMs,
0));
}
// Verify that a buffer size has been established.
EXPECT_EQ(0, self_->checkBuffSize);
// We now have established the required buffer size. Let us verify that we
// fill up before leaving the startup phase for normal processing.
int buffer_size = 0;
int target_buffer_size = kDeviceBufMs * samples_per_frame_ / 10 * 3 / 4;
process_time_ms = 0;
for (; process_time_ms <= kMaxConvergenceMs; process_time_ms += 10) {
RenderAndCapture(kDeviceBufMs);
buffer_size += samples_per_frame_;
Add an extended filter mode to AEC. Re-land: http://review.webrtc.org/2151007/ TBR=bjornv@webrtc.org Original change description: This mode extends the filter length from the current 48 ms to 128 ms. It is runtime selectable which allows it to be enabled through experiment. We reuse the DelayCorrection infrastructure to avoid having to replumb everything up to libjingle. Increases AEC complexity by ~50% on modern x86 CPUs. Measurements (in percent of usage on one core): Machine/CPU Normal Extended MacBook Retina (Early 2013), Core i7 Ivy Bridge (2.7 GHz, hyperthreaded) 0.6% 0.9% MacBook Air (Late 2010), Core 2 Duo (2.13 GHz) 1.4% 2.7% Chromebook Pixel, Core i5 Ivy Bridge (1.8 GHz) 0.6% 1.0% Samsung ARM Chromebook, Samsung Exynos 5 Dual (1.7 GHz) 3.2% 5.6% The relative value is large of course but the absolute should be acceptable in order to have a working AEC on some platforms. Detailed changes to the algorithm: - The filter length is changed from 48 to 128 ms. This comes with tuning of several parameters: i) filter adaptation stepsize and error threshold; ii) non-linear processing smoothing and overdrive. - Option to ignore the reported delays on platforms which we deem sufficiently unreliable. Currently this will be enabled in Chromium for Mac. - Faster startup times by removing the excessive "startup phase" processing of reported delays. - Much more conservative adjustments to the far-end read pointer. We smooth the delay difference more heavily, and back off from the difference more. Adjustments force a readaptation of the filter, so they should be avoided except when really necessary. Corresponds to these changes: https://chromereviews.googleplex.com/9412014 https://chromereviews.googleplex.com/9514013 https://chromereviews.googleplex.com/9960013 BUG=454,827,1261 Review URL: https://webrtc-codereview.appspot.com/2295006 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4848 4adac7df-926f-26a2-2b94-8c16560cd09d
2013-09-25 23:17:38 +00:00
if (self_->startup_phase == 0) {
// We have left the startup phase.
break;
}
}
// Verify convergence time.
EXPECT_GT(kMaxConvergenceMs, process_time_ms);
// Verify that the buffer has reached the desired size.
EXPECT_LE(target_buffer_size, WebRtcAec_system_delay(self_->aec));
// Verify normal behavior (system delay is kept constant) after startup by
// running a couple of calls to BufferFarend() and Process().
for (int j = 0; j < 6; j++) {
int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
RenderAndCapture(kDeviceBufMs);
EXPECT_EQ(system_delay_before_calls, WebRtcAec_system_delay(self_->aec));
}
}
}
TEST_F(SystemDelayTest, CorrectDelayWhenBufferUnderrun) {
// Here we test a buffer under run scenario. If we keep on calling
// WebRtcAec_Process() we will finally run out of data, but should
// automatically stuff the buffer. We verify this behavior by checking if the
// system delay goes negative.
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
RunStableStartup();
// The AEC has now left the Startup phase. We now have at most
// |kStableConvergenceMs| in the buffer. Keep on calling Process() until
// we run out of data and verify that the system delay is non-negative.
for (int j = 0; j <= kStableConvergenceMs; j += 10) {
EXPECT_EQ(0,
WebRtcAec_Process(handle_,
near_,
NULL,
out_,
NULL,
samples_per_frame_,
kDeviceBufMs,
0));
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
}
}
}
TEST_F(SystemDelayTest, CorrectDelayDuringDrift) {
// This drift test should verify that the system delay is never exceeding the
// device buffer. The drift is simulated by decreasing the reported device
// buffer size by 1 ms every 100 ms. If the device buffer size goes below 30
// ms we jump (add) 10 ms to give a repeated pattern.
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
RunStableStartup();
// We have now left the startup phase and proceed with normal processing.
int jump = 0;
for (int j = 0; j < 1000; j++) {
// Drift = -1 ms per 100 ms of data.
int device_buf_ms = kDeviceBufMs - (j / 10) + jump;
int device_buf = MapBufferSizeToSamples(device_buf_ms);
if (device_buf_ms < 30) {
// Add 10 ms data, taking affect next frame.
jump += 10;
}
RenderAndCapture(device_buf_ms);
// Verify that the system delay does not exceed the device buffer.
EXPECT_GE(device_buf, WebRtcAec_system_delay(self_->aec));
// Verify that the system delay is non-negative.
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
}
}
}
TEST_F(SystemDelayTest, ShouldRecoverAfterGlitch) {
// This glitch test should verify that the system delay recovers if there is
// a glitch in data. The data glitch is constructed as 200 ms of buffering
// after which the stable procedure continues. The glitch is never reported by
// the device.
// The system is said to be in a non-causal state if the difference between
// the device buffer and system delay is less than a block (64 samples).
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
RunStableStartup();
int device_buf = MapBufferSizeToSamples(kDeviceBufMs);
// Glitch state.
for (int j = 0; j < 20; j++) {
EXPECT_EQ(0, WebRtcAec_BufferFarend(handle_, far_, samples_per_frame_));
// No need to verify system delay, since that is done in a separate test.
}
// Verify that we are in a non-causal state, i.e.,
// |system_delay| > |device_buf|.
EXPECT_LT(device_buf, WebRtcAec_system_delay(self_->aec));
// Recover state. Should recover at least 4 ms of data per 10 ms, hence a
// glitch of 200 ms will take at most 200 * 10 / 4 = 500 ms to recover from.
bool non_causal = true; // We are currently in a non-causal state.
for (int j = 0; j < 50; j++) {
int system_delay_before = WebRtcAec_system_delay(self_->aec);
RenderAndCapture(kDeviceBufMs);
int system_delay_after = WebRtcAec_system_delay(self_->aec);
// We have recovered if |device_buf| - |system_delay_after| >= 64 (one
// block). During recovery |system_delay_after| < |system_delay_before|,
// otherwise they are equal.
if (non_causal) {
EXPECT_LT(system_delay_after, system_delay_before);
if (device_buf - system_delay_after >= 64) {
non_causal = false;
}
} else {
EXPECT_EQ(system_delay_before, system_delay_after);
}
// Verify that the system delay is non-negative.
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
}
// Check that we have recovered.
EXPECT_FALSE(non_causal);
}
}
TEST_F(SystemDelayTest, UnaffectedWhenSpuriousDeviceBufferValues) {
// This spurious device buffer data test aims at verifying that the system
// delay is unaffected by large outliers.
// The system is said to be in a non-causal state if the difference between
// the device buffer and system delay is less than a block (64 samples).
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
RunStableStartup();
int device_buf = MapBufferSizeToSamples(kDeviceBufMs);
// Normal state. We are currently not in a non-causal state.
bool non_causal = false;
// Run 1 s and replace device buffer size with 500 ms every 100 ms.
for (int j = 0; j < 100; j++) {
int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
int device_buf_ms = kDeviceBufMs;
if (j % 10 == 0) {
device_buf_ms = 500;
}
RenderAndCapture(device_buf_ms);
// Check for non-causality.
if (device_buf - WebRtcAec_system_delay(self_->aec) < 64) {
non_causal = true;
}
EXPECT_FALSE(non_causal);
EXPECT_EQ(system_delay_before_calls, WebRtcAec_system_delay(self_->aec));
// Verify that the system delay is non-negative.
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
}
}
}
TEST_F(SystemDelayTest, CorrectImpactWhenTogglingDeviceBufferValues) {
// This test aims at verifying that the system delay is "unaffected" by
// toggling values reported by the device.
// The test is constructed such that every other device buffer value is zero
// and then 2 * |kDeviceBufMs|, hence the size is constant on the average. The
// zero values will force us into a non-causal state and thereby lowering the
// system delay until we basically runs out of data. Once that happens the
// buffer will be stuffed.
// TODO(bjornv): This test will have a better impact if we verified that the
// delay estimate goes up when the system delay goes done to meet the average
// device buffer size.
for (size_t i = 0; i < kNumSampleRates; i++) {
Init(kSampleRateHz[i]);
RunStableStartup();
int device_buf = MapBufferSizeToSamples(kDeviceBufMs);
// Normal state. We are currently not in a non-causal state.
bool non_causal = false;
// Loop through 100 frames (both render and capture), which equals 1 s of
// data. Every odd frame we set the device buffer size to 2 * |kDeviceBufMs|
// and even frames we set the device buffer size to zero.
for (int j = 0; j < 100; j++) {
int system_delay_before_calls = WebRtcAec_system_delay(self_->aec);
int device_buf_ms = 2 * (j % 2) * kDeviceBufMs;
RenderAndCapture(device_buf_ms);
// Check for non-causality, compared with the average device buffer size.
non_causal |= (device_buf - WebRtcAec_system_delay(self_->aec) < 64);
EXPECT_GE(system_delay_before_calls, WebRtcAec_system_delay(self_->aec));
// Verify that the system delay is non-negative.
EXPECT_LE(0, WebRtcAec_system_delay(self_->aec));
}
// Verify we are not in a non-causal state.
EXPECT_FALSE(non_causal);
}
}
} // namespace