rtc_interceptor/gcc/
slope.rs1use super::arrival_group::{ArrivalGroupAccumulator, InterGroupDelay};
4use super::kalman::Kalman;
5use crate::rtpfb::acknowledgement::PacketReport;
6use std::time::{Duration, Instant};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct DelayTrend {
11 pub measurement_ms: f64,
13 pub estimate_ms: f64,
15 pub at: Instant,
17 pub size: usize,
19}
20
21#[derive(Debug, Clone, Default)]
27pub struct SlopeEstimator {
28 groups: ArrivalGroupAccumulator,
29 kalman: Kalman,
30}
31
32impl SlopeEstimator {
33 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn with_burst_interval(burst_interval: Duration) -> Self {
40 Self {
41 groups: ArrivalGroupAccumulator::new(burst_interval),
42 kalman: Kalman::new(),
43 }
44 }
45
46 pub fn estimate_ms(&self) -> f64 {
48 self.kalman.estimate()
49 }
50
51 pub fn accumulate(&mut self, report: &PacketReport) -> Option<DelayTrend> {
53 let delay = self.groups.accumulate(report)?;
54 Some(self.filter(delay))
55 }
56
57 pub fn flush(&mut self) -> Option<DelayTrend> {
59 let delay = self.groups.flush()?;
60 Some(self.filter(delay))
61 }
62
63 fn filter(&mut self, delay: InterGroupDelay) -> DelayTrend {
64 DelayTrend {
65 measurement_ms: delay.delta_ms,
66 estimate_ms: self.kalman.update(delay.delta_ms),
67 at: delay.at,
68 size: delay.size,
69 }
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use rtcp::transport_feedbacks::cc_feedback_report::Ecn;
77
78 fn report(departure: Instant, arrival_ms: u64) -> PacketReport {
79 PacketReport {
80 ssrc: 1,
81 id: 0,
82 rtp_sequence_number: 0,
83 is_twcc: true,
84 twcc_sequence_number: 0,
85 size: 1200,
86 arrived: true,
87 departure,
88 arrival: Some(Duration::from_millis(arrival_ms)),
89 ecn: Ecn::default(),
90 }
91 }
92
93 #[test]
95 fn a_steady_path_reads_flat() {
96 let epoch = Instant::now();
97 let mut slope = SlopeEstimator::new();
98
99 for burst in 0..40u64 {
100 slope.accumulate(&report(
101 epoch + Duration::from_millis(burst * 20),
102 100 + burst * 20,
103 ));
104 }
105
106 assert!(
107 slope.estimate_ms().abs() < 1.0,
108 "a steady path should read about zero, got {}",
109 slope.estimate_ms()
110 );
111 }
112
113 #[test]
115 fn a_queueing_path_reads_positive() {
116 let epoch = Instant::now();
117 let mut slope = SlopeEstimator::new();
118
119 for burst in 0..40u64 {
120 slope.accumulate(&report(
121 epoch + Duration::from_millis(burst * 20),
122 100 + burst * 26,
123 ));
124 }
125
126 assert!(
127 slope.estimate_ms() > 3.0,
128 "6 ms of queue per group should read clearly positive, got {}",
129 slope.estimate_ms()
130 );
131 }
132
133 #[test]
135 fn a_reading_carries_the_group_size() {
136 let epoch = Instant::now();
137 let mut slope = SlopeEstimator::new();
138
139 slope.accumulate(&report(epoch, 100));
140 slope.accumulate(&report(epoch + Duration::from_millis(20), 120));
141 let reading = slope
142 .accumulate(&report(epoch + Duration::from_millis(40), 140))
143 .expect("the second group closes against the first");
144
145 assert_eq!(1200, reading.size);
146 }
147}