SCReAMv2 Implementation Differences

Date: October 13, 2026
Commit: 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) and the IETF Working Group draft draft-ietf-ccwg-rfc8298bis-screamv2-01 (datatracker).

The WebRTC implementation is currently in development and some features are not yet fully implemented.

4. Detailed Description of SCReAMv2 Sender Algorithm

4.1. Sender Side State

4.1.1. Status Update When Sending Data

  • Differences in Default Parameters:
  • Initial Reference Window Initialization:
    • The spec initializes ref_wnd to MIN_REF_WND (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.

4.1.2. Status Update on Receiving Feedback

  • The WebRTC implementation normalizes TransportPacketsFeedback into a flat ScreamFeedback struct in scream_feedback.h and 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), 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.
  • 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.
  • 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 and scream_v2.cc:L396-L404.
    • 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 and scream_v2.cc:L406-L419.

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 and an optional per-packet EWMA loss_rate filter in 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 (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.
    • 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.
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).
    • 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), 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).
    • 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.
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).
    • 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.
  • 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).
    • The WebRTC implementation (always enabled) instead computes two separate scaling factors in DelayBasedCongestionControl (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.
      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.

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).
    • 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.
  • 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).
    • 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):
      • 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.
  • 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).
    • WebRTC defaults to a lower factor of 2.0 (backoff_scale_factor_close_to_ref_window_i in scream_v2_parameters.cc:L43-L45), 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).
    • 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.
  • 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).
    • 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.
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).
    • 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.
  • 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).
    • 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.
  • 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), and notes in 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) 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.
    • 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.
      2. feedback_hold_time_ updates are frozen. See scream_v2.cc:L78-L80.
      3. ScreamNetworkController reports is_bandwidth_limited = false in TargetTransferRate. See scream_network_controller.cc:L239.

4.3. Sender Transmission Control

4.3.1. Send Window Calculation

  • ScreamV2::max_data_in_flight() (scream_v2.cc:L371-L382) 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, 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).

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).
    • 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).
      • ScreamNetworkController::GetPacingRate() floors this at min_total_allocated_bitrate * pacing_factor (scream_network_controller.cc:L215-L220).
      • 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.
  • 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 and scream_network_controller.cc:L291-L326.

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).
    • 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).
  • 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.
      • 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.

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).
    • 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).
      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.

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, 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 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).
    • 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).

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).
    • 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.