Reland "Piping audio interruption metrics to API layer"

The metrics are now added as RTCNonStandardStatsMember objects in
RTCMediaStreamTrackStats. Unit tests are updated.

This is a reland of https://webrtc-review.googlesource.com/c/src/+/134303,
with fixes.

TBR=kwiberg@webrtc.org

Bug: webrtc:10549
Change-Id: I29dcc6fbfc69156715664e71acfa054c1b2d9038
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/134500
Commit-Queue: Henrik Lundin <henrik.lundin@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Ivo Creusen <ivoc@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27806}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 4ddfa93..96c7a03 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -321,6 +321,10 @@
   RTCNonStandardStatsMember<uint64_t> jitter_buffer_flushes;
   RTCNonStandardStatsMember<uint64_t> delayed_packet_outage_samples;
   RTCNonStandardStatsMember<double> relative_packet_arrival_delay;
+  // TODO(henrik.lundin): Add description of the interruption metrics at
+  // https://github.com/henbos/webrtc-provisional-stats/issues/17
+  RTCNonStandardStatsMember<uint32_t> interruption_count;
+  RTCNonStandardStatsMember<double> total_interruption_duration;
   // Non-standard video-only members.
   // https://henbos.github.io/webrtc-provisional-stats/#RTCVideoReceiverStats-dict*
   RTCNonStandardStatsMember<uint32_t> freeze_count;
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 56cef2c..3b85064 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -2264,6 +2264,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/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 611dfe3..8849d86 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -490,6 +490,13 @@
       voice_receiver_info.delayed_packet_outage_samples;
   audio_track_stats->relative_packet_arrival_delay =
       voice_receiver_info.relative_packet_arrival_delay_seconds;
+  audio_track_stats->interruption_count =
+      voice_receiver_info.interruption_count >= 0
+          ? voice_receiver_info.interruption_count
+          : 0;
+  audio_track_stats->total_interruption_duration =
+      static_cast<double>(voice_receiver_info.total_interruption_duration_ms) /
+      rtc::kNumMillisecsPerSec;
   return audio_track_stats;
 }
 
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index ba80732..0539379 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -1429,6 +1429,8 @@
   voice_receiver_info.jitter_buffer_flushes = 7;
   voice_receiver_info.delayed_packet_outage_samples = 15;
   voice_receiver_info.relative_packet_arrival_delay_seconds = 16;
+  voice_receiver_info.interruption_count = 7788;
+  voice_receiver_info.total_interruption_duration_ms = 778899;
 
   stats_->CreateMockRtpSendersReceiversAndChannels(
       {}, {std::make_pair(remote_audio_track.get(), voice_receiver_info)}, {},
@@ -1466,6 +1468,8 @@
   expected_remote_audio_track.jitter_buffer_flushes = 7;
   expected_remote_audio_track.delayed_packet_outage_samples = 15;
   expected_remote_audio_track.relative_packet_arrival_delay = 16;
+  expected_remote_audio_track.interruption_count = 7788;
+  expected_remote_audio_track.total_interruption_duration = 778.899;
   ASSERT_TRUE(report->Get(expected_remote_audio_track.id()));
   EXPECT_EQ(expected_remote_audio_track,
             report->Get(expected_remote_audio_track.id())
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index bb13c20..d576c60 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -654,6 +654,10 @@
           media_stream_track.delayed_packet_outage_samples);
       verifier.TestMemberIsNonNegative<double>(
           media_stream_track.relative_packet_arrival_delay);
+      verifier.TestMemberIsNonNegative<uint32_t>(
+          media_stream_track.interruption_count);
+      verifier.TestMemberIsNonNegative<double>(
+          media_stream_track.total_interruption_duration);
     } else {
       verifier.TestMemberIsUndefined(media_stream_track.jitter_buffer_delay);
       verifier.TestMemberIsUndefined(
@@ -666,6 +670,9 @@
           media_stream_track.delayed_packet_outage_samples);
       verifier.TestMemberIsUndefined(
           media_stream_track.relative_packet_arrival_delay);
+      verifier.TestMemberIsUndefined(media_stream_track.interruption_count);
+      verifier.TestMemberIsUndefined(
+          media_stream_track.total_interruption_duration);
     }
     return verifier.ExpectAllMembersSuccessfullyTested();
   }
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 89d5a85..6c914e7 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -389,6 +389,8 @@
                      &jitter_buffer_flushes,
                      &delayed_packet_outage_samples,
                      &relative_packet_arrival_delay,
+                     &interruption_count,
+                     &total_interruption_duration,
                      &freeze_count,
                      &pause_count,
                      &total_freezes_duration,
@@ -442,6 +444,8 @@
       relative_packet_arrival_delay(
           "relativePacketArrivalDelay",
           {NonStandardGroupId::kRtcStatsRelativePacketArrivalDelay}),
+      interruption_count("interruptionCount"),
+      total_interruption_duration("totalInterruptionDuration"),
       freeze_count("freezeCount"),
       pause_count("pauseCount"),
       total_freezes_duration("totalFreezesDuration"),
@@ -484,6 +488,8 @@
       jitter_buffer_flushes(other.jitter_buffer_flushes),
       delayed_packet_outage_samples(other.delayed_packet_outage_samples),
       relative_packet_arrival_delay(other.relative_packet_arrival_delay),
+      interruption_count(other.interruption_count),
+      total_interruption_duration(other.total_interruption_duration),
       freeze_count(other.freeze_count),
       pause_count(other.pause_count),
       total_freezes_duration(other.total_freezes_duration),