| /* |
| * 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 <cstdlib> |
| #include <limits> |
| #include <optional> |
| |
| #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), std::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), std::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(ToString(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. |
| std::optional<DataRate> 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(ToString(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(ToString(i)); |
| now += kLargeInterval; |
| stats.Update(kLargePacketSize, now); |
| } |
| std::optional<DataRate> 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(ToString(i)); |
| now += kLargeInterval; |
| stats.Update(kSmallPacketSize, now); |
| |
| std::optional<DataRate> 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(ToString(i)); |
| now += kSmallInterval; |
| stats.Update(kSmallPacketSize, now); |
| |
| std::optional<DataRate> 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), std::nullopt); |
| stats.Update(kPacketSize, now); |
| // Single measurment after reset is not enough to estimate the rate. |
| EXPECT_EQ(stats.Rate(now), std::nullopt); |
| |
| // Manual reset, add the same check again. |
| stats.Reset(); |
| EXPECT_EQ(stats.Rate(now), std::nullopt); |
| now += kInterval; |
| stats.Update(kPacketSize, now); |
| EXPECT_EQ(stats.Rate(now), std::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), std::nullopt); |
| now += kInterval; |
| stats.Update(0, now); |
| std::optional<DataRate> last_bitrate = stats.Rate(now); |
| EXPECT_GT(last_bitrate, DataRate::Zero()); |
| now += kInterval; |
| while (now < start + kWindow) { |
| SCOPED_TRACE(ToString(now - start)); |
| stats.Update(0, now); |
| |
| std::optional<DataRate> 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<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 |