Piping audio interruption metrics to API layer

Bug: webrtc:10549
Change-Id: Ie6abe5819c5b73dc5f5f89bdc375bad77f44ce97
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/134303
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Reviewed-by: Ivo Creusen <ivoc@webrtc.org>
Commit-Queue: Henrik Lundin <henrik.lundin@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27788}
diff --git a/api/stats_types.cc b/api/stats_types.cc
index 4c69a82..cbc3b81 100644
--- a/api/stats_types.cc
+++ b/api/stats_types.cc
@@ -565,6 +565,8 @@
       return "googInitiator";
     case kStatsValueNameInterframeDelayMaxMs:
       return "googInterframeDelayMax";
+    case kStatsValueNameInterruptionCount:
+      return "googInterruptionCount";
     case kStatsValueNameIssuerId:
       return "googIssuerId";
     case kStatsValueNameJitterReceived:
@@ -647,6 +649,8 @@
       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 0e97eaf..0631339 100644
--- a/api/stats_types.h
+++ b/api/stats_types.h
@@ -192,6 +192,7 @@
     kStatsValueNameHugeFramesSent,
     kStatsValueNameInitiator,
     kStatsValueNameInterframeDelayMaxMs,  // Max over last 10 seconds.
+    kStatsValueNameInterruptionCount,
     kStatsValueNameIssuerId,
     kStatsValueNameJitterBufferMs,
     kStatsValueNameJitterReceived,
@@ -232,6 +233,7 @@
     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 b4948ee..677ee20 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
@@ -226,6 +226,8 @@
   stats.relative_packet_arrival_delay_seconds =
       static_cast<double>(ns.relativePacketArrivalDelayMs) /
       static_cast<double>(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 257042b..9091afd 100644
--- a/call/audio_receive_stream.h
+++ b/call/audio_receive_stream.h
@@ -79,6 +79,8 @@
     absl::optional<int64_t> 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 710fd1a..69570e7 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -514,6 +514,10 @@
   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 9110d55..3e4c6bd 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -2274,6 +2274,8 @@
     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 da7d621..c10a71c 100644
--- a/modules/audio_coding/acm2/acm_receiver.cc
+++ b/modules/audio_coding/acm2/acm_receiver.cc
@@ -259,6 +259,9 @@
       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 8063a29..621c478 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 @@
   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 c0c836f..d91850f 100644
--- a/modules/audio_coding/neteq/include/neteq.h
+++ b/modules/audio_coding/neteq/include/neteq.h
@@ -90,8 +90,8 @@
   // 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 025ed69..8269526 100644
--- a/modules/audio_coding/neteq/neteq_impl_unittest.cc
+++ b/modules/audio_coding/neteq/neteq_impl_unittest.cc
@@ -745,7 +745,7 @@
   }
 
   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 a851074..abfa3c5 100644
--- a/modules/audio_coding/neteq/statistics_calculator_unittest.cc
+++ b/modules/audio_coding/neteq/statistics_calculator_unittest.cc
@@ -135,31 +135,31 @@
   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 @@
   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 @@
   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 c7bec9c..7c3aed8 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 @@
   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 e1930a1..91afe26 100644
--- a/pc/stats_collector.cc
+++ b/pc/stats_collector.cc
@@ -165,6 +165,9 @@
       {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 3118ef9..9a514f0 100644
--- a/pc/stats_collector_unittest.cc
+++ b/pc/stats_collector_unittest.cc
@@ -383,6 +383,14 @@
   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,