From ae02609645ea1da6d2381369c7d1b2d9492ec871 Mon Sep 17 00:00:00 2001 From: Ivo Creusen Date: Mon, 20 Nov 2017 13:07:16 +0100 Subject: [PATCH] Add parallel stats interface with optional stats to APM. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This new parallel GetStatistics function uses Optionals to indicate if stats are valid or not, and no longer relies on default values. It also takes an argument to indicate if receive streams are present, and if not several stats will not be set. Bug: b/67926135 Change-Id: I175de1c65c414bea6ec9ca8b0b16f07cb2308d9f Reviewed-on: https://webrtc-review.googlesource.com/17942 Commit-Queue: Ivo Creusen Reviewed-by: Per Kjellander Reviewed-by: Henrik Boström Reviewed-by: Henrik Lundin Cr-Commit-Position: refs/heads/master@{#20789} --- api/mediastreaminterface.cc | 22 +++ api/mediastreaminterface.h | 24 +++ .../audio_processing/audio_processing_impl.cc | 61 ++++++- .../audio_processing/audio_processing_impl.h | 1 + .../audio_processing_unittest.cc | 162 ++++++++++++++++++ .../include/audio_processing.h | 37 ++++ pc/statscollector.cc | 95 +++++----- pc/statscollector.h | 5 +- pc/statscollector_unittest.cc | 19 ++ 9 files changed, 383 insertions(+), 43 deletions(-) diff --git a/api/mediastreaminterface.cc b/api/mediastreaminterface.cc index 465aac6f4b..92bca162af 100644 --- a/api/mediastreaminterface.cc +++ b/api/mediastreaminterface.cc @@ -15,4 +15,26 @@ namespace webrtc { const char MediaStreamTrackInterface::kVideoKind[] = "video"; const char MediaStreamTrackInterface::kAudioKind[] = "audio"; +// TODO(ivoc): Remove this when the function becomes pure virtual. +AudioProcessorInterface::AudioProcessorStatistics +AudioProcessorInterface::GetStats(bool /*has_remote_tracks*/) { + AudioProcessorStats stats; + GetStats(&stats); + AudioProcessorStatistics new_stats; + new_stats.aec_divergent_filter_fraction = + rtc::Optional(stats.aec_divergent_filter_fraction); + new_stats.aec_quality_min = rtc::Optional(stats.aec_quality_min); + new_stats.echo_delay_median_ms = + rtc::Optional(stats.echo_delay_median_ms); + new_stats.echo_delay_std_ms = rtc::Optional(stats.echo_delay_std_ms); + new_stats.echo_return_loss = rtc::Optional(stats.echo_return_loss); + new_stats.echo_return_loss_enhancement = + rtc::Optional(stats.echo_return_loss_enhancement); + new_stats.residual_echo_likelihood = + rtc::Optional(stats.residual_echo_likelihood); + new_stats.residual_echo_likelihood_recent_max = + rtc::Optional(stats.residual_echo_likelihood_recent_max); + return new_stats; +} + } // namespace webrtc diff --git a/api/mediastreaminterface.h b/api/mediastreaminterface.h index fa074a0533..2cc5923d7e 100644 --- a/api/mediastreaminterface.h +++ b/api/mediastreaminterface.h @@ -225,6 +225,9 @@ class AudioSourceInterface : public MediaSourceInterface { // statistics. class AudioProcessorInterface : public rtc::RefCountInterface { public: + // Deprecated, use AudioProcessorStatistics instead. + // TODO(ivoc): Remove this when all implementations have switched to the new + // GetStats function. See b/67926135. struct AudioProcessorStats { AudioProcessorStats() : typing_noise_detected(false), @@ -248,10 +251,31 @@ class AudioProcessorInterface : public rtc::RefCountInterface { float residual_echo_likelihood_recent_max; float aec_divergent_filter_fraction; }; + // This struct maintains the optionality of the stats, and will replace the + // regular stats struct when all users have been updated. + struct AudioProcessorStatistics { + bool typing_noise_detected = false; + rtc::Optional echo_return_loss; + rtc::Optional echo_return_loss_enhancement; + rtc::Optional echo_delay_median_ms; + rtc::Optional echo_delay_std_ms; + rtc::Optional aec_quality_min; + rtc::Optional residual_echo_likelihood; + rtc::Optional residual_echo_likelihood_recent_max; + rtc::Optional aec_divergent_filter_fraction; + }; // Get audio processor statistics. virtual void GetStats(AudioProcessorStats* stats) = 0; + // Get audio processor statistics. The |has_remote_tracks| argument should be + // set if there are active remote tracks (this would usually be true during + // a call). If there are no remote tracks some of the stats will not be set by + // the AudioProcessor, because they only make sense if there is at least one + // remote track. + // TODO(ivoc): Make pure virtual when all implementions are updated. + virtual AudioProcessorStatistics GetStats(bool has_remote_tracks); + protected: virtual ~AudioProcessorInterface() {} }; diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc index 5e47f1adc5..c8d0970502 100644 --- a/modules/audio_processing/audio_processing_impl.cc +++ b/modules/audio_processing/audio_processing_impl.cc @@ -1557,18 +1557,31 @@ AudioProcessing::AudioProcessingStatistics::AudioProcessingStatistics( AudioProcessing::AudioProcessingStatistics::~AudioProcessingStatistics() = default; +AudioProcessing::AudioProcessingStats::AudioProcessingStats() = default; + +AudioProcessing::AudioProcessingStats::AudioProcessingStats( + const AudioProcessingStats& other) = default; + +AudioProcessing::AudioProcessingStats::~AudioProcessingStats() = default; + // TODO(ivoc): Remove this when GetStatistics() becomes pure virtual. AudioProcessing::AudioProcessingStatistics AudioProcessing::GetStatistics() const { return AudioProcessingStatistics(); } +// TODO(ivoc): Remove this when GetStatistics() becomes pure virtual. +AudioProcessing::AudioProcessingStats AudioProcessing::GetStatistics( + bool has_remote_tracks) const { + return AudioProcessingStats(); +} + AudioProcessing::AudioProcessingStatistics AudioProcessingImpl::GetStatistics() const { AudioProcessingStatistics stats; EchoCancellation::Metrics metrics; - int success = public_submodules_->echo_cancellation->GetMetrics(&metrics); - if (success == Error::kNoError) { + if (public_submodules_->echo_cancellation->GetMetrics(&metrics) == + Error::kNoError) { stats.a_nlp.Set(metrics.a_nlp); stats.divergent_filter_fraction = metrics.divergent_filter_fraction; stats.echo_return_loss.Set(metrics.echo_return_loss); @@ -1590,6 +1603,50 @@ AudioProcessing::AudioProcessingStatistics AudioProcessingImpl::GetStatistics() return stats; } +AudioProcessing::AudioProcessingStats AudioProcessingImpl::GetStatistics( + bool has_remote_tracks) const { + AudioProcessingStats stats; + if (has_remote_tracks) { + EchoCancellation::Metrics metrics; + if (public_submodules_->echo_cancellation->GetMetrics(&metrics) == + Error::kNoError) { + if (metrics.divergent_filter_fraction != -1.0f) { + stats.divergent_filter_fraction = + rtc::Optional(metrics.divergent_filter_fraction); + } + if (metrics.echo_return_loss.instant != -100) { + stats.echo_return_loss = + rtc::Optional(metrics.echo_return_loss.instant); + } + if (metrics.echo_return_loss_enhancement.instant != -100) { + stats.echo_return_loss_enhancement = + rtc::Optional(metrics.echo_return_loss_enhancement.instant); + } + } + if (config_.residual_echo_detector.enabled) { + rtc::CritScope cs_capture(&crit_capture_); + stats.residual_echo_likelihood = rtc::Optional( + private_submodules_->residual_echo_detector->echo_likelihood()); + stats.residual_echo_likelihood_recent_max = + rtc::Optional(private_submodules_->residual_echo_detector + ->echo_likelihood_recent_max()); + } + int delay_median, delay_std; + float fraction_poor_delays; + if (public_submodules_->echo_cancellation->GetDelayMetrics( + &delay_median, &delay_std, &fraction_poor_delays) == + Error::kNoError) { + if (delay_median >= 0) { + stats.delay_median_ms = rtc::Optional(delay_median); + } + if (delay_std >= 0) { + stats.delay_standard_deviation_ms = rtc::Optional(delay_std); + } + } + } + return stats; +} + EchoCancellation* AudioProcessingImpl::echo_cancellation() const { return public_submodules_->echo_cancellation.get(); } diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h index ef3d973f9d..021a52037c 100644 --- a/modules/audio_processing/audio_processing_impl.h +++ b/modules/audio_processing/audio_processing_impl.h @@ -106,6 +106,7 @@ class AudioProcessingImpl : public AudioProcessing { RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_capture_); AudioProcessingStatistics GetStatistics() const override; + AudioProcessingStats GetStatistics(bool has_remote_tracks) const override; // Methods returning pointers to APM submodules. // No locks are aquired in those, as those locks diff --git a/modules/audio_processing/audio_processing_unittest.cc b/modules/audio_processing/audio_processing_unittest.cc index 2375a0d2b2..e3de729d8f 100644 --- a/modules/audio_processing/audio_processing_unittest.cc +++ b/modules/audio_processing/audio_processing_unittest.cc @@ -2952,4 +2952,166 @@ TEST(ApmConfiguration, EchoControlInjection) { apm->ProcessReverseStream(&audio); apm->ProcessStream(&audio); } + +std::unique_ptr CreateApm(bool use_AEC2) { + Config old_config; + if (use_AEC2) { + old_config.Set(new ExtendedFilter(true)); + old_config.Set(new DelayAgnostic(true)); + } + std::unique_ptr apm(AudioProcessing::Create(old_config)); + if (!apm) { + return apm; + } + + ProcessingConfig processing_config = { + {{32000, 1}, {32000, 1}, {32000, 1}, {32000, 1}}}; + + if (apm->Initialize(processing_config) != 0) { + return nullptr; + } + + // Disable all components except for an AEC and the residual echo detector. + AudioProcessing::Config config; + config.residual_echo_detector.enabled = true; + config.echo_canceller3.enabled = false; + config.high_pass_filter.enabled = false; + config.gain_controller2.enabled = false; + config.level_controller.enabled = false; + apm->ApplyConfig(config); + EXPECT_EQ(apm->gain_control()->Enable(false), 0); + EXPECT_EQ(apm->level_estimator()->Enable(false), 0); + EXPECT_EQ(apm->noise_suppression()->Enable(false), 0); + EXPECT_EQ(apm->voice_detection()->Enable(false), 0); + + if (use_AEC2) { + EXPECT_EQ(apm->echo_control_mobile()->Enable(false), 0); + EXPECT_EQ(apm->echo_cancellation()->enable_metrics(true), 0); + EXPECT_EQ(apm->echo_cancellation()->enable_delay_logging(true), 0); + EXPECT_EQ(apm->echo_cancellation()->Enable(true), 0); + } else { + EXPECT_EQ(apm->echo_cancellation()->Enable(false), 0); + EXPECT_EQ(apm->echo_control_mobile()->Enable(true), 0); + } + return apm; +} + +#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) || defined(WEBRTC_MAC) +#define MAYBE_ApmStatistics DISABLED_ApmStatistics +#else +#define MAYBE_ApmStatistics ApmStatistics +#endif + +TEST(MAYBE_ApmStatistics, AEC2EnabledTest) { + // Set up APM with AEC2 and process some audio. + std::unique_ptr apm = CreateApm(true); + ASSERT_TRUE(apm); + + // Set up an audioframe. + AudioFrame frame; + frame.num_channels_ = 1; + SetFrameSampleRate(&frame, AudioProcessing::NativeRate::kSampleRate48kHz); + + // Fill the audio frame with a sawtooth pattern. + int16_t* ptr = frame.mutable_data(); + for (size_t i = 0; i < frame.kMaxDataSizeSamples; i++) { + ptr[i] = 10000 * ((i % 3) - 1); + } + + // Do some processing. + for (int i = 0; i < 200; i++) { + EXPECT_EQ(apm->ProcessReverseStream(&frame), 0); + EXPECT_EQ(apm->set_stream_delay_ms(0), 0); + EXPECT_EQ(apm->ProcessStream(&frame), 0); + } + + // Test statistics interface. + AudioProcessing::AudioProcessingStats stats = apm->GetStatistics(true); + // We expect all statistics to be set and have a sensible value. + ASSERT_TRUE(stats.residual_echo_likelihood); + EXPECT_GE(*stats.residual_echo_likelihood, 0.0); + EXPECT_LE(*stats.residual_echo_likelihood, 1.0); + ASSERT_TRUE(stats.residual_echo_likelihood_recent_max); + EXPECT_GE(*stats.residual_echo_likelihood_recent_max, 0.0); + EXPECT_LE(*stats.residual_echo_likelihood_recent_max, 1.0); + ASSERT_TRUE(stats.echo_return_loss); + EXPECT_NE(*stats.echo_return_loss, -100.0); + ASSERT_TRUE(stats.echo_return_loss_enhancement); + EXPECT_NE(*stats.echo_return_loss_enhancement, -100.0); + ASSERT_TRUE(stats.divergent_filter_fraction); + EXPECT_NE(*stats.divergent_filter_fraction, -1.0); + ASSERT_TRUE(stats.delay_standard_deviation_ms); + EXPECT_GE(*stats.delay_standard_deviation_ms, 0); + // We don't check stats.delay_median_ms since it takes too long to settle to a + // value. At least 20 seconds of data need to be processed before it will get + // a value, which would make this test take too much time. + + // If there are no receive streams, we expect the stats not to be set. The + // 'false' argument signals to APM that no receive streams are currently + // active. In that situation the statistics would get stuck at their last + // calculated value (AEC and echo detection need at least one stream in each + // direction), so to avoid that, they should not be set by APM. + stats = apm->GetStatistics(false); + EXPECT_FALSE(stats.residual_echo_likelihood); + EXPECT_FALSE(stats.residual_echo_likelihood_recent_max); + EXPECT_FALSE(stats.echo_return_loss); + EXPECT_FALSE(stats.echo_return_loss_enhancement); + EXPECT_FALSE(stats.divergent_filter_fraction); + EXPECT_FALSE(stats.delay_median_ms); + EXPECT_FALSE(stats.delay_standard_deviation_ms); +} + +TEST(MAYBE_ApmStatistics, AECMEnabledTest) { + // Set up APM with AECM and process some audio. + std::unique_ptr apm = CreateApm(false); + ASSERT_TRUE(apm); + + // Set up an audioframe. + AudioFrame frame; + frame.num_channels_ = 1; + SetFrameSampleRate(&frame, AudioProcessing::NativeRate::kSampleRate48kHz); + + // Fill the audio frame with a sawtooth pattern. + int16_t* ptr = frame.mutable_data(); + for (size_t i = 0; i < frame.kMaxDataSizeSamples; i++) { + ptr[i] = 10000 * ((i % 3) - 1); + } + + // Do some processing. + for (int i = 0; i < 200; i++) { + EXPECT_EQ(apm->ProcessReverseStream(&frame), 0); + EXPECT_EQ(apm->set_stream_delay_ms(0), 0); + EXPECT_EQ(apm->ProcessStream(&frame), 0); + } + + // Test statistics interface. + AudioProcessing::AudioProcessingStats stats = apm->GetStatistics(true); + // We expect only the residual echo detector statistics to be set and have a + // sensible value. + EXPECT_TRUE(stats.residual_echo_likelihood); + if (stats.residual_echo_likelihood) { + EXPECT_GE(*stats.residual_echo_likelihood, 0.0); + EXPECT_LE(*stats.residual_echo_likelihood, 1.0); + } + EXPECT_TRUE(stats.residual_echo_likelihood_recent_max); + if (stats.residual_echo_likelihood_recent_max) { + EXPECT_GE(*stats.residual_echo_likelihood_recent_max, 0.0); + EXPECT_LE(*stats.residual_echo_likelihood_recent_max, 1.0); + } + EXPECT_FALSE(stats.echo_return_loss); + EXPECT_FALSE(stats.echo_return_loss_enhancement); + EXPECT_FALSE(stats.divergent_filter_fraction); + EXPECT_FALSE(stats.delay_median_ms); + EXPECT_FALSE(stats.delay_standard_deviation_ms); + + // If there are no receive streams, we expect the stats not to be set. + stats = apm->GetStatistics(false); + EXPECT_FALSE(stats.residual_echo_likelihood); + EXPECT_FALSE(stats.residual_echo_likelihood_recent_max); + EXPECT_FALSE(stats.echo_return_loss); + EXPECT_FALSE(stats.echo_return_loss_enhancement); + EXPECT_FALSE(stats.divergent_filter_fraction); + EXPECT_FALSE(stats.delay_median_ms); + EXPECT_FALSE(stats.delay_standard_deviation_ms); +} } // namespace webrtc diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h index cdc1cb3d95..09fecabd6c 100644 --- a/modules/audio_processing/include/audio_processing.h +++ b/modules/audio_processing/include/audio_processing.h @@ -20,6 +20,7 @@ #include #include +#include "api/optional.h" #include "modules/audio_processing/beamformer/array_util.h" #include "modules/audio_processing/include/config.h" #include "rtc_base/arraysize.h" @@ -563,9 +564,45 @@ class AudioProcessing : public rtc::RefCountInterface { float residual_echo_likelihood_recent_max = -1.0f; }; + // This version of the stats uses Optionals, it will replace the regular + // AudioProcessingStatistics struct. + struct AudioProcessingStats { + AudioProcessingStats(); + AudioProcessingStats(const AudioProcessingStats& other); + ~AudioProcessingStats(); + + // AEC Statistics. + // ERL = 10log_10(P_far / P_echo) + rtc::Optional echo_return_loss; + // ERLE = 10log_10(P_echo / P_out) + rtc::Optional echo_return_loss_enhancement; + // Fraction of time that the AEC linear filter is divergent, in a 1-second + // non-overlapped aggregation window. + rtc::Optional divergent_filter_fraction; + + // The delay metrics consists of the delay median and standard deviation. It + // also consists of the fraction of delay estimates that can make the echo + // cancellation perform poorly. The values are aggregated until the first + // call to |GetStatistics()| and afterwards aggregated and updated every + // second. Note that if there are several clients pulling metrics from + // |GetStatistics()| during a session the first call from any of them will + // change to one second aggregation window for all. + rtc::Optional delay_median_ms; + rtc::Optional delay_standard_deviation_ms; + + // Residual echo detector likelihood. + rtc::Optional residual_echo_likelihood; + // Maximum residual echo likelihood from the last time period. + rtc::Optional residual_echo_likelihood_recent_max; + }; + // TODO(ivoc): Make this pure virtual when all subclasses have been updated. virtual AudioProcessingStatistics GetStatistics() const; + // This returns the stats as optionals and it will replace the regular + // GetStatistics. + virtual AudioProcessingStats GetStatistics(bool has_remote_tracks) const; + // These provide access to the component interfaces and should never return // NULL. The pointers will be valid for the lifetime of the APM instance. // The memory for these objects is entirely managed internally. diff --git a/pc/statscollector.cc b/pc/statscollector.cc index 95201769a6..6b27dfd290 100644 --- a/pc/statscollector.cc +++ b/pc/statscollector.cc @@ -99,42 +99,50 @@ void ExtractCommonReceiveProperties(const cricket::MediaReceiverInfo& info, report->AddString(StatsReport::kStatsValueNameCodecName, info.codec_name); } -void SetAudioProcessingStats(StatsReport* report, - bool typing_noise_detected, - int echo_return_loss, - int echo_return_loss_enhancement, - int echo_delay_median_ms, - float aec_quality_min, - int echo_delay_std_ms, - float residual_echo_likelihood, - float residual_echo_likelihood_recent_max) { +void SetAudioProcessingStats( + StatsReport* report, + bool typing_noise_detected, + rtc::Optional echo_return_loss, + rtc::Optional echo_return_loss_enhancement, + rtc::Optional echo_delay_median_ms, + rtc::Optional aec_quality_min, + rtc::Optional echo_delay_std_ms, + rtc::Optional residual_echo_likelihood, + rtc::Optional residual_echo_likelihood_recent_max) { report->AddBoolean(StatsReport::kStatsValueNameTypingNoiseState, typing_noise_detected); - if (aec_quality_min >= 0.0f) { + // TODO(ivoc): Remove the checks for default values once the whole stat + // chain uses optionals. + if (aec_quality_min && *aec_quality_min >= 0.0) { report->AddFloat(StatsReport::kStatsValueNameEchoCancellationQualityMin, - aec_quality_min); + *aec_quality_min); } - const IntForAdd ints[] = { - { StatsReport::kStatsValueNameEchoDelayMedian, echo_delay_median_ms }, - { StatsReport::kStatsValueNameEchoDelayStdDev, echo_delay_std_ms }, - }; - for (const auto& i : ints) { - if (i.value >= 0) { - report->AddInt(i.name, i.value); - } + if (echo_delay_median_ms && *echo_delay_median_ms >= 0) { + report->AddInt(StatsReport::kStatsValueNameEchoDelayMedian, + *echo_delay_median_ms); + } + if (echo_delay_std_ms && *echo_delay_std_ms >= 0) { + report->AddInt(StatsReport::kStatsValueNameEchoDelayStdDev, + *echo_delay_std_ms); } // These can take on valid negative values. - report->AddInt(StatsReport::kStatsValueNameEchoReturnLoss, echo_return_loss); - report->AddInt(StatsReport::kStatsValueNameEchoReturnLossEnhancement, - echo_return_loss_enhancement); - if (residual_echo_likelihood >= 0.0f) { - report->AddFloat(StatsReport::kStatsValueNameResidualEchoLikelihood, - residual_echo_likelihood); + if (echo_return_loss) { + report->AddInt(StatsReport::kStatsValueNameEchoReturnLoss, + static_cast(*echo_return_loss)); } - if (residual_echo_likelihood_recent_max >= 0.0f) { + if (echo_return_loss_enhancement) { + report->AddInt(StatsReport::kStatsValueNameEchoReturnLossEnhancement, + static_cast(*echo_return_loss_enhancement)); + } + if (residual_echo_likelihood && *residual_echo_likelihood >= 0.0) { + report->AddFloat(StatsReport::kStatsValueNameResidualEchoLikelihood, + *residual_echo_likelihood); + } + if (residual_echo_likelihood_recent_max && + *residual_echo_likelihood_recent_max >= 0.0) { report->AddFloat( StatsReport::kStatsValueNameResidualEchoLikelihoodRecentMax, - residual_echo_likelihood_recent_max); + *residual_echo_likelihood_recent_max); } } @@ -196,11 +204,16 @@ void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) { void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) { ExtractCommonSendProperties(info, report); + // TODO(ivoc): Update VoiceSenderInfo to pass Optionals all the way from APM. SetAudioProcessingStats( - report, info.typing_noise_detected, info.echo_return_loss, - info.echo_return_loss_enhancement, info.echo_delay_median_ms, - info.aec_quality_min, info.echo_delay_std_ms, - info.residual_echo_likelihood, info.residual_echo_likelihood_recent_max); + report, info.typing_noise_detected, + rtc::Optional(info.echo_return_loss), + rtc::Optional(info.echo_return_loss_enhancement), + rtc::Optional(info.echo_delay_median_ms), + rtc::Optional(info.aec_quality_min), + rtc::Optional(info.echo_delay_std_ms), + rtc::Optional(info.residual_echo_likelihood), + rtc::Optional(info.residual_echo_likelihood_recent_max)); const FloatForAdd floats[] = { { StatsReport::kStatsValueNameTotalAudioEnergy, info.total_input_energy }, @@ -877,7 +890,7 @@ void StatsCollector::ExtractVoiceInfo() { ExtractStatsFromList(voice_info.senders, transport_id, this, StatsReport::kSend); - UpdateStatsFromExistingLocalAudioTracks(); + UpdateStatsFromExistingLocalAudioTracks(voice_info.receivers.size() > 0); } void StatsCollector::ExtractVideoInfo( @@ -973,7 +986,8 @@ StatsReport* StatsCollector::GetReport(const StatsReport::StatsType& type, return reports_.Find(StatsReport::NewIdWithDirection(type, id, direction)); } -void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() { +void StatsCollector::UpdateStatsFromExistingLocalAudioTracks( + bool has_remote_tracks) { RTC_DCHECK(pc_->signaling_thread()->IsCurrent()); // Loop through the existing local audio tracks. for (const auto& it : local_audio_tracks_) { @@ -996,12 +1010,13 @@ void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() { continue; report->set_timestamp(stats_gathering_started_); - UpdateReportFromAudioTrack(track, report); + UpdateReportFromAudioTrack(track, report, has_remote_tracks); } } void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track, - StatsReport* report) { + StatsReport* report, + bool has_remote_tracks) { RTC_DCHECK(pc_->signaling_thread()->IsCurrent()); RTC_DCHECK(track != NULL); @@ -1015,8 +1030,8 @@ void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track, auto audio_processor(track->GetAudioProcessor()); if (audio_processor.get()) { - AudioProcessorInterface::AudioProcessorStats stats; - audio_processor->GetStats(&stats); + AudioProcessorInterface::AudioProcessorStatistics stats = + audio_processor->GetStats(has_remote_tracks); SetAudioProcessingStats( report, stats.typing_noise_detected, stats.echo_return_loss, @@ -1025,8 +1040,10 @@ void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track, stats.residual_echo_likelihood, stats.residual_echo_likelihood_recent_max); - report->AddFloat(StatsReport::kStatsValueNameAecDivergentFilterFraction, - stats.aec_divergent_filter_fraction); + if (stats.aec_divergent_filter_fraction) { + report->AddFloat(StatsReport::kStatsValueNameAecDivergentFilterFraction, + *stats.aec_divergent_filter_fraction); + } } } diff --git a/pc/statscollector.h b/pc/statscollector.h index bdfe64b958..9f74d11d7f 100644 --- a/pc/statscollector.h +++ b/pc/statscollector.h @@ -120,9 +120,10 @@ class StatsCollector { StatsReport::Direction direction); // Helper method to get stats from the local audio tracks. - void UpdateStatsFromExistingLocalAudioTracks(); + void UpdateStatsFromExistingLocalAudioTracks(bool has_remote_tracks); void UpdateReportFromAudioTrack(AudioTrackInterface* track, - StatsReport* report); + StatsReport* report, + bool has_remote_tracks); // Helper method to get the id for the track identified by ssrc. // |direction| tells if the track is for sending or receiving. diff --git a/pc/statscollector_unittest.cc b/pc/statscollector_unittest.cc index 104ee77c1b..2211f3fb54 100644 --- a/pc/statscollector_unittest.cc +++ b/pc/statscollector_unittest.cc @@ -87,6 +87,18 @@ class FakeAudioProcessor : public webrtc::AudioProcessorInterface { stats->aec_quality_min = 5.1f; stats->echo_delay_std_ms = 6; } + + AudioProcessorInterface::AudioProcessorStatistics GetStats( + bool /*has_recv_streams*/) override { + AudioProcessorStatistics stats; + stats.typing_noise_detected = true; + stats.echo_return_loss = rtc::Optional(2.0); + stats.echo_return_loss_enhancement = rtc::Optional(3.0); + stats.echo_delay_median_ms = rtc::Optional(4); + stats.aec_quality_min = rtc::Optional(5.1); + stats.echo_delay_std_ms = rtc::Optional(6); + return stats; + } }; class FakeAudioTrack @@ -128,6 +140,13 @@ class FakeAudioProcessorWithInitValue : public webrtc::AudioProcessorInterface { stats->aec_quality_min = -1.0f; stats->echo_delay_std_ms = -1; } + + AudioProcessorInterface::AudioProcessorStatistics GetStats( + bool /*has_recv_streams*/) override { + AudioProcessorStatistics stats; + stats.typing_noise_detected = false; + return stats; + } }; class FakeAudioTrackWithInitValue