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