blob: b3a0d90419e33656b60ea0af24253309db6b3a86 [file]
/*
* Copyright 2025 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 "modules/congestion_controller/scream/scream_network_controller.h"
#include "api/environment/environment.h"
#include "api/transport/network_control.h"
#include "api/transport/network_types.h"
#include "api/units/data_rate.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "modules/congestion_controller/scream/test/cc_feedback_generator.h"
#include "system_wrappers/include/clock.h"
#include "test/create_test_environment.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::Eq;
using ::testing::Field;
using ::testing::Lt;
using ::testing::Optional;
constexpr double kPacingFactor = 1.1;
TEST(ScreamControllerTest, CanConstruct) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
}
TEST(ScreamControllerTest, OnNetworkAvailabilityUpdatesTargetRateAndPacerRate) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
config.constraints.starting_rate = DataRate::KilobitsPerSec(123);
config.stream_based_config.max_total_allocated_bitrate =
DataRate::KilobitsPerSec(456);
ScreamNetworkController scream_controller(config);
NetworkControlUpdate update =
scream_controller.OnNetworkAvailability({.network_available = true});
ASSERT_TRUE(update.has_updates());
ASSERT_TRUE(update.target_rate.has_value());
EXPECT_EQ(update.target_rate->target_rate, config.constraints.starting_rate);
ASSERT_TRUE(update.pacer_config);
EXPECT_EQ(update.pacer_config->data_window,
*config.constraints.starting_rate * kPacingFactor *
PacerConfig::kDefaultTimeInterval);
}
TEST(ScreamControllerTest,
OnTransportPacketsFeedbackUpdatesTargetRateAndPacerRate) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
// Simulation with infinite capacity.
CcFeedbackGenerator feedback_generator({});
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(DataRate::KilobitsPerSec(100),
clock);
NetworkControlUpdate update =
scream_controller.OnTransportPacketsFeedback(feedback);
ASSERT_TRUE(update.has_updates());
ASSERT_TRUE(update.target_rate.has_value());
EXPECT_GT(update.target_rate->target_rate, DataRate::KilobitsPerSec(100));
ASSERT_TRUE(update.pacer_config);
EXPECT_EQ(update.pacer_config->data_window,
update.target_rate->target_rate * kPacingFactor *
PacerConfig::kDefaultTimeInterval);
}
TEST(ScreamControllerTest,
OnNetworkRouteChangeResetsScreamAndUpdatesTargetRate) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
config.constraints.starting_rate = DataRate::KilobitsPerSec(50);
config.stream_based_config.max_total_allocated_bitrate =
DataRate::KilobitsPerSec(1000);
ScreamNetworkController scream_controller(config);
scream_controller.OnNetworkAvailability({.network_available = true});
CcFeedbackGenerator feedback_generator({});
DataRate send_rate = DataRate::KilobitsPerSec(100);
for (int i = 0; i < 10; ++i) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(send_rate, clock);
NetworkControlUpdate update =
scream_controller.OnTransportPacketsFeedback(feedback);
if (update.target_rate.has_value()) {
send_rate = update.target_rate->target_rate;
}
}
ASSERT_GT(send_rate, DataRate::KilobitsPerSec(50));
NetworkRouteChange route_change;
route_change.constraints.starting_rate = config.constraints.starting_rate =
DataRate::KilobitsPerSec(123);
route_change.at_time = clock.CurrentTime();
NetworkControlUpdate update =
scream_controller.OnNetworkRouteChange(route_change);
ASSERT_TRUE(update.has_updates());
ASSERT_TRUE(update.target_rate.has_value());
EXPECT_EQ(update.target_rate->target_rate,
route_change.constraints.starting_rate);
ASSERT_TRUE(update.pacer_config);
EXPECT_EQ(update.pacer_config->data_window,
*route_change.constraints.starting_rate * kPacingFactor *
PacerConfig::kDefaultTimeInterval);
}
TEST(ScreamControllerTest, TargetRateRampsUptoTargetConstraints) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
config.constraints.max_data_rate = DataRate::KilobitsPerSec(300);
ScreamNetworkController scream_controller(config);
// Simulation with infinite capacity.
CcFeedbackGenerator feedback_generator({});
DataRate target_rate = DataRate::KilobitsPerSec(100);
for (int i = 0; i < 10; ++i) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(target_rate, clock);
NetworkControlUpdate update =
scream_controller.OnTransportPacketsFeedback(feedback);
if (update.target_rate.has_value()) {
target_rate = update.target_rate->target_rate;
}
}
EXPECT_EQ(target_rate, DataRate::KilobitsPerSec(300));
// Reduce the constraints and expect the next target rate is bound by it.
TargetRateConstraints constraints;
constraints.max_data_rate = DataRate::KilobitsPerSec(200);
scream_controller.OnTargetRateConstraints(constraints);
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(target_rate, clock);
NetworkControlUpdate update =
scream_controller.OnTransportPacketsFeedback(feedback);
ASSERT_TRUE(update.target_rate.has_value());
EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(200));
}
TEST(ScreamControllerTest, TargetRateLimitedByRemoteBitrateReport) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
config.constraints.max_data_rate = DataRate::KilobitsPerSec(1000);
ScreamNetworkController scream_controller(config);
// Simulation with infinite capacity.
CcFeedbackGenerator feedback_generator({});
DataRate target_rate = DataRate::KilobitsPerSec(100);
for (int i = 0; i < 10; ++i) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
target_rate, clock, [&](const SentPacket& packet) {
scream_controller.OnSentPacket(packet);
});
NetworkControlUpdate update =
scream_controller.OnTransportPacketsFeedback(feedback);
if (update.target_rate.has_value()) {
target_rate = update.target_rate->target_rate;
}
}
EXPECT_EQ(target_rate, DataRate::KilobitsPerSec(1000));
RemoteBitrateReport msg;
msg.bandwidth = DataRate::KilobitsPerSec(500);
msg.receive_time = clock.CurrentTime();
NetworkControlUpdate update = scream_controller.OnRemoteBitrateReport(msg);
ASSERT_TRUE(update.target_rate.has_value());
EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(500));
for (int i = 0; i < 2; ++i) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
target_rate, clock, [&](const SentPacket& packet) {
scream_controller.OnSentPacket(packet);
});
update = scream_controller.OnTransportPacketsFeedback(feedback);
if (update.target_rate.has_value()) {
EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(500));
}
}
}
TEST(ScreamControllerTest, PacingWindowReducedIfCeCongestedStreamsConfigured) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
CcFeedbackGenerator feedback_generator({
.network_config = {.link_capacity = DataRate::KilobitsPerSec(900)},
.send_as_ect1 = true,
});
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
StreamsConfig streams_config;
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000);
scream_controller.OnStreamsConfig(streams_config);
NetworkControlUpdate update;
DataRate send_rate = DataRate::KilobitsPerSec(500);
for (int i = 0; i < 20; ++i) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
send_rate, clock, [&](const SentPacket& packet) {
scream_controller.OnSentPacket(packet);
});
update = scream_controller.OnTransportPacketsFeedback(feedback);
if (update.target_rate.has_value()) {
send_rate = update.target_rate->target_rate;
}
}
EXPECT_THAT(update.pacer_config,
Optional(Field(&PacerConfig::time_window,
Lt(PacerConfig::kDefaultTimeInterval))));
}
TEST(ScreamControllerTest,
PacingWindowReducedIfDelayCongestedStreamsConfigured) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
CcFeedbackGenerator feedback_generator(
{.network_config = {.link_capacity = DataRate::KilobitsPerSec(900)},
.send_as_ect1 = false}); // Scream will react to delay increase, not CE.
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
StreamsConfig streams_config;
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000);
scream_controller.OnStreamsConfig(streams_config);
NetworkControlUpdate update;
DataRate send_rate = DataRate::KilobitsPerSec(500);
for (int i = 0; i < 30; ++i) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
send_rate, clock, [&](const SentPacket& packet) {
scream_controller.OnSentPacket(packet);
});
update = scream_controller.OnTransportPacketsFeedback(feedback);
if (update.target_rate.has_value()) {
send_rate = update.target_rate->target_rate;
}
}
EXPECT_THAT(update.pacer_config,
Optional(Field(&PacerConfig::time_window,
Lt(PacerConfig::kDefaultTimeInterval))));
}
TEST(ScreamControllerTest, PacingWindowNotReducedIfNotCongested) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
CcFeedbackGenerator feedback_generator(
{.network_config = {.link_capacity = DataRate::KilobitsPerSec(9000)},
.send_as_ect1 = false}); // Scream will react to delay increase, not CE.
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
StreamsConfig streams_config;
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000);
scream_controller.OnStreamsConfig(streams_config);
NetworkControlUpdate update;
DataRate send_rate = DataRate::KilobitsPerSec(500);
for (int i = 0; i < 30; ++i) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
send_rate, clock, [&](const SentPacket& packet) {
scream_controller.OnSentPacket(packet);
});
update = scream_controller.OnTransportPacketsFeedback(feedback);
if (update.target_rate.has_value()) {
send_rate = update.target_rate->target_rate;
}
}
EXPECT_THAT(update.pacer_config,
Optional(Field(&PacerConfig::time_window,
Eq(PacerConfig::kDefaultTimeInterval))));
}
TEST(ScreamControllerTest, InitiallyPaddingIsAllowedToReachNeededRate) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
CcFeedbackGenerator feedback_generator(
{.network_config = {.queue_delay_ms = 10,
.link_capacity = DataRate::KilobitsPerSec(5000)},
.send_as_ect1 = true});
StreamsConfig streams_config;
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000);
scream_controller.OnStreamsConfig(streams_config);
DataRate send_rate = DataRate::KilobitsPerSec(50);
DataRate target_rate = DataRate::Zero();
bool padding_set = false;
Timestamp padding_stop = Timestamp::Zero();
Timestamp start_time = clock.CurrentTime();
while (clock.CurrentTime() < start_time + TimeDelta::Seconds(1)) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
send_rate, clock,
[&](SentPacket packet) { scream_controller.OnSentPacket(packet); });
NetworkControlUpdate update =
scream_controller.OnTransportPacketsFeedback(feedback);
if (update.pacer_config.has_value()) {
if (update.pacer_config->pad_rate() != DataRate::Zero()) {
padding_set = true;
// Set the send rate equal to the padding rate.
send_rate = update.pacer_config->pad_rate();
// Pacing rate is rounded.
EXPECT_GT(
update.pacer_config->pad_rate(),
update.target_rate->target_rate - DataRate::KilobitsPerSec(1));
EXPECT_LT(
update.pacer_config->pad_rate(),
update.target_rate->target_rate + DataRate::KilobitsPerSec(1));
} else if (padding_set && padding_stop.IsZero()) {
padding_stop = clock.CurrentTime();
}
}
if (update.target_rate) {
target_rate = update.target_rate->target_rate;
}
}
EXPECT_TRUE(padding_set);
// Target rate should reach max needed rate.
EXPECT_GE(target_rate, (*streams_config.max_total_allocated_bitrate));
// But not much more, since seen data in flight should limit the target rate
// increase.
EXPECT_LE(target_rate, 1.5 * (*streams_config.max_total_allocated_bitrate));
// Padding should stop when target is reached.
EXPECT_LT(padding_stop - start_time, TimeDelta::Seconds(1));
}
struct PaddingTestResult {
DataRate target_rate;
Timestamp padding_start;
Timestamp padding_stop;
};
PaddingTestResult ProcessUntilPaddingStartAndStop(
SimulatedClock& clock,
ScreamNetworkController& scream_controller,
CcFeedbackGenerator& feedback_generator,
bool increase_send_rate = true) {
DataRate target_rate = DataRate::Zero();
Timestamp padding_start = Timestamp::Zero();
Timestamp padding_stop = Timestamp::Zero();
Timestamp start_time = clock.CurrentTime();
DataRate send_rate = DataRate::KilobitsPerSec(50);
while (clock.CurrentTime() < start_time + TimeDelta::Seconds(10)) {
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
send_rate, clock,
[&](SentPacket packet) { scream_controller.OnSentPacket(packet); });
NetworkControlUpdate update =
scream_controller.OnTransportPacketsFeedback(feedback);
if (update.pacer_config.has_value()) {
if (update.pacer_config->pad_rate() != DataRate::Zero()) {
padding_start = clock.CurrentTime();
if (increase_send_rate) {
// Set the send rate equal to the padding rate.
send_rate = update.pacer_config->pad_rate();
}
} else if (!padding_start.IsZero() && padding_stop.IsZero()) {
padding_stop = clock.CurrentTime();
}
}
if (update.target_rate) {
target_rate = update.target_rate->target_rate;
}
if (!padding_stop.IsZero()) {
break;
}
}
EXPECT_FALSE(padding_start.IsZero());
EXPECT_FALSE(padding_stop.IsZero());
return {.target_rate = target_rate,
.padding_start = padding_start,
.padding_stop = padding_stop};
}
TEST(ScreamControllerTest, PaddingStopIfNetworkCongested) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
CcFeedbackGenerator feedback_generator(
{.network_config = {.queue_delay_ms = 10,
.link_capacity = DataRate::KilobitsPerSec(500)},
.send_as_ect1 = true});
StreamsConfig streams_config;
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000);
scream_controller.OnStreamsConfig(streams_config);
PaddingTestResult result = ProcessUntilPaddingStartAndStop(
clock, scream_controller, feedback_generator);
EXPECT_LT(result.target_rate, DataRate::KilobitsPerSec(700));
// Padding should stop when congestion is detected.
EXPECT_LT(result.padding_stop - result.padding_start, TimeDelta::Seconds(1));
}
TEST(ScreamControllerTest, PeriodicallyAllowPadding) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
CcFeedbackGenerator feedback_generator(
{.network_config = {.queue_delay_ms = 10,
.link_capacity = DataRate::KilobitsPerSec(15000)},
.send_as_ect1 = true});
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
StreamsConfig streams_config;
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000);
scream_controller.OnStreamsConfig(streams_config);
PaddingTestResult result_1 = ProcessUntilPaddingStartAndStop(
clock, scream_controller, feedback_generator,
/*increase_send_rate=*/false);
PaddingTestResult result_2 = ProcessUntilPaddingStartAndStop(
clock, scream_controller, feedback_generator);
TimeDelta padding_duration = result_1.padding_stop - result_1.padding_start;
TimeDelta time_between_padding =
result_2.padding_start - result_1.padding_stop;
EXPECT_GT(padding_duration, TimeDelta::Millis(2800));
EXPECT_LT(padding_duration, TimeDelta::Millis(3100));
EXPECT_GT(time_between_padding, TimeDelta::Millis(2900));
EXPECT_LT(time_between_padding, TimeDelta::Millis(3300));
}
TEST(ScreamControllerTest, PadsToMinOf2xCurrentMaxAndEverSeenMax) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
ScreamNetworkController scream_controller(config);
CcFeedbackGenerator feedback_generator(
{.network_config = {.queue_delay_ms = 50,
.link_capacity = DataRate::KilobitsPerSec(5000)},
.send_as_ect1 = true});
StreamsConfig streams_config;
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(1000);
scream_controller.OnStreamsConfig(streams_config);
// Even if max_total_allocated_bitrate is lowered, padding is still allowed up
// to 2x the new max and previous max.
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(300);
scream_controller.OnStreamsConfig(streams_config);
PaddingTestResult result_1 = ProcessUntilPaddingStartAndStop(
clock, scream_controller, feedback_generator);
EXPECT_LT(result_1.target_rate, DataRate::KilobitsPerSec(700));
streams_config.max_total_allocated_bitrate = DataRate::KilobitsPerSec(800);
scream_controller.OnStreamsConfig(streams_config);
PaddingTestResult result_2 = ProcessUntilPaddingStartAndStop(
clock, scream_controller, feedback_generator);
EXPECT_LT(result_2.target_rate, DataRate::KilobitsPerSec(1100));
}
TEST(ScreamControllerTest, CanSetStartBitrate) {
SimulatedClock clock(Timestamp::Seconds(1'234));
Environment env = CreateTestEnvironment({.time = &clock});
NetworkControllerConfig config(env);
config.constraints.starting_rate = DataRate::KilobitsPerSec(3000);
ScreamNetworkController scream_controller(config);
CcFeedbackGenerator feedback_generator(
{.network_config = {.queue_delay_ms = 50,
.link_capacity = DataRate::KilobitsPerSec(5000)}});
TransportPacketsFeedback feedback =
feedback_generator.ProcessUntilNextFeedback(
/*send_rate=*/DataRate::KilobitsPerSec(100), clock,
[&](SentPacket packet) { scream_controller.OnSentPacket(packet); });
NetworkControlUpdate update =
scream_controller.OnTransportPacketsFeedback(feedback);
EXPECT_GE(update.target_rate->target_rate, DataRate::KilobitsPerSec(2980));
EXPECT_LT(update.target_rate->target_rate, DataRate::KilobitsPerSec(3300));
}
} // namespace
} // namespace webrtc