1use std::cmp::Ordering;
9use std::time::{Duration, Instant};
10use ustreamer_proto::quality::{EncodeMode, EncodeParams, QualityTier};
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct NetworkMetrics {
15 pub rtt: Duration,
17 pub packet_loss_ratio: Option<f32>,
19}
20
21impl NetworkMetrics {
22 pub fn new(rtt: Duration) -> Self {
24 Self {
25 rtt,
26 packet_loss_ratio: None,
27 }
28 }
29
30 pub fn with_packet_loss(mut self, packet_loss_ratio: f32) -> Self {
32 self.packet_loss_ratio = Some(packet_loss_ratio.clamp(0.0, 1.0));
33 self
34 }
35}
36
37#[derive(Debug, Clone)]
39pub struct QualityConfig {
40 pub settle_timeout: Duration,
42 pub idle_timeout: Duration,
44 pub idle_fps: u32,
46 pub ultra_rtt_limit: Duration,
48 pub high_res_rtt_limit: Duration,
50 pub standard_rtt_limit: Duration,
52 pub ultra_loss_limit: f32,
54 pub high_res_loss_limit: f32,
56 pub standard_loss_limit: f32,
58 pub downgrade_hysteresis: u32,
60 pub upgrade_hysteresis: u32,
62}
63
64impl Default for QualityConfig {
65 fn default() -> Self {
66 Self {
67 settle_timeout: Duration::from_millis(200),
68 idle_timeout: Duration::from_secs(2),
69 idle_fps: 5,
70 ultra_rtt_limit: Duration::from_millis(8),
71 high_res_rtt_limit: Duration::from_millis(18),
72 standard_rtt_limit: Duration::from_millis(40),
73 ultra_loss_limit: 0.005,
74 high_res_loss_limit: 0.02,
75 standard_loss_limit: 0.05,
76 downgrade_hysteresis: 2,
77 upgrade_hysteresis: 6,
78 }
79 }
80}
81
82pub struct QualityController {
84 config: QualityConfig,
85 last_input_time: Instant,
86 requested_tier: QualityTier,
87 network_cap_tier: QualityTier,
88 last_network_metrics: Option<NetworkMetrics>,
89 lossless_sent: bool,
90 consecutive_degraded_samples: u32,
91 consecutive_recovered_samples: u32,
92}
93
94impl QualityController {
95 pub fn new(config: QualityConfig) -> Self {
96 Self {
97 config,
98 last_input_time: Instant::now(),
99 requested_tier: QualityTier::Standard,
100 network_cap_tier: QualityTier::Ultra,
101 last_network_metrics: None,
102 lossless_sent: false,
103 consecutive_degraded_samples: 0,
104 consecutive_recovered_samples: 0,
105 }
106 }
107
108 pub fn on_input(&mut self) {
110 self.last_input_time = Instant::now();
111 self.lossless_sent = false;
112 }
113
114 pub fn on_transport_rtt(&mut self, rtt: Duration) {
116 self.on_network_metrics(NetworkMetrics::new(rtt));
117 }
118
119 pub fn on_network_metrics(&mut self, metrics: NetworkMetrics) {
125 let sampled_tier = self.sampled_network_tier(metrics);
126 self.last_network_metrics = Some(metrics);
127
128 match tier_rank(sampled_tier).cmp(&tier_rank(self.network_cap_tier)) {
129 Ordering::Less => {
130 self.consecutive_recovered_samples = 0;
131 self.consecutive_degraded_samples += 1;
132 if self.consecutive_degraded_samples >= self.config.downgrade_hysteresis.max(1) {
133 self.network_cap_tier = sampled_tier;
134 self.consecutive_degraded_samples = 0;
135 }
136 }
137 Ordering::Greater => {
138 self.consecutive_degraded_samples = 0;
139 self.consecutive_recovered_samples += 1;
140 if self.consecutive_recovered_samples >= self.config.upgrade_hysteresis.max(1) {
141 self.network_cap_tier =
142 min_tier(step_up_tier(self.network_cap_tier), sampled_tier);
143 self.consecutive_recovered_samples = 0;
144 }
145 }
146 Ordering::Equal => {
147 self.consecutive_degraded_samples = 0;
148 self.consecutive_recovered_samples = 0;
149 }
150 }
151 }
152
153 pub fn requested_tier(&self) -> QualityTier {
155 self.requested_tier
156 }
157
158 pub fn network_cap_tier(&self) -> QualityTier {
160 self.network_cap_tier
161 }
162
163 pub fn current_tier(&self) -> QualityTier {
165 min_tier(self.requested_tier, self.network_cap_tier)
166 }
167
168 pub fn last_network_metrics(&self) -> Option<NetworkMetrics> {
170 self.last_network_metrics
171 }
172
173 pub fn frame_params(&mut self) -> EncodeParams {
175 let idle_duration = self.last_input_time.elapsed();
176
177 let mode = if idle_duration >= self.config.settle_timeout && !self.lossless_sent {
178 self.lossless_sent = true;
179 EncodeMode::LosslessRefine
180 } else if idle_duration >= self.config.idle_timeout {
181 EncodeMode::IdleLowFps
182 } else {
183 EncodeMode::Interactive
184 };
185
186 let (width, height, fps, bitrate, max_bitrate): (u32, u32, u32, u64, u64) =
187 match self.current_tier() {
188 QualityTier::Low => (1280, 720, 30, 5_000_000, 10_000_000),
189 QualityTier::Standard => (1920, 1080, 60, 15_000_000, 30_000_000),
190 QualityTier::HighRes => (3840, 2160, 30, 40_000_000, 80_000_000),
191 QualityTier::Ultra => (3840, 2160, 60, 80_000_000, 150_000_000),
192 };
193 let (bitrate, max_bitrate) = match mode {
194 EncodeMode::LosslessRefine => (max_bitrate, max_bitrate.saturating_mul(2)),
195 _ => (bitrate, max_bitrate),
196 };
197
198 let target_fps = match mode {
199 EncodeMode::IdleLowFps => self.config.idle_fps,
200 EncodeMode::LosslessRefine => fps,
201 EncodeMode::Interactive => fps,
202 };
203
204 EncodeParams {
205 width,
206 height,
207 target_fps,
208 bitrate_bps: bitrate,
209 max_bitrate_bps: max_bitrate,
210 mode,
211 force_keyframe: mode == EncodeMode::LosslessRefine,
212 }
213 }
214
215 pub fn set_tier(&mut self, tier: QualityTier) {
217 self.requested_tier = tier;
218 }
219
220 fn sampled_network_tier(&self, metrics: NetworkMetrics) -> QualityTier {
221 let loss = metrics.packet_loss_ratio.unwrap_or(0.0).clamp(0.0, 1.0);
222
223 if metrics.rtt > self.config.standard_rtt_limit || loss > self.config.standard_loss_limit {
224 QualityTier::Low
225 } else if metrics.rtt > self.config.high_res_rtt_limit
226 || loss > self.config.high_res_loss_limit
227 {
228 QualityTier::Standard
229 } else if metrics.rtt > self.config.ultra_rtt_limit || loss > self.config.ultra_loss_limit {
230 QualityTier::HighRes
231 } else {
232 QualityTier::Ultra
233 }
234 }
235}
236
237fn tier_rank(tier: QualityTier) -> u8 {
238 match tier {
239 QualityTier::Low => 0,
240 QualityTier::Standard => 1,
241 QualityTier::HighRes => 2,
242 QualityTier::Ultra => 3,
243 }
244}
245
246fn min_tier(a: QualityTier, b: QualityTier) -> QualityTier {
247 if tier_rank(a) <= tier_rank(b) { a } else { b }
248}
249
250fn step_up_tier(tier: QualityTier) -> QualityTier {
251 match tier {
252 QualityTier::Low => QualityTier::Standard,
253 QualityTier::Standard => QualityTier::HighRes,
254 QualityTier::HighRes => QualityTier::Ultra,
255 QualityTier::Ultra => QualityTier::Ultra,
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn defaults_to_standard_without_network_feedback() {
265 let mut controller = QualityController::new(Default::default());
266
267 let params = controller.frame_params();
268
269 assert_eq!(controller.requested_tier(), QualityTier::Standard);
270 assert_eq!(controller.network_cap_tier(), QualityTier::Ultra);
271 assert_eq!(controller.current_tier(), QualityTier::Standard);
272 assert_eq!(
273 (params.width, params.height, params.target_fps),
274 (1920, 1080, 60)
275 );
276 }
277
278 #[test]
279 fn degraded_rtt_caps_requested_ultra_tier() {
280 let mut controller = QualityController::new(Default::default());
281 controller.set_tier(QualityTier::Ultra);
282
283 controller.on_transport_rtt(Duration::from_millis(20));
284 assert_eq!(controller.current_tier(), QualityTier::Ultra);
285
286 controller.on_transport_rtt(Duration::from_millis(20));
287 assert_eq!(controller.network_cap_tier(), QualityTier::Standard);
288 assert_eq!(controller.current_tier(), QualityTier::Standard);
289
290 let params = controller.frame_params();
291 assert_eq!(
292 (params.width, params.height, params.target_fps),
293 (1920, 1080, 60)
294 );
295 }
296
297 #[test]
298 fn recovery_happens_one_tier_at_a_time() {
299 let mut controller = QualityController::new(Default::default());
300 controller.set_tier(QualityTier::Ultra);
301
302 for _ in 0..controller.config.downgrade_hysteresis {
303 controller.on_network_metrics(
304 NetworkMetrics::new(Duration::from_millis(1)).with_packet_loss(0.06),
305 );
306 }
307 assert_eq!(controller.network_cap_tier(), QualityTier::Low);
308
309 for _ in 0..controller.config.upgrade_hysteresis {
310 controller.on_transport_rtt(Duration::from_millis(1));
311 }
312 assert_eq!(controller.network_cap_tier(), QualityTier::Standard);
313
314 for _ in 0..controller.config.upgrade_hysteresis {
315 controller.on_transport_rtt(Duration::from_millis(1));
316 }
317 assert_eq!(controller.network_cap_tier(), QualityTier::HighRes);
318 }
319
320 #[test]
321 fn requested_tier_remains_a_hard_upper_bound() {
322 let mut controller = QualityController::new(Default::default());
323 controller.set_tier(QualityTier::HighRes);
324
325 for _ in 0..controller.config.upgrade_hysteresis {
326 controller.on_transport_rtt(Duration::from_millis(1));
327 }
328
329 assert_eq!(controller.network_cap_tier(), QualityTier::Ultra);
330 assert_eq!(controller.current_tier(), QualityTier::HighRes);
331 }
332
333 #[test]
334 fn settle_refine_forces_one_keyframe_until_next_input() {
335 let mut controller = QualityController::new(Default::default());
336 controller.last_input_time =
337 Instant::now() - controller.config.settle_timeout - Duration::from_millis(1);
338
339 let refine = controller.frame_params();
340 assert_eq!(refine.mode, EncodeMode::LosslessRefine);
341 assert!(refine.force_keyframe);
342 assert_eq!(refine.bitrate_bps, 30_000_000);
343 assert_eq!(refine.max_bitrate_bps, 60_000_000);
344
345 let idle = controller.frame_params();
346 assert_eq!(idle.mode, EncodeMode::Interactive);
347 assert!(!idle.force_keyframe);
348
349 controller.on_input();
350 assert_eq!(controller.frame_params().mode, EncodeMode::Interactive);
351 }
352}