goog_cc/
loss_based_bandwidth_estimation.rs

1/*
2 *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11use crate::api::{
12    transport::PacketResult,
13    units::{DataRate, TimeDelta, Timestamp},
14};
15
16#[derive(Clone, Debug)]
17pub struct LossBasedControlConfig {
18    pub enabled: bool,                             // Enabled
19    pub min_increase_factor: f64,                  // min_incr
20    pub max_increase_factor: f64,                  // max_incr
21    pub increase_low_rtt: TimeDelta,               // incr_low_rtt
22    pub increase_high_rtt: TimeDelta,              // incr_high_rtt
23    pub decrease_factor: f64,                      // decr
24    pub loss_window: TimeDelta,                    // loss_win
25    pub loss_max_window: TimeDelta,                // loss_max_win
26    pub acknowledged_rate_max_window: TimeDelta,   // ackrate_max_win
27    pub increase_offset: DataRate,                 // incr_offset
28    pub loss_bandwidth_balance_increase: DataRate, // balance_incr
29    pub loss_bandwidth_balance_decrease: DataRate, // balance_decr
30    pub loss_bandwidth_balance_reset: DataRate,    // balance_reset
31    pub loss_bandwidth_balance_exponent: f64,      // exponent
32    pub allow_resets: bool,                        // resets
33    pub decrease_interval: TimeDelta,              // decr_intvl
34    pub loss_report_timeout: TimeDelta,            // timeout
35}
36
37impl Default for LossBasedControlConfig {
38    fn default() -> Self {
39        Self {
40            enabled: false,
41            min_increase_factor: 1.02,
42            max_increase_factor: 1.08,
43            increase_low_rtt: TimeDelta::from_millis(200),
44            increase_high_rtt: TimeDelta::from_millis(800),
45            decrease_factor: 0.99,
46            loss_window: TimeDelta::from_millis(800),
47            loss_max_window: TimeDelta::from_millis(800),
48            acknowledged_rate_max_window: TimeDelta::from_millis(800),
49            increase_offset: DataRate::from_bits_per_sec(1000),
50            loss_bandwidth_balance_increase: DataRate::from_kilobits_per_sec_float(0.5),
51            loss_bandwidth_balance_decrease: DataRate::from_kilobits_per_sec(4),
52            loss_bandwidth_balance_reset: DataRate::from_kilobits_per_sec_float(0.1),
53            loss_bandwidth_balance_exponent: 0.5,
54            allow_resets: false,
55            decrease_interval: TimeDelta::from_millis(300),
56            loss_report_timeout: TimeDelta::from_millis(6000),
57        }
58    }
59}
60
61// Estimates an upper BWE limit based on loss.
62// It requires knowledge about lost packets and acknowledged bitrate.
63// Ie, this class require transport feedback.
64pub struct LossBasedBandwidthEstimation {
65    config: LossBasedControlConfig,
66    average_loss: f64,
67    average_loss_max: f64,
68    loss_based_bitrate: DataRate,
69    acknowledged_bitrate_max: DataRate,
70    acknowledged_bitrate_last_update: Timestamp,
71    time_last_decrease: Timestamp,
72    has_decreased_since_last_loss_report: bool,
73    last_loss_packet_report: Timestamp,
74    last_loss_ratio: f64,
75}
76
77impl LossBasedBandwidthEstimation {
78    // Expecting RTCP feedback to be sent with roughly 1s intervals, a 5s gap
79    // indicates a channel outage.
80    const MAX_RTCP_FEEDBACK_INTERVAL: TimeDelta = TimeDelta::from_millis(5000);
81
82    pub fn new(config: LossBasedControlConfig) -> Self {
83        Self {
84            config,
85            average_loss: 0.0,
86            average_loss_max: 0.0,
87            loss_based_bitrate: DataRate::zero(),
88            acknowledged_bitrate_max: DataRate::zero(),
89            acknowledged_bitrate_last_update: Timestamp::minus_infinity(),
90            time_last_decrease: Timestamp::minus_infinity(),
91            has_decreased_since_last_loss_report: false,
92            last_loss_packet_report: Timestamp::minus_infinity(),
93            last_loss_ratio: 0.0,
94        }
95    }
96
97    // Returns the new estimate.
98    pub fn update(
99        &mut self,
100        at_time: Timestamp,
101        min_bitrate: DataRate,
102        wanted_bitrate: DataRate,
103        last_round_trip_time: TimeDelta,
104    ) -> DataRate {
105        if self.loss_based_bitrate.is_zero() {
106            self.loss_based_bitrate = wanted_bitrate;
107        }
108
109        // Only increase if loss has been low for some time.
110        let loss_estimate_for_increase: f64 = self.average_loss_max;
111        // Avoid multiple decreases from averaging over one loss spike.
112        let loss_estimate_for_decrease: f64 = self.average_loss.min(self.last_loss_ratio);
113        let allow_decrease: bool = !self.has_decreased_since_last_loss_report
114            && (at_time - self.time_last_decrease
115                >= last_round_trip_time + self.config.decrease_interval);
116        // If packet lost reports are too old, dont increase bitrate.
117        let loss_report_valid: bool =
118            at_time - self.last_loss_packet_report < 1.2 * Self::MAX_RTCP_FEEDBACK_INTERVAL;
119
120        if loss_report_valid
121            && self.config.allow_resets
122            && loss_estimate_for_increase < self.loss_reset_threshold()
123        {
124            self.loss_based_bitrate = wanted_bitrate;
125        } else if loss_report_valid && loss_estimate_for_increase < self.loss_increase_threshold() {
126            // Increase bitrate by RTT-adaptive ratio.
127            let new_increased_bitrate: DataRate = min_bitrate
128                * get_increase_factor(&self.config, last_round_trip_time)
129                + self.config.increase_offset;
130            // The bitrate that would make the loss "just high enough".
131            let new_increased_bitrate_cap: DataRate = bitrate_from_loss(
132                loss_estimate_for_increase,
133                self.config.loss_bandwidth_balance_increase,
134                self.config.loss_bandwidth_balance_exponent,
135            );
136            let new_increased_bitrate =
137                std::cmp::min(new_increased_bitrate, new_increased_bitrate_cap);
138            self.loss_based_bitrate = std::cmp::max(new_increased_bitrate, self.loss_based_bitrate);
139        } else if loss_estimate_for_decrease > self.loss_decrease_threshold() && allow_decrease {
140            // The bitrate that would make the loss "just acceptable".
141            let new_decreased_bitrate_floor: DataRate = bitrate_from_loss(
142                loss_estimate_for_decrease,
143                self.config.loss_bandwidth_balance_decrease,
144                self.config.loss_bandwidth_balance_exponent,
145            );
146            let new_decreased_bitrate: DataRate =
147                std::cmp::max(self.decreased_bitrate(), new_decreased_bitrate_floor);
148            if new_decreased_bitrate < self.loss_based_bitrate {
149                self.time_last_decrease = at_time;
150                self.has_decreased_since_last_loss_report = true;
151                self.loss_based_bitrate = new_decreased_bitrate;
152            }
153        }
154        self.loss_based_bitrate
155    }
156    pub fn update_acknowledged_bitrate(
157        &mut self,
158        acknowledged_bitrate: DataRate,
159        at_time: Timestamp,
160    ) {
161        let time_passed: TimeDelta = if self.acknowledged_bitrate_last_update.is_finite() {
162            at_time - self.acknowledged_bitrate_last_update
163        } else {
164            TimeDelta::from_seconds(1)
165        };
166        self.acknowledged_bitrate_last_update = at_time;
167        if acknowledged_bitrate > self.acknowledged_bitrate_max {
168            self.acknowledged_bitrate_max = acknowledged_bitrate;
169        } else {
170            self.acknowledged_bitrate_max -=
171                exponential_update(self.config.acknowledged_rate_max_window, time_passed)
172                    * (self.acknowledged_bitrate_max - acknowledged_bitrate);
173        }
174    }
175    pub fn initialize(&mut self, bitrate: DataRate) {
176        self.loss_based_bitrate = bitrate;
177        self.average_loss = 0.0;
178        self.average_loss_max = 0.0;
179    }
180    pub fn enabled(&self) -> bool {
181        self.config.enabled
182    }
183    // Returns true if LossBasedBandwidthEstimation is enabled and have
184    // received loss statistics. Ie, this class require transport feedback.
185    pub fn in_use(&self) -> bool {
186        self.enabled() && self.last_loss_packet_report.is_finite()
187    }
188    pub fn update_loss_statistics(&mut self, packet_results: &[PacketResult], at_time: Timestamp) {
189        if packet_results.is_empty() {
190            unreachable!();
191        }
192        let mut loss_count: i64 = 0;
193        for pkt in packet_results {
194            loss_count += if !pkt.is_received() { 1 } else { 0 };
195        }
196        self.last_loss_ratio = (loss_count) as f64 / packet_results.len() as f64;
197        let time_passed: TimeDelta = if self.last_loss_packet_report.is_finite() {
198            at_time - self.last_loss_packet_report
199        } else {
200            TimeDelta::from_seconds(1)
201        };
202        self.last_loss_packet_report = at_time;
203        self.has_decreased_since_last_loss_report = false;
204
205        self.average_loss += exponential_update(self.config.loss_window, time_passed)
206            * (self.last_loss_ratio - self.average_loss);
207        if self.average_loss > self.average_loss_max {
208            self.average_loss_max = self.average_loss;
209        } else {
210            self.average_loss_max += exponential_update(self.config.loss_max_window, time_passed)
211                * (self.average_loss - self.average_loss_max);
212        }
213    }
214
215    /* unused
216    pub fn get_estimate(&self) -> DataRate {
217        self.loss_based_bitrate
218    }
219    */
220
221    fn loss_increase_threshold(&self) -> f64 {
222        loss_from_bitrate(
223            self.loss_based_bitrate,
224            self.config.loss_bandwidth_balance_increase,
225            self.config.loss_bandwidth_balance_exponent,
226        )
227    }
228    fn loss_decrease_threshold(&self) -> f64 {
229        loss_from_bitrate(
230            self.loss_based_bitrate,
231            self.config.loss_bandwidth_balance_decrease,
232            self.config.loss_bandwidth_balance_exponent,
233        )
234    }
235    fn loss_reset_threshold(&self) -> f64 {
236        loss_from_bitrate(
237            self.loss_based_bitrate,
238            self.config.loss_bandwidth_balance_reset,
239            self.config.loss_bandwidth_balance_exponent,
240        )
241    }
242
243    fn decreased_bitrate(&self) -> DataRate {
244        self.config.decrease_factor * self.acknowledged_bitrate_max
245    }
246}
247
248// Increase slower when RTT is high.
249fn get_increase_factor(config: &LossBasedControlConfig, mut rtt: TimeDelta) -> f64 {
250    // Clamp the RTT
251    if rtt < config.increase_low_rtt {
252        rtt = config.increase_low_rtt;
253    } else if rtt > config.increase_high_rtt {
254        rtt = config.increase_high_rtt;
255    }
256    let rtt_range = config.increase_high_rtt - config.increase_low_rtt;
257    if rtt_range <= TimeDelta::zero() {
258        unreachable!(); // Only on misconfiguration.
259    }
260    let rtt_offset = rtt - config.increase_low_rtt;
261    let relative_offset = (rtt_offset / rtt_range).clamp(0.0, 1.0);
262    let factor_range = config.max_increase_factor - config.min_increase_factor;
263    config.min_increase_factor + (1.0 - relative_offset) * factor_range
264}
265
266fn loss_from_bitrate(bitrate: DataRate, loss_bandwidth_balance: DataRate, exponent: f64) -> f64 {
267    if loss_bandwidth_balance >= bitrate {
268        return 1.0;
269    }
270    (loss_bandwidth_balance / bitrate).powf(exponent)
271}
272
273fn bitrate_from_loss(loss: f64, loss_bandwidth_balance: DataRate, exponent: f64) -> DataRate {
274    if exponent <= 0.0 {
275        unreachable!();
276    }
277    if loss < 1e-5 {
278        return DataRate::infinity();
279    }
280    loss_bandwidth_balance * loss.powf(-1.0 / exponent)
281}
282
283fn exponential_update(window: TimeDelta, interval: TimeDelta) -> f64 {
284    // Use the convention that exponential window length (which is really
285    // infinite) is the time it takes to dampen to 1/e.
286    if window <= TimeDelta::zero() {
287        unreachable!();
288    }
289
290    1.0 - (interval / window * -1.0).exp()
291}