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