blob: 402afcb4e1b6b7061e7eb59b22fb249325fd6c84 [file] [log] [blame]
/*
* Copyright (c) 2018 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 <queue>
#include "api/test/network_emulation/create_cross_traffic.h"
#include "api/test/network_emulation/cross_traffic.h"
#include "api/transport/goog_cc_factory.h"
#include "api/units/data_rate.h"
#include "logging/rtc_event_log/mock/mock_rtc_event_log.h"
#include "test/field_trial.h"
#include "test/gtest.h"
#include "test/scenario/scenario.h"
using ::testing::_;
using ::testing::Field;
using ::testing::Matcher;
using ::testing::NiceMock;
using ::testing::Property;
namespace webrtc {
namespace test {
namespace {
// Count dips from a constant high bandwidth level within a short window.
int CountBandwidthDips(std::queue<DataRate> bandwidth_history,
DataRate threshold) {
if (bandwidth_history.empty())
return true;
DataRate first = bandwidth_history.front();
bandwidth_history.pop();
int dips = 0;
bool state_high = true;
while (!bandwidth_history.empty()) {
if (bandwidth_history.front() + threshold < first && state_high) {
++dips;
state_high = false;
} else if (bandwidth_history.front() == first) {
state_high = true;
} else if (bandwidth_history.front() > first) {
// If this is toggling we will catch it later when front becomes first.
state_high = false;
}
bandwidth_history.pop();
}
return dips;
}
GoogCcNetworkControllerFactory CreateFeedbackOnlyFactory() {
GoogCcFactoryConfig config;
config.feedback_only = true;
return GoogCcNetworkControllerFactory(std::move(config));
}
const uint32_t kInitialBitrateKbps = 60;
const DataRate kInitialBitrate = DataRate::KilobitsPerSec(kInitialBitrateKbps);
const float kDefaultPacingRate = 2.5f;
CallClient* CreateVideoSendingClient(
Scenario* s,
CallClientConfig config,
std::vector<EmulatedNetworkNode*> send_link,
std::vector<EmulatedNetworkNode*> return_link) {
auto* client = s->CreateClient("send", std::move(config));
auto* route = s->CreateRoutes(client, send_link,
s->CreateClient("return", CallClientConfig()),
return_link);
s->CreateVideoStream(route->forward(), VideoStreamConfig());
return client;
}
void UpdatesTargetRateBasedOnLinkCapacity(std::string test_name = "") {
ScopedFieldTrials trial("WebRTC-SendSideBwe-WithOverhead/Enabled/");
auto factory = CreateFeedbackOnlyFactory();
Scenario s("googcc_unit/target_capacity" + test_name, false);
CallClientConfig config;
config.transport.cc_factory = &factory;
config.transport.rates.min_rate = DataRate::KilobitsPerSec(10);
config.transport.rates.max_rate = DataRate::KilobitsPerSec(1500);
config.transport.rates.start_rate = DataRate::KilobitsPerSec(300);
auto send_net = s.CreateMutableSimulationNode([](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(500);
c->delay = TimeDelta::Millis(100);
c->loss_rate = 0.0;
});
auto ret_net = s.CreateMutableSimulationNode(
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); });
StatesPrinter* truth = s.CreatePrinter(
"send.truth.txt", TimeDelta::PlusInfinity(), {send_net->ConfigPrinter()});
auto* client = CreateVideoSendingClient(&s, config, {send_net->node()},
{ret_net->node()});
truth->PrintRow();
s.RunFor(TimeDelta::Seconds(25));
truth->PrintRow();
EXPECT_NEAR(client->target_rate().kbps(), 450, 100);
send_net->UpdateConfig([](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(800);
c->delay = TimeDelta::Millis(100);
});
truth->PrintRow();
s.RunFor(TimeDelta::Seconds(20));
truth->PrintRow();
EXPECT_NEAR(client->target_rate().kbps(), 750, 150);
send_net->UpdateConfig([](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(100);
c->delay = TimeDelta::Millis(200);
});
ret_net->UpdateConfig(
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(200); });
truth->PrintRow();
s.RunFor(TimeDelta::Seconds(50));
truth->PrintRow();
EXPECT_NEAR(client->target_rate().kbps(), 90, 25);
}
DataRate RunRembDipScenario(std::string test_name) {
Scenario s(test_name);
NetworkSimulationConfig net_conf;
net_conf.bandwidth = DataRate::KilobitsPerSec(2000);
net_conf.delay = TimeDelta::Millis(50);
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = DataRate::KilobitsPerSec(1000);
});
auto send_net = {s.CreateSimulationNode(net_conf)};
auto ret_net = {s.CreateSimulationNode(net_conf)};
auto* route = s.CreateRoutes(
client, send_net, s.CreateClient("return", CallClientConfig()), ret_net);
s.CreateVideoStream(route->forward(), VideoStreamConfig());
s.RunFor(TimeDelta::Seconds(10));
EXPECT_GT(client->send_bandwidth().kbps(), 1500);
DataRate RembLimit = DataRate::KilobitsPerSec(250);
client->SetRemoteBitrate(RembLimit);
s.RunFor(TimeDelta::Seconds(1));
EXPECT_EQ(client->send_bandwidth(), RembLimit);
DataRate RembLimitLifted = DataRate::KilobitsPerSec(10000);
client->SetRemoteBitrate(RembLimitLifted);
s.RunFor(TimeDelta::Seconds(10));
return client->send_bandwidth();
}
} // namespace
class GoogCcNetworkControllerTest : public ::testing::Test {
protected:
GoogCcNetworkControllerTest()
: current_time_(Timestamp::Millis(123456)), factory_() {}
~GoogCcNetworkControllerTest() override {}
void SetUp() override {
controller_ = factory_.Create(InitialConfig());
NetworkControlUpdate update =
controller_->OnProcessInterval(DefaultInterval());
EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate);
EXPECT_EQ(update.pacer_config->data_rate(),
kInitialBitrate * kDefaultPacingRate);
EXPECT_EQ(update.probe_cluster_configs[0].target_data_rate,
kInitialBitrate * 3);
EXPECT_EQ(update.probe_cluster_configs[1].target_data_rate,
kInitialBitrate * 5);
}
// Custom setup - use an observer that tracks the target bitrate, without
// prescribing on which iterations it must change (like a mock would).
void TargetBitrateTrackingSetup() {
controller_ = factory_.Create(InitialConfig());
OnUpdate(controller_->OnProcessInterval(DefaultInterval()));
}
NetworkControllerConfig InitialConfig(
int starting_bandwidth_kbps = kInitialBitrateKbps,
int min_data_rate_kbps = 0,
int max_data_rate_kbps = 5 * kInitialBitrateKbps) {
NetworkControllerConfig config;
config.constraints.at_time = current_time_;
config.constraints.min_data_rate =
DataRate::KilobitsPerSec(min_data_rate_kbps);
config.constraints.max_data_rate =
DataRate::KilobitsPerSec(max_data_rate_kbps);
config.constraints.starting_rate =
DataRate::KilobitsPerSec(starting_bandwidth_kbps);
config.event_log = &event_log_;
return config;
}
ProcessInterval DefaultInterval() {
ProcessInterval interval;
interval.at_time = current_time_;
return interval;
}
RemoteBitrateReport CreateBitrateReport(DataRate rate) {
RemoteBitrateReport report;
report.receive_time = current_time_;
report.bandwidth = rate;
return report;
}
PacketResult CreateResult(int64_t arrival_time_ms,
int64_t send_time_ms,
size_t payload_size,
PacedPacketInfo pacing_info) {
PacketResult packet_result;
packet_result.sent_packet = SentPacket();
packet_result.sent_packet.send_time = Timestamp::Millis(send_time_ms);
packet_result.sent_packet.size = DataSize::Bytes(payload_size);
packet_result.sent_packet.pacing_info = pacing_info;
packet_result.receive_time = Timestamp::Millis(arrival_time_ms);
return packet_result;
}
NetworkRouteChange CreateRouteChange(
absl::optional<DataRate> start_rate = absl::nullopt,
absl::optional<DataRate> min_rate = absl::nullopt,
absl::optional<DataRate> max_rate = absl::nullopt) {
NetworkRouteChange route_change;
route_change.at_time = current_time_;
route_change.constraints.at_time = current_time_;
route_change.constraints.min_data_rate = min_rate;
route_change.constraints.max_data_rate = max_rate;
route_change.constraints.starting_rate = start_rate;
return route_change;
}
void AdvanceTimeMilliseconds(int timedelta_ms) {
current_time_ += TimeDelta::Millis(timedelta_ms);
}
void OnUpdate(NetworkControlUpdate update) {
if (update.target_rate)
target_bitrate_ = update.target_rate->target_rate;
}
void PacketTransmissionAndFeedbackBlock(int64_t runtime_ms, int64_t delay) {
int64_t delay_buildup = 0;
int64_t start_time_ms = current_time_.ms();
while (current_time_.ms() - start_time_ms < runtime_ms) {
constexpr size_t kPayloadSize = 1000;
PacketResult packet =
CreateResult(current_time_.ms() + delay_buildup, current_time_.ms(),
kPayloadSize, PacedPacketInfo());
delay_buildup += delay;
OnUpdate(controller_->OnSentPacket(packet.sent_packet));
TransportPacketsFeedback feedback;
feedback.feedback_time = packet.receive_time;
feedback.packet_feedbacks.push_back(packet);
OnUpdate(controller_->OnTransportPacketsFeedback(feedback));
AdvanceTimeMilliseconds(50);
OnUpdate(controller_->OnProcessInterval(DefaultInterval()));
}
}
Timestamp current_time_;
absl::optional<DataRate> target_bitrate_;
NiceMock<MockRtcEventLog> event_log_;
GoogCcNetworkControllerFactory factory_;
std::unique_ptr<NetworkControllerInterface> controller_;
};
TEST_F(GoogCcNetworkControllerTest, ReactsToChangedNetworkConditions) {
// Test no change.
AdvanceTimeMilliseconds(25);
OnUpdate(controller_->OnProcessInterval(DefaultInterval()));
NetworkControlUpdate update;
OnUpdate(controller_->OnRemoteBitrateReport(
CreateBitrateReport(kInitialBitrate * 2)));
AdvanceTimeMilliseconds(25);
update = controller_->OnProcessInterval(DefaultInterval());
EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate * 2);
EXPECT_EQ(update.pacer_config->data_rate(),
kInitialBitrate * 2 * kDefaultPacingRate);
OnUpdate(
controller_->OnRemoteBitrateReport(CreateBitrateReport(kInitialBitrate)));
AdvanceTimeMilliseconds(25);
update = controller_->OnProcessInterval(DefaultInterval());
EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate);
EXPECT_EQ(update.pacer_config->data_rate(),
kInitialBitrate * kDefaultPacingRate);
}
// Test congestion window pushback on network delay happens.
TEST_F(GoogCcNetworkControllerTest, CongestionWindowPushbackOnNetworkDelay) {
auto factory = CreateFeedbackOnlyFactory();
ScopedFieldTrials trial(
"WebRTC-CongestionWindow/QueueSize:800,MinBitrate:30000/");
Scenario s("googcc_unit/cwnd_on_delay", false);
auto send_net =
s.CreateMutableSimulationNode([=](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(1000);
c->delay = TimeDelta::Millis(100);
});
auto ret_net = s.CreateSimulationNode(
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); });
CallClientConfig config;
config.transport.cc_factory = &factory;
// Start high so bandwidth drop has max effect.
config.transport.rates.start_rate = DataRate::KilobitsPerSec(300);
config.transport.rates.max_rate = DataRate::KilobitsPerSec(2000);
config.transport.rates.min_rate = DataRate::KilobitsPerSec(10);
auto* client = CreateVideoSendingClient(&s, std::move(config),
{send_net->node()}, {ret_net});
s.RunFor(TimeDelta::Seconds(10));
send_net->PauseTransmissionUntil(s.Now() + TimeDelta::Seconds(10));
s.RunFor(TimeDelta::Seconds(3));
// After 3 seconds without feedback from any sent packets, we expect that the
// target rate is reduced to the minimum pushback threshold
// kDefaultMinPushbackTargetBitrateBps, which is defined as 30 kbps in
// congestion_window_pushback_controller.
EXPECT_LT(client->target_rate().kbps(), 40);
}
// Test congestion window pushback on network delay happens.
TEST_F(GoogCcNetworkControllerTest,
CongestionWindowPushbackDropFrameOnNetworkDelay) {
auto factory = CreateFeedbackOnlyFactory();
ScopedFieldTrials trial(
"WebRTC-CongestionWindow/QueueSize:800,MinBitrate:30000,DropFrame:true/");
Scenario s("googcc_unit/cwnd_on_delay", false);
auto send_net =
s.CreateMutableSimulationNode([=](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(1000);
c->delay = TimeDelta::Millis(100);
});
auto ret_net = s.CreateSimulationNode(
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); });
CallClientConfig config;
config.transport.cc_factory = &factory;
// Start high so bandwidth drop has max effect.
config.transport.rates.start_rate = DataRate::KilobitsPerSec(300);
config.transport.rates.max_rate = DataRate::KilobitsPerSec(2000);
config.transport.rates.min_rate = DataRate::KilobitsPerSec(10);
auto* client = CreateVideoSendingClient(&s, std::move(config),
{send_net->node()}, {ret_net});
s.RunFor(TimeDelta::Seconds(10));
send_net->PauseTransmissionUntil(s.Now() + TimeDelta::Seconds(10));
s.RunFor(TimeDelta::Seconds(3));
// As the dropframe is set, after 3 seconds without feedback from any sent
// packets, we expect that the target rate is not reduced by congestion
// window.
EXPECT_GT(client->target_rate().kbps(), 300);
}
TEST_F(GoogCcNetworkControllerTest, OnNetworkRouteChanged) {
NetworkControlUpdate update;
DataRate new_bitrate = DataRate::BitsPerSec(200000);
update = controller_->OnNetworkRouteChange(CreateRouteChange(new_bitrate));
EXPECT_EQ(update.target_rate->target_rate, new_bitrate);
EXPECT_EQ(update.pacer_config->data_rate(), new_bitrate * kDefaultPacingRate);
EXPECT_EQ(update.probe_cluster_configs.size(), 2u);
// If the bitrate is reset to -1, the new starting bitrate will be
// the minimum default bitrate.
const DataRate kDefaultMinBitrate = DataRate::KilobitsPerSec(5);
update = controller_->OnNetworkRouteChange(CreateRouteChange());
EXPECT_EQ(update.target_rate->target_rate, kDefaultMinBitrate);
EXPECT_NEAR(update.pacer_config->data_rate().bps<double>(),
kDefaultMinBitrate.bps<double>() * kDefaultPacingRate, 10);
EXPECT_EQ(update.probe_cluster_configs.size(), 2u);
}
TEST_F(GoogCcNetworkControllerTest, ProbeOnRouteChange) {
NetworkControlUpdate update;
update = controller_->OnNetworkRouteChange(CreateRouteChange(
2 * kInitialBitrate, DataRate::Zero(), 20 * kInitialBitrate));
EXPECT_TRUE(update.pacer_config.has_value());
EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate * 2);
EXPECT_EQ(update.probe_cluster_configs.size(), 2u);
EXPECT_EQ(update.probe_cluster_configs[0].target_data_rate,
kInitialBitrate * 6);
EXPECT_EQ(update.probe_cluster_configs[1].target_data_rate,
kInitialBitrate * 12);
update = controller_->OnProcessInterval(DefaultInterval());
}
// Bandwidth estimation is updated when feedbacks are received.
// Feedbacks which show an increasing delay cause the estimation to be reduced.
TEST_F(GoogCcNetworkControllerTest, UpdatesDelayBasedEstimate) {
TargetBitrateTrackingSetup();
const int64_t kRunTimeMs = 6000;
// The test must run and insert packets/feedback long enough that the
// BWE computes a valid estimate. This is first done in an environment which
// simulates no bandwidth limitation, and therefore not built-up delay.
PacketTransmissionAndFeedbackBlock(kRunTimeMs, 0);
ASSERT_TRUE(target_bitrate_.has_value());
// Repeat, but this time with a building delay, and make sure that the
// estimation is adjusted downwards.
DataRate bitrate_before_delay = *target_bitrate_;
PacketTransmissionAndFeedbackBlock(kRunTimeMs, 50);
EXPECT_LT(*target_bitrate_, bitrate_before_delay);
}
TEST_F(GoogCcNetworkControllerTest,
PaddingRateLimitedByCongestionWindowInTrial) {
ScopedFieldTrials trial(
"WebRTC-CongestionWindow/QueueSize:200,MinBitrate:30000/");
Scenario s("googcc_unit/padding_limited", false);
auto send_net =
s.CreateMutableSimulationNode([=](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(1000);
c->delay = TimeDelta::Millis(100);
});
auto ret_net = s.CreateSimulationNode(
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); });
CallClientConfig config;
// Start high so bandwidth drop has max effect.
config.transport.rates.start_rate = DataRate::KilobitsPerSec(1000);
config.transport.rates.max_rate = DataRate::KilobitsPerSec(2000);
auto* client = s.CreateClient("send", config);
auto* route =
s.CreateRoutes(client, {send_net->node()},
s.CreateClient("return", CallClientConfig()), {ret_net});
VideoStreamConfig video;
video.stream.pad_to_rate = config.transport.rates.max_rate;
s.CreateVideoStream(route->forward(), video);
// Run for a few seconds to allow the controller to stabilize.
s.RunFor(TimeDelta::Seconds(10));
// Check that padding rate matches target rate.
EXPECT_NEAR(client->padding_rate().kbps(), client->target_rate().kbps(), 1);
// Check this is also the case when congestion window pushback kicks in.
send_net->PauseTransmissionUntil(s.Now() + TimeDelta::Seconds(1));
EXPECT_NEAR(client->padding_rate().kbps(), client->target_rate().kbps(), 1);
}
TEST_F(GoogCcNetworkControllerTest, LimitsToFloorIfRttIsHighInTrial) {
// The field trial limits maximum RTT to 2 seconds, higher RTT means that the
// controller backs off until it reaches the minimum configured bitrate. This
// allows the RTT to recover faster than the regular control mechanism would
// achieve.
const DataRate kBandwidthFloor = DataRate::KilobitsPerSec(50);
ScopedFieldTrials trial("WebRTC-Bwe-MaxRttLimit/limit:2s,floor:" +
std::to_string(kBandwidthFloor.kbps()) + "kbps/");
// In the test case, we limit the capacity and add a cross traffic packet
// burst that blocks media from being sent. This causes the RTT to quickly
// increase above the threshold in the trial.
const DataRate kLinkCapacity = DataRate::KilobitsPerSec(100);
const TimeDelta kBufferBloatDuration = TimeDelta::Seconds(10);
Scenario s("googcc_unit/limit_trial", false);
auto send_net = s.CreateSimulationNode([=](NetworkSimulationConfig* c) {
c->bandwidth = kLinkCapacity;
c->delay = TimeDelta::Millis(100);
});
auto ret_net = s.CreateSimulationNode(
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(100); });
CallClientConfig config;
config.transport.rates.start_rate = kLinkCapacity;
auto* client = CreateVideoSendingClient(&s, config, {send_net}, {ret_net});
// Run for a few seconds to allow the controller to stabilize.
s.RunFor(TimeDelta::Seconds(10));
const DataSize kBloatPacketSize = DataSize::Bytes(1000);
const int kBloatPacketCount =
static_cast<int>(kBufferBloatDuration * kLinkCapacity / kBloatPacketSize);
// This will cause the RTT to be large for a while.
s.TriggerPacketBurst({send_net}, kBloatPacketCount, kBloatPacketSize.bytes());
// Wait to allow the high RTT to be detected and acted upon.
s.RunFor(TimeDelta::Seconds(6));
// By now the target rate should have dropped to the minimum configured rate.
EXPECT_NEAR(client->target_rate().kbps(), kBandwidthFloor.kbps(), 5);
}
TEST_F(GoogCcNetworkControllerTest, UpdatesTargetRateBasedOnLinkCapacity) {
UpdatesTargetRateBasedOnLinkCapacity();
}
TEST_F(GoogCcNetworkControllerTest, StableEstimateDoesNotVaryInSteadyState) {
auto factory = CreateFeedbackOnlyFactory();
Scenario s("googcc_unit/stable_target", false);
CallClientConfig config;
config.transport.cc_factory = &factory;
NetworkSimulationConfig net_conf;
net_conf.bandwidth = DataRate::KilobitsPerSec(500);
net_conf.delay = TimeDelta::Millis(100);
auto send_net = s.CreateSimulationNode(net_conf);
auto ret_net = s.CreateSimulationNode(net_conf);
auto* client = CreateVideoSendingClient(&s, config, {send_net}, {ret_net});
// Run for a while to allow the estimate to stabilize.
s.RunFor(TimeDelta::Seconds(30));
DataRate min_stable_target = DataRate::PlusInfinity();
DataRate max_stable_target = DataRate::MinusInfinity();
DataRate min_target = DataRate::PlusInfinity();
DataRate max_target = DataRate::MinusInfinity();
// Measure variation in steady state.
for (int i = 0; i < 20; ++i) {
auto stable_target_rate = client->stable_target_rate();
auto target_rate = client->target_rate();
EXPECT_LE(stable_target_rate, target_rate);
min_stable_target = std::min(min_stable_target, stable_target_rate);
max_stable_target = std::max(max_stable_target, stable_target_rate);
min_target = std::min(min_target, target_rate);
max_target = std::max(max_target, target_rate);
s.RunFor(TimeDelta::Seconds(1));
}
// We should expect drops by at least 15% (default backoff.)
EXPECT_LT(min_target / max_target, 0.85);
// We should expect the stable target to be more stable than the immediate one
EXPECT_GE(min_stable_target / max_stable_target, min_target / max_target);
}
TEST_F(GoogCcNetworkControllerTest,
LossBasedControlUpdatesTargetRateBasedOnLinkCapacity) {
ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/");
// TODO(srte): Should the behavior be unaffected at low loss rates?
UpdatesTargetRateBasedOnLinkCapacity("_loss_based");
}
TEST_F(GoogCcNetworkControllerTest,
LossBasedControlDoesModestBackoffToHighLoss) {
ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/");
Scenario s("googcc_unit/high_loss_channel", false);
CallClientConfig config;
config.transport.rates.min_rate = DataRate::KilobitsPerSec(10);
config.transport.rates.max_rate = DataRate::KilobitsPerSec(1500);
config.transport.rates.start_rate = DataRate::KilobitsPerSec(300);
auto send_net = s.CreateSimulationNode([](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(2000);
c->delay = TimeDelta::Millis(200);
c->loss_rate = 0.1;
});
auto ret_net = s.CreateSimulationNode(
[](NetworkSimulationConfig* c) { c->delay = TimeDelta::Millis(200); });
auto* client = CreateVideoSendingClient(&s, config, {send_net}, {ret_net});
s.RunFor(TimeDelta::Seconds(120));
// Without LossBasedControl trial, bandwidth drops to ~10 kbps.
EXPECT_GT(client->target_rate().kbps(), 100);
}
DataRate AverageBitrateAfterCrossInducedLoss(std::string name) {
Scenario s(name, false);
NetworkSimulationConfig net_conf;
net_conf.bandwidth = DataRate::KilobitsPerSec(1000);
net_conf.delay = TimeDelta::Millis(100);
// Short queue length means that we'll induce loss when sudden TCP traffic
// spikes are induced. This corresponds to ca 200 ms for a packet size of 1000
// bytes. Such limited buffers are common on for instance wifi routers.
net_conf.packet_queue_length_limit = 25;
auto send_net = {s.CreateSimulationNode(net_conf)};
auto ret_net = {s.CreateSimulationNode(net_conf)};
auto* client = s.CreateClient("send", CallClientConfig());
auto* callee = s.CreateClient("return", CallClientConfig());
auto* route = s.CreateRoutes(client, send_net, callee, ret_net);
// TODO(srte): Make this work with RTX enabled or remove it.
auto* video = s.CreateVideoStream(route->forward(), [](VideoStreamConfig* c) {
c->stream.use_rtx = false;
});
s.RunFor(TimeDelta::Seconds(10));
for (int i = 0; i < 4; ++i) {
// Sends TCP cross traffic inducing loss.
auto* tcp_traffic = s.net()->StartCrossTraffic(CreateFakeTcpCrossTraffic(
s.net()->CreateRoute(send_net), s.net()->CreateRoute(ret_net),
FakeTcpConfig()));
s.RunFor(TimeDelta::Seconds(2));
// Allow the ccongestion controller to recover.
s.net()->StopCrossTraffic(tcp_traffic);
s.RunFor(TimeDelta::Seconds(20));
}
// Querying the video stats from within the expected runtime environment
// (i.e. the TQ that belongs to the CallClient, not the Scenario TQ that
// we're currently on).
VideoReceiveStream::Stats video_receive_stats;
auto* video_stream = video->receive();
callee->SendTask([&video_stream, &video_receive_stats]() {
video_receive_stats = video_stream->GetStats();
});
return DataSize::Bytes(
video_receive_stats.rtp_stats.packet_counter.TotalBytes()) /
s.TimeSinceStart();
}
TEST_F(GoogCcNetworkControllerTest,
LossBasedRecoversFasterAfterCrossInducedLoss) {
// This test acts as a reference for the test below, showing that without the
// trial, we have worse behavior.
DataRate average_bitrate_without_loss_based =
AverageBitrateAfterCrossInducedLoss("googcc_unit/no_cross_loss_based");
// We recover bitrate better when subject to loss spikes from cross traffic
// when loss based controller is used.
ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/");
SetUp();
DataRate average_bitrate_with_loss_based =
AverageBitrateAfterCrossInducedLoss("googcc_unit/cross_loss_based");
EXPECT_GE(average_bitrate_with_loss_based,
average_bitrate_without_loss_based * 1.1);
}
TEST_F(GoogCcNetworkControllerTest, LossBasedEstimatorCapsRateAtModerateLoss) {
ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/");
Scenario s("googcc_unit/moderate_loss_channel", false);
CallClientConfig config;
config.transport.rates.min_rate = DataRate::KilobitsPerSec(10);
config.transport.rates.max_rate = DataRate::KilobitsPerSec(5000);
config.transport.rates.start_rate = DataRate::KilobitsPerSec(1000);
NetworkSimulationConfig network;
network.bandwidth = DataRate::KilobitsPerSec(2000);
network.delay = TimeDelta::Millis(100);
// 3% loss rate is in the moderate loss rate region at 2000 kbps, limiting the
// bitrate increase.
network.loss_rate = 0.03;
auto send_net = s.CreateMutableSimulationNode(network);
auto* client = s.CreateClient("send", std::move(config));
auto* route = s.CreateRoutes(client, {send_net->node()},
s.CreateClient("return", CallClientConfig()),
{s.CreateSimulationNode(network)});
s.CreateVideoStream(route->forward(), VideoStreamConfig());
// Allow the controller to stabilize at the lower bitrate.
s.RunFor(TimeDelta::Seconds(1));
// This increase in capacity would cause the target bitrate to increase to
// over 4000 kbps without LossBasedControl.
send_net->UpdateConfig([](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(5000);
});
s.RunFor(TimeDelta::Seconds(20));
// Using LossBasedControl, the bitrate will not increase over 2500 kbps since
// we have detected moderate loss.
EXPECT_LT(client->target_rate().kbps(), 2500);
}
TEST_F(GoogCcNetworkControllerTest, MaintainsLowRateInSafeResetTrial) {
const DataRate kLinkCapacity = DataRate::KilobitsPerSec(200);
const DataRate kStartRate = DataRate::KilobitsPerSec(300);
ScopedFieldTrials trial("WebRTC-Bwe-SafeResetOnRouteChange/Enabled/");
Scenario s("googcc_unit/safe_reset_low");
auto* send_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) {
c->bandwidth = kLinkCapacity;
c->delay = TimeDelta::Millis(10);
});
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = kStartRate;
});
auto* route = s.CreateRoutes(
client, {send_net}, s.CreateClient("return", CallClientConfig()),
{s.CreateSimulationNode(NetworkSimulationConfig())});
s.CreateVideoStream(route->forward(), VideoStreamConfig());
// Allow the controller to stabilize.
s.RunFor(TimeDelta::Millis(500));
EXPECT_NEAR(client->send_bandwidth().kbps(), kLinkCapacity.kbps(), 50);
s.ChangeRoute(route->forward(), {send_net});
// Allow new settings to propagate.
s.RunFor(TimeDelta::Millis(100));
// Under the trial, the target should be unchanged for low rates.
EXPECT_NEAR(client->send_bandwidth().kbps(), kLinkCapacity.kbps(), 50);
}
TEST_F(GoogCcNetworkControllerTest, CutsHighRateInSafeResetTrial) {
const DataRate kLinkCapacity = DataRate::KilobitsPerSec(1000);
const DataRate kStartRate = DataRate::KilobitsPerSec(300);
ScopedFieldTrials trial("WebRTC-Bwe-SafeResetOnRouteChange/Enabled/");
Scenario s("googcc_unit/safe_reset_high_cut");
auto send_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) {
c->bandwidth = kLinkCapacity;
c->delay = TimeDelta::Millis(50);
});
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = kStartRate;
});
auto* route = s.CreateRoutes(
client, {send_net}, s.CreateClient("return", CallClientConfig()),
{s.CreateSimulationNode(NetworkSimulationConfig())});
s.CreateVideoStream(route->forward(), VideoStreamConfig());
// Allow the controller to stabilize.
s.RunFor(TimeDelta::Millis(500));
EXPECT_NEAR(client->send_bandwidth().kbps(), kLinkCapacity.kbps(), 300);
s.ChangeRoute(route->forward(), {send_net});
// Allow new settings to propagate.
s.RunFor(TimeDelta::Millis(50));
// Under the trial, the target should be reset from high values.
EXPECT_NEAR(client->send_bandwidth().kbps(), kStartRate.kbps(), 30);
}
TEST_F(GoogCcNetworkControllerTest, DetectsHighRateInSafeResetTrial) {
ScopedFieldTrials trial(
"WebRTC-Bwe-SafeResetOnRouteChange/Enabled,ack/"
"WebRTC-SendSideBwe-WithOverhead/Enabled/");
const DataRate kInitialLinkCapacity = DataRate::KilobitsPerSec(200);
const DataRate kNewLinkCapacity = DataRate::KilobitsPerSec(800);
const DataRate kStartRate = DataRate::KilobitsPerSec(300);
Scenario s("googcc_unit/safe_reset_high_detect");
auto* initial_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) {
c->bandwidth = kInitialLinkCapacity;
c->delay = TimeDelta::Millis(50);
});
auto* new_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) {
c->bandwidth = kNewLinkCapacity;
c->delay = TimeDelta::Millis(50);
});
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = kStartRate;
});
auto* route = s.CreateRoutes(
client, {initial_net}, s.CreateClient("return", CallClientConfig()),
{s.CreateSimulationNode(NetworkSimulationConfig())});
s.CreateVideoStream(route->forward(), VideoStreamConfig());
// Allow the controller to stabilize.
s.RunFor(TimeDelta::Millis(2000));
EXPECT_NEAR(client->send_bandwidth().kbps(), kInitialLinkCapacity.kbps(), 50);
s.ChangeRoute(route->forward(), {new_net});
// Allow new settings to propagate, but not probes to be received.
s.RunFor(TimeDelta::Millis(50));
// Under the field trial, the target rate should be unchanged since it's lower
// than the starting rate.
EXPECT_NEAR(client->send_bandwidth().kbps(), kInitialLinkCapacity.kbps(), 50);
// However, probing should have made us detect the higher rate.
// NOTE: This test causes high loss rate, and the loss-based estimator reduces
// the bitrate, making the test fail if we wait longer than one second here.
s.RunFor(TimeDelta::Millis(1000));
EXPECT_GT(client->send_bandwidth().kbps(), kNewLinkCapacity.kbps() - 300);
}
TEST_F(GoogCcNetworkControllerTest,
TargetRateReducedOnPacingBufferBuildupInTrial) {
// Configure strict pacing to ensure build-up.
ScopedFieldTrials trial(
"WebRTC-CongestionWindow/QueueSize:100,MinBitrate:30000/"
"WebRTC-Video-Pacing/factor:1.0/"
"WebRTC-AddPacingToCongestionWindowPushback/Enabled/");
const DataRate kLinkCapacity = DataRate::KilobitsPerSec(1000);
const DataRate kStartRate = DataRate::KilobitsPerSec(1000);
Scenario s("googcc_unit/pacing_buffer_buildup");
auto* net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) {
c->bandwidth = kLinkCapacity;
c->delay = TimeDelta::Millis(50);
});
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = kStartRate;
});
auto* route = s.CreateRoutes(
client, {net}, s.CreateClient("return", CallClientConfig()),
{s.CreateSimulationNode(NetworkSimulationConfig())});
s.CreateVideoStream(route->forward(), VideoStreamConfig());
// Allow some time for the buffer to build up.
s.RunFor(TimeDelta::Seconds(5));
// Without trial, pacer delay reaches ~250 ms.
EXPECT_LT(client->GetStats().pacer_delay_ms, 150);
}
TEST_F(GoogCcNetworkControllerTest, NoBandwidthTogglingInLossControlTrial) {
ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/");
Scenario s("googcc_unit/no_toggling");
auto* send_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(2000);
c->loss_rate = 0.2;
c->delay = TimeDelta::Millis(10);
});
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = DataRate::KilobitsPerSec(300);
});
auto* route = s.CreateRoutes(
client, {send_net}, s.CreateClient("return", CallClientConfig()),
{s.CreateSimulationNode(NetworkSimulationConfig())});
s.CreateVideoStream(route->forward(), VideoStreamConfig());
// Allow the controller to initialize.
s.RunFor(TimeDelta::Millis(250));
std::queue<DataRate> bandwidth_history;
const TimeDelta step = TimeDelta::Millis(50);
for (TimeDelta time = TimeDelta::Zero(); time < TimeDelta::Millis(2000);
time += step) {
s.RunFor(step);
const TimeDelta window = TimeDelta::Millis(500);
if (bandwidth_history.size() >= window / step)
bandwidth_history.pop();
bandwidth_history.push(client->send_bandwidth());
EXPECT_LT(
CountBandwidthDips(bandwidth_history, DataRate::KilobitsPerSec(100)),
2);
}
}
TEST_F(GoogCcNetworkControllerTest, NoRttBackoffCollapseWhenVideoStops) {
ScopedFieldTrials trial("WebRTC-Bwe-MaxRttLimit/limit:2s/");
Scenario s("googcc_unit/rttbackoff_video_stop");
auto* send_net = s.CreateSimulationNode([&](NetworkSimulationConfig* c) {
c->bandwidth = DataRate::KilobitsPerSec(2000);
c->delay = TimeDelta::Millis(100);
});
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = DataRate::KilobitsPerSec(1000);
});
auto* route = s.CreateRoutes(
client, {send_net}, s.CreateClient("return", CallClientConfig()),
{s.CreateSimulationNode(NetworkSimulationConfig())});
auto* video = s.CreateVideoStream(route->forward(), VideoStreamConfig());
// Allow the controller to initialize, then stop video.
s.RunFor(TimeDelta::Seconds(1));
video->send()->Stop();
s.RunFor(TimeDelta::Seconds(4));
EXPECT_GT(client->send_bandwidth().kbps(), 1000);
}
TEST_F(GoogCcNetworkControllerTest, NoCrashOnVeryLateFeedback) {
Scenario s;
auto ret_net = s.CreateMutableSimulationNode(NetworkSimulationConfig());
auto* route = s.CreateRoutes(
s.CreateClient("send", CallClientConfig()),
{s.CreateSimulationNode(NetworkSimulationConfig())},
s.CreateClient("return", CallClientConfig()), {ret_net->node()});
auto* video = s.CreateVideoStream(route->forward(), VideoStreamConfig());
s.RunFor(TimeDelta::Seconds(5));
// Delay feedback by several minutes. This will cause removal of the send time
// history for the packets as long as kSendTimeHistoryWindow is configured for
// a shorter time span.
ret_net->PauseTransmissionUntil(s.Now() + TimeDelta::Seconds(300));
// Stopping video stream while waiting to save test execution time.
video->send()->Stop();
s.RunFor(TimeDelta::Seconds(299));
// Starting to cause addition of new packet to history, which cause old
// packets to be removed.
video->send()->Start();
// Runs until the lost packets are received. We expect that this will run
// without causing any runtime failures.
s.RunFor(TimeDelta::Seconds(2));
}
TEST_F(GoogCcNetworkControllerTest, IsFairToTCP) {
Scenario s("googcc_unit/tcp_fairness");
NetworkSimulationConfig net_conf;
net_conf.bandwidth = DataRate::KilobitsPerSec(1000);
net_conf.delay = TimeDelta::Millis(50);
auto* client = s.CreateClient("send", [&](CallClientConfig* c) {
c->transport.rates.start_rate = DataRate::KilobitsPerSec(1000);
});
auto send_net = {s.CreateSimulationNode(net_conf)};
auto ret_net = {s.CreateSimulationNode(net_conf)};
auto* route = s.CreateRoutes(
client, send_net, s.CreateClient("return", CallClientConfig()), ret_net);
s.CreateVideoStream(route->forward(), VideoStreamConfig());
s.net()->StartCrossTraffic(CreateFakeTcpCrossTraffic(
s.net()->CreateRoute(send_net), s.net()->CreateRoute(ret_net),
FakeTcpConfig()));
s.RunFor(TimeDelta::Seconds(10));
// Currently only testing for the upper limit as we in practice back out
// quite a lot in this scenario. If this behavior is fixed, we should add a
// lower bound to ensure it stays fixed.
EXPECT_LT(client->send_bandwidth().kbps(), 750);
}
TEST(GoogCcScenario, FastRampupOnRembCapLifted) {
DataRate final_estimate =
RunRembDipScenario("googcc_unit/default_fast_rampup_on_remb_cap_lifted");
EXPECT_GT(final_estimate.kbps(), 1500);
}
TEST(GoogCcScenario, SlowRampupOnRembCapLiftedWithFieldTrial) {
ScopedFieldTrials trial("WebRTC-Bwe-ReceiverLimitCapsOnly/Disabled/");
DataRate final_estimate =
RunRembDipScenario("googcc_unit/legacy_slow_rampup_on_remb_cap_lifted");
EXPECT_LT(final_estimate.kbps(), 1000);
}
} // namespace test
} // namespace webrtc