| <!-- go/cmark --> |
| |
| <!--* freshness: {owner: 'perkj' reviewed: '2026-10-13'} *--> |
| |
| # SCReAMv2 Implementation Differences |
| |
| **Date:** October 13, 2026\ |
| **Commit:** |
| [65217155f735a780676d34c073ef2447589a71c1](https://webrtc.googlesource.com/src/+/65217155f735a780676d34c073ef2447589a71c1)\ |
| **Note:** This document was automatically generated by an AI assistant (Gemini) |
| comparing the WebRTC source code with the IETF specification. |
| |
| This document describes the differences between the WebRTC implementation of |
| SCReAMv2 in `/modules/congestion_controller/scream` (see |
| [index.md](/modules/congestion_controller/scream/g3doc/index.md)) and the IETF |
| Working Group draft |
| [draft-ietf-ccwg-rfc8298bis-screamv2-01](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html) |
| ([datatracker](https://datatracker.ietf.org/doc/draft-ietf-ccwg-rfc8298bis-screamv2/)). |
| |
| The WebRTC implementation is currently in development and some features are not |
| yet fully implemented. |
| |
| [TOC] |
| |
| ## 4. Detailed Description of SCReAMv2 Sender Algorithm |
| |
| ### 4.1. Sender Side State |
| |
| #### 4.1.1. Status Update When Sending Data |
| |
| - **Differences in Default Parameters:** |
| - `min_ref_window` (`MIN_REF_WND` in spec): The spec recommends **3000 bytes** |
| ([Section 4.2.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2)). |
| The WebRTC implementation defaults to **1000 bytes** in |
| [scream_v2_parameters.cc:L26](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2_parameters.cc#26). |
| - `max_segment_size` (`MSS` in spec): The spec recommends **1000 bytes** |
| ([Section 4.2.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2)). |
| The WebRTC implementation defaults to **1280 bytes** in |
| [scream_v2_parameters.cc:L34](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2_parameters.cc#34). |
| - `bytes_in_flight_head_room` (`BYTES_IN_FLIGHT_HEAD_ROOM` in spec): The spec |
| recommends **1.5** |
| ([Section 4.2.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2)). |
| The WebRTC implementation defaults to **1.1** in |
| [scream_v2_parameters.cc:L35](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2_parameters.cc#35). |
| - **Initial Reference Window Initialization:** |
| - The spec initializes `ref_wnd` to `MIN_REF_WND` |
| ([Section 4.2.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2)). |
| - The WebRTC implementation initializes `ref_window_` to `min_ref_window` at |
| construction, and upon receiving the **first feedback** with valid delay |
| metrics, resets `ref_window_` to |
| `max(min_ref_window, target_rate_ * (rtt + feedback_hold_time_))` using the |
| configured starting bitrate (default 300 kbps). See |
| [scream_v2.cc:L83-L94](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#83). |
| |
| #### 4.1.2. Status Update on Receiving Feedback |
| |
| - The WebRTC implementation normalizes `TransportPacketsFeedback` into a flat |
| `ScreamFeedback` struct in |
| [scream_feedback.h](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_feedback.h) |
| and |
| [scream_feedback.cc](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_feedback.cc). |
| - **`acked_not_marked_size` vs. `bytes_newly_acked`:** |
| - Instead of accumulating `bytes_newly_acked` and `bytes_newly_acked_ce` |
| across feedback messages and resetting them on window updates as described |
| in the spec |
| ([Section 4.1.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.1.2)), |
| `ParseScreamFeedback` directly computes `acked_not_marked_size` per feedback |
| (the sum of sizes of all packets in the feedback that are not ECN-CE marked, |
| including lost packets) and `num_ce_marked_packets`. See |
| [scream_feedback.cc:L44-L46](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_feedback.cc#44). |
| - **Burst-Windowed One-Way Delay Filtering:** |
| - Instead of computing queue delay over every packet in a feedback report, |
| `ParseScreamFeedback` computes `min_one_way_delay` and `max_one_way_delay` |
| only over packets with unambiguous receive times sent within a recent |
| tail/burst window from the latest received packet: a base window of |
| `burst_window_min` (default 25 ms), extended up to `burst_window_max` |
| (default 50 ms) as long as consecutive packets are sent within |
| `burst_window_max_gap` (default 3 ms). This prevents earlier sparse packets |
| in long feedback intervals (e.g., audio-only or ALR) from skewing delay and |
| jitter estimates. See |
| [scream_feedback.cc:L86-L118](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_feedback.cc#86). |
| - **Feedback Hold Time and Feedback Interval Tracking:** |
| - **Feedback Hold Time (`feedback_hold_time_`):** Tracks an EWMA (gain |
| `feedback_hold_time_avg_g = 1/8`) of the duration between the receive times |
| of the first and last packets in a feedback report plus the last packet's |
| receiver `arrival_time_offset`. This hold time is added to `rtt` when |
| initializing `ref_window_`, clamping `ref_window_` on initial congestion, |
| scaling window increase for small RTTs, and computing `target_rate_`. See |
| [scream_feedback.cc:L124-L127](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_feedback.cc#124) |
| and |
| [scream_v2.cc:L396-L404](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#396). |
| - **Feedback Interval (`feedback_interval_`):** Tracks an EWMA (gain |
| `feedback_interval_avg_g = 0.1`) of the time between consecutive feedback |
| messages. When `use_feedback_interval_for_virtual_rtt` is enabled (default |
| `true`), `ReactionInterval()` is defined as |
| `max(virtual_rtt, feedback_interval_)` instead of fixed `VIRTUAL_RTT`. See |
| [scream_v2.cc:L115-L119](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#115) |
| and |
| [scream_v2.cc:L406-L419](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#406). |
| |
| ### 4.2. Network Congestion Control |
| |
| #### 4.2.1. Congestion Detection: Delay, Data Unit Loss and ECN-CE |
| |
| ##### 4.2.1.1. Detecting Lost Data Units |
| |
| - **Biased Asymmetric Step Filter (`LossEstimator`):** |
| - The spec describes RACK-like loss detection with a reordering window in |
| [Section 4.2.1.1](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.1.1) |
| and an optional per-packet EWMA `loss_rate` filter in |
| [Section 4.5.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.5.2). |
| - WebRTC relies on transport-level feedback flags |
| (`reported_lost_for_the_first_time` and |
| `reported_recovered_for_the_first_time`) and passes them to |
| [LossEstimator](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/loss_estimator.h) |
| ([loss_estimator.cc](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/loss_estimator.cc)), |
| which implements a **biased asymmetric step filter** (leaky bucket) tracking |
| `congestion_level_` in `[0.0, 1.0]` at RTT boundaries |
| (`max(virtual_rtt, rtt)`): |
| - **Reordering Recovery:** Tracks `unrecovered_lost_packets_`, incrementing |
| on first-time loss reports and decrementing on subsequent out-of-order |
| recovery reports (and resetting after a full RTT without losses or |
| recoveries). If `unrecovered_lost_packets_` drops to `0`, |
| `congestion_level_` and `loss_event_this_rtt_` are immediately reset to |
| `0`. |
| - **Step Filter Mechanics:** Steps up by |
| `1.0 / rtts_with_loss_before_backoff` (default `+1/3`) on an RTT with |
| loss, and steps down by `1.0 / lossless_rtts_before_clear` (default |
| `-1/2`) on a lossless RTT. This produces a net-negative drift under low |
| uniform random wireless loss (e.g., 1% packet loss) while reaching |
| `congested()` (`>= 0.99`) after 3 consecutive RTTs with loss. |
| - **Impact on Backoff:** Loss triggers a reference window backoff only if |
| `loss_estimator_.congested()` (`congestion_level_ >= 0.99`) **or** queue |
| delay congestion (`IsQueueDelayDetected()`, i.e. |
| `queue_delay_avg_ > queue_delay_target / 2`) is simultaneously active. See |
| [scream_v2.cc:L203-L210](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#203). |
| - **Impact on Window Increase:** Reference window growth is blocked whenever |
| `loss_congestion_level() >= 0.01` (unless simultaneously backing off in the |
| same feedback). Because `congestion_level_` decays by `0.5` per lossless |
| RTT, it takes **2 full lossless RTTs** after peak loss congestion for window |
| growth to resume. See |
| [scream_v2.cc:L284](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#284). |
| |
| ##### 4.2.1.2. Receiving ECN-CE with classic ECN |
| |
| - **Classic ECN Not Supported:** |
| - The spec defines a classic ECN mode (`IS_L4S == false`) where `ref_wnd` is |
| scaled by a fixed `BETA_ECN` (default 0.8) |
| ([Section 4.2.1.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.1.2)). |
| - The WebRTC implementation **does not support classic ECN** and always |
| operates in L4S mode when ECN-CE markings are received. |
| |
| ##### 4.2.1.3. Receiving ECN-CE for L4S |
| |
| - **`l4s_alpha` Update Frequency and RTT Gain Scaling:** |
| - Draft `-01` accumulates delivered and marked data units and updates |
| `l4s_alpha` at most once per `min(10 ms, s_rtt)` using an asymmetric |
| fast-attack / slow-decay EWMA (`L4S_AVG_G_UP = 1/8`, |
| `L4S_AVG_G_DOWN = 1/128`) |
| ([Section 4.2.1.3](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.1.3)), |
| while the latest editor's draft (`draft-ietf-ccwg-rfc8298bis-screamv2-02`) |
| uses a symmetric EWMA with `L4S_AVG_G = 1/16`. |
| - The WebRTC implementation updates `l4s_alpha_` on **every feedback message** |
| with received packets using a standard EWMA filter with `l4s_avg_g = 1/16` |
| ([scream_v2_parameters.cc:L27](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2_parameters.cc#27)). |
| - When `scale_l4s_alpha_with_rtt` is enabled (default `true`), WebRTC divides |
| `l4s_avg_g` by `steps_per_rtt = max(1.0, rtt / ReactionInterval())` so that |
| `l4s_alpha_` converges at a consistent rate per RTT regardless of how many |
| feedback reports arrive per RTT. See |
| [scream_v2.cc:L178-L194](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#178). |
| |
| ##### 4.2.1.4. Detecting Increased Queue Delay |
| |
| - **Queue Delay Average (`queue_delay_avg_`) Filtering and Update Frequency:** |
| - The spec updates `qdelay_avg` at most once per `min(virtual_rtt, s_rtt)` |
| directly from the latest `qdelay` sample |
| ([Section 4.2.1.4](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.1.4)). |
| - WebRTC updates `queue_delay_avg_` on **every feedback message**, passing |
| `min(min_queue_delay, last_queue_delay_sample_)` (the minimum of the current |
| and previous feedback's minimum queue delay) into the slow-attack |
| (`queue_delay_avg_g = 1/4`), instantaneous-decay EWMA filter. Taking the |
| minimum of two consecutive samples filters out single-feedback delay spikes. |
| See |
| [delay_based_congestion_control.cc:L50-L75](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/delay_based_congestion_control.cc#50). |
| - **Jitter and Standing-Queue Scaling Factors (`ref_wnd_delay_scale`):** |
| - In draft `-01`, the spec defines an optional `REDUCE_JITTER` mechanism that |
| tracks `qdelay_max_avg` and `qdelay_min_avg`, computes an EWMA of their |
| difference `qdelay_dev_avg` (`QDELAY_DEV_AVG_G = 1/32`), and derives a |
| single scaling factor |
| `ref_wnd_delay_scale = clamp(1.0 - qdelay_dev_avg / QDELAY_DEV_THRESHOLD, 0.0, 1.0)` |
| with `QDELAY_DEV_THRESHOLD = 10 ms` |
| ([Section 4.2.1.4](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.1.4)). |
| - The WebRTC implementation (always enabled) instead computes **two separate |
| scaling factors** in |
| [DelayBasedCongestionControl](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/delay_based_congestion_control.h) |
| ([delay_based_congestion_control.cc](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/delay_based_congestion_control.cc)), |
| both using an EWMA gain of `delay_min_and_latency_diff_avg_g = 1/32` (input |
| capped at `2 * threshold`) and a linear mapping |
| `clamp(0.1 + 1.2 * (norm - avg) / norm, min_val, 1.0)` that is `1.0` at or |
| below `norm / 4` (2.5 ms) and `0.1` at `norm` (10 ms): |
| 1. `ref_window_scale_factor_due_to_avg_min_delay` (threshold |
| `queue_delay_min_threshold = 10 ms`): Based on `queue_delay_min_avg_` |
| (EWMA of `min_queue_delay` per feedback). Used to scale down L4S backoff, |
| scale down reference window increase, and (with `allow_zero = true`, |
| reaching `0.0` at `13/12 * norm`) scale `ref_window_overhead` in |
| `max_data_in_flight()`. See |
| [delay_based_congestion_control.cc:L125-L131](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/delay_based_congestion_control.cc#125). |
| 2. `ref_window_scale_factor_due_to_latency_difference` (threshold |
| `latency_diff_threshold = 10 ms`): Based on `latency_difference_avg_` |
| (EWMA of `max_one_way_delay - min_one_way_delay` within the feedback's |
| burst window). Used to scale down reference window increase when |
| intra-burst delay variation indicates the send rate is close to |
| bottleneck capacity. See |
| [delay_based_congestion_control.cc:L134-L140](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/delay_based_congestion_control.cc#134). |
| |
| #### 4.2.2. Reference Window Update |
| |
| ##### 4.2.2.1. Reference Window Reduction |
| |
| - **Congestion Reaction Interval:** |
| - The spec gates congestion reactions by |
| `now - last_reaction_to_congestion_time >= min(VIRTUAL_RTT, s_rtt)` |
| ([Section 4.2.2.1](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2.1)). |
| - WebRTC gates reactions by `min(rtt, ReactionInterval())`, where |
| `ReactionInterval()` is `max(virtual_rtt, feedback_interval_)` when |
| `use_feedback_interval_for_virtual_rtt` is true. See |
| [scream_v2.cc:L218-L220](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#218). |
| - **Non-Linear Backoff Scaling (`ScaleBackoff`) & In-Flight Burst |
| Compensation:** |
| - The spec scales `backoff_t` linearly by dividing by |
| `max(1.0, s_rtt / VIRTUAL_RTT)` on every reaction interval during an RTT |
| ([Section 4.2.2.1](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2.1)). |
| - When `use_non_linear_backoff_scale` is enabled (default `true`), WebRTC uses |
| a stateful 4-state backoff scaler in `ScreamV2::ScaleBackoff()` |
| ([scream_v2.cc:L121-L176](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#121)): |
| - **Immediate Full Reduction:** Applies the full `backoff` on the first |
| congestion event of a cycle, rather than spreading it across multiple |
| feedbacks over the RTT. |
| - **Wait for In-Flight Data to Drain:** Keeps resetting the 1-RTT |
| observation timer while `data_in_flight > ref_window_` (for up to one RTT |
| after the last window decrease), since bottleneck queues cannot drain |
| until actual in-flight bytes drop to `ref_window_`. |
| - **Incremental Worsening Only:** During the 1-RTT observation window, if |
| `backoff` increases above `last_backoff_`, only the incremental delta |
| `(backoff - last_backoff_) / (1.0 - last_backoff_)` is applied; otherwise |
| `0.0` is returned. |
| - **Excess In-Flight Scaling (`ref_window_share`):** If |
| `data_in_flight > ref_window_ * bytes_in_flight_head_room`, the backoff is |
| scaled by `ref_window_ / data_in_flight` so transient bursts (e.g., |
| keyframes) do not cause an excessive reduction of `ref_window_`. |
| - **Initial Reference Window Clamping on First Congestion:** |
| - When `allow_initial_ref_window_clamping` is enabled (default `true`), on the |
| very first congestion reaction (if `received_rate_` is finite), WebRTC |
| clamps `ref_window_` down to |
| `max(min_ref_window, received_rate_ * (rtt + feedback_hold_time_))` if that |
| produces a larger reduction than `backoff`. This quickly corrects an overly |
| high initial starting bitrate. See |
| [scream_v2.cc:L265-L278](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#265). |
| - **Inflection Point (`ref_window_i_`) Scaling Factor:** |
| - The spec uses a factor of **8** in |
| `scl_t = ((ref_wnd - ref_wnd_i) / ref_wnd_i) * 8` |
| ([Section 4.2.2.1](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2.1)). |
| - WebRTC defaults to a lower factor of **2.0** |
| (`backoff_scale_factor_close_to_ref_window_i` in |
| [scream_v2_parameters.cc:L43-L45](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2_parameters.cc#43)), |
| resulting in slower, more cautious window changes near `ref_window_i_`. |
| - **L4S Backoff Attenuation Condition and Floor:** |
| - The spec attenuates L4S backoff by `max(0.25, scl_t)` and |
| `max(0.25, ref_wnd_delay_scale)` when `qdelay < qdelay_target * 0.25` |
| ([Section 4.2.2.1](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2.1)). |
| - WebRTC applies this attenuation when |
| `!delay_based_congestion_control_.IsQueueDelayDetected()`, which corresponds |
| to `queue_delay_avg_ <= queue_delay_target / 2` (default 30 ms), and uses a |
| lower floor of `0.1` (instead of `0.25`) for |
| `ref_window_scale_factor_due_to_avg_min_delay()`. See |
| [scream_v2.cc:L228-L240](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#228). |
| - **Inflection Point (`ref_window_i_`) Update Timing:** |
| - In the spec, `ref_wnd_i` is updated to `ref_wnd` *before* the reduction |
| whenever `is_loss_t || is_ce_t || is_virtual_ce_t` is true and |
| `ref_wnd_i_update_allowed` is true, and `ref_wnd_i_update_allowed` is |
| re-enabled when `ref_wnd` increases during the increase step |
| ([Section 4.2.2.1](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2.1)). |
| - In WebRTC, because window reduction and window increase can both execute on |
| the same feedback message, `ref_window_i_` and `allow_ref_window_i_update_` |
| are updated at the end of `UpdateRefWindow()` based on the **net change** in |
| `ref_window_` compared to `previous_ref_window` (recording |
| `previous_ref_window` only on a net decrease, and re-arming |
| `allow_ref_window_i_update_` only on a net increase). See |
| [scream_v2.cc:L340-L352](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#340). |
| |
| ##### 4.2.2.2. Reference Window Increase |
| |
| - **When Reference Window Increase Runs:** |
| - In the spec, the reference window increase step runs unconditionally on |
| feedback (even when congestion occurs, to maintain the L4S 2-marks/RTT |
| equilibrium) |
| ([Section 4.2.2.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2.2)). |
| - In WebRTC, the increase runs only when either no congestion is active |
| (`!is_ce && loss_congestion_level() < 0.01 && !is_virtual_ce`) **or** a |
| congestion backoff reaction occurred on the current feedback |
| (`last_reaction_to_congestion_time_ == parsed.feedback_time`). If congestion |
| is present on a feedback that is within `ReactionInterval()` of the previous |
| reaction, window growth is skipped. See |
| [scream_v2.cc:L284-L285](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#284). |
| - **RTT Scaling for Small RTTs Includes `feedback_hold_time_`:** |
| - The spec scales down window increase for small RTTs using |
| `min(1.0, s_rtt / VIRTUAL_RTT)` |
| ([Section 4.2.2.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2.2)). |
| - WebRTC includes `feedback_hold_time_` in the numerator when scaling for |
| small RTTs: `(rtt + feedback_hold_time_) / virtual_rtt`. See |
| [scream_v2.cc:L294-L300](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#294). |
| - **Application-Limited (ALR) State Tracking:** |
| - The spec restricts `ref_wnd` growth using |
| `max_allowed_t = MSS + max(max_bytes_in_flight, max_bytes_in_flight_prev) * BYTES_IN_FLIGHT_HEAD_ROOM` |
| and `target_bitrate < TARGET_BITRATE_MAX` |
| ([Section 4.2.2.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.2.2.2)), |
| and notes in |
| [Section 6](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-6) |
| that RTT should be updated with a longer time constant when bytes in flight |
| is low. |
| - WebRTC implements both `max_allowed_ref_window()` clamping |
| ([scream_v2.cc:L337-L341](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#337)) |
| and an explicit `is_application_limited_` (ALR) state (enabled via |
| `enable_alr`, default `true`), entered when |
| `max_allowed_ref_window() < ref_window_` and |
| `received_rate_ < alr_threshold * target_rate_` (with `alr_threshold = 0.9`, |
| where `received_rate_` is measured over `received_rate_window = 100 ms`). |
| See |
| [scream_v2.cc:L69-L73](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#69). |
| - When `is_application_limited_` is true: |
| 1. The smoothed RTT EWMA gain is reduced from `smoothed_rtt_avg_g` (`1/8`) |
| to `smoothed_rtt_avg_in_alr_g` (`1/128`). See |
| [delay_based_congestion_control.cc:L104-L106](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/delay_based_congestion_control.cc#104). |
| 2. `feedback_hold_time_` updates are frozen. See |
| [scream_v2.cc:L78-L80](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#78). |
| 3. `ScreamNetworkController` reports `is_bandwidth_limited = false` in |
| `TargetTransferRate`. See |
| [scream_network_controller.cc:L239](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_network_controller.cc#239). |
| |
| ### 4.3. Sender Transmission Control |
| |
| #### 4.3.1. Send Window Calculation |
| |
| - `ScreamV2::max_data_in_flight()` |
| ([scream_v2.cc:L371-L382](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#371)) |
| computes `ref_window_ * ref_window_overhead` with `ref_window_overhead` |
| interpolated between `ref_window_overhead_min` (1.5) and |
| `ref_window_overhead_max` (3.0) as in |
| [Section 4.3.1](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.3.1), |
| using `ref_window_scale_factor_due_to_avg_min_delay(/*allow_zero=*/true)` in |
| place of `ref_wnd_delay_scale`. |
| - `ScreamNetworkController` exports `max_data_in_flight()` as |
| `update.congestion_window` to WebRTC's pacer/transport |
| ([scream_network_controller.cc:L276](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_network_controller.cc#276)). |
| |
| #### 4.3.2. Packet Pacing |
| |
| - **Pacing Rate Formula & No Relaxed Pacing Multiplier:** |
| - The spec defines `PACKET_PACING_HEADROOM = 1.5`, `RATE_PACE_MIN = 50 kbps`, |
| and a "relaxed pacing" multiplier (`MAX_RELAXED_PACING_FACTOR = 4.0`, |
| `RELAXED_PACING_LIMIT_LOW = 0.8`) that increases `pace_bitrate` up to `4x` |
| when `target_bitrate` approaches `TARGET_BITRATE_MAX` |
| ([Section 4.3.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.3.2)). |
| - WebRTC does not implement the spec's relaxed pacing rate multiplier. |
| Instead: |
| - `ScreamV2::pacing_rate()` computes |
| `max(target_rate() * pacing_factor, pacing_rate_received_factor * received_rate_)` |
| with `pacing_factor = 1.1` and `pacing_rate_received_factor = 0.8` |
| ([scream_v2.h:L50-L57](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.h#50)). |
| - `ScreamNetworkController::GetPacingRate()` floors this at |
| `min_total_allocated_bitrate * pacing_factor` |
| ([scream_network_controller.cc:L215-L220](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_network_controller.cc#215)). |
| - To relax pacing when the link is uncongested, |
| `ScreamNetworkController::MaybeCreatePacerConfig()` adjusts the WebRTC |
| pacer's **burst time window** (`current_pacing_window_`): it uses a tight |
| `10 ms` pacing window after recent congestion, and relaxes to |
| `default_pacing_window_` once |
| `allow_large_pacing_bursts_after_congestion_time` (default 15 s) has |
| elapsed since `last_reaction_to_congestion_time()`. See |
| [scream_network_controller.cc:L284-L289](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_network_controller.cc#284). |
| - **Bandwidth Probing and Periodic Padding (WebRTC Addition):** |
| - The spec does not define active bandwidth probing or padding. |
| - `ScreamNetworkController` implements initial BWE probing |
| (`initial_probing_duration = 6 s`) and periodic padding bursts |
| (`periodic_padding_duration = 3 s` every |
| `time_between_periodic_padding = 3 s`, gated by |
| `allow_padding_after_last_congestion_time = 1 s` since the last reference |
| window decrease) when `target_rate` is below the maximum allocated/target |
| bitrate. See |
| [scream_network_controller.cc:L64-L89](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_network_controller.cc#64) |
| and |
| [scream_network_controller.cc:L291-L326](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_network_controller.cc#291). |
| |
| ### 4.4. Media Rate Control |
| |
| - **Target Bitrate Calculation:** |
| - The spec calculates `target_bitrate = tmp_t * 8 * ref_wnd / s_rtt`, where |
| `tmp_t` includes: |
| 1. Small-window scaling `1.0 - clamp(ref_wnd_ratio - 0.1, 0.0, 0.2)`, |
| 2. Packetization overhead scaling `MSS / (MSS + PACKET_OVERHEAD)`, and |
| 3. Sender queue and frame size variation division |
| `1 / (1.2 + rate_adjust_factor + frame_size_dev)` |
| ([Section 4.4](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.4)). |
| - WebRTC's `ScreamV2::UpdateTargetRate()` includes the small-window scaling |
| `(1.0 - clamp(ref_window_mss_ratio() - 0.1, 0.0, 0.2))` and computes |
| `scale_target_rate * (ref_window_ / (non_zero_smoothed_rtt + feedback_hold_time_))`, |
| clamped to `[min_target_bitrate_, max_target_bitrate_]`. It **omits** the |
| `MSS / (MSS + PACKET_OVERHEAD)` factor and the |
| `1.2 + rate_adjust_factor + frame_size_dev` divisor (noted as a TODO in |
| [scream_v2.cc:L458-L459](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#458)). |
| - **Pacer Queue Pushback and Congestion Encoder Pause (WebRTC Alternative):** |
| - Instead of `rate_adjust_factor` and `frame_size_dev` inside `ScreamV2`, |
| `ScreamNetworkController` monitors the WebRTC pacer queue delay |
| (`pacing_delay = pacer_queue_size_ / pacing_rate`): |
| - **`cwnd_reduce_ratio` Pushback:** Scales linearly from `0.0` at |
| `min_pacing_delay_for_pushback` (default 100 ms) to `1.0` at |
| `max_pacing_delay_for_pushback` (default 500 ms) and is reported in |
| `TargetTransferRate::cwnd_reduce_ratio` to push back video encoders. See |
| [scream_network_controller.cc:L222-L235](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_network_controller.cc#222). |
| - **Encoder Pause:** If `data_in_flight_ > max_data_in_flight()` and |
| `pacing_delay > max_pacing_delay_for_pushback` (500 ms), |
| `ScreamNetworkController` sets `encoder_paused_due_to_congestion_ = true` |
| and reports a `target_rate` of `0` until `!is_congested` and |
| `pacing_delay < min_pacing_delay_for_pushback` (100 ms). See |
| [scream_network_controller.cc:L246-L258](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_network_controller.cc#246). |
| |
| ### 4.5. Additional Functions |
| |
| #### 4.5.1. Clock Drift Issues and Remedies |
| |
| - **Base Delay Filter and Queue Draining:** |
| - The spec describes tracking `qdelay_min` per RTT into a slow EWMA |
| `qdelay_min_avg` (`QDELAY_MIN_AVG_ALPHA = 1/256`). If |
| `qdelay_min_avg > qdelay_target / 4`, it resets the queue delay history and |
| halves `target_bitrate` for `max(5 * s_rtt, 0.2 s)` |
| ([Section 4.5.1](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.5.1)). |
| - WebRTC implements two complementary mechanisms: |
| 1. **LEDBAT Sliding Window Base Delay Filter:** Tracks minimum one-way delay |
| using `WindowedMinFilter<TimeDelta>` over `base_delay_window_length = 10` |
| intervals of `base_delay_history_update_interval = 1 minute` |
| ([delay_based_congestion_control.cc:L56-L61](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/delay_based_congestion_control.cc#56)). |
| 2. **Persistent Queue Delay Drain and Reset:** Instead of checking an EWMA |
| against `qdelay_target / 4` and resetting *before* draining, WebRTC |
| checks whether `min_queue_delay` has remained continuously above |
| `queue_delay_drain_threshold` (default 5 ms) for |
| `queue_delay_drain_period` (default 20 s) via `IsQueueDrainedInTime()`. |
| When exceeded, `UpdateTargetRate()` **first** halves `target_rate` |
| (`0.5 * target_rate`) for |
| `max(100 ms, queue_delay_drain_rtts * smoothed_rtt)` (default 5 RTTs) to |
| attempt to drain a standing self-inflicted queue. Only if |
| `min_queue_delay` fails to drop below the threshold by the end of that |
| drain period does WebRTC call `ResetQueueDelay()` to reset the base delay |
| history. See |
| [scream_v2.cc:L435-L456](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.cc#435). |
| |
| #### 4.5.2. Link Layer Losses and Rate Policers |
| |
| - **Link Layer Losses:** |
| - Instead of the per-packet EWMA `loss_rate` filter in |
| [Section 4.5.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.5.2), |
| WebRTC filters non-congestion link-layer losses using `LossEstimator`'s |
| biased asymmetric step filter (see Section 4.2.1.1 above), while sharing the |
| spec's principle of triggering immediate loss backoff when queue delay is |
| also elevated (`is_virtual_ce`, i.e., |
| `queue_delay_avg_ > queue_delay_target / 2` vs. |
| `qdelay_avg > qdelay_target / 4` in the spec). |
| - **Rate Policer Detection Not Implemented:** |
| - The optional rate policer detection and `max_policed_ref_wnd` cap |
| (`LOSS_RATE_THRESHOLD_POLICER`, `BETA_LOSS_POLICER`) in |
| [Section 4.5.2](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.5.2) |
| are **not implemented** in WebRTC. |
| |
| #### 4.5.3. Reference Window Undershoot at Congestion |
| |
| - **Different Undershoot Prevention Mechanism:** |
| - The spec describes an optional check |
| (`if target_bitrate < acked_bitrate * ACKED_BITRATE_MARGIN`, with |
| `ACKED_BITRATE_MARGIN = 0.8`, scale `backoff_t` by |
| `BACKOFF_SCALE_LOW_TARGETRATE = 0.25`) to avoid undershooting when an RTT |
| spike has already depressed `target_bitrate` |
| ([Section 4.5.3](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.5.3)). |
| - WebRTC **does not implement** `ACKED_BITRATE_MARGIN` or |
| `BACKOFF_SCALE_LOW_TARGETRATE`. Instead, WebRTC mitigates backoff undershoot |
| via `ScaleBackoff()` (waiting one full RTT after |
| `data_in_flight <= ref_window_` before starting a new full reduction, and |
| scaling backoff by `ref_window_ / data_in_flight` when in-flight bytes |
| exceed `ref_window_ * bytes_in_flight_head_room`; see Section 4.2.2.1). |
| - WebRTC also uses `received_rate_` (acknowledged bitrate over a 100 ms |
| window) to floor the pacing rate (`0.8 * received_rate_`) so pacing does not |
| collapse if `target_rate()` temporarily dips during an RTT spike |
| ([scream_v2.h:L51-L55](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2.h#51)). |
| |
| #### 4.5.4. Competing Flows Compensation |
| |
| - **Not Implemented:** |
| - The spec describes an optional dynamic `qdelay_target` adjustment |
| (`adjust_qdelay_target` between `QDELAY_TARGET_LO = 60 ms` and |
| `QDELAY_TARGET_HI = 400 ms`) to compete with aggressive loss-based flows |
| over tail-drop queues |
| ([Section 4.5.4](https://www.ietf.org/archive/id/draft-ietf-ccwg-rfc8298bis-screamv2-01.html#section-4.5.4)). |
| - The WebRTC implementation **does not implement** competing flows |
| compensation; `queue_delay_target` remains static (default 60 ms). This is |
| noted as a TODO in |
| [scream_v2_parameters.h:L121-L122](https://webrtc.googlesource.com/src/+/main/modules/congestion_controller/scream/scream_v2_parameters.h#121). |