1use crate::api::{
12 transport::PacketResult,
13 units::{DataRate, TimeDelta, Timestamp},
14};
15
16#[derive(Clone, Debug)]
17pub struct LossBasedControlConfig {
18 pub enabled: bool, pub min_increase_factor: f64, pub max_increase_factor: f64, pub increase_low_rtt: TimeDelta, pub increase_high_rtt: TimeDelta, pub decrease_factor: f64, pub loss_window: TimeDelta, pub loss_max_window: TimeDelta, pub acknowledged_rate_max_window: TimeDelta, pub increase_offset: DataRate, pub loss_bandwidth_balance_increase: DataRate, pub loss_bandwidth_balance_decrease: DataRate, pub loss_bandwidth_balance_reset: DataRate, pub loss_bandwidth_balance_exponent: f64, pub allow_resets: bool, pub decrease_interval: TimeDelta, pub loss_report_timeout: TimeDelta, }
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
61pub 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 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 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 let loss_estimate_for_increase: f64 = self.average_loss_max;
111 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 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 let new_increased_bitrate: DataRate = min_bitrate
128 * get_increase_factor(&self.config, last_round_trip_time)
129 + self.config.increase_offset;
130 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 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 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 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
248fn get_increase_factor(config: &LossBasedControlConfig, mut rtt: TimeDelta) -> f64 {
250 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!(); }
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 if window <= TimeDelta::zero() {
287 unreachable!();
288 }
289
290 1.0 - (interval / window * -1.0).exp()
291}