/* * Copyright 2026 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/congestion_controller/scream/loss_estimator.h" #include #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "modules/congestion_controller/scream/scream_feedback.h" #include "modules/congestion_controller/scream/scream_v2_parameters.h" namespace webrtc { LossEstimator::LossEstimator(const ScreamV2Parameters& params) : virtual_rtt_(params.virtual_rtt.Get()), rtts_with_loss_before_backoff_( params.rtts_with_loss_before_backoff.Get()), lossless_rtts_before_clear_(params.lossless_rtts_before_clear.Get()) {} bool LossEstimator::Update(const ScreamFeedback& parsed, TimeDelta rtt) { const TimeDelta max_rtt = std::max(virtual_rtt_, rtt); if (parsed.feedback_time - last_loss_or_recovery_time_ > max_rtt) { // Reset the unrecovered lost packet count if no new losses or recoveries // were reported for a full RTT. This prevents old, permanently lost // packets (which will never be recovered) from keeping this counter // positive indefinitely. unrecovered_lost_packets_ = 0; } bool has_lost_packets = false; if (parsed.num_lost_packets > 0 || parsed.num_recovered_packets > 0) { last_loss_or_recovery_time_ = parsed.feedback_time; } if (parsed.num_recovered_packets > 0) { unrecovered_lost_packets_ = std::max(0, unrecovered_lost_packets_ - parsed.num_recovered_packets); if (unrecovered_lost_packets_ == 0) { congestion_level_ = 0.0; loss_event_this_rtt_ = false; } } if (parsed.num_lost_packets > 0) { has_lost_packets = true; unrecovered_lost_packets_ += parsed.num_lost_packets; loss_event_this_rtt_ = true; } if (parsed.feedback_time - last_rtt_update_time_ >= max_rtt) { last_rtt_update_time_ = parsed.feedback_time; if (loss_event_this_rtt_) { // Asymmetric step filter: increment by 1.0 / // rtts_with_loss_before_backoff_ on a [0.0, 1.0] normalized scale. This // takes exactly rtts_with_loss_before_backoff_ consecutive RTTs with loss // to trigger backoff (reaching the 1.0 threshold). congestion_level_ = std::min( 1.0, congestion_level_ + 1.0 / rtts_with_loss_before_backoff_); } else { // Asymmetric step filter: decrement by 1.0 / lossless_rtts_before_clear_ // on a [0.0, 1.0] normalized scale. // Assuming ~50 packets are transmitted per RTT, a 1% uniform random // packet loss rate results in a ~40% probability of at least one loss // event per RTT. Under this noise profile (40% loss RTTs, 60% lossless // RTTs), the expected drift per RTT is strongly negative: // 0.4 * (+1.0/3) + 0.6 * (-1.0/2) = -0.167 (or -0.5 on a [0, 3] scale). // This completely filters out spurious uniform random losses, while // preserving memory of recent congestion after one lossless RTT (drops // to 0.5, allowing fast 2-RTT back-to-back reaction). congestion_level_ = std::max(0.0, congestion_level_ - 1.0 / lossless_rtts_before_clear_); } loss_event_this_rtt_ = false; } return has_lost_packets; } } // namespace webrtc