Add TargetTransferRate.is_bwe_limited and support in googcc and scream This is exposed in order to know if the congestion controller currently believe WebRTC packet sending is constrained by the BWE. Change-Id: Ia4bd825a59018d3e7814285e235a30d2ee7c8167 Bug: webrtc:519655843 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/478441 Commit-Queue: Björn Terelius <terelius@webrtc.org> Auto-Submit: Per Kjellander <perkj@webrtc.org> Reviewed-by: Björn Terelius <terelius@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47901}
diff --git a/api/transport/network_types.h b/api/transport/network_types.h index 6d92778..f1f3736 100644 --- a/api/transport/network_types.h +++ b/api/transport/network_types.h
@@ -274,6 +274,10 @@ NetworkEstimate network_estimate; DataRate target_rate = DataRate::Zero(); double cwnd_reduce_ratio = 0; + + // True if WebRTC is actively sending near estimated link capacity (not in + // ALR). + bool is_bandwidth_limited = true; }; // Contains updates of network controller comand state. Using optionals to
diff --git a/modules/congestion_controller/goog_cc/goog_cc_network_control.cc b/modules/congestion_controller/goog_cc/goog_cc_network_control.cc index 2ba1aa6..f84c779 100644 --- a/modules/congestion_controller/goog_cc/goog_cc_network_control.cc +++ b/modules/congestion_controller/goog_cc/goog_cc_network_control.cc
@@ -573,6 +573,8 @@ update.target_rate->at_time = at_time; update.target_rate->target_rate = last_pushback_target_rate_; + update.target_rate->is_bandwidth_limited = + !alr_detector_.GetApplicationLimitedRegionStartTime().has_value(); update.pacer_config = GetPacingRates(at_time); update.congestion_window = current_data_window_; return update; @@ -602,16 +604,21 @@ } } + bool is_bandwidth_limited = + !alr_detector_.GetApplicationLimitedRegionStartTime().has_value(); + if ((loss_based_target_rate != last_loss_based_target_rate_) || (loss_based_state != last_loss_base_state_) || (fraction_loss != last_estimated_fraction_loss_) || (round_trip_time != last_estimated_round_trip_time_) || - (pushback_target_rate != last_pushback_target_rate_)) { + (pushback_target_rate != last_pushback_target_rate_) || + (is_bandwidth_limited != last_is_bandwidth_limited_)) { last_loss_based_target_rate_ = loss_based_target_rate; last_pushback_target_rate_ = pushback_target_rate; last_estimated_fraction_loss_ = fraction_loss; last_estimated_round_trip_time_ = round_trip_time; last_loss_base_state_ = loss_based_state; + last_is_bandwidth_limited_ = is_bandwidth_limited; alr_detector_.SetEstimatedBitrate(loss_based_target_rate); @@ -629,6 +636,7 @@ target_rate_msg.network_estimate.round_trip_time = round_trip_time; target_rate_msg.network_estimate.loss_rate_ratio = fraction_loss / 255.0f; target_rate_msg.network_estimate.bwe_period = bwe_period; + target_rate_msg.is_bandwidth_limited = is_bandwidth_limited; update->target_rate = target_rate_msg;
diff --git a/modules/congestion_controller/goog_cc/goog_cc_network_control.h b/modules/congestion_controller/goog_cc/goog_cc_network_control.h index 618d172..74c9fa1 100644 --- a/modules/congestion_controller/goog_cc/goog_cc_network_control.h +++ b/modules/congestion_controller/goog_cc/goog_cc_network_control.h
@@ -133,6 +133,7 @@ DataRate max_padding_rate_; bool previously_in_alr_ = false; + bool last_is_bandwidth_limited_ = true; std::optional<DataSize> current_data_window_; };
diff --git a/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc b/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc index d267d86..225ba8a 100644 --- a/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc +++ b/modules/congestion_controller/goog_cc/goog_cc_network_control_unittest.cc
@@ -1021,5 +1021,74 @@ EXPECT_LE(client->target_rate().kbps(), 300); } +TEST(GoogCcNetworkControllerTest, + TriggersTargetRateUpdateWhenBandwidthLimitedChanges) { + NetworkControllerTestFixture fixture; + std::unique_ptr<NetworkControllerInterface> controller = + fixture.CreateController(); + Timestamp current_time = Timestamp::Millis(123456); + + NetworkControlUpdate update = controller->OnNetworkAvailability( + {.at_time = current_time, .network_available = true}); + update = controller->OnProcessInterval({.at_time = current_time}); + ASSERT_TRUE(update.target_rate.has_value()); + EXPECT_TRUE(update.target_rate->is_bandwidth_limited); + + // Send packet to initialize ALR detector + SentPacket sent_packet; + sent_packet.send_time = current_time; + sent_packet.size = DataSize::Bytes(1000); + update = controller->OnSentPacket(sent_packet); + + // Wait 10 seconds without sending any packets (this will make budget ratio + // accumulate and enter ALR) + current_time += TimeDelta::Seconds(10); + // Send a tiny packet to trigger AlrDetector state change evaluation + sent_packet.send_time = current_time; + sent_packet.size = DataSize::Bytes(1); + update = controller->OnSentPacket(sent_packet); + + bool alr_detected = false; + if (update.target_rate.has_value() && + !update.target_rate->is_bandwidth_limited) { + alr_detected = true; + } else { + // Process interval should trigger target rate update if not already + // triggered by OnSentPacket + update = controller->OnProcessInterval({.at_time = current_time}); + if (update.target_rate.has_value() && + !update.target_rate->is_bandwidth_limited) { + alr_detected = true; + } + } + EXPECT_TRUE(alr_detected); + + // Now send a large burst of packets to leave ALR. + // Target rate is kInitialBitrate (60kbps). Budget ratio needs to drop below + // 0.5. We send 50 packets of 1000 bytes. + bool bandwidth_limited_detected = false; + for (int i = 0; i < 50; ++i) { + current_time += TimeDelta::Millis(1); + sent_packet.send_time = current_time; + sent_packet.size = DataSize::Bytes(1000); + update = controller->OnSentPacket(sent_packet); + if (update.target_rate.has_value() && + update.target_rate->is_bandwidth_limited) { + bandwidth_limited_detected = true; + } + } + + if (!bandwidth_limited_detected) { + // Process interval should trigger target rate update if not already + // triggered by OnSentPacket + update = controller->OnProcessInterval({.at_time = current_time}); + if (update.target_rate.has_value() && + update.target_rate->is_bandwidth_limited) { + bandwidth_limited_detected = true; + } + } + EXPECT_TRUE(bandwidth_limited_detected); +} + } // namespace test } // namespace webrtc
diff --git a/modules/congestion_controller/scream/scream_network_controller.cc b/modules/congestion_controller/scream/scream_network_controller.cc index fab186e..a33b3e6 100644 --- a/modules/congestion_controller/scream/scream_network_controller.cc +++ b/modules/congestion_controller/scream/scream_network_controller.cc
@@ -200,8 +200,11 @@ NetworkControlUpdate ScreamNetworkController::CreateUpdate(Timestamp now) { NetworkControlUpdate update; - if (scream_->target_rate() != reported_target_rate_) { + bool is_bandwidth_limited = !scream_->is_application_limited(); + if (scream_->target_rate() != reported_target_rate_ || + is_bandwidth_limited != reported_is_bandwidth_limited_) { reported_target_rate_ = scream_->target_rate(); + reported_is_bandwidth_limited_ = is_bandwidth_limited; TargetTransferRate target_rate_msg; target_rate_msg.at_time = now; target_rate_msg.target_rate = scream_->target_rate(); @@ -210,6 +213,7 @@ // TODO: bugs.webrtc.org/447037083 - bwe_period must currently be set but // it seems like it is not used for anything sensible. Try to remove it. target_rate_msg.network_estimate.bwe_period = TimeDelta::Millis(25); + target_rate_msg.is_bandwidth_limited = is_bandwidth_limited; update.target_rate = target_rate_msg; } update.pacer_config = MaybeCreatePacerConfig(now);
diff --git a/modules/congestion_controller/scream/scream_network_controller.h b/modules/congestion_controller/scream/scream_network_controller.h index 2a03ce6..ed4c6ba 100644 --- a/modules/congestion_controller/scream/scream_network_controller.h +++ b/modules/congestion_controller/scream/scream_network_controller.h
@@ -74,6 +74,7 @@ DataRate reported_target_rate_; DataRate reported_padding_rate_; DataRate reported_pacing_rate_; + bool reported_is_bandwidth_limited_ = true; }; } // namespace webrtc
diff --git a/modules/congestion_controller/scream/scream_network_controller_unittest.cc b/modules/congestion_controller/scream/scream_network_controller_unittest.cc index 1ea8b61..c9bee8e 100644 --- a/modules/congestion_controller/scream/scream_network_controller_unittest.cc +++ b/modules/congestion_controller/scream/scream_network_controller_unittest.cc
@@ -677,5 +677,121 @@ EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(300)); } +TEST(ScreamControllerTest, ReportsIsBandwidthLimited) { + SimulatedClock clock(Timestamp::Seconds(1'234)); + Environment env = CreateTestEnvironment({.time = &clock}); + NetworkControllerConfig config(env); + config.constraints.starting_rate = DataRate::KilobitsPerSec(100); + config.constraints.max_data_rate = DataRate::KilobitsPerSec(5000); + ScreamNetworkController scream_controller(config); + + // Initially, is_bandwidth_limited should be true because + // is_application_limited starts as false. + NetworkControlUpdate update = scream_controller.OnNetworkAvailability( + {.at_time = clock.CurrentTime(), .network_available = true}); + ASSERT_TRUE(update.target_rate.has_value()); + EXPECT_TRUE(update.target_rate->is_bandwidth_limited); + + CcFeedbackGenerator feedback_generator( + {.network_config = {.queue_delay_ms = 10, + .link_capacity = DataRate::KilobitsPerSec(2000)}}); + + DataRate target_rate = DataRate::KilobitsPerSec(100); + // Send at full BWE rate so we are not application limited. + for (int i = 0; i < 20; ++i) { + TransportPacketsFeedback feedback = + feedback_generator.ProcessUntilNextFeedback( + target_rate, clock, + [&](SentPacket packet) { scream_controller.OnSentPacket(packet); }); + update = scream_controller.OnTransportPacketsFeedback(feedback); + if (update.target_rate.has_value()) { + target_rate = update.target_rate->target_rate; + EXPECT_TRUE(update.target_rate->is_bandwidth_limited); + } + } + + // Now send at a very low rate (e.g. 50kbps) while BWE remains high. + // This should trigger application limited state. + bool alr_detected = false; + for (int i = 0; i < 50; ++i) { + TransportPacketsFeedback feedback = + feedback_generator.ProcessUntilNextFeedback( + DataRate::KilobitsPerSec(50), clock, + [&](SentPacket packet) { scream_controller.OnSentPacket(packet); }); + update = scream_controller.OnTransportPacketsFeedback(feedback); + if (update.target_rate.has_value()) { + if (!update.target_rate->is_bandwidth_limited) { + alr_detected = true; + break; + } + } + } + EXPECT_TRUE(alr_detected); +} + +TEST(ScreamControllerTest, ReportsIsBandwidthLimitedEvenIfTargetRateClamped) { + SimulatedClock clock(Timestamp::Seconds(1'234)); + Environment env = CreateTestEnvironment({.time = &clock}); + NetworkControllerConfig config(env); + config.constraints.starting_rate = DataRate::KilobitsPerSec(500); + config.constraints.min_data_rate = DataRate::KilobitsPerSec(500); + config.constraints.max_data_rate = DataRate::KilobitsPerSec(500); + ScreamNetworkController scream_controller(config); + + NetworkControlUpdate update = scream_controller.OnNetworkAvailability( + {.at_time = clock.CurrentTime(), .network_available = true}); + ASSERT_TRUE(update.target_rate.has_value()); + EXPECT_TRUE(update.target_rate->is_bandwidth_limited); + EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(500)); + + CcFeedbackGenerator feedback_generator( + {.network_config = {.queue_delay_ms = 150, + .link_capacity = DataRate::KilobitsPerSec(2000)}}); + + // Warm up to build up data in flight so that we are not application limited. + for (int i = 0; i < 5; ++i) { + TransportPacketsFeedback feedback = + feedback_generator.ProcessUntilNextFeedback( + DataRate::KilobitsPerSec(500), clock, + [&](SentPacket packet) { scream_controller.OnSentPacket(packet); }); + scream_controller.OnTransportPacketsFeedback(feedback); + } + + // Now we should be actively sending near capacity (is_bandwidth_limited = + // true). + for (int i = 0; i < 15; ++i) { + TransportPacketsFeedback feedback = + feedback_generator.ProcessUntilNextFeedback( + DataRate::KilobitsPerSec(500), clock, + [&](SentPacket packet) { scream_controller.OnSentPacket(packet); }); + update = scream_controller.OnTransportPacketsFeedback(feedback); + if (update.target_rate.has_value()) { + EXPECT_TRUE(update.target_rate->is_bandwidth_limited); + EXPECT_EQ(update.target_rate->target_rate, DataRate::KilobitsPerSec(500)); + } + } + + // Now send at a very low rate (50kbps). + // The target rate is clamped at 500kbps so it cannot change. + // We expect an update.target_rate to be generated anyway because + // is_bandwidth_limited changes to false. + bool alr_detected = false; + for (int i = 0; i < 50; ++i) { + TransportPacketsFeedback feedback = + feedback_generator.ProcessUntilNextFeedback( + DataRate::KilobitsPerSec(50), clock, + [&](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)); + if (!update.target_rate->is_bandwidth_limited) { + alr_detected = true; + break; + } + } + } + EXPECT_TRUE(alr_detected); +} + } // namespace } // namespace webrtc