SCReAM: Add cwnd pushback calculation to ScreamNetworkController

Calculate cwnd_reduce_ratio in ScreamNetworkController based on pacer queue size and data in flight exceeding max_data_in_flight.

- Add min_pacing_delay_for_pushback (default 100ms) and max_pacing_delay_for_pushback (default 500ms) field trial parameters to ScreamV2Parameters.
- Calculate cwnd_reduce_ratio using pacing_rate and pacer_queue_size_ or data_in_flight > max_data_in_flight.
- Add unit tests verifying cwnd_reduce_ratio behavior when pacer queue grows/shrinks and when congestion window is full.

Bug: webrtc:447037083
Change-Id: Ic7983241d7d41fdecc0eabbcf9fc384558631575
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/490860
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#48225}
diff --git a/modules/congestion_controller/scream/scream_network_controller.cc b/modules/congestion_controller/scream/scream_network_controller.cc
index 40b66c5..8cc68ce 100644
--- a/modules/congestion_controller/scream/scream_network_controller.cc
+++ b/modules/congestion_controller/scream/scream_network_controller.cc
@@ -11,6 +11,7 @@
 #include "modules/congestion_controller/scream/scream_network_controller.h"
 
 #include <algorithm>
+#include <cmath>
 #include <memory>
 #include <optional>
 #include <utility>
@@ -119,9 +120,10 @@
 
 NetworkControlUpdate ScreamNetworkController::OnProcessInterval(
     ProcessInterval msg) {
-  NetworkControlUpdate update;
-  update.pacer_config = MaybeCreatePacerConfig(msg.at_time);
-  return update;
+  if (msg.pacer_queue) {
+    pacer_queue_size_ = *msg.pacer_queue;
+  }
+  return CreateUpdate(msg.at_time);
 }
 
 NetworkControlUpdate ScreamNetworkController::OnRemoteBitrateReport(
@@ -139,6 +141,7 @@
 
 NetworkControlUpdate ScreamNetworkController::OnSentPacket(SentPacket msg) {
   scream_->OnPacketSent(msg.data_in_flight);
+  data_in_flight_ = msg.data_in_flight;
   if (msg.data_in_flight > scream_->max_data_in_flight() ||
       scream_->delay_based_congestion_control().IsQueueDelayDetected()) {
     return CreateUpdate(msg.send_time);
@@ -195,19 +198,44 @@
 NetworkControlUpdate ScreamNetworkController::OnTransportPacketsFeedback(
     TransportPacketsFeedback msg) {
   scream_->OnTransportPacketsFeedback(msg);
+  data_in_flight_ = msg.data_in_flight;
   return CreateUpdate(msg.feedback_time);
 }
 
+double ScreamNetworkController::CalculateCwndReduceRatio() const {
+  if (data_in_flight_ > scream_->max_data_in_flight()) {
+    return 1.0;
+  }
+
+  double cwnd_reduce_ratio = 0.0;
+  if (scream_->pacing_rate() > DataRate::Zero() &&
+      !pacer_queue_size_.IsZero()) {
+    TimeDelta pacing_delay = pacer_queue_size_ / scream_->pacing_rate();
+    TimeDelta min_delay = params_.min_pacing_delay_for_pushback.Get();
+    TimeDelta max_delay = params_.max_pacing_delay_for_pushback.Get();
+    if (max_delay > min_delay) {
+      double ratio = (pacing_delay - min_delay) / (max_delay - min_delay);
+      cwnd_reduce_ratio += std::clamp(ratio, 0.0, 1.0);
+    }
+  }
+  return std::clamp(cwnd_reduce_ratio, 0.0, 1.0);
+}
+
 NetworkControlUpdate ScreamNetworkController::CreateUpdate(Timestamp now) {
   NetworkControlUpdate update;
   bool is_bandwidth_limited = !scream_->is_application_limited();
+  double cwnd_reduce_ratio = CalculateCwndReduceRatio();
+
   if (scream_->target_rate() != reported_target_rate_ ||
-      is_bandwidth_limited != reported_is_bandwidth_limited_) {
+      is_bandwidth_limited != reported_is_bandwidth_limited_ ||
+      std::abs(cwnd_reduce_ratio - reported_cwnd_reduce_ratio_) > 0.1) {
     reported_target_rate_ = scream_->target_rate();
     reported_is_bandwidth_limited_ = is_bandwidth_limited;
+    reported_cwnd_reduce_ratio_ = cwnd_reduce_ratio;
     TargetTransferRate target_rate_msg;
     target_rate_msg.at_time = now;
     target_rate_msg.target_rate = scream_->target_rate();
+    target_rate_msg.cwnd_reduce_ratio = cwnd_reduce_ratio;
     target_rate_msg.network_estimate.at_time = now;
     target_rate_msg.network_estimate.round_trip_time = scream_->rtt();
     target_rate_msg.is_bandwidth_limited = is_bandwidth_limited;
diff --git a/modules/congestion_controller/scream/scream_network_controller.h b/modules/congestion_controller/scream/scream_network_controller.h
index ed4c6ba..af9adfb 100644
--- a/modules/congestion_controller/scream/scream_network_controller.h
+++ b/modules/congestion_controller/scream/scream_network_controller.h
@@ -17,6 +17,7 @@
 #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 "modules/congestion_controller/scream/scream_v2.h"
@@ -51,6 +52,12 @@
   NetworkControlUpdate CreateFirstUpdate(Timestamp now);
   NetworkControlUpdate CreateUpdate(Timestamp now);
   std::optional<PacerConfig> MaybeCreatePacerConfig(Timestamp now);
+  // Calculates a ratio in [0.0, 1.0] indicating how much the video encoder
+  // should reduce its target bitrate (pushback) due to network or pacer queue
+  // build-up. Returns 1.0 if data in flight exceeds max_data_in_flight.
+  // Otherwise, if pacer queue delay exceeds min_pacing_delay_for_pushback,
+  // the ratio scales linearly up to 1.0 at max_pacing_delay_for_pushback.
+  double CalculateCwndReduceRatio() const;
 
   Environment env_;
   const ScreamV2Parameters params_;
@@ -68,6 +75,8 @@
   DataRate max_seen_total_allocated_bitrate_ = DataRate::Zero();
   Timestamp initial_bwe_probe_end_time_ = Timestamp::MinusInfinity();
   Timestamp padding_interval_end_time_ = Timestamp::MinusInfinity();
+  DataSize pacer_queue_size_ = DataSize::Zero();
+  DataSize data_in_flight_ = DataSize::Zero();
 
   // Values last reported in a NetworkControlUpdate. Used for finding out if an
   // update needs to be reported.
@@ -75,6 +84,7 @@
   DataRate reported_padding_rate_;
   DataRate reported_pacing_rate_;
   bool reported_is_bandwidth_limited_ = true;
+  double reported_cwnd_reduce_ratio_ = 0.0;
 };
 
 }  // 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 0461527..1960fba 100644
--- a/modules/congestion_controller/scream/scream_network_controller_unittest.cc
+++ b/modules/congestion_controller/scream/scream_network_controller_unittest.cc
@@ -890,5 +890,60 @@
   EXPECT_GE(target_rate, DataRate::KilobitsPerSec(9800));
 }
 
+TEST(ScreamControllerTest, CwndReduceRatioSetWhenPacerQueueGrowsAndShrinks) {
+  SimulatedClock clock(Timestamp::Seconds(1'234));
+  Environment env = CreateTestEnvironment({.time = &clock});
+  NetworkControllerConfig config(env);
+  config.constraints.starting_rate = DataRate::KilobitsPerSec(1000);
+  ScreamNetworkController scream_controller(config);
+
+  scream_controller.OnNetworkAvailability(
+      {.at_time = clock.CurrentTime(), .network_available = true});
+
+  // Starting rate is 1000 kbps, so pacing rate = 1.1 * 1000 kbps = 1100 kbps
+  // (137,500 bytes/sec). Pacer queue of 41250 bytes gives (41250/137500) =
+  // 300ms pacing delay. Expected ratio: (300ms - 100ms) / (500ms - 100ms) = 200
+  // / 400 = 0.5.
+  ProcessInterval msg1;
+  msg1.at_time = clock.CurrentTime();
+  msg1.pacer_queue = DataSize::Bytes(41250);
+
+  NetworkControlUpdate update1 = scream_controller.OnProcessInterval(msg1);
+  ASSERT_TRUE(update1.target_rate.has_value());
+  EXPECT_NEAR(update1.target_rate->cwnd_reduce_ratio, 0.5, 0.05);
+
+  // Send ProcessInterval with empty pacer queue -> cwnd_reduce_ratio drops back
+  // to 0.0.
+  clock.AdvanceTime(TimeDelta::Millis(100));
+  ProcessInterval msg2;
+  msg2.at_time = clock.CurrentTime();
+  msg2.pacer_queue = DataSize::Zero();
+
+  NetworkControlUpdate update2 = scream_controller.OnProcessInterval(msg2);
+  ASSERT_TRUE(update2.target_rate.has_value());
+  EXPECT_EQ(update2.target_rate->cwnd_reduce_ratio, 0.0);
+}
+
+TEST(ScreamControllerTest, CwndReduceRatioSetToOneWhenCongestionWindowIsFull) {
+  SimulatedClock clock(Timestamp::Seconds(1'234));
+  Environment env = CreateTestEnvironment({.time = &clock});
+  NetworkControllerConfig config(env);
+  config.constraints.starting_rate = DataRate::KilobitsPerSec(1000);
+  ScreamNetworkController scream_controller(config);
+
+  scream_controller.OnNetworkAvailability(
+      {.at_time = clock.CurrentTime(), .network_available = true});
+
+  // Send a packet where data_in_flight (12000 bytes) exceeds max_data_in_flight
+  // (~10000 bytes).
+  SentPacket sent_packet;
+  sent_packet.send_time = clock.CurrentTime();
+  sent_packet.data_in_flight = DataSize::Bytes(12000);
+
+  NetworkControlUpdate update = scream_controller.OnSentPacket(sent_packet);
+  ASSERT_TRUE(update.target_rate.has_value());
+  EXPECT_EQ(update.target_rate->cwnd_reduce_ratio, 1.0);
+}
+
 }  // namespace
 }  // namespace webrtc
diff --git a/modules/congestion_controller/scream/scream_v2_parameters.cc b/modules/congestion_controller/scream/scream_v2_parameters.cc
index edc4a43..f0dee98 100644
--- a/modules/congestion_controller/scream/scream_v2_parameters.cc
+++ b/modules/congestion_controller/scream/scream_v2_parameters.cc
@@ -69,7 +69,11 @@
           TimeDelta::Seconds(15)),
       enable_alr("EnableAlr", true),
       alr_threshold("AlrThreshold", 0.9),
-      received_rate_window("ReceivedRateWindow", TimeDelta::Millis(100)) {
+      received_rate_window("ReceivedRateWindow", TimeDelta::Millis(100)),
+      min_pacing_delay_for_pushback("MinPacingDelayForPushback",
+                                    TimeDelta::Millis(100)),
+      max_pacing_delay_for_pushback("MaxPacingDelayForPushback",
+                                    TimeDelta::Millis(500)) {
   ParseFieldTrial({&min_ref_window,
                    &l4s_avg_g_up,
                    &l4s_avg_g_down,
@@ -108,7 +112,9 @@
                    &allow_large_pacing_bursts_after_congestion_time,
                    &enable_alr,
                    &alr_threshold,
-                   &received_rate_window},
+                   &received_rate_window,
+                   &min_pacing_delay_for_pushback,
+                   &max_pacing_delay_for_pushback},
                   trials.Lookup("WebRTC-Bwe-ScreamV2"));
 }
 
diff --git a/modules/congestion_controller/scream/scream_v2_parameters.h b/modules/congestion_controller/scream/scream_v2_parameters.h
index d21c444..9f6f99f 100644
--- a/modules/congestion_controller/scream/scream_v2_parameters.h
+++ b/modules/congestion_controller/scream/scream_v2_parameters.h
@@ -159,6 +159,11 @@
 
   // Window over which received rate is calculated.
   FieldTrialParameter<TimeDelta> received_rate_window;
+
+  // Minimum pacing delay before starting cwnd pushback reduction.
+  FieldTrialParameter<TimeDelta> min_pacing_delay_for_pushback;
+  // Maximum pacing delay for full cwnd pushback reduction.
+  FieldTrialParameter<TimeDelta> max_pacing_delay_for_pushback;
 };
 
 }  // namespace webrtc
diff --git a/test/peer_scenario/bwe_integration_tests/scream_test.cc b/test/peer_scenario/bwe_integration_tests/scream_test.cc
index bc1947f..57b5b09 100644
--- a/test/peer_scenario/bwe_integration_tests/scream_test.cc
+++ b/test/peer_scenario/bwe_integration_tests/scream_test.cc
@@ -545,7 +545,7 @@
   SendMediaTestResult result = SendMediaInOneDirection(std::move(params), s);
   EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
                                               DataRate::KilobitsPerSec(1300),
-                                              DataRate::KilobitsPerSec(2300))));
+                                              DataRate::KilobitsPerSec(2500))));
 }
 
 TEST(ScreamTest, MaybeTest(LinkCapacity2MbpsRtt50msEcn)) {