diff --git a/webrtc/modules/audio_processing/BUILD.gn b/webrtc/modules/audio_processing/BUILD.gn index bbcc8d018f..84f25b5e8e 100644 --- a/webrtc/modules/audio_processing/BUILD.gn +++ b/webrtc/modules/audio_processing/BUILD.gn @@ -38,6 +38,8 @@ rtc_static_library("audio_processing") { "aec3/block_framer.h", "aec3/block_processor.cc", "aec3/block_processor.h", + "aec3/block_processor_metrics.cc", + "aec3/block_processor_metrics.h", "aec3/cascaded_biquad_filter.cc", "aec3/cascaded_biquad_filter.h", "aec3/comfort_noise_generator.cc", @@ -52,6 +54,8 @@ rtc_static_library("audio_processing") { "aec3/echo_path_variability.h", "aec3/echo_remover.cc", "aec3/echo_remover.h", + "aec3/echo_remover_metrics.cc", + "aec3/echo_remover_metrics.h", "aec3/erl_estimator.cc", "aec3/erl_estimator.h", "aec3/erle_estimator.cc", @@ -75,6 +79,8 @@ rtc_static_library("audio_processing") { "aec3/render_delay_buffer.h", "aec3/render_delay_controller.cc", "aec3/render_delay_controller.h", + "aec3/render_delay_controller_metrics.cc", + "aec3/render_delay_controller_metrics.h", "aec3/render_signal_analyzer.cc", "aec3/render_signal_analyzer.h", "aec3/residual_echo_estimator.cc", @@ -563,6 +569,7 @@ if (rtc_include_tests) { "aec3/aec3_fft_unittest.cc", "aec3/aec_state_unittest.cc", "aec3/block_framer_unittest.cc", + "aec3/block_processor_metrics_unittest.cc", "aec3/block_processor_unittest.cc", "aec3/cascaded_biquad_filter_unittest.cc", "aec3/comfort_noise_generator_unittest.cc", @@ -570,6 +577,7 @@ if (rtc_include_tests) { "aec3/echo_canceller3_unittest.cc", "aec3/echo_path_delay_estimator_unittest.cc", "aec3/echo_path_variability_unittest.cc", + "aec3/echo_remover_metrics_unittest.cc", "aec3/echo_remover_unittest.cc", "aec3/erl_estimator_unittest.cc", "aec3/erle_estimator_unittest.cc", @@ -582,6 +590,7 @@ if (rtc_include_tests) { "aec3/output_selector_unittest.cc", "aec3/power_echo_model_unittest.cc", "aec3/render_delay_buffer_unittest.cc", + "aec3/render_delay_controller_metrics_unittest.cc", "aec3/render_delay_controller_unittest.cc", "aec3/render_signal_analyzer_unittest.cc", "aec3/residual_echo_estimator_unittest.cc", diff --git a/webrtc/modules/audio_processing/aec3/aec3_common.h b/webrtc/modules/audio_processing/aec3/aec3_common.h index 3a5e835e21..1d4a9feba1 100644 --- a/webrtc/modules/audio_processing/aec3/aec3_common.h +++ b/webrtc/modules/audio_processing/aec3/aec3_common.h @@ -26,6 +26,11 @@ namespace webrtc { enum class Aec3Optimization { kNone, kSse2 }; +constexpr int kMetricsReportingIntervalBlocks = 10 * 250; +constexpr int kMetricsComputationBlocks = 9; +constexpr int kMetricsCollectionBlocks = + kMetricsReportingIntervalBlocks - kMetricsComputationBlocks; + constexpr size_t kFftLengthBy2 = 64; constexpr size_t kFftLengthBy2Plus1 = kFftLengthBy2 + 1; constexpr size_t kFftLengthBy2Minus1 = kFftLengthBy2 - 1; diff --git a/webrtc/modules/audio_processing/aec3/block_processor.cc b/webrtc/modules/audio_processing/aec3/block_processor.cc index 223b693813..5055b3f77d 100644 --- a/webrtc/modules/audio_processing/aec3/block_processor.cc +++ b/webrtc/modules/audio_processing/aec3/block_processor.cc @@ -13,9 +13,9 @@ #include "webrtc/base/constructormagic.h" #include "webrtc/base/optional.h" #include "webrtc/modules/audio_processing/aec3/aec3_common.h" +#include "webrtc/modules/audio_processing/aec3/block_processor_metrics.h" #include "webrtc/modules/audio_processing/aec3/echo_path_variability.h" #include "webrtc/modules/audio_processing/logging/apm_data_dumper.h" -#include "webrtc/system_wrappers/include/logging.h" namespace webrtc { namespace { @@ -44,6 +44,7 @@ class BlockProcessorImpl final : public BlockProcessor { std::unique_ptr render_buffer_; std::unique_ptr delay_controller_; std::unique_ptr echo_remover_; + BlockProcessorMetrics metrics_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(BlockProcessorImpl); }; @@ -88,8 +89,9 @@ void BlockProcessorImpl::ProcessCapture( delay_controller_->AlignmentHeadroomSamples(), EchoPathVariability(echo_path_gain_change, render_delay_change), capture_signal_saturation, render_block, capture_block); + metrics_.UpdateCapture(false); } else { - LOG(LS_INFO) << "AEC3 empty render buffer"; + metrics_.UpdateCapture(true); } } @@ -102,10 +104,10 @@ bool BlockProcessorImpl::BufferRender(std::vector>* block) { !delay_controller_->AnalyzeRender((*block)[0]); const bool render_buffer_overrun = !render_buffer_->Insert(block); if (delay_controller_overrun || render_buffer_overrun) { - LOG(LS_INFO) << "AEC3 buffer overrrun"; + metrics_.UpdateRender(true); return false; } - + metrics_.UpdateRender(false); return true; } diff --git a/webrtc/modules/audio_processing/aec3/block_processor_metrics.cc b/webrtc/modules/audio_processing/aec3/block_processor_metrics.cc new file mode 100644 index 0000000000..3eeafa2e45 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_processor_metrics.cc @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2017 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_metrics.h" + +#include "webrtc/modules/audio_processing/aec3/aec3_common.h" +#include "webrtc/system_wrappers/include/metrics.h" + +namespace webrtc { + +namespace { + +enum class RenderUnderrunCategory { + kNone, + kFew, + kSeveral, + kMany, + kConstant, + kNumCategories +}; + +enum class RenderOverrunCategory { + kNone, + kFew, + kSeveral, + kMany, + kConstant, + kNumCategories +}; + +} // namespace + +void BlockProcessorMetrics::UpdateCapture(bool underrun) { + ++capture_block_counter_; + if (underrun) { + ++render_buffer_underruns_; + } + + if (capture_block_counter_ == kMetricsReportingIntervalBlocks) { + metrics_reported_ = true; + + RenderUnderrunCategory underrun_category; + if (render_buffer_underruns_ == 0) { + underrun_category = RenderUnderrunCategory::kNone; + } else if (render_buffer_underruns_ > (capture_block_counter_ >> 1)) { + underrun_category = RenderUnderrunCategory::kConstant; + } else if (render_buffer_underruns_ > 100) { + underrun_category = RenderUnderrunCategory::kMany; + } else if (render_buffer_underruns_ > 10) { + underrun_category = RenderUnderrunCategory::kSeveral; + } else { + underrun_category = RenderUnderrunCategory::kFew; + } + RTC_HISTOGRAM_ENUMERATION( + "WebRTC.Audio.EchoCanceller.RenderUnderruns", + static_cast(underrun_category), + static_cast(RenderUnderrunCategory::kNumCategories)); + + RenderOverrunCategory overrun_category; + if (render_buffer_overruns_ == 0) { + overrun_category = RenderOverrunCategory::kNone; + } else if (render_buffer_overruns_ > (buffer_render_calls_ >> 1)) { + overrun_category = RenderOverrunCategory::kConstant; + } else if (render_buffer_overruns_ > 100) { + overrun_category = RenderOverrunCategory::kMany; + } else if (render_buffer_overruns_ > 10) { + overrun_category = RenderOverrunCategory::kSeveral; + } else { + overrun_category = RenderOverrunCategory::kFew; + } + RTC_HISTOGRAM_ENUMERATION( + "WebRTC.Audio.EchoCanceller.RenderOverruns", + static_cast(overrun_category), + static_cast(RenderOverrunCategory::kNumCategories)); + + ResetMetrics(); + capture_block_counter_ = 0; + } else { + metrics_reported_ = false; + } +} + +void BlockProcessorMetrics::UpdateRender(bool overrun) { + ++buffer_render_calls_; + if (overrun) { + ++render_buffer_overruns_; + } +} + +void BlockProcessorMetrics::ResetMetrics() { + render_buffer_underruns_ = 0; + render_buffer_overruns_ = 0; + buffer_render_calls_ = 0; +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/block_processor_metrics.h b/webrtc/modules/audio_processing/aec3/block_processor_metrics.h new file mode 100644 index 0000000000..6c278cd793 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_processor_metrics.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 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_METRICS_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_METRICS_H_ + +#include "webrtc/base/constructormagic.h" + +namespace webrtc { + +// Handles the reporting of metrics for the block_processor. +class BlockProcessorMetrics { + public: + BlockProcessorMetrics() = default; + + // Updates the metric with new capture data. + void UpdateCapture(bool underrun); + + // Updates the metric with new render data. + void UpdateRender(bool overrun); + + // Returns true if the metrics have just been reported, otherwise false. + bool MetricsReported() { return metrics_reported_; } + + private: + // Resets the metrics. + void ResetMetrics(); + + int capture_block_counter_ = 0; + bool metrics_reported_ = false; + int render_buffer_underruns_ = 0; + int render_buffer_overruns_ = 0; + int buffer_render_calls_ = 0; + + RTC_DISALLOW_COPY_AND_ASSIGN(BlockProcessorMetrics); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_BLOCK_PROCESSOR_METRICS_H_ diff --git a/webrtc/modules/audio_processing/aec3/block_processor_metrics_unittest.cc b/webrtc/modules/audio_processing/aec3/block_processor_metrics_unittest.cc new file mode 100644 index 0000000000..8ab1048556 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/block_processor_metrics_unittest.cc @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017 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/aec3_common.h" +#include "webrtc/modules/audio_processing/aec3/block_processor_metrics.h" + +#include "webrtc/test/gtest.h" + +namespace webrtc { + +// Verify the general functionality of BlockProcessorMetrics. +TEST(BlockProcessorMetrics, NormalUsage) { + BlockProcessorMetrics metrics; + + for (int j = 0; j < 3; ++j) { + for (int k = 0; k < kMetricsReportingIntervalBlocks - 1; ++k) { + metrics.UpdateRender(false); + metrics.UpdateRender(false); + metrics.UpdateCapture(false); + EXPECT_FALSE(metrics.MetricsReported()); + } + metrics.UpdateCapture(false); + EXPECT_TRUE(metrics.MetricsReported()); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/echo_remover.cc b/webrtc/modules/audio_processing/aec3/echo_remover.cc index 2d6328655c..a0e03fbeab 100644 --- a/webrtc/modules/audio_processing/aec3/echo_remover.cc +++ b/webrtc/modules/audio_processing/aec3/echo_remover.cc @@ -21,6 +21,7 @@ #include "webrtc/modules/audio_processing/aec3/aec_state.h" #include "webrtc/modules/audio_processing/aec3/comfort_noise_generator.h" #include "webrtc/modules/audio_processing/aec3/echo_path_variability.h" +#include "webrtc/modules/audio_processing/aec3/echo_remover_metrics.h" #include "webrtc/modules/audio_processing/aec3/fft_buffer.h" #include "webrtc/modules/audio_processing/aec3/fft_data.h" #include "webrtc/modules/audio_processing/aec3/output_selector.h" @@ -90,6 +91,7 @@ class EchoRemoverImpl final : public EchoRemover { bool echo_leakage_detected_ = false; std::array x_old_; AecState aec_state_; + EchoRemoverMetrics metrics_; RTC_DISALLOW_COPY_AND_ASSIGN(EchoRemoverImpl); }; @@ -209,6 +211,9 @@ void EchoRemoverImpl::ProcessBlock( doubletalk ? 0.001f : 0.0001f, &G); suppression_filter_.ApplyGain(comfort_noise, high_band_comfort_noise, G, y); + // Update the metrics. + metrics_.Update(aec_state_, cng_.NoiseSpectrum(), G); + // Debug outputs for the purpose of development and analysis. data_dumper_->DumpRaw("aec3_N2", cng_.NoiseSpectrum()); data_dumper_->DumpRaw("aec3_suppressor_gain", G); diff --git a/webrtc/modules/audio_processing/aec3/echo_remover_metrics.cc b/webrtc/modules/audio_processing/aec3/echo_remover_metrics.cc new file mode 100644 index 0000000000..16a36f4fb9 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/echo_remover_metrics.cc @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2017 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_remover_metrics.h" + +#include +#include +#include + +#include "webrtc/system_wrappers/include/metrics.h" + +namespace webrtc { + +namespace { + +constexpr float kOneByMetricsCollectionBlocks = 1.f / kMetricsCollectionBlocks; + +} // namespace + +EchoRemoverMetrics::DbMetric::DbMetric() : DbMetric(0.f, 0.f, 0.f) {} +EchoRemoverMetrics::DbMetric::DbMetric(float sum_value, + float floor_value, + float ceil_value) + : sum_value(sum_value), floor_value(floor_value), ceil_value(ceil_value) {} + +void EchoRemoverMetrics::DbMetric::Update(float value) { + sum_value += value; + floor_value = std::min(floor_value, value); + ceil_value = std::max(ceil_value, value); +} + +EchoRemoverMetrics::EchoRemoverMetrics() { + ResetMetrics(); +} + +void EchoRemoverMetrics::ResetMetrics() { + erl_.fill(DbMetric(0.f, 10000.f, 0.000f)); + erle_.fill(DbMetric(0.f, 0.f, 1000.f)); + comfort_noise_.fill(DbMetric(0.f, 100000000.f, 0.f)); + suppressor_gain_.fill(DbMetric(0.f, 1.f, 0.f)); + active_render_count_ = 0; + saturated_capture_ = false; +} + +void EchoRemoverMetrics::Update( + const AecState& aec_state, + const std::array& comfort_noise_spectrum, + const std::array& suppressor_gain) { + metrics_reported_ = false; + if (++block_counter_ <= kMetricsCollectionBlocks) { + aec3::UpdateDbMetric(aec_state.Erl(), &erl_); + aec3::UpdateDbMetric(aec_state.Erle(), &erle_); + aec3::UpdateDbMetric(comfort_noise_spectrum, &comfort_noise_); + aec3::UpdateDbMetric(suppressor_gain, &suppressor_gain_); + active_render_count_ += (aec_state.ActiveRender() ? 1 : 0); + saturated_capture_ = saturated_capture_ || aec_state.SaturatedCapture(); + } else { + // Report the metrics over several frames in order to lower the impact of + // the logarithms involved on the computational complexity. + constexpr int kMetricsCollectionBlocksBy2 = kMetricsCollectionBlocks / 2; + constexpr float kComfortNoiseScaling = 1.f / (kBlockSize * kBlockSize); + switch (block_counter_) { + case kMetricsCollectionBlocks + 1: + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErleBand0.Average", + aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, + kOneByMetricsCollectionBlocks, + erle_[0].sum_value), + 0, 19, 20); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErleBand0.Max", + aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, 1.f, + erle_[0].ceil_value), + 0, 19, 20); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErleBand0.Min", + aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, 1.f, + erle_[0].floor_value), + 0, 19, 20); + break; + case kMetricsCollectionBlocks + 2: + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErleBand1.Average", + aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, + kOneByMetricsCollectionBlocks, + erle_[1].sum_value), + 0, 19, 20); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErleBand1.Max", + aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, 1.f, + erle_[1].ceil_value), + 0, 19, 20); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErleBand1.Min", + aec3::TransformDbMetricForReporting(true, 0.f, 19.f, 0.f, 1.f, + erle_[1].floor_value), + 0, 19, 20); + break; + case kMetricsCollectionBlocks + 3: + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErlBand0.Average", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, + kOneByMetricsCollectionBlocks, + erl_[0].sum_value), + 0, 59, 30); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErlBand0.Max", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, 1.f, + erl_[0].ceil_value), + 0, 59, 30); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErlBand0.Min", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, 1.f, + erl_[0].floor_value), + 0, 59, 30); + break; + case kMetricsCollectionBlocks + 4: + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErlBand1.Average", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, + kOneByMetricsCollectionBlocks, + erl_[1].sum_value), + 0, 59, 30); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErlBand1.Max", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, 1.f, + erl_[1].ceil_value), + 0, 59, 30); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ErlBand1.Min", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 30.f, 1.f, + erl_[1].floor_value), + 0, 59, 30); + break; + case kMetricsCollectionBlocks + 5: + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ComfortNoiseBand0.Average", + aec3::TransformDbMetricForReporting( + true, 0.f, 89.f, -90.3f, + kComfortNoiseScaling * kOneByMetricsCollectionBlocks, + comfort_noise_[0].sum_value), + 0, 89, 45); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ComfortNoiseBand0.Max", + aec3::TransformDbMetricForReporting(true, 0.f, 89.f, -90.3f, + kComfortNoiseScaling, + comfort_noise_[0].ceil_value), + 0, 89, 45); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ComfortNoiseBand0.Min", + aec3::TransformDbMetricForReporting(true, 0.f, 89.f, -90.3f, + kComfortNoiseScaling, + comfort_noise_[0].floor_value), + 0, 89, 45); + break; + case kMetricsCollectionBlocks + 6: + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ComfortNoiseBand1.Average", + aec3::TransformDbMetricForReporting( + true, 0.f, 89.f, -90.3f, + kComfortNoiseScaling * kOneByMetricsCollectionBlocks, + comfort_noise_[1].sum_value), + 0, 89, 45); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ComfortNoiseBand1.Max", + aec3::TransformDbMetricForReporting(true, 0.f, 89.f, -90.3f, + kComfortNoiseScaling, + comfort_noise_[1].ceil_value), + 0, 89, 45); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.ComfortNoiseBand1.Min", + aec3::TransformDbMetricForReporting(true, 0.f, 89.f, -90.3f, + kComfortNoiseScaling, + comfort_noise_[1].floor_value), + 0, 89, 45); + break; + case kMetricsCollectionBlocks + 7: + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.SuppressorGainBand0.Average", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 0.f, + kOneByMetricsCollectionBlocks, + suppressor_gain_[0].sum_value), + 0, 59, 30); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.SuppressorGainBand0.Max", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 0.f, 1.f, + suppressor_gain_[0].ceil_value), + 0, 59, 30); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.SuppressorGainBand0.Min", + aec3::TransformDbMetricForReporting( + true, 0.f, 59.f, 0.f, 1.f, suppressor_gain_[0].floor_value), + 0, 59, 30); + break; + case kMetricsCollectionBlocks + 8: + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.SuppressorGainBand1.Average", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 0.f, + kOneByMetricsCollectionBlocks, + suppressor_gain_[1].sum_value), + 0, 59, 30); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.SuppressorGainBand1.Max", + aec3::TransformDbMetricForReporting(true, 0.f, 59.f, 0.f, 1.f, + suppressor_gain_[1].ceil_value), + 0, 59, 30); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.SuppressorGainBand1.Min", + aec3::TransformDbMetricForReporting( + true, 0.f, 59.f, 0.f, 1.f, suppressor_gain_[1].floor_value), + 0, 59, 30); + break; + case kMetricsCollectionBlocks + 9: + RTC_HISTOGRAM_BOOLEAN( + "WebRTC.Audio.EchoCanceller.UsableLinearEstimate", + static_cast(aec_state.UsableLinearEstimate() ? 1 : 0)); + RTC_HISTOGRAM_BOOLEAN( + "WebRTC.Audio.EchoCanceller.ModelBasedAecFeasible", + static_cast(aec_state.ModelBasedAecFeasible() ? 1 : 0)); + RTC_HISTOGRAM_BOOLEAN( + "WebRTC.Audio.EchoCanceller.ActiveRender", + static_cast( + active_render_count_ > kMetricsCollectionBlocksBy2 ? 1 : 0)); + RTC_HISTOGRAM_COUNTS_LINEAR( + "WebRTC.Audio.EchoCanceller.FilterDelay", + aec_state.FilterDelay() ? *aec_state.FilterDelay() + 1 : 0, 0, 30, + 31); + RTC_HISTOGRAM_BOOLEAN("WebRTC.Audio.EchoCanceller.CaptureSaturation", + static_cast(saturated_capture_ ? 1 : 0)); + metrics_reported_ = true; + RTC_DCHECK_EQ(kMetricsReportingIntervalBlocks, block_counter_); + block_counter_ = 0; + ResetMetrics(); + break; + default: + RTC_NOTREACHED(); + break; + } + } +} + +namespace aec3 { + +void UpdateDbMetric(const std::array& value, + std::array* statistic) { + RTC_DCHECK(statistic); + // Truncation is intended in the band width computation. + constexpr int kNumBands = 2; + constexpr int kBandWidth = 65 / kNumBands; + constexpr float kOneByBandWidth = 1.f / kBandWidth; + RTC_DCHECK_EQ(kNumBands, statistic->size()); + RTC_DCHECK_EQ(65, value.size()); + for (size_t k = 0; k < statistic->size(); ++k) { + float average_band = + std::accumulate(value.begin() + kBandWidth * k, + value.begin() + kBandWidth * (k + 1), 0.f) * + kOneByBandWidth; + (*statistic)[k].Update(average_band); + } +} + +int TransformDbMetricForReporting(bool negate, + float min_value, + float max_value, + float offset, + float scaling, + float value) { + float new_value = 10.f * log10(value * scaling + 1e-10f) + offset; + if (negate) { + new_value = -new_value; + } + return static_cast(std::max(min_value, std::min(max_value, new_value))); +} + +} // namespace aec3 + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/echo_remover_metrics.h b/webrtc/modules/audio_processing/aec3/echo_remover_metrics.h new file mode 100644 index 0000000000..aaca1596d7 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/echo_remover_metrics.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2017 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_ECHO_REMOVER_METRICS_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_REMOVER_METRICS_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/modules/audio_processing/aec3/aec_state.h" + +namespace webrtc { + +// Handles the reporting of metrics for the echo remover. +class EchoRemoverMetrics { + public: + struct DbMetric { + DbMetric(); + DbMetric(float sum_value, float floor_value, float ceil_value); + void Update(float value); + float sum_value; + float floor_value; + float ceil_value; + }; + + EchoRemoverMetrics(); + + // Updates the metric with new data. + void Update( + const AecState& aec_state, + const std::array& comfort_noise_spectrum, + const std::array& suppressor_gain); + + // Returns true if the metrics have just been reported, otherwise false. + bool MetricsReported() { return metrics_reported_; } + + private: + // Resets the metrics. + void ResetMetrics(); + + int block_counter_ = 0; + std::array erl_; + std::array erle_; + std::array comfort_noise_; + std::array suppressor_gain_; + int active_render_count_ = 0; + bool saturated_capture_ = false; + bool metrics_reported_ = false; + + RTC_DISALLOW_COPY_AND_ASSIGN(EchoRemoverMetrics); +}; + +namespace aec3 { + +// Updates a banded metric of type DbMetric with the values in the supplied +// array. +void UpdateDbMetric(const std::array& value, + std::array* statistic); + +// Transforms a DbMetric from the linear domain into the logarithmic domain. +int TransformDbMetricForReporting(bool negate, + float min_value, + float max_value, + float offset, + float scaling, + float value); + +} // namespace aec3 + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_ECHO_REMOVER_METRICS_H_ diff --git a/webrtc/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc b/webrtc/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc new file mode 100644 index 0000000000..8030f812f6 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/echo_remover_metrics_unittest.cc @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2017 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_remover_metrics.h" + +#include + +#include "webrtc/modules/audio_processing/aec3/aec_state.h" +#include "webrtc/modules/audio_processing/aec3/aec3_fft.h" +#include "webrtc/test/gtest.h" + +namespace webrtc { + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) + +// Verifies the check for non-null input. +TEST(UpdateDbMetric, NullValue) { + std::array value; + value.fill(0.f); + EXPECT_DEATH(aec3::UpdateDbMetric(value, nullptr), ""); +} + +#endif + +// Verifies the updating functionality of UpdateDbMetric. +TEST(UpdateDbMetric, Updating) { + std::array value; + std::array statistic; + statistic.fill(EchoRemoverMetrics::DbMetric(0.f, 100.f, -100.f)); + constexpr float kValue0 = 10.f; + constexpr float kValue1 = 20.f; + std::fill(value.begin(), value.begin() + 32, kValue0); + std::fill(value.begin() + 32, value.begin() + 64, kValue1); + + aec3::UpdateDbMetric(value, &statistic); + EXPECT_FLOAT_EQ(kValue0, statistic[0].sum_value); + EXPECT_FLOAT_EQ(kValue0, statistic[0].ceil_value); + EXPECT_FLOAT_EQ(kValue0, statistic[0].floor_value); + EXPECT_FLOAT_EQ(kValue1, statistic[1].sum_value); + EXPECT_FLOAT_EQ(kValue1, statistic[1].ceil_value); + EXPECT_FLOAT_EQ(kValue1, statistic[1].floor_value); + + aec3::UpdateDbMetric(value, &statistic); + EXPECT_FLOAT_EQ(2.f * kValue0, statistic[0].sum_value); + EXPECT_FLOAT_EQ(kValue0, statistic[0].ceil_value); + EXPECT_FLOAT_EQ(kValue0, statistic[0].floor_value); + EXPECT_FLOAT_EQ(2.f * kValue1, statistic[1].sum_value); + EXPECT_FLOAT_EQ(kValue1, statistic[1].ceil_value); + EXPECT_FLOAT_EQ(kValue1, statistic[1].floor_value); +} + +// Verifies that the TransformDbMetricForReporting method produces the desired +// output for values for dBFS. +TEST(TransformDbMetricForReporting, DbFsScaling) { + std::array x; + FftData X; + std::array X2; + Aec3Fft fft; + x.fill(1000.f); + fft.ZeroPaddedFft(x, &X); + X.Spectrum(Aec3Optimization::kNone, &X2); + + float offset = -10.f * log10(32768.f * 32768.f); + EXPECT_NEAR(offset, -90.3f, 0.1f); + EXPECT_EQ( + static_cast(30.3f), + aec3::TransformDbMetricForReporting( + true, 0.f, 90.f, offset, 1.f / (kBlockSize * kBlockSize), X2[0])); +} + +// Verifies that the TransformDbMetricForReporting method is able to properly +// limit the output. +TEST(TransformDbMetricForReporting, Limits) { + EXPECT_EQ( + 0, + aec3::TransformDbMetricForReporting(false, 0.f, 10.f, 0.f, 1.f, 0.001f)); + EXPECT_EQ( + 10, + aec3::TransformDbMetricForReporting(false, 0.f, 10.f, 0.f, 1.f, 100.f)); +} + +// Verifies that the TransformDbMetricForReporting method is able to properly +// negate output. +TEST(TransformDbMetricForReporting, Negate) { + EXPECT_EQ( + 10, + aec3::TransformDbMetricForReporting(true, -20.f, 20.f, 0.f, 1.f, 0.1f)); + EXPECT_EQ( + -10, + aec3::TransformDbMetricForReporting(true, -20.f, 20.f, 0.f, 1.f, 10.f)); +} + +// Verify the Update functionality of DbMetric. +TEST(DbMetric, Update) { + EchoRemoverMetrics::DbMetric metric(0.f, 20.f, -20.f); + constexpr int kNumValues = 100; + constexpr float kValue = 10.f; + for (int k = 0; k < kNumValues; ++k) { + metric.Update(kValue); + } + EXPECT_FLOAT_EQ(kValue * kNumValues, metric.sum_value); + EXPECT_FLOAT_EQ(kValue, metric.ceil_value); + EXPECT_FLOAT_EQ(kValue, metric.floor_value); +} + +// Verify the constructor functionality of DbMetric. +TEST(DbMetric, Constructor) { + EchoRemoverMetrics::DbMetric metric; + EXPECT_FLOAT_EQ(0.f, metric.sum_value); + EXPECT_FLOAT_EQ(0.f, metric.ceil_value); + EXPECT_FLOAT_EQ(0.f, metric.floor_value); + + metric = EchoRemoverMetrics::DbMetric(1.f, 2.f, 3.f); + EXPECT_FLOAT_EQ(1.f, metric.sum_value); + EXPECT_FLOAT_EQ(2.f, metric.floor_value); + EXPECT_FLOAT_EQ(3.f, metric.ceil_value); +} + +// Verify the general functionality of EchoRemoverMetrics. +TEST(EchoRemoverMetrics, NormalUsage) { + EchoRemoverMetrics metrics; + AecState aec_state; + std::array comfort_noise_spectrum; + std::array suppressor_gain; + comfort_noise_spectrum.fill(10.f); + suppressor_gain.fill(1.f); + for (int j = 0; j < 3; ++j) { + for (int k = 0; k < kMetricsReportingIntervalBlocks - 1; ++k) { + metrics.Update(aec_state, comfort_noise_spectrum, suppressor_gain); + EXPECT_FALSE(metrics.MetricsReported()); + } + metrics.Update(aec_state, comfort_noise_spectrum, suppressor_gain); + EXPECT_TRUE(metrics.MetricsReported()); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/render_delay_controller.cc b/webrtc/modules/audio_processing/aec3/render_delay_controller.cc index 834e1e7641..195d8cd161 100644 --- a/webrtc/modules/audio_processing/aec3/render_delay_controller.cc +++ b/webrtc/modules/audio_processing/aec3/render_delay_controller.cc @@ -18,7 +18,7 @@ #include "webrtc/base/constructormagic.h" #include "webrtc/modules/audio_processing/aec3/aec3_common.h" #include "webrtc/modules/audio_processing/aec3/echo_path_delay_estimator.h" -#include "webrtc/system_wrappers/include/logging.h" +#include "webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.h" namespace webrtc { @@ -81,6 +81,7 @@ class RenderDelayControllerImpl final : public RenderDelayController { int echo_path_delay_samples_ = 0; size_t align_call_counter_ = 0; rtc::Optional headroom_samples_; + RenderDelayControllerMetrics metrics_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RenderDelayControllerImpl); }; @@ -151,6 +152,8 @@ size_t RenderDelayControllerImpl::GetDelay( headroom_samples_ = rtc::Optional(); } + metrics_.Update(echo_path_delay_samples, delay_); + data_dumper_->DumpRaw("aec3_render_delay_controller_delay", 1, &echo_path_delay_samples_); data_dumper_->DumpRaw("aec3_render_delay_controller_buffer_delay", delay_); diff --git a/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.cc b/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.cc new file mode 100644 index 0000000000..b84b9160a6 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.cc @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017 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/render_delay_controller_metrics.h" + +#include + +#include "webrtc/modules/audio_processing/aec3/aec3_common.h" +#include "webrtc/system_wrappers/include/metrics.h" + +namespace webrtc { + +namespace { + +enum class DelayReliabilityCategory { + kNone, + kPoor, + kMedium, + kGood, + kExcellent, + kNumCategories +}; +enum class DelayChangesCategory { + kNone, + kFew, + kSeveral, + kMany, + kConstant, + kNumCategories +}; + +} // namespace + +void RenderDelayControllerMetrics::Update(rtc::Optional delay_samples, + size_t buffer_delay_blocks) { + ++call_counter_; + + if (!initial_update) { + if (delay_samples) { + ++reliable_delay_estimate_counter_; + size_t delay_blocks = (*delay_samples) / kBlockSize; + + if (delay_blocks != delay_blocks_) { + ++delay_change_counter_; + delay_blocks_ = delay_blocks; + } + } + } else if (++initial_call_counter_ == 5 * 250) { + initial_update = false; + } + + if (call_counter_ == kMetricsReportingIntervalBlocks) { + int value_to_report = static_cast(delay_blocks_); + value_to_report = std::min(124, value_to_report); + RTC_HISTOGRAM_COUNTS_LINEAR("WebRTC.Audio.EchoCanceller.EchoPathDelay", + value_to_report, 0, 124, 125); + + value_to_report = static_cast(buffer_delay_blocks); + value_to_report = std::min(124, value_to_report); + RTC_HISTOGRAM_COUNTS_LINEAR("WebRTC.Audio.EchoCanceller.BufferDelay", + value_to_report, 0, 124, 125); + + DelayReliabilityCategory delay_reliability; + if (reliable_delay_estimate_counter_ == 0) { + delay_reliability = DelayReliabilityCategory::kNone; + } else if (reliable_delay_estimate_counter_ > (call_counter_ >> 1)) { + delay_reliability = DelayReliabilityCategory::kExcellent; + } else if (reliable_delay_estimate_counter_ > 100) { + delay_reliability = DelayReliabilityCategory::kGood; + } else if (reliable_delay_estimate_counter_ > 10) { + delay_reliability = DelayReliabilityCategory::kMedium; + } else { + delay_reliability = DelayReliabilityCategory::kPoor; + } + RTC_HISTOGRAM_ENUMERATION( + "WebRTC.Audio.EchoCanceller.ReliableDelayEstimates", + static_cast(delay_reliability), + static_cast(DelayReliabilityCategory::kNumCategories)); + + DelayChangesCategory delay_changes; + if (delay_change_counter_ == 0) { + delay_changes = DelayChangesCategory::kNone; + } else if (delay_change_counter_ > 10) { + delay_changes = DelayChangesCategory::kConstant; + } else if (delay_change_counter_ > 5) { + delay_changes = DelayChangesCategory::kMany; + } else if (delay_change_counter_ > 2) { + delay_changes = DelayChangesCategory::kSeveral; + } else { + delay_changes = DelayChangesCategory::kFew; + } + RTC_HISTOGRAM_ENUMERATION( + "WebRTC.Audio.EchoCanceller.DelayChanges", + static_cast(delay_changes), + static_cast(DelayChangesCategory::kNumCategories)); + + metrics_reported_ = true; + call_counter_ = 0; + ResetMetrics(); + } else { + metrics_reported_ = false; + } +} + +void RenderDelayControllerMetrics::ResetMetrics() { + delay_change_counter_ = 0; + reliable_delay_estimate_counter_ = 0; +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.h b/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.h new file mode 100644 index 0000000000..9b9ea86437 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 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_RENDER_DELAY_CONTROLLER_METRICS_H_ +#define WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_CONTROLLER_METRICS_H_ + +#include "webrtc/base/constructormagic.h" +#include "webrtc/base/optional.h" + +namespace webrtc { + +// Handles the reporting of metrics for the render delay controller. +class RenderDelayControllerMetrics { + public: + RenderDelayControllerMetrics() = default; + + // Updates the metric with new data. + void Update(rtc::Optional delay_samples, size_t buffer_delay_blocks); + + // Returns true if the metrics have just been reported, otherwise false. + bool MetricsReported() { return metrics_reported_; } + + private: + // Resets the metrics. + void ResetMetrics(); + + size_t delay_blocks_ = 0; + int reliable_delay_estimate_counter_ = 0; + int delay_change_counter_ = 0; + int call_counter_ = 0; + int initial_call_counter_ = 0; + bool metrics_reported_ = false; + bool initial_update = true; + + RTC_DISALLOW_COPY_AND_ASSIGN(RenderDelayControllerMetrics); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_AUDIO_PROCESSING_AEC3_RENDER_DELAY_CONTROLLER_METRICS_H_ diff --git a/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics_unittest.cc b/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics_unittest.cc new file mode 100644 index 0000000000..2551e8a704 --- /dev/null +++ b/webrtc/modules/audio_processing/aec3/render_delay_controller_metrics_unittest.cc @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 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/base/optional.h" +#include "webrtc/modules/audio_processing/aec3/aec3_common.h" +#include "webrtc/modules/audio_processing/aec3/render_delay_controller_metrics.h" + +#include "webrtc/test/gtest.h" + +namespace webrtc { + +// Verify the general functionality of RenderDelayControllerMetrics. +TEST(RenderDelayControllerMetrics, NormalUsage) { + RenderDelayControllerMetrics metrics; + + for (int j = 0; j < 3; ++j) { + for (int k = 0; k < kMetricsReportingIntervalBlocks - 1; ++k) { + metrics.Update(rtc::Optional(), 0); + EXPECT_FALSE(metrics.MetricsReported()); + } + metrics.Update(rtc::Optional(), 0); + EXPECT_TRUE(metrics.MetricsReported()); + } +} + +} // namespace webrtc