1use core::fmt;
2use serde::{Deserialize, Serialize};
3
4pub const TICKS_PER_SECOND: i64 = 120_000;
6
7pub const MAX_SERIALIZED_TICKS: i64 = (1 << 53) - 1;
10
11pub const PRESENTATION_SLACK: MediaTime = MediaTime(TICKS_PER_SECOND / 2_000 + 1);
14
15pub const ROUNDING_SCAN_FRAMES: usize = 64;
18
19pub const ROUNDING_SCAN_BYTES: u64 = 16 * 1024 * 1024;
22
23#[must_use]
31pub fn rounding_quantum(timestamps: &[i64]) -> i64 {
32 let mut sorted = timestamps.to_vec();
33 sorted.sort_unstable();
34 sorted.dedup();
35 let intervals = sorted
36 .windows(2)
37 .map(|pair| i128::from(pair[1]) - i128::from(pair[0]))
38 .collect::<Vec<_>>();
39 let (Some(&first), Some(&shortest)) = (intervals.first(), intervals.iter().min()) else {
40 return 0;
41 };
42 let quantum = intervals.iter().fold(0_i128, |quantum, &interval| {
43 gcd(quantum, (interval - first).abs())
44 });
45 if quantum == 0 || quantum.saturating_mul(2) >= shortest {
46 return 0;
47 }
48 i64::try_from(quantum).unwrap_or(0)
49}
50
51fn gcd(mut a: i128, mut b: i128) -> i128 {
52 while b != 0 {
53 (a, b) = (b, a % b);
54 }
55 a
56}
57
58#[must_use]
71pub fn presentation_slack(num: i64, den: i64, quantum: i64) -> MediaTime {
72 if num <= 0 || den <= 0 {
73 return PRESENTATION_SLACK;
74 }
75 let (units, cap) = if quantum > 0 {
76 (i128::from(quantum), i128::MAX)
77 } else {
78 (1, i128::from(PRESENTATION_SLACK.0))
79 };
80 let (rounding, halves) = (
82 i128::from(TICKS_PER_SECOND)
83 .saturating_mul(i128::from(num))
84 .saturating_mul(units),
85 2 * i128::from(den),
86 );
87 let half = rounding.saturating_add(halves - 1) / halves;
88 let slack = half.saturating_add(1).min(cap);
89 MediaTime(i64::try_from(slack).unwrap_or(MAX_SERIALIZED_TICKS))
90}
91
92#[derive(
96 Copy, Clone, Debug, Default, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize,
97)]
98#[serde(try_from = "i64")]
99pub struct MediaTime(pub i64);
100
101#[cfg(feature = "schema")]
102impl schemars::JsonSchema for MediaTime {
103 fn schema_name() -> std::borrow::Cow<'static, str> {
104 "MediaTime".into()
105 }
106
107 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
108 schemars::json_schema!({
109 "description": "A signed position or duration in timeline ticks (120 000 per second), at most the largest integer a JavaScript number holds exactly either way.",
110 "type": "integer",
111 "format": "int64",
112 "minimum": -MAX_SERIALIZED_TICKS,
113 "maximum": MAX_SERIALIZED_TICKS,
114 })
115 }
116}
117
118impl TryFrom<i64> for MediaTime {
119 type Error = String;
120
121 fn try_from(ticks: i64) -> Result<Self, Self::Error> {
122 if ticks.unsigned_abs() > MAX_SERIALIZED_TICKS.unsigned_abs() {
123 return Err(format!(
124 "a time of {ticks} ticks is beyond {MAX_SERIALIZED_TICKS} either way"
125 ));
126 }
127 Ok(Self(ticks))
128 }
129}
130
131impl MediaTime {
132 pub const ZERO: Self = Self(0);
134 #[must_use]
136 pub const fn from_ticks(ticks: i64) -> Self {
137 Self(ticks)
138 }
139 #[must_use]
141 pub const fn ticks(self) -> i64 {
142 self.0
143 }
144 #[must_use]
146 pub const fn presentation_deadline(self, slack: Self) -> Self {
147 Self(self.0.saturating_add(slack.0))
148 }
149 #[must_use]
151 pub const fn from_seconds(seconds: i64) -> Self {
152 Self(seconds.saturating_mul(TICKS_PER_SECOND))
153 }
154 #[must_use]
156 pub const fn checked_add(self, other: Self) -> Option<Self> {
157 match self.0.checked_add(other.0) {
158 Some(value) => Some(Self(value)),
159 None => None,
160 }
161 }
162 #[must_use]
164 pub const fn checked_sub(self, other: Self) -> Option<Self> {
165 match self.0.checked_sub(other.0) {
166 Some(value) => Some(Self(value)),
167 None => None,
168 }
169 }
170}
171
172impl fmt::Display for MediaTime {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 write!(f, "{} ticks", self.0)
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 #[test]
182 fn seconds_and_overflow() {
183 assert_eq!(MediaTime::from_seconds(1).ticks(), TICKS_PER_SECOND);
184 assert_eq!(MediaTime(i64::MAX).checked_add(MediaTime(1)), None);
185 }
186
187 #[test]
188 fn a_time_base_bounds_how_late_a_shown_frame_may_be() {
189 assert_eq!(presentation_slack(1, 1_000, 0), MediaTime(61));
191 assert_eq!(presentation_slack(1, 90_000, 0), MediaTime(2));
192 assert_eq!(presentation_slack(1, 30_000, 0), MediaTime(3));
193 assert_eq!(presentation_slack(1, 15_360, 0), MediaTime(5));
194 assert_eq!(presentation_slack(1, TICKS_PER_SECOND, 0), MediaTime(2));
195 assert_eq!(presentation_slack(1_001, 30_000, 0), PRESENTATION_SLACK);
197 assert_eq!(presentation_slack(1, 600, 0), PRESENTATION_SLACK);
198 assert_eq!(presentation_slack(0, 1, 0), PRESENTATION_SLACK);
199 assert_eq!(presentation_slack(1, 0, 0), PRESENTATION_SLACK);
200 assert_eq!(presentation_slack(1, 0, 16), PRESENTATION_SLACK);
201 assert_eq!(
202 MediaTime(8_008).presentation_deadline(presentation_slack(1, 1_000, 0)),
203 MediaTime(8_069)
204 );
205 }
206
207 #[test]
208 fn a_rounding_the_timestamps_show_bounds_it_however_coarse() {
209 assert_eq!(presentation_slack(1, 16_000, 16), MediaTime(61));
211 assert_eq!(presentation_slack(1, 600, 1), MediaTime(101));
212 assert_eq!(presentation_slack(1, 100, 1), MediaTime(601));
213 assert_eq!(presentation_slack(1, 1_000, 1), MediaTime(61));
214 assert_eq!(presentation_slack(1, 90_000, 90), MediaTime(61));
215 assert_eq!(presentation_slack(1, 1_000, -1), MediaTime(61));
216 assert_eq!(
217 presentation_slack(i64::MAX, 1, i64::MAX),
218 MediaTime(MAX_SERIALIZED_TICKS)
219 );
220 }
221
222 fn rounded(num: i64, den: i64, base: i64, count: i64) -> Vec<i64> {
224 (0..count)
225 .map(|frame| (2 * frame * den * base + num) / (2 * num))
226 .collect()
227 }
228
229 #[test]
230 fn the_spacing_of_the_first_timestamps_shows_their_rounding() {
231 assert_eq!(rounding_quantum(&rounded(30_000, 1_001, 1_000, 64)), 1);
234 let remuxed = rounded(30_000, 1_001, 1_000, 64)
235 .into_iter()
236 .map(|ms| ms * 16)
237 .collect::<Vec<_>>();
238 assert_eq!(rounding_quantum(&remuxed), 16);
239 assert_eq!(rounding_quantum(&rounded(30_000, 1_001, 600, 64)), 1);
240 assert_eq!(rounding_quantum(&rounded(30_000, 1_001, 100, 64)), 1);
241 assert_eq!(rounding_quantum(&rounded(30, 1, 600, 64)), 0);
243 assert_eq!(rounding_quantum(&rounded(25, 1, 1_000, 64)), 0);
244 assert_eq!(rounding_quantum(&(0..64).collect::<Vec<_>>()), 0);
245 assert_eq!(
247 rounding_quantum(&[0, 1_500, 3_000, 6_000, 7_500, 10_500]),
248 0
249 );
250 assert_eq!(rounding_quantum(&[0, 17, 33, 67, 83, 100, 133, 150]), 1);
252 assert_eq!(rounding_quantum(&[67, 0, 33, 33, 100, 133]), 1);
254 assert_eq!(rounding_quantum(&[0, 33]), 0);
255 assert_eq!(rounding_quantum(&[]), 0);
256 assert_eq!(rounding_quantum(&[i64::MIN, 0, i64::MAX]), 1);
257 }
258}