Make scream  more resilient to delay spikes

Use the minimum queue_delay from the last two feedback when calculating average queue_delay. This decrease the chance of reducing reference window due to increased delay that is only seen in one feedback.

Also replace usage of queue_delay_dev_norm with DelayBasedCongestionControl::ref_window_scale_factor_due_to_avg_min_delay and DelayBasedCongestionControl::ref_window_scale_factor_due_to_latency_difference
Both are used similarly to the old queue_delay_dev_norm, but are calculated based on the minimum average min queue delay and the average
latency difference per feeedback.

This replace previous:
if (l4s_alpha < 0.0001)
   increment_t *= max(0.1, 1.0 - qdelay_avg / (qdelay_target / 4))
end

Bug: webrtc:447037083
Change-Id: Ic713258b2d0a50b486dc93a89f7c947156d86907
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/453300
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47129}
diff --git a/modules/congestion_controller/scream/delay_based_congestion_control.cc b/modules/congestion_controller/scream/delay_based_congestion_control.cc
index 4cc5e0d..94be767 100644
--- a/modules/congestion_controller/scream/delay_based_congestion_control.cc
+++ b/modules/congestion_controller/scream/delay_based_congestion_control.cc
@@ -39,15 +39,16 @@
   if (received_packets.empty()) {
     return;
   }
-  TimeDelta one_way_delay_sum;
+
   TimeDelta min_one_way_delay = TimeDelta::PlusInfinity();
+  TimeDelta max_one_way_delay = TimeDelta::Zero();
 
   for (const PacketResult& packet : received_packets) {
     TimeDelta one_way_delay =
         packet.receive_time - packet.sent_packet.send_time;
     next_base_delay_ = std::min(next_base_delay_, one_way_delay);
-    one_way_delay_sum += one_way_delay;
     min_one_way_delay = std::min(min_one_way_delay, one_way_delay);
+    max_one_way_delay = std::max(max_one_way_delay, one_way_delay);
   }
   // `arrival_time_offset` is null if TWCC is used. We assume feedback was sent
   // when the last sent packet was received.
@@ -66,8 +67,12 @@
   } else {
     min_queue_delay_above_threshold_start_ = Timestamp::MinusInfinity();
   }
-  UpdateQueueDelayAverage(one_way_delay_sum /
-                          static_cast<int>(received_packets.size()));
+  UpdateQueueDelayAverage(std::min(min_queue_delay, last_queue_delay_sample_));
+  last_queue_delay_sample_ = min_queue_delay;
+  UpdateQueueDelayMinAverage(min_queue_delay);
+  UpdateLatencyDifferenceAverage(min_one_way_delay.IsFinite()
+                                     ? max_one_way_delay - min_one_way_delay
+                                     : TimeDelta::Zero());
 
   if (msg.feedback_time - last_base_delay_update_ >=
       params_.base_delay_history_update_interval.Get()) {
@@ -78,26 +83,37 @@
 }
 
 void DelayBasedCongestionControl::UpdateQueueDelayAverage(
-    TimeDelta one_way_delay) {
-  TimeDelta current_qdelay = one_way_delay - min_base_delay();
-
+    TimeDelta min_qdelay_in_feedback) {
   // `queue_delay_avg_` is updated with a slow attack,fast decay EWMA
   // filter.
-  if (current_qdelay < queue_delay_avg_) {
-    queue_delay_avg_ = current_qdelay;
+  if (min_qdelay_in_feedback < queue_delay_avg_) {
+    queue_delay_avg_ = min_qdelay_in_feedback;
   } else {
     queue_delay_avg_ =
-        params_.queue_delay_avg_g.Get() * current_qdelay +
+        params_.queue_delay_avg_g.Get() * min_qdelay_in_feedback +
         (1.0 - params_.queue_delay_avg_g.Get()) * queue_delay_avg_;
   }
+}
 
-  queue_delay_dev_norm_ =
-      (1.0 - params_.queue_delay_dev_avg_g.Get()) * queue_delay_dev_norm_ +
-      params_.queue_delay_dev_avg_g.Get() *
-          std::clamp((current_qdelay -
-                      params_.queue_delay_dev_normalization.Get() / 4) /
-                         params_.queue_delay_dev_normalization.Get(),
-                     0.0, 0.2);
+void DelayBasedCongestionControl::UpdateQueueDelayMinAverage(
+    TimeDelta packet_qdelay) {
+  RTC_DCHECK(packet_qdelay >= TimeDelta::Zero());
+  queue_delay_min_avg_ =
+      (1.0 - params_.delay_min_and_latency_diff_avg_g.Get()) *
+          queue_delay_min_avg_ +
+      params_.delay_min_and_latency_diff_avg_g.Get() *
+          std::min(packet_qdelay, 2 * params_.queue_delay_min_threshold.Get());
+}
+
+void DelayBasedCongestionControl::UpdateLatencyDifferenceAverage(
+    TimeDelta packet_latency_diff) {
+  RTC_DCHECK(packet_latency_diff >= TimeDelta::Zero());
+  latency_difference_avg_ =
+      (1.0 - params_.delay_min_and_latency_diff_avg_g.Get()) *
+          latency_difference_avg_ +
+      params_.delay_min_and_latency_diff_avg_g.Get() *
+          std::min(packet_latency_diff,
+                   2 * params_.latency_diff_threshold.Get());
 }
 
 void DelayBasedCongestionControl::UpdateSmoothedRtt(TimeDelta rtt_sample) {
@@ -123,7 +139,26 @@
   min_queue_delay_above_threshold_start_ = Timestamp::MinusInfinity();
   last_update_qdelay_avg_time_ = Timestamp::MinusInfinity();
   queue_delay_avg_ = TimeDelta::PlusInfinity();
-  queue_delay_dev_norm_ = 0.0;
+  queue_delay_min_avg_ = TimeDelta::Zero();
+  latency_difference_avg_ = TimeDelta::Zero();
+}
+
+double
+DelayBasedCongestionControl::ref_window_scale_factor_due_to_avg_min_delay(
+    bool allow_zero) const {
+  TimeDelta norm = params_.queue_delay_min_threshold.Get();
+  // Reaches 0.1 at norm, and 1.0 at norm / 4
+  return std::clamp(0.1 + 1.2 * (norm - queue_delay_min_avg_) / norm,
+                    allow_zero ? 0.0 : 0.1, 1.0);
+}
+
+double
+DelayBasedCongestionControl::ref_window_scale_factor_due_to_latency_difference()
+    const {
+  TimeDelta norm = params_.latency_diff_threshold.Get();
+  // Reaches 0.1 at norm, and 1.0 at norm / 4
+  return std::clamp(0.1 + 1.2 * (norm - latency_difference_avg_) / norm, 0.1,
+                    1.0);
 }
 
 DataSize DelayBasedCongestionControl::UpdateReferenceWindow(
diff --git a/modules/congestion_controller/scream/delay_based_congestion_control.h b/modules/congestion_controller/scream/delay_based_congestion_control.h
index d8041b4..91d1483 100644
--- a/modules/congestion_controller/scream/delay_based_congestion_control.h
+++ b/modules/congestion_controller/scream/delay_based_congestion_control.h
@@ -63,29 +63,21 @@
 
   TimeDelta queue_delay() const { return queue_delay_avg_; }
 
-  double queue_delay_dev_norm() const { return queue_delay_dev_norm_; }
+  TimeDelta queue_delay_min_avg() const { return queue_delay_min_avg_; }
+  TimeDelta latency_difference_avg() const { return latency_difference_avg_; }
 
-  // Scales the reference window increase/decrease due to increased delay if L4S
-  // does not appear to be used. See 4.2.2.1.
-  double ref_window_scale_factor_due_to_increased_delay() const {
-    return std::clamp(
-        1 - queue_delay_avg_ / (params_.queue_delay_target.Get() / 4), 0.1,
-        1.0);
-  }
+  // Scale factor used for scaling reference window increase/decrease due to
+  // minimum average queue delay per feedback. The scale factor is 0.1
+  // if the delay is higher than the threshold, and increases linearly to 1.0 if
+  // the delay is lower than the threshold / 4.
+  double ref_window_scale_factor_due_to_avg_min_delay(
+      bool allow_zero = false) const;
 
-  // Scales the reference window increase/decrease due to delay variation.
-  // See 4.2.2.1 and 4.2.2.2. The scale factor is 0.0 if the delay variation
-  // is higher than the threshold, and decreases linearly to 1.0 if the delay
-  // variation is zero.
-  double ref_window_scale_factor_due_to_delay_variation(
-      double ref_window_mss_ratio) const {
-    // 4.2.1.4.
-    double queue_delay_dev_norm_threshold =
-        std::max(0.05, 0.1 * (1.0 - ref_window_mss_ratio / 0.1));
-    return std::clamp((queue_delay_dev_norm_threshold - queue_delay_dev_norm_) /
-                          queue_delay_dev_norm_threshold,
-                      0.0, 1.0);
-  }
+  // Scale factor used for scaling reference window increase if
+  // latency differences per feedback is larger than the threshold. If the send
+  // rate is close to link capacity, the difference between min and max latency
+  // per feedback increases and the scale factor decreases.
+  double ref_window_scale_factor_due_to_latency_difference() const;
 
   TimeDelta rtt() const { return last_smoothed_rtt_; }
 
@@ -97,6 +89,8 @@
   }
   void UpdateSmoothedRtt(TimeDelta rtt_sample);
   void UpdateQueueDelayAverage(TimeDelta one_way_delay);
+  void UpdateQueueDelayMinAverage(TimeDelta packet_qdelay);
+  void UpdateLatencyDifferenceAverage(TimeDelta packet_latency_diff);
 
   const ScreamV2Parameters params_;
 
@@ -111,8 +105,10 @@
   Timestamp min_queue_delay_above_threshold_start_ = Timestamp::MinusInfinity();
   TimeDelta last_smoothed_rtt_ = TimeDelta::Zero();
   Timestamp last_update_qdelay_avg_time_ = Timestamp::MinusInfinity();
+  TimeDelta last_queue_delay_sample_ = TimeDelta::PlusInfinity();
   TimeDelta queue_delay_avg_ = TimeDelta::PlusInfinity();
-  double queue_delay_dev_norm_ = 0.0;
+  TimeDelta queue_delay_min_avg_ = TimeDelta::Zero();
+  TimeDelta latency_difference_avg_ = TimeDelta::Zero();
 };
 
 }  // namespace webrtc
diff --git a/modules/congestion_controller/scream/delay_based_congestion_control_unittest.cc b/modules/congestion_controller/scream/delay_based_congestion_control_unittest.cc
index a2940ae..eea60d3 100644
--- a/modules/congestion_controller/scream/delay_based_congestion_control_unittest.cc
+++ b/modules/congestion_controller/scream/delay_based_congestion_control_unittest.cc
@@ -229,6 +229,118 @@
   EXPECT_FALSE(delay_controller.IsQueueDrainedInTime(clock.CurrentTime()));
 }
 
+TEST(DelayBasedCongestionControlTest,
+     RefWindowScaleFactorDueToMinAverageQueueDelay) {
+  SimulatedClock clock(Timestamp::Seconds(1234));
+  Environment env = CreateTestEnvironment({.time = &clock});
+  ScreamV2Parameters params(env.field_trials());
+  DelayBasedCongestionControl delay_controller(params);
+
+  const TimeDelta kQueueDelyMinThreshold =
+      params.queue_delay_min_threshold.Get();
+
+  // Helper to establish queue delay.
+  // Assumes base delay is 100ms.
+  auto feed_packets = [&](TimeDelta qdelay, int count) {
+    for (int i = 0; i < count; ++i) {
+      clock.AdvanceTime(TimeDelta::Millis(10));
+      TransportPacketsFeedback msg;
+      msg.feedback_time = clock.CurrentTime();
+      PacketResult packet;
+      packet.receive_time = clock.CurrentTime();
+      packet.sent_packet.send_time =
+          clock.CurrentTime() - TimeDelta::Millis(100) - qdelay;
+      packet.sent_packet.sequence_number = i;
+      msg.packet_feedbacks.push_back(packet);
+      delay_controller.OnTransportPacketsFeedback(msg);
+    }
+  };
+
+  // Establish base delay (100ms) with qdelay=0.
+  feed_packets(TimeDelta::Zero(), 100);
+  EXPECT_DOUBLE_EQ(
+      delay_controller.ref_window_scale_factor_due_to_avg_min_delay(), 1.0);
+
+  // Drive queue delay to kQueueDelyMinThreshold.
+  feed_packets(kQueueDelyMinThreshold + TimeDelta::Millis(10), 250);
+  EXPECT_NEAR(delay_controller.ref_window_scale_factor_due_to_avg_min_delay(),
+              0.1, 0.01);
+
+  // Drive queue delay to `0.5 * kQueueDelyMinThreshold`.
+  // Expected: 0.1 + 1.2 * 0.5 = 0.7
+  feed_packets(kQueueDelyMinThreshold / 2, 250);
+  EXPECT_NEAR(delay_controller.ref_window_scale_factor_due_to_avg_min_delay(),
+              0.7, 0.01);
+
+  // Drive queue delay to `0.25 * kQueueDelyMinThreshold`.
+  // Expected: 0.1 + 1.2 * 0.75 = 1.0.
+  feed_packets(kQueueDelyMinThreshold / 4, 250);
+  EXPECT_NEAR(delay_controller.ref_window_scale_factor_due_to_avg_min_delay(),
+              1.0, 0.01);
+}
+
+TEST(DelayBasedCongestionControlTest,
+     RefWindowScaleFactorDueToLatencyDifference) {
+  SimulatedClock clock(Timestamp::Seconds(1234));
+  Environment env = CreateTestEnvironment({.time = &clock});
+  ScreamV2Parameters params(env.field_trials());
+  DelayBasedCongestionControl delay_controller(params);
+
+  const TimeDelta kLatencyThreshold = params.latency_diff_threshold.Get();
+
+  // Helper to establish latency differences within feedbacks.
+  // Assumes a base one-way delay of 100ms.
+  auto feed_packets = [&](TimeDelta latency_diff, int count) {
+    for (int i = 0; i < count; ++i) {
+      clock.AdvanceTime(TimeDelta::Millis(10));
+      TransportPacketsFeedback msg;
+      msg.feedback_time = clock.CurrentTime();
+
+      PacketResult packet1;
+      packet1.receive_time = clock.CurrentTime();
+      packet1.sent_packet.send_time =
+          clock.CurrentTime() - TimeDelta::Millis(100);
+      packet1.sent_packet.sequence_number = i * 2;
+
+      PacketResult packet2;
+      packet2.receive_time = clock.CurrentTime();
+      packet2.sent_packet.send_time =
+          clock.CurrentTime() - TimeDelta::Millis(100) - latency_diff;
+      packet2.sent_packet.sequence_number = i * 2 + 1;
+
+      msg.packet_feedbacks.push_back(packet1);
+      msg.packet_feedbacks.push_back(packet2);
+      delay_controller.OnTransportPacketsFeedback(msg);
+    }
+  };
+
+  // Establish 0 latency difference.
+  feed_packets(TimeDelta::Zero(), 100);
+  EXPECT_DOUBLE_EQ(
+      delay_controller.ref_window_scale_factor_due_to_latency_difference(),
+      1.0);
+
+  // Drive to `kLatencyThreshold`.
+  feed_packets(kLatencyThreshold + TimeDelta::Millis(10), 250);
+  EXPECT_NEAR(
+      delay_controller.ref_window_scale_factor_due_to_latency_difference(), 0.1,
+      0.01);
+
+  // Drive to `0.5 * kLatencyThreshold`.
+  // Expected: 0.1 + 1.2 * 0.5 = 0.7
+  feed_packets(kLatencyThreshold / 2, 250);
+  EXPECT_NEAR(
+      delay_controller.ref_window_scale_factor_due_to_latency_difference(), 0.7,
+      0.01);
+
+  // Drive to `0.25 * kLatencyThreshold`.
+  // Expected: 0.1 + 1.2 * 0.75 = 1.0.
+  feed_packets(kLatencyThreshold / 4, 250);
+  EXPECT_NEAR(
+      delay_controller.ref_window_scale_factor_due_to_latency_difference(), 1.0,
+      0.01);
+}
+
 // TODO: bugs.webrtc.org/447037083 - add tests for clock drift in feedback NTP
 // time.
 
diff --git a/modules/congestion_controller/scream/scream_network_controller_unittest.cc b/modules/congestion_controller/scream/scream_network_controller_unittest.cc
index b3a0d90..745f70d 100644
--- a/modules/congestion_controller/scream/scream_network_controller_unittest.cc
+++ b/modules/congestion_controller/scream/scream_network_controller_unittest.cc
@@ -420,7 +420,7 @@
   PaddingTestResult result = ProcessUntilPaddingStartAndStop(
       clock, scream_controller, feedback_generator);
 
-  EXPECT_LT(result.target_rate, DataRate::KilobitsPerSec(700));
+  EXPECT_LT(result.target_rate, DataRate::KilobitsPerSec(720));
   // Padding should stop when congestion is detected.
   EXPECT_LT(result.padding_stop - result.padding_start, TimeDelta::Seconds(1));
 }
diff --git a/modules/congestion_controller/scream/scream_v2.cc b/modules/congestion_controller/scream/scream_v2.cc
index 3836c93..fc5d533 100644
--- a/modules/congestion_controller/scream/scream_v2.cc
+++ b/modules/congestion_controller/scream/scream_v2.cc
@@ -180,8 +180,7 @@
         // of competing TCP Prague flows.
         backoff *=
             std::max(0.1, delay_based_congestion_control_
-                              .ref_window_scale_factor_due_to_delay_variation(
-                                  ref_window_mss_ratio()));
+                              .ref_window_scale_factor_due_to_avg_min_delay());
       }
 
       if (msg.feedback_time - last_reaction_to_congestion_time_ >
@@ -233,23 +232,17 @@
         increase_scale_factor *
         std::max(0.25, ref_window_scale_factor_close_to_ref_window_i());
 
-    // Limit increase if L4S not enabled and queue delay is increased.
-    if (l4s_alpha_ < 0.0001) {
-      increase_scale_factor =
-          increase_scale_factor *
-          delay_based_congestion_control_
-              .ref_window_scale_factor_due_to_increased_delay();
-    }
-
     // Put a additional restriction on reference window growth if rtt varies a
     // lot.
     // Better to enforce a slow increase in reference window and get
     // a more stable bitrate.
+    increase_scale_factor = increase_scale_factor *
+                            delay_based_congestion_control_
+                                .ref_window_scale_factor_due_to_avg_min_delay();
     increase_scale_factor =
         increase_scale_factor *
-        std::max(0.1, delay_based_congestion_control_
-                          .ref_window_scale_factor_due_to_delay_variation(
-                              ref_window_mss_ratio()));
+        delay_based_congestion_control_
+            .ref_window_scale_factor_due_to_latency_difference();
 
     // Use lower multiplicative scale factor if congestion was detected
     // recently.
@@ -299,8 +292,6 @@
       << " is_virtual_ce=" << is_virtual_ce << " is_loss=" << is_loss
       << " smoothed_rtt=" << delay_based_congestion_control_.rtt().ms()
       << ", queue_delay=" << delay_based_congestion_control_.queue_delay().ms()
-      << ", queue_delay_dev_norm="
-      << delay_based_congestion_control_.queue_delay_dev_norm()
       << " feedback_hold" << feedback_hold_time_.ms()
       << ", target_rate =" << target_rate_.kbps();
 }
@@ -312,8 +303,8 @@
       (params_.ref_window_overhead_max.Get() -
        params_.ref_window_overhead_min.Get()) *
           delay_based_congestion_control_
-              .ref_window_scale_factor_due_to_delay_variation(
-                  ref_window_mss_ratio());
+              .ref_window_scale_factor_due_to_avg_min_delay(
+                  /*allow_zero=*/true);
 
   return ref_window_ * ref_window_overhead;
 }
diff --git a/modules/congestion_controller/scream/scream_v2_parameters.cc b/modules/congestion_controller/scream/scream_v2_parameters.cc
index 16c91b2..63c01c4 100644
--- a/modules/congestion_controller/scream/scream_v2_parameters.cc
+++ b/modules/congestion_controller/scream/scream_v2_parameters.cc
@@ -41,8 +41,10 @@
       ref_window_overhead_min("RefWinMin", 1.5),
       ref_window_overhead_max("RefWinMax", 3.0),
       queue_delay_avg_g("QDelayAvgG", 1.0 / 4.0),
-      queue_delay_dev_avg_g("QDelayDevAvgG", 1.0 / 64.0),
-      queue_delay_dev_normalization("QDelayDevNorm", TimeDelta::Millis(25)),
+      delay_min_and_latency_diff_avg_g("DelayMinAndLatencyDiffAvgG",
+                                       1.0 / 32.0),
+      queue_delay_min_threshold("QDelayMinThreshold", TimeDelta::Millis(10)),
+      latency_diff_threshold("LatencyDiffThreshold", TimeDelta::Millis(10)),
       base_delay_window_length("BaseDelayWindowLength", 10),
       base_delay_history_update_interval("BaseDelayHistoryUpdateInterval",
                                          TimeDelta::Minutes(1)),
@@ -76,8 +78,9 @@
                    &ref_window_overhead_min,
                    &ref_window_overhead_max,
                    &queue_delay_avg_g,
-                   &queue_delay_dev_avg_g,
-                   &queue_delay_dev_normalization,
+                   &delay_min_and_latency_diff_avg_g,
+                   &queue_delay_min_threshold,
+                   &latency_diff_threshold,
                    &base_delay_window_length,
                    &base_delay_history_update_interval,
                    &queue_delay_target,
diff --git a/modules/congestion_controller/scream/scream_v2_parameters.h b/modules/congestion_controller/scream/scream_v2_parameters.h
index 53c8296..9dafdba 100644
--- a/modules/congestion_controller/scream/scream_v2_parameters.h
+++ b/modules/congestion_controller/scream/scream_v2_parameters.h
@@ -74,10 +74,16 @@
   // Exponentially Weighted Moving Average (EWMA) factor for updating queue
   // delay.
   FieldTrialParameter<double> queue_delay_avg_g;
-  FieldTrialParameter<double> queue_delay_dev_avg_g;
+  // Exponentially Weighted Moving Average (EWMA) factor for updating average
+  // min queue delay and average latency difference.
+  FieldTrialParameter<double> delay_min_and_latency_diff_avg_g;
 
-  // Normalization factor for queue delay variation.
-  FieldTrialParameter<TimeDelta> queue_delay_dev_normalization;
+  // Thresholds for average min queue delay. Used for limiting ref_window
+  // increase.
+  FieldTrialParameter<TimeDelta> queue_delay_min_threshold;
+  // Thresholds for average latency difference. Used for limiting ref_window
+  // increase.
+  FieldTrialParameter<TimeDelta> latency_diff_threshold;
 
   // Determines the length of the base delay history when estimating one way
   // delay (owd)
diff --git a/rtc_tools/rtc_event_log_visualizer/analyze_bwe.cc b/rtc_tools/rtc_event_log_visualizer/analyze_bwe.cc
index d0cf941..30682e4 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyze_bwe.cc
+++ b/rtc_tools/rtc_event_log_visualizer/analyze_bwe.cc
@@ -644,6 +644,41 @@
   plot->SetTitle("Simulated BWE behavior");
 }
 
+void CreateScreamSimulationDelayGraph(const ParsedRtcEventLog& parsed_log,
+                                      const AnalyzerConfig& config,
+                                      Plot* plot) {
+  TimeSeries smoothed_rtt_series("Smoothed RTT", LineStyle::kStep);
+  TimeSeries queue_delay_series("Queue delay", LineStyle::kStep);
+  TimeSeries queue_delay_min_avg_series("Queue delay min avg",
+                                        LineStyle::kStep);
+  TimeSeries latency_difference_avg_series("Latency difference avg",
+                                           LineStyle::kStep);
+
+  LogScreamSimulation simulation({.rate_window = config.window_duration_},
+                                 config.env_);
+  simulation.ProcessEventsInLog(parsed_log);
+
+  for (const LogScreamSimulation::State& state : simulation.updates()) {
+    smoothed_rtt_series.points.emplace_back(config.GetCallTimeSec(state.time),
+                                            state.smoothed_rtt.ms());
+    queue_delay_series.points.emplace_back(config.GetCallTimeSec(state.time),
+                                           state.queue_delay.ms());
+    queue_delay_min_avg_series.points.emplace_back(
+        config.GetCallTimeSec(state.time), state.queue_delay_min_avg.ms());
+    latency_difference_avg_series.points.emplace_back(
+        config.GetCallTimeSec(state.time), state.latency_difference_avg.ms());
+  }
+  plot->AppendTimeSeries(std::move(smoothed_rtt_series));
+  plot->AppendTimeSeries(std::move(queue_delay_series));
+  plot->AppendTimeSeries(std::move(queue_delay_min_avg_series));
+  plot->AppendTimeSeries(std::move(latency_difference_avg_series));
+
+  plot->SetXAxis(config.CallBeginTimeSec(), config.CallEndTimeSec(), "Time (s)",
+                 kLeftMargin, kRightMargin);
+  plot->SetSuggestedYAxis(0, 50, "Delay (ms)", kBottomMargin, kTopMargin);
+  plot->SetTitle("Simulated Scream delays");
+}
+
 void CreateScreamSimulationBitrateGraph(const ParsedRtcEventLog& parsed_log,
                                         const AnalyzerConfig& config,
                                         Plot* plot) {
@@ -757,10 +792,10 @@
   TimeSeries queue_delay_dev_norm_series("QueueDelayDevNorm", LineStyle::kStep);
   TimeSeries l4s_alpha_series("L4sAlpha", LineStyle::kStep);
   TimeSeries l4s_alpha_v_series("L4sAlphaV", LineStyle::kStep);
-  TimeSeries ref_window_scale_factor_due_to_increased_delay(
-      "RefWindowScaleFactorDueToIncreasedDelay", LineStyle::kStep);
-  TimeSeries ref_window_scale_factor_due_to_delay_variation(
-      "RefWindowScaleFactorDueToDelayVariation", LineStyle::kStep);
+  TimeSeries ref_window_scale_factor_due_to_min_delay_variation(
+      "RefWindowScaleFactorDueToAvgMinQueueDelay", LineStyle::kStep);
+  TimeSeries ref_window_scale_factor_due_to_latency_difference(
+      "RefWindowScaleFactorDueToAvgLatencyDifference", LineStyle::kStep);
   TimeSeries ref_window_scale_factor_close_to_ref_window_i(
       "RefWindowScaleFactorCloseToRefWindowI", LineStyle::kStep);
   TimeSeries ref_window_combined_increase_scale_factor(
@@ -777,12 +812,12 @@
                                          state.l4s_alpha);
     l4s_alpha_v_series.points.emplace_back(config.GetCallTimeSec(state.time),
                                            state.l4s_alpha_v);
-    ref_window_scale_factor_due_to_increased_delay.points.emplace_back(
+    ref_window_scale_factor_due_to_min_delay_variation.points.emplace_back(
         config.GetCallTimeSec(state.time),
-        state.ref_window_scale_factor_due_to_increased_delay);
-    ref_window_scale_factor_due_to_delay_variation.points.emplace_back(
+        state.ref_window_scale_factor_due_to_avg_min_delay);
+    ref_window_scale_factor_due_to_latency_difference.points.emplace_back(
         config.GetCallTimeSec(state.time),
-        state.ref_window_scale_factor_due_to_delay_variation);
+        state.ref_window_scale_factor_due_to_latency_difference);
     ref_window_scale_factor_close_to_ref_window_i.points.emplace_back(
         config.GetCallTimeSec(state.time),
         state.ref_window_scale_factor_close_to_ref_window_i);
@@ -794,9 +829,9 @@
   plot->AppendTimeSeries(std::move(l4s_alpha_series));
   plot->AppendTimeSeries(std::move(l4s_alpha_v_series));
   plot->AppendTimeSeries(
-      std::move(ref_window_scale_factor_due_to_increased_delay));
+      std::move(ref_window_scale_factor_due_to_min_delay_variation));
   plot->AppendTimeSeries(
-      std::move(ref_window_scale_factor_due_to_delay_variation));
+      std::move(ref_window_scale_factor_due_to_latency_difference));
   plot->AppendTimeSeries(
       std::move(ref_window_scale_factor_close_to_ref_window_i));
   plot->AppendTimeSeries(std::move(ref_window_combined_increase_scale_factor));
diff --git a/rtc_tools/rtc_event_log_visualizer/analyze_bwe.h b/rtc_tools/rtc_event_log_visualizer/analyze_bwe.h
index fd4b041..5de8598 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyze_bwe.h
+++ b/rtc_tools/rtc_event_log_visualizer/analyze_bwe.h
@@ -40,10 +40,13 @@
                                  const AnalyzerConfig& config,
                                  Plot* plot);
 
+void CreateScreamSimulationDelayGraph(const ParsedRtcEventLog& parsed_log,
+                                      const AnalyzerConfig& config,
+                                      Plot* plot);
+
 void CreateScreamSimulationBitrateGraph(const ParsedRtcEventLog& parsed_log,
                                         const AnalyzerConfig& config,
                                         Plot* plot);
-
 void CreateScreamSimulationRefWindowGraph(const ParsedRtcEventLog& parsed_log,
                                           const AnalyzerConfig& config,
                                           Plot* plot);
diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.cc b/rtc_tools/rtc_event_log_visualizer/analyzer.cc
index 72ef110..a44d683 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyzer.cc
+++ b/rtc_tools/rtc_event_log_visualizer/analyzer.cc
@@ -172,6 +172,9 @@
   plots_.RegisterPlot("simulated_goog_cc", [this](Plot* plot) {
     this->CreateGoogCcSimulationGraph(plot);
   });
+  plots_.RegisterPlot("simulated_scream_delay", [this](Plot* plot) {
+    this->CreateScreamSimulationDelayGraph(plot);
+  });
   plots_.RegisterPlot("simulated_scream_bitrates", [this](Plot* plot) {
     this->CreateScreamSimulationBitrateGraph(plot);
   });
@@ -403,6 +406,10 @@
   webrtc::CreateGoogCcSimulationGraph(parsed_log_, config_, plot);
 }
 
+void EventLogAnalyzer::CreateScreamSimulationDelayGraph(Plot* plot) const {
+  webrtc::CreateScreamSimulationDelayGraph(parsed_log_, config_, plot);
+}
+
 void EventLogAnalyzer::CreateScreamSimulationBitrateGraph(Plot* plot) const {
   webrtc::CreateScreamSimulationBitrateGraph(parsed_log_, config_, plot);
 }
diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.h b/rtc_tools/rtc_event_log_visualizer/analyzer.h
index 511d002..60d2244 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyzer.h
+++ b/rtc_tools/rtc_event_log_visualizer/analyzer.h
@@ -131,6 +131,7 @@
   void CreateScreamRefWindowGraph(Plot* plot) const;
   void CreateScreamDelayEstimateGraph(Plot* plot) const;
   void CreateGoogCcSimulationGraph(Plot* plot) const;
+  void CreateScreamSimulationDelayGraph(Plot* plot) const;
   void CreateScreamSimulationBitrateGraph(Plot* plot) const;
   void CreateScreamSimulationRefWindowGraph(Plot* plot) const;
   void CreateScreamSimulationRatiosGraph(Plot* plot) const;
diff --git a/rtc_tools/rtc_event_log_visualizer/log_scream_simulation.cc b/rtc_tools/rtc_event_log_visualizer/log_scream_simulation.cc
index 5e97896..e52e131 100644
--- a/rtc_tools/rtc_event_log_visualizer/log_scream_simulation.cc
+++ b/rtc_tools/rtc_event_log_visualizer/log_scream_simulation.cc
@@ -172,17 +172,18 @@
       .max_data_in_flight = scream_->max_data_in_flight(),
       .data_in_flight = data_in_flight_,
       .send_window_usage = send_window_usage_,
-      .queue_delay_dev_norm =
-          scream_->delay_based_congestion_control().queue_delay_dev_norm(),
-      .ref_window_scale_factor_due_to_increased_delay =
-          scream_->delay_based_congestion_control().IsQueueDelayDetected()
-              ? 0.0
-              : scream_->delay_based_congestion_control()
-                    .ref_window_scale_factor_due_to_increased_delay(),
-      .ref_window_scale_factor_due_to_delay_variation =
+      .smoothed_rtt = scream_->delay_based_congestion_control().rtt(),
+      .queue_delay = scream_->delay_based_congestion_control().queue_delay(),
+      .queue_delay_min_avg =
+          scream_->delay_based_congestion_control().queue_delay_min_avg(),
+      .latency_difference_avg =
+          scream_->delay_based_congestion_control().latency_difference_avg(),
+      .ref_window_scale_factor_due_to_avg_min_delay =
           scream_->delay_based_congestion_control()
-              .ref_window_scale_factor_due_to_delay_variation(
-                  scream_->ref_window_mss_ratio()),
+              .ref_window_scale_factor_due_to_avg_min_delay(),
+      .ref_window_scale_factor_due_to_latency_difference =
+          scream_->delay_based_congestion_control()
+              .ref_window_scale_factor_due_to_latency_difference(),
       .ref_window_scale_factor_close_to_ref_window_i =
           scream_->ref_window_scale_factor_close_to_ref_window_i(),
       .ref_window_combined_increase_scale_factor =
diff --git a/rtc_tools/rtc_event_log_visualizer/log_scream_simulation.h b/rtc_tools/rtc_event_log_visualizer/log_scream_simulation.h
index 9641f77..252880a 100644
--- a/rtc_tools/rtc_event_log_visualizer/log_scream_simulation.h
+++ b/rtc_tools/rtc_event_log_visualizer/log_scream_simulation.h
@@ -55,9 +55,14 @@
     // last packet was sent before the state was captured.
     SendWindowUsage send_window_usage = kBelowRefWindow;
 
+    TimeDelta smoothed_rtt = TimeDelta::Zero();
+    TimeDelta queue_delay = TimeDelta::Zero();
+    TimeDelta queue_delay_min_avg = TimeDelta::Zero();
+    TimeDelta latency_difference_avg = TimeDelta::Zero();
+
     double queue_delay_dev_norm = 0;
-    double ref_window_scale_factor_due_to_increased_delay = 0.0;
-    double ref_window_scale_factor_due_to_delay_variation = 0.0;
+    double ref_window_scale_factor_due_to_avg_min_delay = 0.0;
+    double ref_window_scale_factor_due_to_latency_difference = 0.0;
     double ref_window_scale_factor_close_to_ref_window_i = 0.0;
     double ref_window_combined_increase_scale_factor = 0.0;
     double l4s_alpha = 0.0;
diff --git a/rtc_tools/rtc_event_log_visualizer/main.cc b/rtc_tools/rtc_event_log_visualizer/main.cc
index 67ac267..f299d9a 100644
--- a/rtc_tools/rtc_event_log_visualizer/main.cc
+++ b/rtc_tools/rtc_event_log_visualizer/main.cc
@@ -251,7 +251,7 @@
        {"incoming_bitrate", "outgoing_bitrate", "incoming_ecn_feedback",
         "outgoing_ecn_feedback"}},
       {"scream",
-       {"scream_delay_estimates", "scream_ref_window",
+       {"scream_ref_window", "simulated_scream_delay",
         "simulated_scream_bitrates", "simulated_scream_ref_window",
         "simulated_scream_ratios", "network_delay_feedback", "pacer_delay"}}};
 
diff --git a/test/peer_scenario/bwe_integration_tests/scream_test.cc b/test/peer_scenario/bwe_integration_tests/scream_test.cc
index 33b5901..8ed6a43 100644
--- a/test/peer_scenario/bwe_integration_tests/scream_test.cc
+++ b/test/peer_scenario/bwe_integration_tests/scream_test.cc
@@ -337,7 +337,7 @@
   EXPECT_THAT(
       result.caller().subspan(1, 3),
       Each(AvailableSendBitrateIsBetween(DataRate::KilobitsPerSec(10),
-                                         DataRate::KilobitsPerSec(100))));
+                                         DataRate::KilobitsPerSec(120))));
   EXPECT_THAT(result.caller()[3],
               CurrentRoundTripTimeIsBetween(TimeDelta::Millis(40),
                                             TimeDelta::Millis(200)));
@@ -347,7 +347,7 @@
   // is disabled.
   EXPECT_THAT(result.caller_stats.back(),
               AvailableSendBitrateIsBetween(DataRate::KilobitsPerSec(100),
-                                            DataRate::KilobitsPerSec(5000)));
+                                            DataRate::KilobitsPerSec(6500)));
 }
 
 TEST(ScreamTest, MaybeTest(LinkCapacity600KbpsRtt20msNoEcn)) {
@@ -363,8 +363,8 @@
 
   SendMediaTestResult result = SendMediaInOneDirection(std::move(params), s);
   EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
-                                              DataRate::KilobitsPerSec(300),
-                                              DataRate::KilobitsPerSec(700))));
+                                              DataRate::KilobitsPerSec(200),
+                                              DataRate::KilobitsPerSec(800))));
 }
 
 TEST(ScreamTest, MaybeTest(LinkCapacity600KbpsRtt100msEcn)) {
@@ -467,7 +467,7 @@
   SendMediaTestResult result = SendMediaInOneDirection(std::move(params), s);
   EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
                                               DataRate::KilobitsPerSec(800),
-                                              DataRate::KilobitsPerSec(1900))));
+                                              DataRate::KilobitsPerSec(2000))));
 }
 
 TEST(ScreamTest, MaybeTest(LinkCapacity2MbpsRtt50msNoEcn)) {
@@ -484,7 +484,7 @@
   SendMediaTestResult result = SendMediaInOneDirection(std::move(params), s);
   EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
                                               DataRate::KilobitsPerSec(1300),
-                                              DataRate::KilobitsPerSec(2300))));
+                                              DataRate::KilobitsPerSec(2400))));
 }
 
 TEST(ScreamTest, MaybeTest(LinkCapacity2MbpsRtt50msEcn)) {
@@ -525,6 +525,26 @@
                                             DataRate::KilobitsPerSec(2600)));
 }
 
+TEST(ScreamTest, MaybeTest(LinkCapacity4MbpsRtt50ms10MsDelayStdDev)) {
+  PeerScenario s(*testing::UnitTest::GetInstance()->current_test_info());
+  NetworkEmulationManager::SimulatedNetworkNode::Builder network_builder(
+      s.net());
+  network_builder.delay_ms(25);
+  network_builder.delay_standard_deviation_ms(10);
+  network_builder.capacity_Mbps(4);
+  SendMediaTestParams params;
+  params.test_duration = TimeDelta::Seconds(30);
+  params.callee_to_caller_path =
+      CreateNetworkPath(network_builder, /*use_dual_pi= */ false);
+  params.caller_to_callee_path =
+      CreateNetworkPath(network_builder, /*use_dual_pi= */ false);
+
+  SendMediaTestResult result = SendMediaInOneDirection(std::move(params), s);
+  EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
+                                              DataRate::KilobitsPerSec(700),
+                                              DataRate::KilobitsPerSec(4100))));
+}
+
 TEST(ScreamTest, MaybeTest(LinkCapacity2MbpsRtt50msNoEcnWithTwcc)) {
   PeerScenario s(*testing::UnitTest::GetInstance()->current_test_info());
   SendMediaTestParams params;
@@ -545,7 +565,7 @@
   // approximately.
   EXPECT_THAT(result.caller().subspan(5), Each(AvailableSendBitrateIsBetween(
                                               DataRate::KilobitsPerSec(1200),
-                                              DataRate::KilobitsPerSec(2300))));
+                                              DataRate::KilobitsPerSec(2400))));
 }
 
 TEST(ScreamTest, MaybeTest(CallerPauseSendingVideoIfFeedbackNotReceived)) {
@@ -627,7 +647,7 @@
   // Ignore estimate during rampup.
   EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
                                               DataRate::KilobitsPerSec(1200),
-                                              DataRate::KilobitsPerSec(2600))));
+                                              DataRate::KilobitsPerSec(2800))));
 }
 
 TEST(ScreamTest, MaybeTest(ScreencastSlideChangeRepeatedDelaySpikes)) {
@@ -645,12 +665,9 @@
                             .change_interval = TimeDelta::Seconds(5)}}};
   SendMediaTestResult result = SendMediaInOneDirection(std::move(params), s);
 
-  // Ignore estimate during rampup.
-  // TODO: bugs.webrtc.org/447037083 - consider improving resilience by limiting
-  // queue delay from one feedback.
-  EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
-                                              DataRate::KilobitsPerSec(100),
-                                              DataRate::KilobitsPerSec(4000))));
+  EXPECT_THAT(result.caller(), Each(AvailableSendBitrateIsBetween(
+                                   DataRate::KilobitsPerSec(500),
+                                   DataRate::KilobitsPerSec(4300))));
 }
 
 TEST(ScreamTest, MaybeTest(LinkCapacity5MbitRepeatedDelaySpikesNoEcn)) {
@@ -664,16 +681,14 @@
       TimeDelta::Millis(200));
   SendMediaTestResult result = SendMediaInOneDirection(std::move(params), s);
 
-  // TODO: bugs.webrtc.org/447037083 - consider improving resilience by limiting
-  // queue delay from one feedback.
   EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
-                                              DataRate::KilobitsPerSec(500),
+                                              DataRate::KilobitsPerSec(1200),
                                               DataRate::KilobitsPerSec(5000))));
 }
 
 TEST(ScreamTest, MaybeTest(RampupFastOnLinkCapacity50Mbit20MsRttNoEcn)) {
   PeerScenario s(*testing::UnitTest::GetInstance()->current_test_info());
-  SendMediaTestParams params{.test_duration = TimeDelta::Millis(300),
+  SendMediaTestParams params{.test_duration = TimeDelta::Seconds(1),
                              .stats_interval = TimeDelta::Millis(100)};
   params.caller_to_callee_path =
       CreateNetworkPath(s, /*use_dual_pi= */ false,
@@ -685,9 +700,14 @@
 
   // TODO: bugs.webrtc.org/447037083 - On a good network, we should be able to
   // let the bitrate increase by 50% every RTT.
-  EXPECT_THAT(result.caller_stats.back(),
+  // After 300ms.
+  EXPECT_THAT(result.caller_stats[2],
               AvailableSendBitrateIsBetween(DataRate::KilobitsPerSec(500),
                                             DataRate::KilobitsPerSec(5000)));
+  // After 1s.
+  EXPECT_THAT(result.caller_stats.back(),
+              AvailableSendBitrateIsBetween(DataRate::KilobitsPerSec(1500),
+                                            DataRate::KilobitsPerSec(5000)));
 }
 
 TEST(ScreamTest, MaybeTest(LinkCapacity100Kbit50msRttNoEcn)) {
@@ -767,7 +787,7 @@
             GetPacketsSent(result.caller_stats[5]));
   EXPECT_THAT(result.caller().subspan(1), Each(AvailableSendBitrateIsBetween(
                                               DataRate::KilobitsPerSec(300),
-                                              DataRate::KilobitsPerSec(1100))));
+                                              DataRate::KilobitsPerSec(1300))));
 }
 
 TEST(ScreamTest, MaybeTest(SendVideoOnlyReturnLinkWithBurstLoss)) {