|  | /* | 
|  | *  Copyright (c) 2023 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 "rtc_base/frequency_tracker.h" | 
|  |  | 
|  | #include <cstdlib> | 
|  | #include <limits> | 
|  | #include <optional> | 
|  |  | 
|  | #include "api/units/frequency.h" | 
|  | #include "api/units/time_delta.h" | 
|  | #include "api/units/timestamp.h" | 
|  | #include "test/gmock.h" | 
|  | #include "test/gtest.h" | 
|  |  | 
|  | namespace webrtc { | 
|  | namespace { | 
|  |  | 
|  | using ::testing::AllOf; | 
|  | using ::testing::Gt; | 
|  | using ::testing::Lt; | 
|  |  | 
|  | constexpr TimeDelta kWindow = TimeDelta::Millis(500); | 
|  | constexpr TimeDelta kEpsilon = TimeDelta::Millis(1); | 
|  |  | 
|  | TEST(FrequencyTrackerTest, ReturnsNulloptInitially) { | 
|  | Timestamp now = Timestamp::Seconds(12'345); | 
|  | FrequencyTracker stats(kWindow); | 
|  |  | 
|  | EXPECT_EQ(stats.Rate(now), std::nullopt); | 
|  | } | 
|  |  | 
|  | TEST(FrequencyTrackerTest, ReturnsNulloptAfterSingleDataPoint) { | 
|  | Timestamp now = Timestamp::Seconds(12'345); | 
|  | FrequencyTracker stats(kWindow); | 
|  |  | 
|  | stats.Update(now); | 
|  | now += TimeDelta::Millis(10); | 
|  |  | 
|  | EXPECT_EQ(stats.Rate(now), std::nullopt); | 
|  | } | 
|  |  | 
|  | TEST(FrequencyTrackerTest, ReturnsRateAfterTwoMeasurements) { | 
|  | Timestamp now = Timestamp::Seconds(12'345); | 
|  | FrequencyTracker stats(kWindow); | 
|  |  | 
|  | stats.Update(now); | 
|  | now += TimeDelta::Millis(1); | 
|  | stats.Update(now); | 
|  |  | 
|  | // 1 event per 1 ms ~= 1'000 events per second. | 
|  | EXPECT_EQ(stats.Rate(now), Frequency::Hertz(1'000)); | 
|  | } | 
|  |  | 
|  | TEST(FrequencyTrackerTest, MeasuresConstantRate) { | 
|  | const Timestamp start = Timestamp::Seconds(12'345); | 
|  | const TimeDelta kInterval = TimeDelta::Millis(10); | 
|  | const Frequency kConstantRate = 1 / kInterval; | 
|  |  | 
|  | Timestamp now = start; | 
|  | FrequencyTracker stats(kWindow); | 
|  |  | 
|  | stats.Update(now); | 
|  | Frequency last_error = Frequency::PlusInfinity(); | 
|  | for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kInterval) { | 
|  | SCOPED_TRACE(ToString(i)); | 
|  | now += kInterval; | 
|  | stats.Update(now); | 
|  |  | 
|  | // Until window is full, rate is measured over a smaller window and might | 
|  | // look larger than the constant rate. | 
|  | std::optional<Frequency> rate = stats.Rate(now); | 
|  | ASSERT_GE(rate, kConstantRate); | 
|  |  | 
|  | // Expect the estimation error to decrease as the window is extended. | 
|  | Frequency error = *rate - kConstantRate; | 
|  | EXPECT_LE(error, last_error); | 
|  | last_error = error; | 
|  | } | 
|  |  | 
|  | // Once window is full, rate measurment should be stable. | 
|  | for (TimeDelta i = TimeDelta::Zero(); i < kInterval; | 
|  | i += TimeDelta::Millis(1)) { | 
|  | SCOPED_TRACE(ToString(i)); | 
|  | EXPECT_EQ(stats.Rate(now + i), kConstantRate); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(FrequencyTrackerTest, CanMeasureFractionalRate) { | 
|  | const TimeDelta kInterval = TimeDelta::Millis(134); | 
|  | Timestamp now = Timestamp::Seconds(12'345); | 
|  | // FrequencyTracker counts number of events in the window, thus when window is | 
|  | // fraction of 1 second, number of events per second would always be integer. | 
|  | const TimeDelta window = TimeDelta::Seconds(2); | 
|  |  | 
|  | FrequencyTracker framerate(window); | 
|  | framerate.Update(now); | 
|  | for (TimeDelta i = TimeDelta::Zero(); i < window; i += kInterval) { | 
|  | now += kInterval; | 
|  | framerate.Update(now); | 
|  | } | 
|  |  | 
|  | // Should be aproximitly 7.5 fps | 
|  | EXPECT_THAT(framerate.Rate(now), | 
|  | AllOf(Gt(Frequency::Hertz(7)), Lt(Frequency::Hertz(8)))); | 
|  | } | 
|  |  | 
|  | TEST(FrequencyTrackerTest, IncreasingThenDecreasingRate) { | 
|  | const int64_t kLargeSize = 1'500; | 
|  | const int64_t kSmallSize = 300; | 
|  | const TimeDelta kLargeInterval = TimeDelta::Millis(10); | 
|  | const TimeDelta kSmallInterval = TimeDelta::Millis(2); | 
|  |  | 
|  | Timestamp now = Timestamp::Seconds(12'345); | 
|  | FrequencyTracker stats(kWindow); | 
|  |  | 
|  | stats.Update(kLargeSize, now); | 
|  | for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kLargeInterval) { | 
|  | SCOPED_TRACE(ToString(i)); | 
|  | now += kLargeInterval; | 
|  | stats.Update(kLargeSize, now); | 
|  | } | 
|  | std::optional<Frequency> last_rate = stats.Rate(now); | 
|  | EXPECT_EQ(last_rate, kLargeSize / kLargeInterval); | 
|  |  | 
|  | // Decrease rate with smaller measurments. | 
|  | for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kLargeInterval) { | 
|  | SCOPED_TRACE(ToString(i)); | 
|  | now += kLargeInterval; | 
|  | stats.Update(kSmallSize, now); | 
|  |  | 
|  | std::optional<Frequency> rate = stats.Rate(now); | 
|  | EXPECT_LT(rate, last_rate); | 
|  |  | 
|  | last_rate = rate; | 
|  | } | 
|  | EXPECT_EQ(last_rate, kSmallSize / kLargeInterval); | 
|  |  | 
|  | // Increase rate with more frequent measurments. | 
|  | for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kSmallInterval) { | 
|  | SCOPED_TRACE(ToString(i)); | 
|  | now += kSmallInterval; | 
|  | stats.Update(kSmallSize, now); | 
|  |  | 
|  | std::optional<Frequency> rate = stats.Rate(now); | 
|  | EXPECT_GE(rate, last_rate); | 
|  |  | 
|  | last_rate = rate; | 
|  | } | 
|  | EXPECT_EQ(last_rate, kSmallSize / kSmallInterval); | 
|  | } | 
|  |  | 
|  | TEST(FrequencyTrackerTest, ResetAfterSilence) { | 
|  | const TimeDelta kInterval = TimeDelta::Millis(10); | 
|  | const int64_t kPixels = 640 * 360; | 
|  |  | 
|  | Timestamp now = Timestamp::Seconds(12'345); | 
|  | FrequencyTracker pixel_rate(kWindow); | 
|  |  | 
|  | // Feed data until window has been filled. | 
|  | pixel_rate.Update(kPixels, now); | 
|  | for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kInterval) { | 
|  | now += kInterval; | 
|  | pixel_rate.Update(kPixels, now); | 
|  | } | 
|  | ASSERT_GT(pixel_rate.Rate(now), Frequency::Zero()); | 
|  |  | 
|  | now += kWindow + kEpsilon; | 
|  | // Silence over window size should trigger auto reset for coming sample. | 
|  | EXPECT_EQ(pixel_rate.Rate(now), std::nullopt); | 
|  | pixel_rate.Update(kPixels, now); | 
|  | // Single measurment after reset is not enough to estimate the rate. | 
|  | EXPECT_EQ(pixel_rate.Rate(now), std::nullopt); | 
|  |  | 
|  | // Manual reset, add the same check again. | 
|  | pixel_rate.Reset(); | 
|  | EXPECT_EQ(pixel_rate.Rate(now), std::nullopt); | 
|  | now += kInterval; | 
|  | pixel_rate.Update(kPixels, now); | 
|  | EXPECT_EQ(pixel_rate.Rate(now), std::nullopt); | 
|  | } | 
|  |  | 
|  | TEST(FrequencyTrackerTest, ReturnsNulloptWhenOverflows) { | 
|  | Timestamp now = Timestamp::Seconds(12'345); | 
|  | FrequencyTracker stats(kWindow); | 
|  |  | 
|  | int64_t very_large_number = std::numeric_limits<int64_t>::max(); | 
|  | stats.Update(very_large_number, now); | 
|  | now += kEpsilon; | 
|  | stats.Update(very_large_number, now); | 
|  |  | 
|  | EXPECT_EQ(stats.Rate(now), std::nullopt); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace webrtc |