Harmonic frame rate metric.

This adds calculation and reporting of harmonic frame rate (HFR) metric.
HFR is calculated as call_duration_secs / sum(frame_duration_secs ^ 2).
It penalizes long freezes and could better represent user experience
related to smoothness of playback.

Bug: none
Change-Id: I4d2d46deaa44bb4221b53969a1c0a334e0c1bde9
Reviewed-on: https://webrtc-review.googlesource.com/c/117661
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26279}
diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc
index 176c89c..29e4483 100644
--- a/video/receive_statistics_proxy_unittest.cc
+++ b/video/receive_statistics_proxy_unittest.cc
@@ -1104,6 +1104,41 @@
   }
 }
 
+TEST_P(ReceiveStatisticsProxyTest, HarmonicFrameRateIsReported) {
+  const VideoContentType content_type = GetParam();
+  const int kInterFrameDelayMs = 33;
+  const int kFreezeDelayMs = 200;
+  const int kCallDurationMs =
+      kMinRequiredSamples * kInterFrameDelayMs + kFreezeDelayMs;
+  webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight);
+
+  for (int i = 0; i < kMinRequiredSamples; ++i) {
+    fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs);
+    statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, content_type);
+    statistics_proxy_->OnRenderedFrame(frame);
+  }
+  // Add extra freeze.
+  fake_clock_.AdvanceTimeMilliseconds(kFreezeDelayMs);
+  statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, content_type);
+  statistics_proxy_->OnRenderedFrame(frame);
+
+  statistics_proxy_.reset();
+  double kSumSquaredInterframeDelaysSecs =
+      (kMinRequiredSamples - 1) *
+      (kInterFrameDelayMs / 1000.0 * kInterFrameDelayMs / 1000.0);
+  kSumSquaredInterframeDelaysSecs +=
+      kFreezeDelayMs / 1000.0 * kFreezeDelayMs / 1000.0;
+  const int kExpectedHarmonicFrameRateFps =
+      std::round(kCallDurationMs / (1000 * kSumSquaredInterframeDelaysSecs));
+  if (videocontenttypehelpers::IsScreenshare(content_type)) {
+    EXPECT_EQ(kExpectedHarmonicFrameRateFps,
+              metrics::MinSample("WebRTC.Video.Screenshare.HarmonicFrameRate"));
+  } else {
+    EXPECT_EQ(kExpectedHarmonicFrameRateFps,
+              metrics::MinSample("WebRTC.Video.HarmonicFrameRate"));
+  }
+}
+
 TEST_P(ReceiveStatisticsProxyTest, PausesAreIgnored) {
   const VideoContentType content_type = GetParam();
   const int kInterFrameDelayMs = 33;
diff --git a/video/video_quality_observer.cc b/video/video_quality_observer.cc
index 5092cb8..ebe5bb1 100644
--- a/video/video_quality_observer.cc
+++ b/video/video_quality_observer.cc
@@ -11,6 +11,7 @@
 #include "video/video_quality_observer.h"
 
 #include <algorithm>
+#include <cmath>
 #include <cstdint>
 #include <string>
 
@@ -41,6 +42,7 @@
       last_frame_pixels_(0),
       is_last_frame_blocky_(false),
       last_unfreeze_time_(0),
+      sum_squared_interframe_delays_secs_(0.0),
       time_in_resolution_ms_(3, 0),
       current_resolution_(Resolution::Low),
       num_resolution_downgrades_(0),
@@ -118,6 +120,15 @@
                                     num_freezes_per_minute);
     log_stream << uma_prefix << ".NumberFreezesPerMinute "
                << num_freezes_per_minute << "\n";
+
+    if (sum_squared_interframe_delays_secs_ > 0.0) {
+      int harmonic_framerate_fps = std::round(
+          video_duration_ms / (1000 * sum_squared_interframe_delays_secs_));
+      RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".HarmonicFrameRate",
+                                      harmonic_framerate_fps);
+      log_stream << uma_prefix << ".HarmonicFrameRate "
+                 << harmonic_framerate_fps << "\n";
+    }
   }
   RTC_LOG(LS_INFO) << log_stream.str();
 }
@@ -134,7 +145,10 @@
 
   if (!is_paused_ && num_frames_rendered_ > 1) {
     // Process inter-frame delay.
-    int64_t interframe_delay_ms = now_ms - last_frame_rendered_ms_;
+    const int64_t interframe_delay_ms = now_ms - last_frame_rendered_ms_;
+    const float interframe_delays_secs = interframe_delay_ms / 1000.0;
+    sum_squared_interframe_delays_secs_ +=
+        interframe_delays_secs * interframe_delays_secs;
     render_interframe_delays_.Add(interframe_delay_ms);
     absl::optional<int> avg_interframe_delay =
         render_interframe_delays_.Avg(kMinFrameSamplesToDetectFreeze);
diff --git a/video/video_quality_observer.h b/video/video_quality_observer.h
index af69dd4..0c60d9a 100644
--- a/video/video_quality_observer.h
+++ b/video/video_quality_observer.h
@@ -57,7 +57,7 @@
   // Decoded timestamp of the last delayed frame.
   int64_t last_unfreeze_time_;
   rtc::SampleCounter render_interframe_delays_;
-  rtc::SampleCounter decode_interframe_delays_;
+  double sum_squared_interframe_delays_secs_;
   // An inter-frame delay is counted as a freeze if it's significantly longer
   // than average inter-frame delay.
   rtc::SampleCounter freezes_durations_;