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/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