srt_protocol/protocol/time/
base.rs1use std::time::Instant;
2
3use crate::packet::{TimeSpan, TimeStamp};
4
5#[derive(Copy, Clone, Debug)]
6pub struct TimeBase {
7 origin_time: Instant,
10
11 reference_time: Instant,
15 reference_ts: TimeStamp,
16}
17
18impl TimeBase {
19 pub fn new(start_time: Instant) -> Self {
20 Self {
21 origin_time: start_time,
22 reference_time: start_time,
23 reference_ts: TimeStamp::MIN,
24 }
25 }
26
27 pub fn timestamp_from(&self, instant: Instant) -> TimeStamp {
28 if instant < self.reference_time {
29 self.reference_ts - (self.reference_time - instant)
30 } else {
31 self.reference_ts + (instant - self.reference_time)
32 }
33 }
34
35 pub fn instant_from(&self, timestamp: TimeStamp) -> Instant {
36 self.reference_time + (timestamp - self.reference_ts)
37 }
38
39 #[allow(clippy::assign_op_pattern)]
40 pub fn adjust(&mut self, now: Instant, drift: TimeSpan) {
41 self.origin_time = self.origin_time - drift;
42 self.reference_time = self.reference_time - drift;
43
44 if now > self.reference_time {
45 let delta = now - self.reference_time;
46 self.reference_time = self.reference_time + delta;
47 self.reference_ts = self.reference_ts + delta;
48 }
49 }
50}
51
52#[cfg(test)]
53mod timebase {
54 use proptest::prelude::*;
55
56 use super::*;
57
58 use std::time::Duration;
59
60 proptest! {
61 #[test]
62 fn timestamp_roundtrip(expected_ts: u32) {
63 let timebase = TimeBase::new(Instant::now() + Duration::from_micros(expected_ts as u64));
64 let expected_ts = TimeStamp::from_micros(expected_ts);
65
66 let ts = timebase.timestamp_from(timebase.instant_from(expected_ts));
67
68 prop_assert_eq!(ts, expected_ts);
69 }
70
71 #[test]
72 fn timestamp_from(expected_offset: i32, n in -2..2) {
73 let expected_offset = TimeSpan::from_micros(expected_offset / 2);
74 let now = Instant::now() + Duration::from_micros(u32::MAX as u64) * 3;
75 let timebase = TimeBase::new(now);
76 let adjustment = ((std::u32::MAX as u64 + 1) as i64) * (n as i64);
80 let instant = if adjustment > 0 {
81 now + Duration::from_micros(adjustment as u64) + expected_offset
82 } else {
83 now.checked_sub(Duration::from_micros(-adjustment as u64)).unwrap() + expected_offset
84 };
85
86 let ts = timebase.timestamp_from(instant);
87
88 prop_assert_eq!(ts, TimeStamp::MIN + expected_offset);
89 }
90
91 #[test]
92 fn adjust(drift: i16, clock_delta: i16) {
93 let start = Instant::now();
94 let drift = TimeSpan::from_micros(drift as i32);
95 let clock_delta = TimeSpan::from_micros(clock_delta as i32);
96 let mut timebase = TimeBase::new(start);
97 let original_ts = timebase.timestamp_from(start);
98 let now = start + clock_delta;
99
100 timebase.adjust(now, drift);
101
102 let original_time = timebase.instant_from(original_ts);
103 assert_eq!(start - drift - original_time, Duration::from_micros(0));
104
105 let ts = timebase.timestamp_from(start);
106 prop_assert_eq!(ts, original_ts - drift);
107 }
108 }
109
110 #[test]
111 fn timestamp_from_past() {
112 let now = Instant::now() + Duration::from_micros(u32::MAX as u64);
113 let timebase = TimeBase::new(now);
114
115 let ts = timebase.timestamp_from(now + TimeSpan::MIN);
116
117 assert_eq!(ts, TimeStamp::MIN + TimeSpan::MIN);
118 }
119
120 #[test]
121 fn timestamp_from_future() {
122 let now = Instant::now();
123 let timebase = TimeBase::new(now);
124
125 let ts = timebase.timestamp_from(now + TimeSpan::MAX);
126
127 assert_eq!(ts, TimeStamp::MIN + TimeSpan::MAX);
128 }
129}