/* * 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 #include #include #include #include #include #include #include #include "absl/strings/string_view.h" #include "absl/types/optional.h" #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/transport/network_control.h" #include "api/transport/network_types.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 "call/video_receive_stream.h" #include "logging/rtc_event_log/mock/mock_rtc_event_log.h" #include "test/field_trial.h" #include "test/gtest.h" #include "test/scenario/call_client.h" #include "test/scenario/column_printer.h" #include "test/scenario/scenario.h" #include "test/scenario/scenario_config.h" using ::testing::IsEmpty; using ::testing::NiceMock; namespace webrtc { namespace test { namespace { // Count dips from a constant high bandwidth level within a short window. int CountBandwidthDips(std::queue 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 send_link, std::vector 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; } NetworkRouteChange CreateRouteChange( Timestamp time, absl::optional start_rate = absl::nullopt, absl::optional min_rate = absl::nullopt, absl::optional max_rate = absl::nullopt) { NetworkRouteChange route_change; route_change.at_time = time; route_change.constraints.at_time = 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; } PacketResult CreatePacketResult(Timestamp arrival_time, Timestamp send_time, size_t payload_size, PacedPacketInfo pacing_info) { PacketResult packet_result; packet_result.sent_packet = SentPacket(); packet_result.sent_packet.send_time = send_time; packet_result.sent_packet.size = DataSize::Bytes(payload_size); packet_result.sent_packet.pacing_info = pacing_info; packet_result.receive_time = arrival_time; return packet_result; } // Simulate sending packets and receiving transport feedback during // `runtime_ms`, then return the final target birate. absl::optional PacketTransmissionAndFeedbackBlock( NetworkControllerInterface* controller, int64_t runtime_ms, int64_t delay, Timestamp& current_time) { NetworkControlUpdate update; absl::optional target_bitrate; 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 = CreatePacketResult(current_time + TimeDelta::Millis(delay_buildup), current_time, kPayloadSize, PacedPacketInfo()); delay_buildup += delay; update = controller->OnSentPacket(packet.sent_packet); if (update.target_rate) { target_bitrate = update.target_rate->target_rate; } TransportPacketsFeedback feedback; feedback.feedback_time = packet.receive_time; feedback.packet_feedbacks.push_back(packet); update = controller->OnTransportPacketsFeedback(feedback); if (update.target_rate) { target_bitrate = update.target_rate->target_rate; } current_time += TimeDelta::Millis(50); update = controller->OnProcessInterval({.at_time = current_time}); if (update.target_rate) { target_bitrate = update.target_rate->target_rate; } } return target_bitrate; } // Create transport packets feedback with a built-up delay. TransportPacketsFeedback CreateTransportPacketsFeedback( TimeDelta per_packet_network_delay, TimeDelta one_way_delay, Timestamp send_time) { TimeDelta delay_buildup = one_way_delay; constexpr int kFeedbackSize = 3; constexpr size_t kPayloadSize = 1000; TransportPacketsFeedback feedback; for (int i = 0; i < kFeedbackSize; ++i) { PacketResult packet = CreatePacketResult( /*arrival_time=*/send_time + delay_buildup, send_time, kPayloadSize, PacedPacketInfo()); delay_buildup += per_packet_network_delay; feedback.feedback_time = packet.receive_time + one_way_delay; feedback.packet_feedbacks.push_back(packet); } return feedback; } // Scenarios: void UpdatesTargetRateBasedOnLinkCapacity(absl::string_view test_name = "") { auto factory = CreateFeedbackOnlyFactory(); Scenario s("googcc_unit/target_capacity" + std::string(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(absl::string_view 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 NetworkControllerTestFixture { public: NetworkControllerTestFixture() : factory_() {} explicit NetworkControllerTestFixture(GoogCcFactoryConfig googcc_config) : factory_(std::move(googcc_config)) {} std::unique_ptr CreateController() { NetworkControllerConfig config = InitialConfig(); std::unique_ptr controller = factory_.Create(config); return controller; } private: 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 = Timestamp::Zero(); 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; } NiceMock event_log_; GoogCcNetworkControllerFactory factory_; }; TEST(GoogCcNetworkControllerTest, InitializeTargetRateOnFirstProcessIntervalAfterNetworkAvailable) { NetworkControllerTestFixture fixture; std::unique_ptr controller = fixture.CreateController(); NetworkControlUpdate update = controller->OnNetworkAvailability( {.at_time = Timestamp::Millis(123456), .network_available = true}); update = controller->OnProcessInterval({.at_time = Timestamp::Millis(123456)}); 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); } TEST(GoogCcNetworkControllerTest, ReactsToChangedNetworkConditions) { NetworkControllerTestFixture fixture; std::unique_ptr controller = fixture.CreateController(); Timestamp current_time = Timestamp::Millis(123); NetworkControlUpdate update = controller->OnNetworkAvailability( {.at_time = current_time, .network_available = true}); update = controller->OnProcessInterval({.at_time = current_time}); update = controller->OnRemoteBitrateReport( {.receive_time = current_time, .bandwidth = kInitialBitrate * 2}); current_time += TimeDelta::Millis(25); update = controller->OnProcessInterval({.at_time = current_time}); EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate * 2); EXPECT_EQ(update.pacer_config->data_rate(), kInitialBitrate * 2 * kDefaultPacingRate); update = controller->OnRemoteBitrateReport( {.receive_time = current_time, .bandwidth = kInitialBitrate}); current_time += TimeDelta::Millis(25); update = controller->OnProcessInterval({.at_time = current_time}); EXPECT_EQ(update.target_rate->target_rate, kInitialBitrate); EXPECT_EQ(update.pacer_config->data_rate(), kInitialBitrate * kDefaultPacingRate); } TEST(GoogCcNetworkControllerTest, OnNetworkRouteChanged) { NetworkControllerTestFixture fixture; std::unique_ptr controller = fixture.CreateController(); Timestamp current_time = Timestamp::Millis(123); NetworkControlUpdate update = controller->OnNetworkAvailability( {.at_time = current_time, .network_available = true}); DataRate new_bitrate = DataRate::BitsPerSec(200000); update = controller->OnNetworkRouteChange( CreateRouteChange(current_time, 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(current_time)); EXPECT_EQ(update.target_rate->target_rate, kDefaultMinBitrate); EXPECT_NEAR(update.pacer_config->data_rate().bps(), kDefaultMinBitrate.bps() * kDefaultPacingRate, 10); EXPECT_EQ(update.probe_cluster_configs.size(), 2u); } TEST(GoogCcNetworkControllerTest, ProbeOnRouteChange) { NetworkControllerTestFixture fixture; std::unique_ptr controller = fixture.CreateController(); Timestamp current_time = Timestamp::Millis(123); NetworkControlUpdate update = controller->OnNetworkAvailability( {.at_time = current_time, .network_available = true}); current_time += TimeDelta::Seconds(3); update = controller->OnNetworkRouteChange( CreateRouteChange(current_time, 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({.at_time = current_time}); } TEST(GoogCcNetworkControllerTest, ProbeAfterRouteChangeWhenTransportWritable) { NetworkControllerTestFixture fixture; std::unique_ptr controller = fixture.CreateController(); Timestamp current_time = Timestamp::Millis(123); NetworkControlUpdate update = controller->OnNetworkAvailability( {.at_time = current_time, .network_available = false}); EXPECT_THAT(update.probe_cluster_configs, IsEmpty()); update = controller->OnNetworkRouteChange( CreateRouteChange(current_time, 2 * kInitialBitrate, DataRate::Zero(), 20 * kInitialBitrate)); // Transport is not writable. So not point in sending a probe. EXPECT_THAT(update.probe_cluster_configs, IsEmpty()); // Probe is sent when transport becomes writable. update = controller->OnNetworkAvailability( {.at_time = current_time, .network_available = true}); EXPECT_THAT(update.probe_cluster_configs, Not(IsEmpty())); } // Bandwidth estimation is updated when feedbacks are received. // Feedbacks which show an increasing delay cause the estimation to be reduced. TEST(GoogCcNetworkControllerTest, UpdatesDelayBasedEstimate) { NetworkControllerTestFixture fixture; std::unique_ptr controller = fixture.CreateController(); const int64_t kRunTimeMs = 6000; Timestamp current_time = Timestamp::Millis(123); NetworkControlUpdate update = controller->OnNetworkAvailability( {.at_time = current_time, .network_available = true}); // 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. absl::optional target_bitrate_before_delay = PacketTransmissionAndFeedbackBlock(controller.get(), kRunTimeMs, 0, current_time); ASSERT_TRUE(target_bitrate_before_delay.has_value()); // Repeat, but this time with a building delay, and make sure that the // estimation is adjusted downwards. absl::optional target_bitrate_after_delay = PacketTransmissionAndFeedbackBlock(controller.get(), kRunTimeMs, 50, current_time); EXPECT_LT(*target_bitrate_after_delay, *target_bitrate_before_delay); } TEST(GoogCcNetworkControllerTest, PaceAtMaxOfLowerLinkCapacityAndBwe) { ScopedFieldTrials trial( "WebRTC-Bwe-PaceAtMaxOfBweAndLowerLinkCapacity/Enabled/"); NetworkControllerTestFixture fixture; std::unique_ptr controller = fixture.CreateController(); Timestamp current_time = Timestamp::Millis(123); NetworkControlUpdate update = controller->OnNetworkAvailability( {.at_time = current_time, .network_available = true}); update = controller->OnProcessInterval({.at_time = current_time}); current_time += TimeDelta::Millis(100); NetworkStateEstimate network_estimate = {.link_capacity_lower = 10 * kInitialBitrate}; update = controller->OnNetworkStateEstimate(network_estimate); // OnNetworkStateEstimate does not trigger processing a new estimate. So add a // dummy loss report to trigger a BWE update in the next process interval. TransportLossReport loss_report; loss_report.start_time = current_time; loss_report.end_time = current_time; loss_report.receive_time = current_time; loss_report.packets_received_delta = 50; loss_report.packets_lost_delta = 1; update = controller->OnTransportLossReport(loss_report); update = controller->OnProcessInterval({.at_time = current_time}); ASSERT_TRUE(update.pacer_config); ASSERT_TRUE(update.target_rate); ASSERT_LT(update.target_rate->target_rate, network_estimate.link_capacity_lower); EXPECT_EQ(update.pacer_config->data_rate().kbps(), network_estimate.link_capacity_lower.kbps() * kDefaultPacingRate); current_time += TimeDelta::Millis(100); // Set a low link capacity estimate and verify that pacing rate is set // relative to loss based/delay based estimate. network_estimate = {.link_capacity_lower = 0.5 * kInitialBitrate}; update = controller->OnNetworkStateEstimate(network_estimate); // Again, we need to inject a dummy loss report to trigger an update of the // BWE in the next process interval. loss_report.start_time = current_time; loss_report.end_time = current_time; loss_report.receive_time = current_time; loss_report.packets_received_delta = 50; loss_report.packets_lost_delta = 0; update = controller->OnTransportLossReport(loss_report); update = controller->OnProcessInterval({.at_time = current_time}); ASSERT_TRUE(update.target_rate); ASSERT_GT(update.target_rate->target_rate, network_estimate.link_capacity_lower); EXPECT_EQ(update.pacer_config->data_rate().kbps(), update.target_rate->target_rate.kbps() * kDefaultPacingRate); } // Test congestion window pushback on network delay happens. TEST(GoogCcScenario, 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(GoogCcScenario, 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(GoogCcScenario, 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(GoogCcScenario, 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(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(GoogCcScenario, UpdatesTargetRateBasedOnLinkCapacity) { UpdatesTargetRateBasedOnLinkCapacity(); } TEST(GoogCcScenario, 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(GoogCcScenario, LossBasedControlUpdatesTargetRateBasedOnLinkCapacity) { ScopedFieldTrials trial("WebRTC-Bwe-LossBasedControl/Enabled/"); // TODO(srte): Should the behavior be unaffected at low loss rates? UpdatesTargetRateBasedOnLinkCapacity("_loss_based"); } TEST(GoogCcScenario, 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(absl::string_view 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). VideoReceiveStreamInterface::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(GoogCcScenario, 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(GoogCcScenario, 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(GoogCcScenario, DetectsHighRateInSafeResetTrial) { ScopedFieldTrials trial("WebRTC-Bwe-SafeResetOnRouteChange/Enabled,ack/"); 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(GoogCcScenario, 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(GoogCcScenario, 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 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(GoogCcScenario, 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(GoogCcScenario, 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(GoogCcScenario, 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, FallbackToLossBasedBweWithoutPacketFeedback) { const DataRate kLinkCapacity = DataRate::KilobitsPerSec(1000); const DataRate kStartRate = DataRate::KilobitsPerSec(1000); Scenario s("googcc_unit/high_loss_channel", false); auto* net = s.CreateMutableSimulationNode([&](NetworkSimulationConfig* c) { c->bandwidth = kLinkCapacity; c->delay = TimeDelta::Millis(100); }); auto* client = s.CreateClient("send", [&](CallClientConfig* c) { c->transport.rates.start_rate = kStartRate; }); auto* route = s.CreateRoutes( client, {net->node()}, s.CreateClient("return", CallClientConfig()), {s.CreateSimulationNode(NetworkSimulationConfig())}); // Create a config without packet feedback. VideoStreamConfig video_config; video_config.stream.packet_feedback = false; s.CreateVideoStream(route->forward(), video_config); s.RunFor(TimeDelta::Seconds(20)); // Bandwith does not backoff because network is normal. EXPECT_GE(client->target_rate().kbps(), 500); // Update the network to create high loss ratio net->UpdateConfig([](NetworkSimulationConfig* c) { c->loss_rate = 0.15; }); s.RunFor(TimeDelta::Seconds(20)); // Bandwidth decreases thanks to loss based bwe v0. EXPECT_LE(client->target_rate().kbps(), 300); } class GoogCcRttTest : public ::testing::TestWithParam { protected: GoogCcFactoryConfig Config(bool feedback_only) { GoogCcFactoryConfig config; config.feedback_only = feedback_only; return config; } }; TEST_P(GoogCcRttTest, CalculatesRttFromTransporFeedback) { GoogCcFactoryConfig config(Config(/*feedback_only=*/GetParam())); if (!GetParam()) { // TODO(diepbp): understand the usage difference between // UpdatePropagationRtt and UpdateRtt GTEST_SKIP() << "This test should run only if " "feedback_only is enabled"; } NetworkControllerTestFixture fixture(std::move(config)); std::unique_ptr controller = fixture.CreateController(); Timestamp current_time = Timestamp::Millis(123); TimeDelta one_way_delay = TimeDelta::Millis(10); absl::optional rtt = absl::nullopt; TransportPacketsFeedback feedback = CreateTransportPacketsFeedback( /*per_packet_network_delay=*/TimeDelta::Millis(50), one_way_delay, /*send_time=*/current_time); NetworkControlUpdate update = controller->OnTransportPacketsFeedback(feedback); current_time += TimeDelta::Millis(50); update = controller->OnProcessInterval({.at_time = current_time}); if (update.target_rate) { rtt = update.target_rate->network_estimate.round_trip_time; } ASSERT_TRUE(rtt.has_value()); EXPECT_EQ(rtt->ms(), 2 * one_way_delay.ms()); } INSTANTIATE_TEST_SUITE_P(GoogCcRttTests, GoogCcRttTest, ::testing::Bool()); } // namespace test } // namespace webrtc