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:
parent
0e81fdf5d2
commit
b1786dbab0
Binary file not shown.
Binary file not shown.
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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*());
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user