diff --git a/api/stats_types.cc b/api/stats_types.cc index 4c69a829ba..cbc3b81041 100644 --- a/api/stats_types.cc +++ b/api/stats_types.cc @@ -565,6 +565,8 @@ const char* StatsReport::Value::display_name() const { return "googInitiator"; case kStatsValueNameInterframeDelayMaxMs: return "googInterframeDelayMax"; + case kStatsValueNameInterruptionCount: + return "googInterruptionCount"; case kStatsValueNameIssuerId: return "googIssuerId"; case kStatsValueNameJitterReceived: @@ -647,6 +649,8 @@ const char* StatsReport::Value::display_name() const { return "googTrackId"; case kStatsValueNameTimingFrameInfo: return "googTimingFrameInfo"; + case kStatsValueNameTotalInterruptionDurationMs: + return "googTotalInterruptionDurationMs"; case kStatsValueNameTypingNoiseState: return "googTypingNoiseState"; case kStatsValueNameWritable: diff --git a/api/stats_types.h b/api/stats_types.h index 0e97eaf596..0631339bf5 100644 --- a/api/stats_types.h +++ b/api/stats_types.h @@ -192,6 +192,7 @@ class StatsReport { kStatsValueNameHugeFramesSent, kStatsValueNameInitiator, kStatsValueNameInterframeDelayMaxMs, // Max over last 10 seconds. + kStatsValueNameInterruptionCount, kStatsValueNameIssuerId, kStatsValueNameJitterBufferMs, kStatsValueNameJitterReceived, @@ -232,6 +233,7 @@ class StatsReport { kStatsValueNameTargetDelayMs, kStatsValueNameTargetEncBitrate, kStatsValueNameTimingFrameInfo, // Result of |TimingFrameInfo::ToString| + kStatsValueNameTotalInterruptionDurationMs, kStatsValueNameTrackId, kStatsValueNameTransmitBitrate, kStatsValueNameTransportType, diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc index b4948eed40..677ee207ee 100644 --- a/audio/audio_receive_stream.cc +++ b/audio/audio_receive_stream.cc @@ -226,6 +226,8 @@ webrtc::AudioReceiveStream::Stats AudioReceiveStream::GetStats() const { stats.relative_packet_arrival_delay_seconds = static_cast(ns.relativePacketArrivalDelayMs) / static_cast(rtc::kNumMillisecsPerSec); + stats.interruption_count = ns.interruptionCount; + stats.total_interruption_duration_ms = ns.totalInterruptionDurationMs; auto ds = channel_receive_->GetDecodingCallStatistics(); stats.decoding_calls_to_silence_generator = ds.calls_to_silence_generator; diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h index 257042b56d..9091afd5ce 100644 --- a/call/audio_receive_stream.h +++ b/call/audio_receive_stream.h @@ -79,6 +79,8 @@ class AudioReceiveStream { absl::optional last_packet_received_timestamp_ms; uint64_t jitter_buffer_flushes = 0; double relative_packet_arrival_delay_seconds = 0.0; + int32_t interruption_count = 0; + int32_t total_interruption_duration_ms = 0; }; struct Config { diff --git a/media/base/media_channel.h b/media/base/media_channel.h index 710fd1a713..69570e7e65 100644 --- a/media/base/media_channel.h +++ b/media/base/media_channel.h @@ -514,6 +514,10 @@ struct VoiceReceiverInfo : public MediaReceiverInfo { uint64_t delayed_packet_outage_samples = 0; // Arrival delay of received audio packets. double relative_packet_arrival_delay_seconds = 0.0; + // Count and total duration of audio interruptions (loss-concealement periods + // longer than 150 ms). + int32_t interruption_count = 0; + int32_t total_interruption_duration_ms = 0; }; struct VideoSenderInfo : public MediaSenderInfo { diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc index 9110d551e6..3e4c6bda60 100644 --- a/media/engine/webrtc_voice_engine.cc +++ b/media/engine/webrtc_voice_engine.cc @@ -2274,6 +2274,8 @@ bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info) { rinfo.jitter_buffer_flushes = stats.jitter_buffer_flushes; rinfo.relative_packet_arrival_delay_seconds = stats.relative_packet_arrival_delay_seconds; + rinfo.interruption_count = stats.interruption_count; + rinfo.total_interruption_duration_ms = stats.total_interruption_duration_ms; info->receivers.push_back(rinfo); } diff --git a/modules/audio_coding/acm2/acm_receiver.cc b/modules/audio_coding/acm2/acm_receiver.cc index da7d62101b..c10a71ca6b 100644 --- a/modules/audio_coding/acm2/acm_receiver.cc +++ b/modules/audio_coding/acm2/acm_receiver.cc @@ -259,6 +259,9 @@ void AcmReceiver::GetNetworkStatistics(NetworkStatistics* acm_stat) { neteq_lifetime_stat.delayed_packet_outage_samples; acm_stat->relativePacketArrivalDelayMs = neteq_lifetime_stat.relative_packet_arrival_delay_ms; + acm_stat->interruptionCount = neteq_lifetime_stat.interruption_count; + acm_stat->totalInterruptionDurationMs = + neteq_lifetime_stat.total_interruption_duration_ms; NetEqOperationsAndState neteq_operations_and_state = neteq_->GetOperationsAndState(); diff --git a/modules/audio_coding/include/audio_coding_module_typedefs.h b/modules/audio_coding/include/audio_coding_module_typedefs.h index 8063a29620..621c478dc1 100644 --- a/modules/audio_coding/include/audio_coding_module_typedefs.h +++ b/modules/audio_coding/include/audio_coding_module_typedefs.h @@ -130,6 +130,10 @@ struct NetworkStatistics { uint64_t delayedPacketOutageSamples; // arrival delay of incoming packets uint64_t relativePacketArrivalDelayMs; + // number of audio interruptions + int32_t interruptionCount; + // total duration of audio interruptions + int32_t totalInterruptionDurationMs; }; } // namespace webrtc diff --git a/modules/audio_coding/neteq/include/neteq.h b/modules/audio_coding/neteq/include/neteq.h index c0c836f6fb..d91850fd77 100644 --- a/modules/audio_coding/neteq/include/neteq.h +++ b/modules/audio_coding/neteq/include/neteq.h @@ -90,8 +90,8 @@ struct NetEqLifetimeStatistics { // An interruption is a loss-concealment event lasting at least 150 ms. The // two stats below count the number os such events and the total duration of // these events. - uint64_t interruption_count = 0; - uint64_t total_interruption_duration_ms = 0; + int32_t interruption_count = 0; + int32_t total_interruption_duration_ms = 0; }; // Metrics that describe the operations performed in NetEq, and the internal diff --git a/modules/audio_coding/neteq/neteq_impl_unittest.cc b/modules/audio_coding/neteq/neteq_impl_unittest.cc index 025ed693d9..826952635f 100644 --- a/modules/audio_coding/neteq/neteq_impl_unittest.cc +++ b/modules/audio_coding/neteq/neteq_impl_unittest.cc @@ -745,7 +745,7 @@ TEST_F(NetEqImplTest, NoAudioInterruptionLoggedBeforeFirstDecode) { } auto lifetime_stats = neteq_->GetLifetimeStatistics(); - EXPECT_EQ(0u, lifetime_stats.interruption_count); + EXPECT_EQ(0, lifetime_stats.interruption_count); } // This test verifies that NetEq can handle comfort noise and enters/quits codec diff --git a/modules/audio_coding/neteq/statistics_calculator_unittest.cc b/modules/audio_coding/neteq/statistics_calculator_unittest.cc index a851074274..abfa3c5e1c 100644 --- a/modules/audio_coding/neteq/statistics_calculator_unittest.cc +++ b/modules/audio_coding/neteq/statistics_calculator_unittest.cc @@ -135,31 +135,31 @@ TEST(StatisticsCalculator, InterruptionCounter) { stats.DecodedOutputPlayed(); stats.EndExpandEvent(fs_hz); auto lts = stats.GetLifetimeStatistics(); - EXPECT_EQ(0u, lts.interruption_count); - EXPECT_EQ(0u, lts.total_interruption_duration_ms); + EXPECT_EQ(0, lts.interruption_count); + EXPECT_EQ(0, lts.total_interruption_duration_ms); // Add an event that is shorter than 150 ms. Should not be logged. stats.ExpandedVoiceSamples(10 * fs_khz, false); // 10 ms. stats.ExpandedNoiseSamples(139 * fs_khz, false); // 139 ms. stats.EndExpandEvent(fs_hz); lts = stats.GetLifetimeStatistics(); - EXPECT_EQ(0u, lts.interruption_count); + EXPECT_EQ(0, lts.interruption_count); // Add an event that is longer than 150 ms. Should be logged. stats.ExpandedVoiceSamples(140 * fs_khz, false); // 140 ms. stats.ExpandedNoiseSamples(11 * fs_khz, false); // 11 ms. stats.EndExpandEvent(fs_hz); lts = stats.GetLifetimeStatistics(); - EXPECT_EQ(1u, lts.interruption_count); - EXPECT_EQ(151u, lts.total_interruption_duration_ms); + EXPECT_EQ(1, lts.interruption_count); + EXPECT_EQ(151, lts.total_interruption_duration_ms); // Add one more long event. stats.ExpandedVoiceSamples(100 * fs_khz, false); // 100 ms. stats.ExpandedNoiseSamples(5000 * fs_khz, false); // 5000 ms. stats.EndExpandEvent(fs_hz); lts = stats.GetLifetimeStatistics(); - EXPECT_EQ(2u, lts.interruption_count); - EXPECT_EQ(5100u + 151u, lts.total_interruption_duration_ms); + EXPECT_EQ(2, lts.interruption_count); + EXPECT_EQ(5100 + 151, lts.total_interruption_duration_ms); } TEST(StatisticsCalculator, InterruptionCounterDoNotLogBeforeDecoding) { @@ -172,7 +172,7 @@ TEST(StatisticsCalculator, InterruptionCounterDoNotLogBeforeDecoding) { stats.ExpandedVoiceSamples(151 * fs_khz, false); // 151 ms. stats.EndExpandEvent(fs_hz); auto lts = stats.GetLifetimeStatistics(); - EXPECT_EQ(0u, lts.interruption_count); + EXPECT_EQ(0, lts.interruption_count); // Call DecodedOutputPlayed(). Logging should happen after this. stats.DecodedOutputPlayed(); @@ -181,7 +181,7 @@ TEST(StatisticsCalculator, InterruptionCounterDoNotLogBeforeDecoding) { stats.ExpandedVoiceSamples(151 * fs_khz, false); // 151 ms. stats.EndExpandEvent(fs_hz); lts = stats.GetLifetimeStatistics(); - EXPECT_EQ(1u, lts.interruption_count); + EXPECT_EQ(1, lts.interruption_count); } } // namespace webrtc diff --git a/modules/audio_coding/neteq/tools/neteq_stats_plotter.cc b/modules/audio_coding/neteq/tools/neteq_stats_plotter.cc index c7bec9c660..7c3aed8312 100644 --- a/modules/audio_coding/neteq/tools/neteq_stats_plotter.cc +++ b/modules/audio_coding/neteq/tools/neteq_stats_plotter.cc @@ -79,9 +79,8 @@ void NetEqStatsPlotter::SimulationEnded(int64_t simulation_time_ms) { const auto lifetime_stats_vector = stats_getter_->lifetime_stats(); if (!lifetime_stats_vector->empty()) { auto lifetime_stats = lifetime_stats_vector->back().second; - printf(" num_interruptions: %" PRId64 "\n", - lifetime_stats.interruption_count); - printf(" sum_interruption_length_ms: %" PRId64 " ms\n", + printf(" num_interruptions: %d\n", lifetime_stats.interruption_count); + printf(" sum_interruption_length_ms: %d ms\n", lifetime_stats.total_interruption_duration_ms); printf(" interruption ratio: %f%%\n", 100.0 * lifetime_stats.total_interruption_duration_ms / diff --git a/pc/stats_collector.cc b/pc/stats_collector.cc index e1930a1fc3..91afe26e5e 100644 --- a/pc/stats_collector.cc +++ b/pc/stats_collector.cc @@ -165,6 +165,9 @@ void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) { {StatsReport::kStatsValueNamePacketsReceived, info.packets_rcvd}, {StatsReport::kStatsValueNamePreferredJitterBufferMs, info.jitter_buffer_preferred_ms}, + {StatsReport::kStatsValueNameInterruptionCount, info.interruption_count}, + {StatsReport::kStatsValueNameTotalInterruptionDurationMs, + info.total_interruption_duration_ms}, }; for (const auto& f : floats) diff --git a/pc/stats_collector_unittest.cc b/pc/stats_collector_unittest.cc index 3118ef9cb5..9a514f0623 100644 --- a/pc/stats_collector_unittest.cc +++ b/pc/stats_collector_unittest.cc @@ -383,6 +383,14 @@ void VerifyVoiceReceiverInfoReport(const StatsReport* report, EXPECT_EQ(rtc::ToString(info.decoding_muted_output), value_in_report); EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameCodecName, &value_in_report)); + EXPECT_TRUE(GetValue(report, StatsReport::kStatsValueNameInterruptionCount, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.interruption_count), value_in_report); + EXPECT_TRUE(GetValue(report, + StatsReport::kStatsValueNameTotalInterruptionDurationMs, + &value_in_report)); + EXPECT_EQ(rtc::ToString(info.total_interruption_duration_ms), + value_in_report); } void VerifyVoiceSenderInfoReport(const StatsReport* report,