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