audio_processing: Added a new AEC delay metric value that gives the amount of poor delays

To more easily determine if for example the AEC is not working properly one could monitor how often the estimated delay is out of bounds. With out of bounds we mean either being negative or too large, where both cases will break the AEC.

A new delay metric is added telling the user how often poor delay values were estimated. This is measured in percentage since last time the metrics were calculated.

All APIs have been updated with a third parameter with EchoCancellation::GetDelayMetrics() giving the option to exclude the new metric not to break existing code.

The new metric has been added to audio_processing_unittests with an additional protobuf member, and reference files accordingly updated.
voe_auto_test has not been updated to display the new metric.

BUG=4246
TESTED=audioproc on files
R=aluebs@webrtc.org, andrew@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/39739004

Cr-Commit-Position: refs/heads/master@{#8230}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8230 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
bjornv@webrtc.org 2015-02-03 06:06:26 +00:00
parent 0e81fdf5d2
commit b1786dbab0
14 changed files with 85 additions and 21 deletions

View File

@ -774,6 +774,7 @@ static void UpdateDelayMetrics(AecCore* self) {
int i = 0;
int delay_values = 0;
int median = 0;
int lookahead = WebRtc_lookahead(self->delay_estimator);
const int kMsPerBlock = PART_LEN / (self->mult * 8);
int64_t l1_norm = 0;
@ -785,6 +786,7 @@ static void UpdateDelayMetrics(AecCore* self) {
// not able to estimate the delay.
self->delay_median = -1;
self->delay_std = -1;
self->fraction_poor_delays = -1;
return;
}
@ -799,8 +801,7 @@ static void UpdateDelayMetrics(AecCore* self) {
}
}
// Account for lookahead.
self->delay_median = (median - WebRtc_lookahead(self->delay_estimator)) *
kMsPerBlock;
self->delay_median = (median - lookahead) * kMsPerBlock;
// Calculate the L1 norm, with median value as central moment.
for (i = 0; i < kHistorySizeBlocks; i++) {
@ -809,6 +810,17 @@ static void UpdateDelayMetrics(AecCore* self) {
self->delay_std = (int)((l1_norm + self->num_delay_values / 2) /
self->num_delay_values) * kMsPerBlock;
// Determine fraction of delays that are out of bounds, that is, either
// negative (anti-causal system) or larger than the AEC filter length.
{
int num_delays_out_of_bounds = self->num_delay_values;
for (i = lookahead; i < lookahead + self->num_partitions; ++i) {
num_delays_out_of_bounds -= self->delay_histogram[i];
}
self->fraction_poor_delays = (float)num_delays_out_of_bounds /
self->num_delay_values;
}
// Reset histogram.
memset(self->delay_histogram, 0, sizeof(self->delay_histogram));
self->num_delay_values = 0;
@ -1563,6 +1575,7 @@ int WebRtcAec_InitAec(AecCore* aec, int sampFreq) {
aec->num_delay_values = 0;
aec->delay_median = -1;
aec->delay_std = -1;
aec->fraction_poor_delays = -1;
aec->signal_delay_correction = 0;
aec->previous_delay = -2; // (-2): Uninitialized.
@ -1833,7 +1846,8 @@ void WebRtcAec_ProcessFrames(AecCore* aec,
}
}
int WebRtcAec_GetDelayMetricsCore(AecCore* self, int* median, int* std) {
int WebRtcAec_GetDelayMetricsCore(AecCore* self, int* median, int* std,
float* fraction_poor_delays) {
assert(self != NULL);
assert(median != NULL);
assert(std != NULL);
@ -1849,6 +1863,7 @@ int WebRtcAec_GetDelayMetricsCore(AecCore* self, int* median, int* std) {
}
*median = self->delay_median;
*std = self->delay_std;
*fraction_poor_delays = self->fraction_poor_delays;
return 0;
}

View File

@ -75,9 +75,14 @@ void WebRtcAec_ProcessFrames(AecCore* aec,
// corresponding amount in ms.
int WebRtcAec_MoveFarReadPtr(AecCore* aec, int elements);
// Calculates the median and standard deviation among the delay estimates
// collected since the last call to this function.
int WebRtcAec_GetDelayMetricsCore(AecCore* self, int* median, int* std);
// Calculates the median, standard deviation and amount of poor values among the
// delay estimates aggregated up to the first call to the function. After that
// first call the metrics are aggregated and updated every second. With poor
// values we mean values that most likely will cause the AEC to perform poorly.
// TODO(bjornv): Consider changing tests and tools to handle constant
// constant aggregation window throughout the session instead.
int WebRtcAec_GetDelayMetricsCore(AecCore* self, int* median, int* std,
float* fraction_poor_delays);
// Returns the echo state (1: echo, 0: no echo).
int WebRtcAec_echo_state(AecCore* self);

View File

@ -131,6 +131,7 @@ struct AecCore {
int num_delay_values;
int delay_median;
int delay_std;
float fraction_poor_delays;
int delay_logging_enabled;
void* delay_estimator_farend;
void* delay_estimator;

View File

@ -556,7 +556,10 @@ int WebRtcAec_GetMetrics(void* handle, AecMetrics* metrics) {
return 0;
}
int WebRtcAec_GetDelayMetrics(void* handle, int* median, int* std) {
int WebRtcAec_GetDelayMetrics(void* handle,
int* median,
int* std,
float* fraction_poor_delays) {
Aec* self = handle;
if (median == NULL) {
self->lastError = AEC_NULL_POINTER_ERROR;
@ -570,7 +573,9 @@ int WebRtcAec_GetDelayMetrics(void* handle, int* median, int* std) {
self->lastError = AEC_UNINITIALIZED_ERROR;
return -1;
}
if (WebRtcAec_GetDelayMetricsCore(self->aec, median, std) == -1) {
if (WebRtcAec_GetDelayMetricsCore(self->aec, median, std,
fraction_poor_delays) ==
-1) {
// Logging disabled.
self->lastError = AEC_UNSUPPORTED_FUNCTION_ERROR;
return -1;

View File

@ -211,17 +211,22 @@ int WebRtcAec_GetMetrics(void* handle, AecMetrics* metrics);
*
* Inputs Description
* -------------------------------------------------------------------
* void* handle Pointer to the AEC instance
* void* handle Pointer to the AEC instance
*
* Outputs Description
* -------------------------------------------------------------------
* int* median Delay median value.
* int* std Delay standard deviation.
* int* median Delay median value.
* int* std Delay standard deviation.
* float* fraction_poor_delays Fraction of the delay estimates that may
* cause the AEC to perform poorly.
*
* int return 0: OK
* int return 0: OK
* -1: error
*/
int WebRtcAec_GetDelayMetrics(void* handle, int* median, int* std);
int WebRtcAec_GetDelayMetrics(void* handle,
int* median,
int* std,
float* fraction_poor_delays);
/*
* Gets the last error code.

View File

@ -281,6 +281,12 @@ bool EchoCancellationImpl::is_delay_logging_enabled() const {
// TODO(bjornv): How should we handle the multi-channel case?
int EchoCancellationImpl::GetDelayMetrics(int* median, int* std) {
float fraction_poor_delays = 0;
return GetDelayMetrics(median, std, &fraction_poor_delays);
}
int EchoCancellationImpl::GetDelayMetrics(int* median, int* std,
float* fraction_poor_delays) {
CriticalSectionScoped crit_scoped(crit_);
if (median == NULL) {
return apm_->kNullPointerError;
@ -294,7 +300,7 @@ int EchoCancellationImpl::GetDelayMetrics(int* median, int* std) {
}
Handle* my_handle = static_cast<Handle*>(handle(0));
if (WebRtcAec_GetDelayMetrics(my_handle, median, std) !=
if (WebRtcAec_GetDelayMetrics(my_handle, median, std, fraction_poor_delays) !=
apm_->kNoError) {
return GetHandleError(my_handle);
}

View File

@ -52,6 +52,8 @@ class EchoCancellationImpl : public EchoCancellation,
virtual int enable_delay_logging(bool enable) OVERRIDE;
virtual bool is_delay_logging_enabled() const OVERRIDE;
virtual int GetDelayMetrics(int* median, int* std) OVERRIDE;
virtual int GetDelayMetrics(int* median, int* std,
float* fraction_poor_delays) OVERRIDE;
virtual struct AecCore* aec_core() const OVERRIDE;
// ProcessingComponent implementation.

View File

@ -487,9 +487,17 @@ class EchoCancellation {
virtual bool is_delay_logging_enabled() const = 0;
// The delay metrics consists of the delay |median| and the delay standard
// deviation |std|. The values are averaged over the time period since the
// last call to |GetDelayMetrics()|.
// deviation |std|. It also consists of the fraction of delay estimates
// |fraction_poor_delays| that can make the echo cancellation perform poorly.
// The values are aggregated until the first call to |GetDelayMetrics()| and
// afterwards aggregated and updated every second.
// Note that if there are several clients pulling metrics from
// |GetDelayMetrics()| during a session the first call from any of them will
// change to one second aggregation window for all.
// TODO(bjornv): Deprecated, remove.
virtual int GetDelayMetrics(int* median, int* std) = 0;
virtual int GetDelayMetrics(int* median, int* std,
float* fraction_poor_delays) = 0;
// Returns a pointer to the low level AEC component. In case of multiple
// channels, the pointer to the first one is returned. A NULL pointer is

View File

@ -48,6 +48,8 @@ class MockEchoCancellation : public EchoCancellation {
bool());
MOCK_METHOD2(GetDelayMetrics,
int(int* median, int* std));
MOCK_METHOD3(GetDelayMetrics,
int(int* median, int* std, float* fraction_poor_delays));
MOCK_CONST_METHOD0(aec_core,
struct AecCore*());
};

View File

@ -635,9 +635,11 @@ void ApmTest::ProcessDelayVerificationTest(int delay_ms, int system_delay_ms,
if (frame_count == 250) {
int median;
int std;
float poor_fraction;
// Discard the first delay metrics to avoid convergence effects.
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->GetDelayMetrics(&median, &std));
apm_->echo_cancellation()->GetDelayMetrics(&median, &std,
&poor_fraction));
}
}
@ -659,8 +661,10 @@ void ApmTest::ProcessDelayVerificationTest(int delay_ms, int system_delay_ms,
// Verify delay metrics.
int median;
int std;
float poor_fraction;
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->GetDelayMetrics(&median, &std));
apm_->echo_cancellation()->GetDelayMetrics(&median, &std,
&poor_fraction));
EXPECT_GE(expected_median_high, median);
EXPECT_LE(expected_median_low, median);
}
@ -847,8 +851,10 @@ TEST_F(ApmTest, EchoCancellation) {
int median = 0;
int std = 0;
float poor_fraction = 0;
EXPECT_EQ(apm_->kNotEnabledError,
apm_->echo_cancellation()->GetDelayMetrics(&median, &std));
apm_->echo_cancellation()->GetDelayMetrics(&median, &std,
&poor_fraction));
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->enable_delay_logging(true));
@ -2026,8 +2032,10 @@ TEST_F(ApmTest, Process) {
apm_->echo_cancellation()->GetMetrics(&echo_metrics));
int median = 0;
int std = 0;
float fraction_poor_delays = 0;
EXPECT_EQ(apm_->kNoError,
apm_->echo_cancellation()->GetDelayMetrics(&median, &std));
apm_->echo_cancellation()->GetDelayMetrics(
&median, &std, &fraction_poor_delays));
int rms_level = apm_->level_estimator()->RMS();
EXPECT_LE(0, rms_level);
@ -2079,6 +2087,8 @@ TEST_F(ApmTest, Process) {
audioproc::Test::DelayMetrics reference_delay = test->delay_metrics();
EXPECT_NEAR(reference_delay.median(), median, kIntNear);
EXPECT_NEAR(reference_delay.std(), std, kIntNear);
EXPECT_NEAR(reference_delay.fraction_poor_delays(), fraction_poor_delays,
kFloatNear);
EXPECT_NEAR(test->rms_level(), rms_level, kIntNear);
@ -2109,6 +2119,7 @@ TEST_F(ApmTest, Process) {
test->mutable_delay_metrics();
message_delay->set_median(median);
message_delay->set_std(std);
message_delay->set_fraction_poor_delays(fraction_poor_delays);
test->set_rms_level(rms_level);

View File

@ -1081,10 +1081,13 @@ void void_main(int argc, char* argv[]) {
if (apm->echo_cancellation()->is_delay_logging_enabled()) {
int median = 0;
int std = 0;
apm->echo_cancellation()->GetDelayMetrics(&median, &std);
float fraction_poor_delays = 0;
apm->echo_cancellation()->GetDelayMetrics(&median, &std,
&fraction_poor_delays);
printf("\n--Delay metrics--\n");
printf("Median: %3d\n", median);
printf("Standard deviation: %3d\n", std);
printf("Poor delay values: %3.1f%%\n", fraction_poor_delays * 100);
}
}

View File

@ -39,6 +39,7 @@ message Test {
message DelayMetrics {
optional int32 median = 1;
optional int32 std = 2;
optional float fraction_poor_delays = 3;
}
optional DelayMetrics delay_metrics = 12;