Add new FrameRateEstimator utility class for more precis FPS estimation.

This class looks first and foremost on frame intervals instead of
number of frames withing the averaging window. This leads to higher
prevision values than the bucketized methods of RateTracker and
RateStatistics.

It is also design to return floating point values, for cases where we
are running at low fps - such as a somewhat common 30/4 = 7.5fps.

Bug: webrtc:10481
Change-Id: I41c36caaf2b7b46edf7927c8dd08e6cde3380884
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/148593
Reviewed-by: Magnus Flodman <mflodman@webrtc.org>
Commit-Queue: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28857}
diff --git a/common_video/BUILD.gn b/common_video/BUILD.gn
index ccb93ee..c0cd959 100644
--- a/common_video/BUILD.gn
+++ b/common_video/BUILD.gn
@@ -13,6 +13,8 @@
 
   sources = [
     "bitrate_adjuster.cc",
+    "frame_rate_estimator.cc",
+    "frame_rate_estimator.h",
     "h264/h264_bitstream_parser.cc",
     "h264/h264_bitstream_parser.h",
     "h264/h264_common.cc",
@@ -42,6 +44,8 @@
   deps = [
     "../api:scoped_refptr",
     "../api/task_queue",
+    "../api/units:time_delta",
+    "../api/units:timestamp",
     "../api/video:encoded_image",
     "../api/video:video_bitrate_allocation",
     "../api/video:video_bitrate_allocator",
@@ -78,6 +82,7 @@
 
     sources = [
       "bitrate_adjuster_unittest.cc",
+      "frame_rate_estimator_unittest.cc",
       "h264/h264_bitstream_parser_unittest.cc",
       "h264/pps_parser_unittest.cc",
       "h264/profile_level_id_unittest.cc",
@@ -102,6 +107,7 @@
       "../rtc_base:checks",
       "../rtc_base:rtc_base_approved",
       "../rtc_base:rtc_base_tests_utils",
+      "../system_wrappers:system_wrappers",
       "../test:fileutils",
       "../test:test_main",
       "../test:test_support",
diff --git a/common_video/frame_rate_estimator.cc b/common_video/frame_rate_estimator.cc
new file mode 100644
index 0000000..86f0722
--- /dev/null
+++ b/common_video/frame_rate_estimator.cc
@@ -0,0 +1,55 @@
+/*
+ *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "common_video/frame_rate_estimator.h"
+
+#include "rtc_base/time_utils.h"
+
+namespace webrtc {
+
+FrameRateEstimator::FrameRateEstimator(TimeDelta averaging_window)
+    : averaging_window_(averaging_window) {}
+
+void FrameRateEstimator::OnFrame(Timestamp time) {
+  CullOld(time);
+  frame_times_.push_back(time);
+}
+
+absl::optional<double> FrameRateEstimator::GetAverageFps() const {
+  if (frame_times_.size() < 2) {
+    return absl::nullopt;
+  }
+  TimeDelta time_span = frame_times_.back() - frame_times_.front();
+  if (time_span < TimeDelta::us(1)) {
+    return absl::nullopt;
+  }
+  TimeDelta avg_frame_interval = time_span / (frame_times_.size() - 1);
+
+  return static_cast<double>(rtc::kNumMicrosecsPerSec) /
+         avg_frame_interval.us();
+}
+
+absl::optional<double> FrameRateEstimator::GetAverageFps(Timestamp now) {
+  CullOld(now);
+  return GetAverageFps();
+}
+
+void FrameRateEstimator::Reset() {
+  frame_times_.clear();
+}
+
+void FrameRateEstimator::CullOld(Timestamp now) {
+  while (!frame_times_.empty() &&
+         frame_times_.front() + averaging_window_ < now) {
+    frame_times_.pop_front();
+  }
+}
+
+}  // namespace webrtc
diff --git a/common_video/frame_rate_estimator.h b/common_video/frame_rate_estimator.h
new file mode 100644
index 0000000..4cdd284
--- /dev/null
+++ b/common_video/frame_rate_estimator.h
@@ -0,0 +1,60 @@
+/*
+ *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef COMMON_VIDEO_FRAME_RATE_ESTIMATOR_H_
+#define COMMON_VIDEO_FRAME_RATE_ESTIMATOR_H_
+
+#include <deque>
+
+#include "absl/types/optional.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+
+namespace webrtc {
+
+// Class used to estimate a frame-rate using inter-frame intervals.
+// Some notes on usage:
+// This class is intended to accurately estimate the frame rate during a
+// continuous stream. Unlike a traditional rate estimator that looks at number
+// of data points within a time window, if the input stops this implementation
+// will not smoothly fall down towards 0. This is done so that the estimated
+// fps is not affected by edge conditions like if we sample just before or just
+// after the next frame.
+// To avoid problems if a stream is stopped and restarted (where estimated fps
+// could look too low), users of this class should explicitly call Reset() on
+// restart.
+// Also note that this class is not thread safe, it's up to the user to guard
+// against concurrent access.
+class FrameRateEstimator {
+ public:
+  explicit FrameRateEstimator(TimeDelta averaging_window);
+
+  // Insert a frame, potentially culling old frames that falls outside the
+  // averaging window.
+  void OnFrame(Timestamp time);
+
+  // Get the current average FPS, based on the frames currently in the window.
+  absl::optional<double> GetAverageFps() const;
+
+  // Move the window so it ends at |now|, and return the new fps estimate.
+  absl::optional<double> GetAverageFps(Timestamp now);
+
+  // Completely clear the averaging window.
+  void Reset();
+
+ private:
+  void CullOld(Timestamp now);
+  const TimeDelta averaging_window_;
+  std::deque<Timestamp> frame_times_;
+};
+
+}  // namespace webrtc
+
+#endif  // COMMON_VIDEO_FRAME_RATE_ESTIMATOR_H_
diff --git a/common_video/frame_rate_estimator_unittest.cc b/common_video/frame_rate_estimator_unittest.cc
new file mode 100644
index 0000000..9058bac
--- /dev/null
+++ b/common_video/frame_rate_estimator_unittest.cc
@@ -0,0 +1,103 @@
+/*
+ *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "common_video/frame_rate_estimator.h"
+
+#include "system_wrappers/include/clock.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+constexpr TimeDelta kDefaultWindow = TimeDelta::Millis<1000>();
+}
+
+class FrameRateEstimatorTest : public ::testing::Test {
+ public:
+  FrameRateEstimatorTest() : clock_(123), estimator_(kDefaultWindow) {}
+
+ protected:
+  SimulatedClock clock_;
+  FrameRateEstimator estimator_;
+};
+
+TEST_F(FrameRateEstimatorTest, NoEstimateWithLessThanTwoFrames) {
+  EXPECT_FALSE(estimator_.GetAverageFps());
+  estimator_.OnFrame(clock_.CurrentTime());
+  EXPECT_FALSE(estimator_.GetAverageFps());
+  clock_.AdvanceTime(TimeDelta::ms(33));
+  EXPECT_FALSE(estimator_.GetAverageFps());
+}
+
+TEST_F(FrameRateEstimatorTest, NoEstimateWithZeroSpan) {
+  // Two frames, but they are spanning 0ms so can't estimate frame rate.
+  estimator_.OnFrame(clock_.CurrentTime());
+  estimator_.OnFrame(clock_.CurrentTime());
+  EXPECT_FALSE(estimator_.GetAverageFps());
+}
+
+TEST_F(FrameRateEstimatorTest, SingleSpanFps) {
+  const double kExpectedFps = 30.0;
+  estimator_.OnFrame(clock_.CurrentTime());
+  clock_.AdvanceTime(TimeDelta::seconds(1) / kExpectedFps);
+  estimator_.OnFrame(clock_.CurrentTime());
+  EXPECT_NEAR(*estimator_.GetAverageFps(), kExpectedFps, 0.001);
+}
+
+TEST_F(FrameRateEstimatorTest, AverageFps) {
+  // Insert frames a intervals corresponding to 10fps for half the window, then
+  // 40fps half the window. The average should be 20fps.
+  const double kLowFps = 10.0;
+  const double kHighFps = 30.0;
+  const double kExpectedFps = 20.0;
+
+  const Timestamp start_time = clock_.CurrentTime();
+  while (clock_.CurrentTime() - start_time < kDefaultWindow / 2) {
+    estimator_.OnFrame(clock_.CurrentTime());
+    clock_.AdvanceTime(TimeDelta::seconds(1) / kLowFps);
+  }
+  while (clock_.CurrentTime() - start_time < kDefaultWindow) {
+    estimator_.OnFrame(clock_.CurrentTime());
+    clock_.AdvanceTime(TimeDelta::seconds(1) / kHighFps);
+  }
+
+  EXPECT_NEAR(*estimator_.GetAverageFps(), kExpectedFps, 0.001);
+}
+
+TEST_F(FrameRateEstimatorTest, CullsOldFramesFromAveragingWindow) {
+  // Two frames, just on the border of the 1s window => 1 fps.
+  estimator_.OnFrame(clock_.CurrentTime());
+  clock_.AdvanceTime(kDefaultWindow);
+  estimator_.OnFrame(clock_.CurrentTime());
+  EXPECT_TRUE(estimator_.GetAverageFps());
+  EXPECT_NEAR(*estimator_.GetAverageFps(), 1.0, 0.001);
+
+  // Oldest frame should just be pushed out the window, leaving a single frame
+  // => no estimate possible.
+  clock_.AdvanceTime(TimeDelta::us(1));
+  EXPECT_FALSE(estimator_.GetAverageFps(clock_.CurrentTime()));
+}
+
+TEST_F(FrameRateEstimatorTest, Reset) {
+  estimator_.OnFrame(clock_.CurrentTime());
+  clock_.AdvanceTime(TimeDelta::seconds(1) / 30);
+  estimator_.OnFrame(clock_.CurrentTime());
+  EXPECT_TRUE(estimator_.GetAverageFps());
+
+  // Clear estimator, no estimate should be possible even after inserting one
+  // new frame.
+  estimator_.Reset();
+  EXPECT_FALSE(estimator_.GetAverageFps());
+  clock_.AdvanceTime(TimeDelta::seconds(1) / 30);
+  estimator_.OnFrame(clock_.CurrentTime());
+  EXPECT_FALSE(estimator_.GetAverageFps());
+}
+
+}  // namespace webrtc