rtc_interceptor/gcc/
estimator.rs1use super::loss::LossController;
4use super::overuse::OveruseDetector;
5use super::rate_calc::RateCalculator;
6use super::rate_control::RateController;
7use super::slope::SlopeEstimator;
8use crate::cc::estimator::{BandwidthEstimator, EstimatorStats};
9use crate::rtpfb::acknowledgement::PacketReport;
10use std::time::Instant;
11
12pub const DEFAULT_INITIAL_BITRATE: f64 = 300_000.0;
14pub const DEFAULT_MIN_BITRATE: f64 = 100_000.0;
16pub const DEFAULT_MAX_BITRATE: f64 = 100_000_000.0;
18
19pub struct Gcc {
49 slope: SlopeEstimator,
50 detector: OveruseDetector,
51 rate: RateCalculator,
52 delay_control: RateController,
53 loss_control: LossController,
54 min: f64,
55 max: f64,
56 target: f64,
57}
58
59impl Default for Gcc {
60 fn default() -> Self {
61 Self::new(
62 DEFAULT_INITIAL_BITRATE,
63 DEFAULT_MIN_BITRATE,
64 DEFAULT_MAX_BITRATE,
65 )
66 }
67}
68
69impl Gcc {
70 pub fn new(initial: f64, min: f64, max: f64) -> Self {
72 let initial = initial.clamp(min, max);
73 Self {
74 slope: SlopeEstimator::new(),
75 detector: OveruseDetector::new(),
76 rate: RateCalculator::default(),
77 delay_control: RateController::new(initial, min, max),
78 loss_control: LossController::new(initial, min, max),
79 min,
80 max,
81 target: initial,
82 }
83 }
84
85 pub fn delay_based_bitrate(&self) -> f64 {
87 self.delay_control.target()
88 }
89
90 pub fn loss_based_bitrate(&self) -> f64 {
92 self.loss_control.target()
93 }
94}
95
96impl BandwidthEstimator for Gcc {
97 fn on_reports(&mut self, now: Instant, reports: &[PacketReport]) {
98 if reports.is_empty() {
99 return;
100 }
101
102 let mut usage = self.detector.usage();
104 for report in reports {
105 if report.arrived {
106 self.rate.add(now, report.size);
107 }
108 if let Some(trend) = self.slope.accumulate(report) {
109 usage = self.detector.update(trend.at, trend.estimate_ms);
110 }
111 }
112
113 let received = self.rate.rate_bits_per_second(now);
114 let delay_target = self.delay_control.update(now, usage, received);
115
116 let lost = reports.iter().filter(|report| !report.arrived).count();
118 let loss_target = self.loss_control.update(now, lost, reports.len());
119
120 self.target = delay_target.min(loss_target).clamp(self.min, self.max);
122 }
123
124 fn target_bitrate(&self) -> f64 {
125 self.target
126 }
127
128 fn stats(&self) -> EstimatorStats {
129 EstimatorStats {
130 delay_based_bitrate: Some(self.delay_control.target()),
131 loss_based_bitrate: Some(self.loss_control.target()),
132 packet_loss: self.loss_control.average_loss(),
133 round_trip_time: None,
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use rtcp::transport_feedbacks::cc_feedback_report::Ecn;
142 use std::time::Duration;
143
144 fn report(id: u64, departure: Instant, arrival_ms: Option<u64>) -> PacketReport {
145 PacketReport {
146 ssrc: 1,
147 id,
148 rtp_sequence_number: id as u16,
149 is_twcc: true,
150 twcc_sequence_number: id as u16,
151 size: 1500,
152 arrived: arrival_ms.is_some(),
153 departure,
154 arrival: arrival_ms.map(Duration::from_millis),
155 ecn: Ecn::default(),
156 }
157 }
158
159 #[test]
161 fn empty_feedback_changes_nothing() {
162 let epoch = Instant::now();
163 let mut gcc = Gcc::default();
164 let before = gcc.target_bitrate();
165
166 gcc.on_reports(epoch, &[]);
167
168 assert_eq!(before, gcc.target_bitrate());
169 }
170
171 #[test]
174 fn the_more_pessimistic_half_wins() {
175 let epoch = Instant::now();
176 let mut gcc = Gcc::default();
177
178 let mut at;
179 for batch in 0..20u64 {
180 at = epoch + Duration::from_millis(batch * 200);
181 let reports: Vec<PacketReport> = (0..10)
183 .map(|index| {
184 let id = batch * 10 + index;
185 let departure = at + Duration::from_millis(index * 10);
186 let arrival = (index % 2 == 0).then(|| batch * 200 + index * 10 + 20);
187 report(id, departure, arrival)
188 })
189 .collect();
190 gcc.on_reports(at, &reports);
191 }
192
193 assert!(
194 gcc.target_bitrate() <= gcc.loss_based_bitrate() + 1.0,
195 "the combined target must not exceed the loss half: {} vs {}",
196 gcc.target_bitrate(),
197 gcc.loss_based_bitrate()
198 );
199 assert!(
200 gcc.target_bitrate() < DEFAULT_INITIAL_BITRATE,
201 "50% loss must bring the target down, got {}",
202 gcc.target_bitrate()
203 );
204 }
205
206 #[test]
208 fn the_target_stays_within_configured_bounds() {
209 let epoch = Instant::now();
210 let mut gcc = Gcc::new(200_000.0, 150_000.0, 250_000.0);
211
212 let mut at;
213 for batch in 0..50u64 {
214 at = epoch + Duration::from_millis(batch * 200);
215 let reports: Vec<PacketReport> = (0..10)
216 .map(|index| {
217 let id = batch * 10 + index;
218 let departure = at + Duration::from_millis(index * 10);
219 report(id, departure, Some(batch * 200 + index * 10 + 20))
220 })
221 .collect();
222 gcc.on_reports(at, &reports);
223
224 assert!(
225 (150_000.0..=250_000.0).contains(&gcc.target_bitrate()),
226 "target left its bounds: {}",
227 gcc.target_bitrate()
228 );
229 }
230 }
231
232 #[test]
235 fn stats_report_both_halves() {
236 let epoch = Instant::now();
237 let mut gcc = Gcc::default();
238 gcc.on_reports(epoch, &[report(0, epoch, Some(20)), report(1, epoch, None)]);
239
240 let stats = gcc.stats();
241 assert!(stats.delay_based_bitrate.is_some());
242 assert!(stats.loss_based_bitrate.is_some());
243 assert!(stats.packet_loss.is_some());
244 }
245}