diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index a6d806d066..7dd8e9cdaf 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -248,7 +248,42 @@ rtc_library("random") { ] } +rtc_library("bitrate_tracker") { + visibility = [ "*" ] + sources = [ + "bitrate_tracker.cc", + "bitrate_tracker.h", + ] + deps = [ + ":rate_statistics", + "../api/units:data_rate", + "../api/units:data_size", + "../api/units:time_delta", + "../api/units:timestamp", + "system:rtc_export", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("frequency_tracker") { + visibility = [ "*" ] + sources = [ + "frequency_tracker.cc", + "frequency_tracker.h", + ] + deps = [ + ":rate_statistics", + "../api/units:frequency", + "../api/units:time_delta", + "../api/units:timestamp", + "system:rtc_export", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + rtc_library("rate_statistics") { + # TODO(bugs.webrtc.org/13756): Restrict visibility to private when all usage + # of the RateStatistics is migrated to BitrateTracker and FrequencyTracker. visibility = [ "*" ] sources = [ "rate_statistics.cc", @@ -1772,6 +1807,7 @@ if (rtc_include_tests) { sources = [ "base64_unittest.cc", "bit_buffer_unittest.cc", + "bitrate_tracker_unittest.cc", "bitstream_reader_unittest.cc", "bounded_inline_vector_unittest.cc", "buffer_queue_unittest.cc", @@ -1783,6 +1819,7 @@ if (rtc_include_tests) { "deprecated/recursive_critical_section_unittest.cc", "event_tracer_unittest.cc", "event_unittest.cc", + "frequency_tracker_unittest.cc", "logging_unittest.cc", "numerics/divide_round_unittest.cc", "numerics/histogram_percentile_counter_unittest.cc", @@ -1816,6 +1853,7 @@ if (rtc_include_tests) { ":async_packet_socket", ":async_udp_socket", ":bit_buffer", + ":bitrate_tracker", ":bitstream_reader", ":bounded_inline_vector", ":buffer", @@ -1827,6 +1865,7 @@ if (rtc_include_tests) { ":criticalsection", ":divide_round", ":event_tracer", + ":frequency_tracker", ":gunit_helpers", ":histogram_percentile_counter", ":ip_address", @@ -1866,7 +1905,11 @@ if (rtc_include_tests) { "../api:make_ref_counted", "../api:scoped_refptr", "../api/numerics", + "../api/units:data_rate", + "../api/units:data_size", + "../api/units:frequency", "../api/units:time_delta", + "../api/units:timestamp", "../system_wrappers", "../test:field_trial", "../test:fileutils", diff --git a/rtc_base/bitrate_tracker.cc b/rtc_base/bitrate_tracker.cc new file mode 100644 index 0000000000..340e444f24 --- /dev/null +++ b/rtc_base/bitrate_tracker.cc @@ -0,0 +1,38 @@ +/* + * 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/bitrate_tracker.h" + +#include "absl/types/optional.h" +#include "api/units/data_rate.h" +#include "api/units/timestamp.h" +#include "rtc_base/rate_statistics.h" + +namespace webrtc { + +BitrateTracker::BitrateTracker(TimeDelta max_window_size) + : impl_(max_window_size.ms(), RateStatistics::kBpsScale) {} + +absl::optional BitrateTracker::Rate(Timestamp now) const { + if (absl::optional rate = impl_.Rate(now.ms())) { + return DataRate::BitsPerSec(*rate); + } + return absl::nullopt; +} + +bool BitrateTracker::SetWindowSize(TimeDelta window_size, Timestamp now) { + return impl_.SetWindowSize(window_size.ms(), now.ms()); +} + +void BitrateTracker::Update(int64_t bytes, Timestamp now) { + impl_.Update(bytes, now.ms()); +} + +} // namespace webrtc diff --git a/rtc_base/bitrate_tracker.h b/rtc_base/bitrate_tracker.h new file mode 100644 index 0000000000..a54bd9a561 --- /dev/null +++ b/rtc_base/bitrate_tracker.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#ifndef RTC_BASE_BITRATE_TRACKER_H_ +#define RTC_BASE_BITRATE_TRACKER_H_ + +#include +#include + +#include "absl/types/optional.h" +#include "api/units/data_rate.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { +// Class to estimate bitrates over running window. +// Timestamps used in Update(), Rate() and SetWindowSize() must never +// decrease for two consecutive calls. +// This class is thread unsafe. +class RTC_EXPORT BitrateTracker { + public: + // max_window_sizes = Maximum window size for the rate estimation. + // Initial window size is set to this, but may be changed + // to something lower by calling SetWindowSize(). + explicit BitrateTracker(TimeDelta max_window_size); + + BitrateTracker(const BitrateTracker&) = default; + BitrateTracker(BitrateTracker&&) = default; + BitrateTracker& operator=(const BitrateTracker&) = delete; + BitrateTracker& operator=(BitrateTracker&&) = delete; + + ~BitrateTracker() = default; + + // Resets instance to original state. + void Reset() { impl_.Reset(); } + + // Updates bitrate with a new data point, moving averaging window as needed. + void Update(int64_t bytes, Timestamp now); + void Update(DataSize size, Timestamp now) { Update(size.bytes(), now); } + + // Returns bitrate, moving averaging window as needed. + // Returns nullopt when bitrate can't be measured. + absl::optional Rate(Timestamp now) const; + + // Update the size of the averaging window. The maximum allowed value for + // `window_size` is `max_window_size` as supplied in the constructor. + bool SetWindowSize(TimeDelta window_size, Timestamp now); + + private: + RateStatistics impl_; +}; +} // namespace webrtc + +#endif // RTC_BASE_BITRATE_TRACKER_H_ diff --git a/rtc_base/bitrate_tracker_unittest.cc b/rtc_base/bitrate_tracker_unittest.cc new file mode 100644 index 0000000000..2129aebfdd --- /dev/null +++ b/rtc_base/bitrate_tracker_unittest.cc @@ -0,0 +1,267 @@ +/* + * 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/bitrate_tracker.h" + +#include +#include + +#include "absl/types/optional.h" +#include "api/units/data_rate.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::Ge; +using ::testing::Le; + +constexpr TimeDelta kWindow = TimeDelta::Millis(500); +constexpr TimeDelta kEpsilon = TimeDelta::Millis(1); + +TEST(BitrateTrackerTest, ReturnsNulloptInitially) { + Timestamp now = Timestamp::Seconds(12'345); + BitrateTracker stats(kWindow); + + EXPECT_EQ(stats.Rate(now), absl::nullopt); +} + +TEST(BitrateTrackerTest, ReturnsNulloptAfterSingleDataPoint) { + Timestamp now = Timestamp::Seconds(12'345); + BitrateTracker stats(kWindow); + + stats.Update(1'500, now); + now += TimeDelta::Millis(10); + + EXPECT_EQ(stats.Rate(now), absl::nullopt); +} + +TEST(BitrateTrackerTest, ReturnsRateAfterTwoMeasurements) { + Timestamp now = Timestamp::Seconds(12'345); + BitrateTracker stats(kWindow); + + stats.Update(1'500, now); + now += TimeDelta::Millis(10); + stats.Update(1'500, now); + + // One packet every 10ms would result in 1.2 Mbps, but until window is full, + // it could be treated as two packets in ~10ms window, measuring twice that + // bitrate. + EXPECT_THAT(stats.Rate(now), AllOf(Ge(DataRate::BitsPerSec(1'200'000)), + Le(DataRate::BitsPerSec(2'400'000)))); +} + +TEST(BitrateTrackerTest, MeasuresConstantRate) { + const Timestamp start = Timestamp::Seconds(12'345); + const TimeDelta kInterval = TimeDelta::Millis(10); + const DataSize kPacketSize = DataSize::Bytes(1'500); + const DataRate kConstantRate = kPacketSize / kInterval; + + Timestamp now = start; + BitrateTracker stats(kWindow); + + stats.Update(kPacketSize, now); + DataSize total_size = kPacketSize; + DataRate last_error = DataRate::PlusInfinity(); + for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kInterval) { + SCOPED_TRACE(i); + now += kInterval; + total_size += kPacketSize; + stats.Update(kPacketSize, now); + + // Until window is full, bitrate is measured over a smaller window and might + // look larger than the constant rate. + absl::optional bitrate = stats.Rate(now); + ASSERT_THAT(bitrate, + AllOf(Ge(kConstantRate), Le(total_size / (now - start)))); + + // Expect the estimation error to decrease as the window is extended. + DataRate error = *bitrate - kConstantRate; + EXPECT_LE(error, last_error); + last_error = error; + } + + // Once window is full, bitrate measurment should be stable. + for (TimeDelta i = TimeDelta::Zero(); i < kInterval; + i += TimeDelta::Millis(1)) { + SCOPED_TRACE(i); + EXPECT_EQ(stats.Rate(now + i), kConstantRate); + } +} + +TEST(BitrateTrackerTest, IncreasingThenDecreasingBitrate) { + const DataSize kLargePacketSize = DataSize::Bytes(1'500); + const DataSize kSmallPacketSize = DataSize::Bytes(300); + const TimeDelta kLargeInterval = TimeDelta::Millis(10); + const TimeDelta kSmallInterval = TimeDelta::Millis(2); + + Timestamp now = Timestamp::Seconds(12'345); + BitrateTracker stats(kWindow); + + stats.Update(kLargePacketSize, now); + for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kLargeInterval) { + SCOPED_TRACE(i); + now += kLargeInterval; + stats.Update(kLargePacketSize, now); + } + absl::optional last_bitrate = stats.Rate(now); + EXPECT_EQ(last_bitrate, kLargePacketSize / kLargeInterval); + + // Decrease bitrate with smaller measurments. + for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kLargeInterval) { + SCOPED_TRACE(i); + now += kLargeInterval; + stats.Update(kSmallPacketSize, now); + + absl::optional bitrate = stats.Rate(now); + EXPECT_LT(bitrate, last_bitrate); + + last_bitrate = bitrate; + } + EXPECT_EQ(last_bitrate, kSmallPacketSize / kLargeInterval); + + // Increase bitrate with more frequent measurments. + for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kSmallInterval) { + SCOPED_TRACE(i); + now += kSmallInterval; + stats.Update(kSmallPacketSize, now); + + absl::optional bitrate = stats.Rate(now); + EXPECT_GE(bitrate, last_bitrate); + + last_bitrate = bitrate; + } + EXPECT_EQ(last_bitrate, kSmallPacketSize / kSmallInterval); +} + +TEST(BitrateTrackerTest, ResetAfterSilence) { + const TimeDelta kInterval = TimeDelta::Millis(10); + const DataSize kPacketSize = DataSize::Bytes(1'500); + + Timestamp now = Timestamp::Seconds(12'345); + BitrateTracker stats(kWindow); + + // Feed data until window has been filled. + stats.Update(kPacketSize, now); + for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kInterval) { + now += kInterval; + stats.Update(kPacketSize, now); + } + ASSERT_GT(stats.Rate(now), DataRate::Zero()); + + now += kWindow + kEpsilon; + // Silence over window size should trigger auto reset for coming sample. + EXPECT_EQ(stats.Rate(now), absl::nullopt); + stats.Update(kPacketSize, now); + // Single measurment after reset is not enough to estimate the rate. + EXPECT_EQ(stats.Rate(now), absl::nullopt); + + // Manual reset, add the same check again. + stats.Reset(); + EXPECT_EQ(stats.Rate(now), absl::nullopt); + now += kInterval; + stats.Update(kPacketSize, now); + EXPECT_EQ(stats.Rate(now), absl::nullopt); +} + +TEST(BitrateTrackerTest, HandlesChangingWindowSize) { + Timestamp now = Timestamp::Seconds(12'345); + BitrateTracker stats(kWindow); + + // Check window size is validated. + EXPECT_TRUE(stats.SetWindowSize(kWindow, now)); + EXPECT_FALSE(stats.SetWindowSize(kWindow + kEpsilon, now)); + EXPECT_FALSE(stats.SetWindowSize(TimeDelta::Zero(), now)); + EXPECT_TRUE(stats.SetWindowSize(kEpsilon, now)); + EXPECT_TRUE(stats.SetWindowSize(kWindow, now)); + + // Fill the buffer at a rate of 10 bytes per 10 ms (8 kbps). + const DataSize kValue = DataSize::Bytes(10); + const TimeDelta kInterval = TimeDelta::Millis(10); + for (TimeDelta i = TimeDelta::Zero(); i < kWindow; i += kInterval) { + now += kInterval; + stats.Update(kValue, now); + } + ASSERT_GT(stats.Rate(now), DataRate::BitsPerSec(8'000)); + + // Halve the window size, rate should stay the same. + EXPECT_TRUE(stats.SetWindowSize(kWindow / 2, now)); + EXPECT_EQ(stats.Rate(now), DataRate::BitsPerSec(8'000)); + + // Double the window size again, rate should stay the same. + // The window won't actually expand until new calls to the `Update`. + EXPECT_TRUE(stats.SetWindowSize(kWindow, now)); + EXPECT_EQ(stats.Rate(now), DataRate::BitsPerSec(8'000)); + + // Fill the now empty window half at twice the rate. + for (TimeDelta i = TimeDelta::Zero(); i < kWindow / 2; i += kInterval) { + now += kInterval; + stats.Update(2 * kValue, now); + } + + // Rate should have increased by 50%. + EXPECT_EQ(stats.Rate(now), DataRate::BitsPerSec(12'000)); +} + +TEST(BitrateTrackerTest, HandlesZeroCounts) { + const DataSize kPacketSize = DataSize::Bytes(1'500); + const TimeDelta kInterval = TimeDelta::Millis(10); + const Timestamp start = Timestamp::Seconds(12'345); + + Timestamp now = start; + BitrateTracker stats(kWindow); + + stats.Update(kPacketSize, now); + ASSERT_EQ(stats.Rate(now), absl::nullopt); + now += kInterval; + stats.Update(0, now); + absl::optional last_bitrate = stats.Rate(now); + EXPECT_GT(last_bitrate, DataRate::Zero()); + now += kInterval; + while (now < start + kWindow) { + SCOPED_TRACE(now - start); + stats.Update(0, now); + + absl::optional bitrate = stats.Rate(now); + EXPECT_GT(bitrate, DataRate::Zero()); + // As window expands, average bitrate decreases. + EXPECT_LT(bitrate, last_bitrate); + + last_bitrate = bitrate; + now += kInterval; + } + + // Initial kPacketSize should be outside the window now, so overall bitrate + // should be zero + EXPECT_EQ(stats.Rate(now), DataRate::Zero()); + + // Single measurment should be enough to get non zero rate. + stats.Update(kPacketSize, now); + EXPECT_EQ(stats.Rate(now), kPacketSize / kWindow); +} + +TEST(BitrateTrackerTest, ReturnsNulloptWhenOverflows) { + Timestamp now = Timestamp::Seconds(12'345); + BitrateTracker stats(kWindow); + + int64_t very_large_number = std::numeric_limits::max(); + stats.Update(very_large_number, now); + now += kEpsilon; + stats.Update(very_large_number, now); + + EXPECT_EQ(stats.Rate(now), absl::nullopt); +} + +} // namespace +} // namespace webrtc diff --git a/rtc_base/frequency_tracker.cc b/rtc_base/frequency_tracker.cc new file mode 100644 index 0000000000..c3be30e3b8 --- /dev/null +++ b/rtc_base/frequency_tracker.cc @@ -0,0 +1,35 @@ +/* + * 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 "absl/types/optional.h" +#include "api/units/frequency.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/rate_statistics.h" + +namespace webrtc { + +FrequencyTracker::FrequencyTracker(TimeDelta max_window_size) + : impl_(max_window_size.ms(), 1'000'000) {} + +absl::optional FrequencyTracker::Rate(Timestamp now) const { + if (absl::optional rate = impl_.Rate(now.ms())) { + return Frequency::MilliHertz(*rate); + } + return absl::nullopt; +} + +void FrequencyTracker::Update(int64_t count, Timestamp now) { + impl_.Update(count, now.ms()); +} + +} // namespace webrtc diff --git a/rtc_base/frequency_tracker.h b/rtc_base/frequency_tracker.h new file mode 100644 index 0000000000..3ee2ab0e1a --- /dev/null +++ b/rtc_base/frequency_tracker.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#ifndef RTC_BASE_FREQUENCY_TRACKER_H_ +#define RTC_BASE_FREQUENCY_TRACKER_H_ + +#include +#include + +#include "absl/types/optional.h" +#include "api/units/frequency.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/system/rtc_export.h" + +namespace webrtc { +// Class to estimate frequency (e.g. frame rate) over running window. +// Timestamps used in Update() and Rate() must never decrease for two +// consecutive calls. +// This class is thread unsafe. +class RTC_EXPORT FrequencyTracker { + public: + explicit FrequencyTracker(TimeDelta window_size); + + FrequencyTracker(const FrequencyTracker&) = default; + FrequencyTracker(FrequencyTracker&&) = default; + FrequencyTracker& operator=(const FrequencyTracker&) = delete; + FrequencyTracker& operator=(FrequencyTracker&&) = delete; + + ~FrequencyTracker() = default; + + // Reset instance to original state. + void Reset() { impl_.Reset(); } + + // Update rate with a new data point, moving averaging window as needed. + void Update(int64_t count, Timestamp now); + void Update(Timestamp now) { Update(1, now); } + + // Returns rate, moving averaging window as needed. + // Returns nullopt when rate can't be measured. + absl::optional Rate(Timestamp now) const; + + private: + RateStatistics impl_; +}; +} // namespace webrtc + +#endif // RTC_BASE_FREQUENCY_TRACKER_H_ diff --git a/rtc_base/frequency_tracker_unittest.cc b/rtc_base/frequency_tracker_unittest.cc new file mode 100644 index 0000000000..00788c3ee8 --- /dev/null +++ b/rtc_base/frequency_tracker_unittest.cc @@ -0,0 +1,203 @@ +/* + * 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 +#include + +#include "absl/types/optional.h" +#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), absl::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), absl::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(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. + absl::optional 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(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(i); + now += kLargeInterval; + stats.Update(kLargeSize, now); + } + absl::optional 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(i); + now += kLargeInterval; + stats.Update(kSmallSize, now); + + absl::optional 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(i); + now += kSmallInterval; + stats.Update(kSmallSize, now); + + absl::optional 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), absl::nullopt); + pixel_rate.Update(kPixels, now); + // Single measurment after reset is not enough to estimate the rate. + EXPECT_EQ(pixel_rate.Rate(now), absl::nullopt); + + // Manual reset, add the same check again. + pixel_rate.Reset(); + EXPECT_EQ(pixel_rate.Rate(now), absl::nullopt); + now += kInterval; + pixel_rate.Update(kPixels, now); + EXPECT_EQ(pixel_rate.Rate(now), absl::nullopt); +} + +TEST(FrequencyTrackerTest, ReturnsNulloptWhenOverflows) { + Timestamp now = Timestamp::Seconds(12'345); + FrequencyTracker stats(kWindow); + + int64_t very_large_number = std::numeric_limits::max(); + stats.Update(very_large_number, now); + now += kEpsilon; + stats.Update(very_large_number, now); + + EXPECT_EQ(stats.Rate(now), absl::nullopt); +} + +} // namespace +} // namespace webrtc