Aggregate and log corruption score.

Bug: webrtc:358039777
Change-Id: I4dade8e6daecf41e5b1f156416935c320f513d0b
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/359160
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Emil Vardar (xWF) <vardar@google.com>
Cr-Commit-Position: refs/heads/main@{#42867}
diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h
index 59ffd39..4bdf81f 100644
--- a/call/video_receive_stream.h
+++ b/call/video_receive_stream.h
@@ -129,6 +129,19 @@
     int64_t first_frame_received_to_decoded_ms = -1;
     absl::optional<uint64_t> qp_sum;
 
+    // TODO(webrtc:357636606): Propagate this score upwards in the chain.
+    // Corruption score, indicating the probability of corruption. Its value is
+    // between 0 and 1, where 0 means no corruption and 1 means that the
+    // compressed frame is corrupted.
+    // However, note that the corruption score may not accurately reflect
+    // corruption. E.g. even if the corruption score is 0, the compressed frame
+    // may still be corrupted and vice versa.
+    absl::optional<double> corruption_score_sum;
+    absl::optional<double> corruption_score_squared_sum;
+    // Number of frames the `corruption_score` was calculated on. This is
+    // usually not the same as `frames_decoded`.
+    uint32_t corruption_score_count = 0;
+
     int current_payload_type = -1;
 
     int total_bitrate_bps = 0;
diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc
index 75512a2..b5c03bf 100644
--- a/video/receive_statistics_proxy.cc
+++ b/video/receive_statistics_proxy.cc
@@ -311,6 +311,17 @@
                  << " " << *height << '\n';
     }
 
+    absl::optional<double> corruption_score = stats.corruption_score.GetMean();
+    if (corruption_score) {
+      // Granularity level: 2e-3.
+      RTC_HISTOGRAM_COUNTS_SPARSE(uma_prefix + ".CorruptionLikelihoodPermille",
+                                  static_cast<int>(*corruption_score * 1000),
+                                  /*min=*/0, /*max=*/1000,
+                                  /*bucket_count=*/500);
+      log_stream << uma_prefix << ".CorruptionLikelihoodPermille" << " "
+                 << static_cast<int>(*corruption_score * 1000) << '\n';
+    }
+
     if (content_type != VideoContentType::UNSPECIFIED) {
       // Don't report these 3 metrics unsliced, as more precise variants
       // are reported separately in this method.
@@ -823,6 +834,25 @@
   avg_rtt_ms_ = avg_rtt_ms;
 }
 
+void ReceiveStatisticsProxy::OnCorruptionScore(double corruption_score,
+                                               VideoContentType content_type) {
+  RTC_DCHECK_RUN_ON(&main_thread_);
+
+  if (!stats_.corruption_score_sum.has_value()) {
+    RTC_DCHECK(!stats_.corruption_score_squared_sum.has_value());
+    RTC_DCHECK_EQ(stats_.corruption_score_count, 0);
+    stats_.corruption_score_sum = 0;
+    stats_.corruption_score_squared_sum = 0;
+  }
+  *stats_.corruption_score_sum += corruption_score;
+  *stats_.corruption_score_squared_sum += corruption_score * corruption_score;
+  ++stats_.corruption_score_count;
+
+  ContentSpecificStats* content_specific_stats =
+      &content_specific_stats_[content_type];
+  content_specific_stats->corruption_score.AddSample(corruption_score);
+}
+
 void ReceiveStatisticsProxy::DecoderThreadStarting() {
   RTC_DCHECK_RUN_ON(&main_thread_);
 }
@@ -849,6 +879,7 @@
   frame_counts.key_frames += other.frame_counts.key_frames;
   frame_counts.delta_frames += other.frame_counts.delta_frames;
   interframe_delay_percentiles.Add(other.interframe_delay_percentiles);
+  corruption_score.MergeStatistics(other.corruption_score);
 }
 
 }  // namespace internal
diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h
index 8e4941f..a678602 100644
--- a/video/receive_statistics_proxy.h
+++ b/video/receive_statistics_proxy.h
@@ -26,6 +26,7 @@
 #include "modules/include/module_common_types.h"
 #include "rtc_base/numerics/histogram_percentile_counter.h"
 #include "rtc_base/numerics/moving_max_counter.h"
+#include "rtc_base/numerics/running_statistics.h"
 #include "rtc_base/numerics/sample_counter.h"
 #include "rtc_base/rate_statistics.h"
 #include "rtc_base/rate_tracker.h"
@@ -105,6 +106,9 @@
   // Implements RtcpCnameCallback.
   void OnCname(uint32_t ssrc, absl::string_view cname) override;
 
+  void OnCorruptionScore(double corruption_score,
+                         VideoContentType content_type);
+
   // Implements RtcpPacketTypeCounterObserver.
   void RtcpPacketTypesCounterUpdated(
       uint32_t ssrc,
@@ -143,6 +147,7 @@
     rtc::SampleCounter qp_counter;
     FrameCounts frame_counts;
     rtc::HistogramPercentileCounter interframe_delay_percentiles;
+    webrtc_impl::RunningStatistics<double> corruption_score;
   };
 
   // Removes info about old frames and then updates the framerate.
diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc
index 276c113..4b5af07 100644
--- a/video/receive_statistics_proxy_unittest.cc
+++ b/video/receive_statistics_proxy_unittest.cc
@@ -26,6 +26,7 @@
 #include "api/video/video_rotation.h"
 #include "rtc_base/thread.h"
 #include "system_wrappers/include/metrics.h"
+#include "test/gmock.h"
 #include "test/gtest.h"
 #include "test/scoped_key_value_config.h"
 #include "test/time_controller/simulated_time_controller.h"
@@ -34,6 +35,8 @@
 namespace webrtc {
 namespace internal {
 namespace {
+using ::testing::DoubleEq;
+
 const TimeDelta kFreqOffsetProcessInterval = TimeDelta::Seconds(40);
 const uint32_t kRemoteSsrc = 456;
 const int kMinRequiredSamples = 200;
@@ -536,6 +539,30 @@
   }
 }
 
+TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsOnCorruptionScore) {
+  EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().corruption_score_sum);
+  EXPECT_EQ(absl::nullopt,
+            statistics_proxy_->GetStats().corruption_score_squared_sum);
+  EXPECT_EQ(0u, statistics_proxy_->GetStats().corruption_score_count);
+
+  const std::vector<double> corruption_scores = {0.5, 0.25, 0.80};
+  const double kExpectedCorruptionScoreSum = 0.5 + 0.25 + 0.80;
+  const double kExpectedCorruptionScoreSquaredSum =
+      0.5 * 0.5 + 0.25 * 0.25 + 0.80 * 0.80;
+  for (size_t i = 0; i < corruption_scores.size(); ++i) {
+    statistics_proxy_->OnCorruptionScore(
+        /*corruption_score=*/corruption_scores[i],
+        VideoContentType::UNSPECIFIED);
+  }
+
+  VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats();
+  EXPECT_THAT(kExpectedCorruptionScoreSum,
+              DoubleEq(*stats.corruption_score_sum));
+  EXPECT_THAT(kExpectedCorruptionScoreSquaredSum,
+              DoubleEq(*stats.corruption_score_squared_sum));
+  EXPECT_EQ(3u, stats.corruption_score_count);
+}
+
 TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsSsrc) {
   EXPECT_EQ(kRemoteSsrc, statistics_proxy_->GetStats().ssrc);
 }
@@ -1480,6 +1507,42 @@
   }
 }
 
+TEST_P(ReceiveStatisticsProxyTestWithContent, CorruptionScore) {
+  const std::vector<double> corruption_scores = {0.5, 0.25, 0.80};
+  const int kCorruptionLikelihoodPermille =
+      static_cast<int>((0.5 + 0.25 + 0.80) / 3 * 1000);
+  for (size_t i = 0; i < corruption_scores.size(); ++i) {
+    statistics_proxy_->OnCorruptionScore(
+        /*corruption_score=*/corruption_scores[i], content_type_);
+  }
+
+  FlushAndGetStats();
+  EXPECT_EQ(3u, statistics_proxy_->GetStats().corruption_score_count);
+
+  statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(),
+                                      nullptr);
+  if (videocontenttypehelpers::IsScreenshare(content_type_)) {
+    EXPECT_METRIC_EQ(
+        1, metrics::NumSamples(
+               "WebRTC.Video.Screenshare.CorruptionLikelihoodPermille"));
+    EXPECT_METRIC_EQ(
+        1, metrics::NumEvents(
+               "WebRTC.Video.Screenshare.CorruptionLikelihoodPermille",
+               kCorruptionLikelihoodPermille));
+    EXPECT_METRIC_EQ(
+        0, metrics::NumSamples("WebRTC.Video.CorruptionLikelihoodPermille"));
+  } else {
+    EXPECT_METRIC_EQ(
+        1, metrics::NumSamples("WebRTC.Video.CorruptionLikelihoodPermille"));
+    EXPECT_METRIC_EQ(
+        1, metrics::NumEvents("WebRTC.Video.CorruptionLikelihoodPermille",
+                              kCorruptionLikelihoodPermille));
+    EXPECT_METRIC_EQ(
+        0, metrics::NumSamples(
+               "WebRTC.Video.Screenshare.CorruptionLikelihoodPermille"));
+  }
+}
+
 TEST_P(ReceiveStatisticsProxyTestWithContent, FreezesAreReported) {
   const TimeDelta kInterFrameDelay = TimeDelta::Millis(33);
   const TimeDelta kFreezeDelay = TimeDelta::Millis(200);