mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00

Only minor positive changes seen in initial testing. Let's default- enable and monitor behavior through the normal release cycle. Bug: b/349561566 Change-Id: Id6b39daa159068bf076acc34888b5d7eaf110329 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/356641 Commit-Queue: Sergey Silkin <ssilkin@webrtc.org> Reviewed-by: Sergey Silkin <ssilkin@webrtc.org> Auto-Submit: Erik Språng <sprang@webrtc.org> Cr-Commit-Position: refs/heads/main@{#42607}
269 lines
12 KiB
C++
269 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2024 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 "video/rate_utilization_tracker.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 "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
|
|
using ::testing::Not;
|
|
|
|
constexpr int kDefaultMaxDataPoints = 10;
|
|
constexpr TimeDelta kDefaultTimeWindow = TimeDelta::Seconds(1);
|
|
constexpr Timestamp kStartTime = Timestamp::Millis(9876654);
|
|
constexpr double kAllowedError = 0.002; // 0.2% error allowed.
|
|
|
|
MATCHER_P(PrettyCloseTo, expected, "") {
|
|
return arg && std::abs(*arg - expected) < kAllowedError;
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, NoDataInNoDataOut) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
EXPECT_FALSE(tracker.GetRateUtilizationFactor(kStartTime).has_value());
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, NoUtilizationWithoutDataPoints) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
tracker.OnDataRateChanged(DataRate::KilobitsPerSec(100), kStartTime);
|
|
EXPECT_FALSE(tracker.GetRateUtilizationFactor(kStartTime).has_value());
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, NoUtilizationWithoutRateUpdates) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
tracker.OnDataProduced(DataSize::Bytes(100), kStartTime);
|
|
EXPECT_FALSE(tracker.GetRateUtilizationFactor(kStartTime).has_value());
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, SingleDataPoint) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime);
|
|
|
|
// From the start, the window is extended to cover the expected duration for
|
|
// the last frame - resulting in 100% utilization.
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime), PrettyCloseTo(1.0));
|
|
|
|
// At the expected frame interval the utilization is still 100%.
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + kFrameInterval),
|
|
PrettyCloseTo(1.0));
|
|
|
|
// After two frame intervals the utilization is half the expected.
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 2 * kFrameInterval),
|
|
PrettyCloseTo(0.5));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, TwoDataPoints) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + kFrameInterval);
|
|
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 2 * kFrameInterval),
|
|
PrettyCloseTo(1.0));
|
|
|
|
// After two three frame interval we have two utilizated intervals and one
|
|
// unitilzed => 2/3 utilization.
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 3 * kFrameInterval),
|
|
PrettyCloseTo(2.0 / 3.0));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, TwoDataPointsConsistentOveruse) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize * 2, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize * 2, kStartTime + kFrameInterval);
|
|
|
|
// Note that the last data point is presumed to be sent at the designated rate
|
|
// and no new data points produced until the buffers empty. Thus the
|
|
// overshoot is just 4/3 unstead of 4/2.
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 2 * kFrameInterval),
|
|
PrettyCloseTo(4.0 / 3.0));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, OveruseWithFrameDrop) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
// First frame is 2x larger than it should be.
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize * 2, kStartTime);
|
|
// Compensate by dropping a frame before the next nominal-size one.
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + 2 * kFrameInterval);
|
|
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 3 * kFrameInterval),
|
|
PrettyCloseTo(1.0));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, VaryingRate) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
// Rate goes up, rate comes down...
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime);
|
|
tracker.OnDataRateChanged(kTargetRate * 2, kStartTime + kFrameInterval);
|
|
tracker.OnDataProduced(kIdealFrameSize * 2, kStartTime + kFrameInterval);
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime + 2 * kFrameInterval);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + 2 * kFrameInterval);
|
|
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 3 * kFrameInterval),
|
|
PrettyCloseTo(1.0));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, VaryingRateMidFrameInterval) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
// First frame 1/3 too large
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize * (3.0 / 2.0), kStartTime);
|
|
|
|
// Mid frame interval double the target rate. Should lead to no overshoot.
|
|
tracker.OnDataRateChanged(kTargetRate * 2, kStartTime + kFrameInterval / 2);
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + kFrameInterval),
|
|
PrettyCloseTo(1.0));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, VaryingRateAfterLastDataPoint) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
// Data point is just after the rate update.
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + TimeDelta::Micros(1));
|
|
|
|
// Half an interval past the last frame double the target rate.
|
|
tracker.OnDataRateChanged(kTargetRate * 2, kStartTime + kFrameInterval / 2);
|
|
|
|
// The last data point should now extend only to 2/3 the way to the next frame
|
|
// interval.
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime +
|
|
kFrameInterval * (2.0 / 3.0)),
|
|
PrettyCloseTo(1.0));
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime +
|
|
kFrameInterval * (2.3 / 3.0)),
|
|
Not(PrettyCloseTo(1.0)));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, DataPointLimit) {
|
|
// Set max data points to two.
|
|
RateUtilizationTracker tracker(/*max_data_points=*/2, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
// Insert two frames that are too large.
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize * 2, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize * 2, kStartTime + 1 * kFrameInterval);
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 1 * kFrameInterval),
|
|
Not(PrettyCloseTo(1.0)));
|
|
|
|
// Insert two frames of the correct size. Past grievances have been forgotten.
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + 2 * kFrameInterval);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + 3 * kFrameInterval);
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 3 * kFrameInterval),
|
|
PrettyCloseTo(1.0));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, WindowSizeLimit) {
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
// Number of data points enough, but time window too small.
|
|
RateUtilizationTracker tracker(/*max_data_points=*/4, /*time_window=*/
|
|
2 * kFrameInterval - TimeDelta::Millis(1));
|
|
|
|
// Insert two frames that are too large.
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize * 2, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize * 2, kStartTime + 1 * kFrameInterval);
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 1 * kFrameInterval),
|
|
Not(PrettyCloseTo(1.0)));
|
|
|
|
// Insert two frames of the correct size. Past grievances have been forgotten.
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + 2 * kFrameInterval);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + 3 * kFrameInterval);
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + 3 * kFrameInterval),
|
|
PrettyCloseTo(1.0));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, EqualTimestampsTreatedAtSameDataPoint) {
|
|
// Set max data points to two.
|
|
RateUtilizationTracker tracker(/*max_data_points=*/2, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime);
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime), PrettyCloseTo(1.0));
|
|
|
|
// This is viewed as an undershoot.
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + (kFrameInterval * 2));
|
|
EXPECT_THAT(
|
|
tracker.GetRateUtilizationFactor(kStartTime + (kFrameInterval * 2)),
|
|
PrettyCloseTo(2.0 / 3.0));
|
|
|
|
// Add the same data point again. Treated as layered frame so will accumulate
|
|
// in the same data point. This is expected to have a send time twice as long
|
|
// now, reducing the undershoot.
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime + (kFrameInterval * 2));
|
|
EXPECT_THAT(
|
|
tracker.GetRateUtilizationFactor(kStartTime + (kFrameInterval * 2)),
|
|
PrettyCloseTo(3.0 / 4.0));
|
|
}
|
|
|
|
TEST(RateUtilizationTrackerTest, FullRateAfterLastDataPoint) {
|
|
RateUtilizationTracker tracker(kDefaultMaxDataPoints, kDefaultTimeWindow);
|
|
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1) / 33;
|
|
constexpr DataRate kTargetRate = DataRate::KilobitsPerSec(100);
|
|
constexpr DataSize kIdealFrameSize = kTargetRate * kFrameInterval;
|
|
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime);
|
|
tracker.OnDataProduced(kIdealFrameSize, kStartTime);
|
|
|
|
// New rate update, but accumulated rate for last data point fully saturated
|
|
// by next to last rate update.
|
|
tracker.OnDataRateChanged(kTargetRate, kStartTime + kFrameInterval * 2);
|
|
|
|
EXPECT_THAT(tracker.GetRateUtilizationFactor(kStartTime + kFrameInterval * 3),
|
|
PrettyCloseTo(1.0 / 3.0));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace webrtc
|