1#![allow(dead_code)]
2use std::collections::BTreeSet;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct TimeToSampleEntry {
13 pub sample_count: u32,
15 pub sample_delta: u32,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct SampleToChunkEntry {
22 pub first_chunk: u32,
24 pub samples_per_chunk: u32,
26 pub sample_description_index: u32,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum SampleSizeMode {
33 Uniform(u32),
35 Variable(Vec<u32>),
37}
38
39#[derive(Debug, Clone)]
41pub struct SampleTable {
42 pub time_to_sample: Vec<TimeToSampleEntry>,
44 pub sample_to_chunk: Vec<SampleToChunkEntry>,
46 pub sample_sizes: SampleSizeMode,
48 pub chunk_offsets: Vec<u64>,
50 pub sync_samples: Option<BTreeSet<u32>>,
53 pub timescale: u32,
55}
56
57impl SampleTable {
58 #[must_use]
60 pub fn new(timescale: u32) -> Self {
61 Self {
62 time_to_sample: Vec::new(),
63 sample_to_chunk: Vec::new(),
64 sample_sizes: SampleSizeMode::Uniform(0),
65 chunk_offsets: Vec::new(),
66 sync_samples: None,
67 timescale,
68 }
69 }
70
71 #[must_use]
73 pub fn sample_count_from_stts(&self) -> u64 {
74 self.time_to_sample
75 .iter()
76 .map(|e| u64::from(e.sample_count))
77 .sum()
78 }
79
80 #[must_use]
82 pub fn sample_count(&self) -> u64 {
83 match &self.sample_sizes {
84 SampleSizeMode::Uniform(_) => self.sample_count_from_stts(),
85 SampleSizeMode::Variable(sizes) => sizes.len() as u64,
86 }
87 }
88
89 #[must_use]
91 pub fn total_duration_ticks(&self) -> u64 {
92 self.time_to_sample
93 .iter()
94 .map(|e| u64::from(e.sample_count) * u64::from(e.sample_delta))
95 .sum()
96 }
97
98 #[must_use]
100 #[allow(clippy::cast_precision_loss)]
101 pub fn duration_seconds(&self) -> f64 {
102 if self.timescale == 0 {
103 return 0.0;
104 }
105 self.total_duration_ticks() as f64 / f64::from(self.timescale)
106 }
107
108 #[must_use]
110 pub fn sample_size(&self, sample_number: u32) -> Option<u32> {
111 if sample_number == 0 {
112 return None;
113 }
114 match &self.sample_sizes {
115 SampleSizeMode::Uniform(sz) => {
116 if u64::from(sample_number) <= self.sample_count() {
117 Some(*sz)
118 } else {
119 None
120 }
121 }
122 SampleSizeMode::Variable(sizes) => sizes.get((sample_number - 1) as usize).copied(),
123 }
124 }
125
126 #[must_use]
128 pub fn is_sync_sample(&self, sample_number: u32) -> bool {
129 match &self.sync_samples {
130 None => true, Some(set) => set.contains(&sample_number),
132 }
133 }
134
135 #[must_use]
137 pub fn nearest_sync_before(&self, sample_number: u32) -> Option<u32> {
138 match &self.sync_samples {
139 None => Some(sample_number),
140 Some(set) => set.range(..=sample_number).next_back().copied(),
141 }
142 }
143
144 #[must_use]
146 pub fn nearest_sync_after(&self, sample_number: u32) -> Option<u32> {
147 match &self.sync_samples {
148 None => Some(sample_number),
149 Some(set) => set.range(sample_number..).next().copied(),
150 }
151 }
152
153 #[must_use]
156 pub fn sample_at_time(&self, ticks: u64) -> Option<u32> {
157 let mut remaining = ticks;
158 let mut sample_num: u64 = 1;
159
160 for entry in &self.time_to_sample {
161 let run_duration = u64::from(entry.sample_count) * u64::from(entry.sample_delta);
162 if remaining < run_duration {
163 if entry.sample_delta == 0 {
164 #[allow(clippy::cast_possible_truncation)]
165 return Some(sample_num as u32);
166 }
167 let offset = remaining / u64::from(entry.sample_delta);
168 #[allow(clippy::cast_possible_truncation)]
169 return Some((sample_num + offset) as u32);
170 }
171 remaining -= run_duration;
172 sample_num += u64::from(entry.sample_count);
173 }
174 None
175 }
176
177 #[must_use]
179 pub fn sample_time(&self, sample_number: u32) -> Option<u64> {
180 if sample_number == 0 {
181 return None;
182 }
183 let target = u64::from(sample_number);
184 let mut current_sample: u64 = 1;
185 let mut current_time: u64 = 0;
186
187 for entry in &self.time_to_sample {
188 let count = u64::from(entry.sample_count);
189 if target < current_sample + count {
190 let offset = target - current_sample;
191 return Some(current_time + offset * u64::from(entry.sample_delta));
192 }
193 current_time += count * u64::from(entry.sample_delta);
194 current_sample += count;
195 }
196 None
197 }
198
199 #[must_use]
201 pub fn total_data_size(&self) -> u64 {
202 match &self.sample_sizes {
203 SampleSizeMode::Uniform(sz) => u64::from(*sz) * self.sample_count(),
204 SampleSizeMode::Variable(sizes) => sizes.iter().map(|&s| u64::from(s)).sum(),
205 }
206 }
207
208 #[must_use]
210 #[allow(clippy::cast_precision_loss)]
211 pub fn average_sample_size(&self) -> f64 {
212 let count = self.sample_count();
213 if count == 0 {
214 return 0.0;
215 }
216 self.total_data_size() as f64 / count as f64
217 }
218
219 #[must_use]
221 pub fn chunk_count(&self) -> usize {
222 self.chunk_offsets.len()
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 fn sample_table_30fps() -> SampleTable {
231 let mut st = SampleTable::new(30_000);
232 st.time_to_sample.push(TimeToSampleEntry {
234 sample_count: 300,
235 sample_delta: 1000,
236 });
237 st.sample_sizes = SampleSizeMode::Uniform(4096);
238 st.chunk_offsets = vec![0, 40960, 81920]; st.sample_to_chunk.push(SampleToChunkEntry {
240 first_chunk: 1,
241 samples_per_chunk: 100,
242 sample_description_index: 1,
243 });
244 st
245 }
246
247 #[test]
248 fn test_sample_count_from_stts() {
249 let st = sample_table_30fps();
250 assert_eq!(st.sample_count_from_stts(), 300);
251 }
252
253 #[test]
254 fn test_sample_count_uniform() {
255 let st = sample_table_30fps();
256 assert_eq!(st.sample_count(), 300);
257 }
258
259 #[test]
260 fn test_sample_count_variable() {
261 let mut st = SampleTable::new(1000);
262 st.sample_sizes = SampleSizeMode::Variable(vec![100, 200, 300]);
263 assert_eq!(st.sample_count(), 3);
264 }
265
266 #[test]
267 fn test_total_duration_ticks() {
268 let st = sample_table_30fps();
269 assert_eq!(st.total_duration_ticks(), 300_000);
270 }
271
272 #[test]
273 fn test_duration_seconds() {
274 let st = sample_table_30fps();
275 assert!((st.duration_seconds() - 10.0).abs() < 0.001);
276 }
277
278 #[test]
279 fn test_duration_seconds_zero_timescale() {
280 let st = SampleTable::new(0);
281 assert!((st.duration_seconds()).abs() < f64::EPSILON);
282 }
283
284 #[test]
285 fn test_sample_size_uniform() {
286 let st = sample_table_30fps();
287 assert_eq!(st.sample_size(1), Some(4096));
288 assert_eq!(st.sample_size(300), Some(4096));
289 assert_eq!(st.sample_size(301), None);
290 assert_eq!(st.sample_size(0), None);
291 }
292
293 #[test]
294 fn test_sample_size_variable() {
295 let mut st = SampleTable::new(1000);
296 st.sample_sizes = SampleSizeMode::Variable(vec![100, 200, 300]);
297 assert_eq!(st.sample_size(1), Some(100));
298 assert_eq!(st.sample_size(3), Some(300));
299 assert_eq!(st.sample_size(4), None);
300 }
301
302 #[test]
303 fn test_is_sync_sample_all_sync() {
304 let st = sample_table_30fps();
305 assert!(st.is_sync_sample(1));
306 assert!(st.is_sync_sample(150));
307 }
308
309 #[test]
310 fn test_is_sync_sample_selective() {
311 let mut st = sample_table_30fps();
312 let mut syncs = BTreeSet::new();
313 syncs.insert(1);
314 syncs.insert(30);
315 syncs.insert(60);
316 st.sync_samples = Some(syncs);
317
318 assert!(st.is_sync_sample(1));
319 assert!(st.is_sync_sample(30));
320 assert!(!st.is_sync_sample(2));
321 }
322
323 #[test]
324 fn test_nearest_sync_before() {
325 let mut st = sample_table_30fps();
326 let mut syncs = BTreeSet::new();
327 syncs.insert(1);
328 syncs.insert(30);
329 syncs.insert(60);
330 st.sync_samples = Some(syncs);
331
332 assert_eq!(st.nearest_sync_before(29), Some(1));
333 assert_eq!(st.nearest_sync_before(30), Some(30));
334 assert_eq!(st.nearest_sync_before(45), Some(30));
335 }
336
337 #[test]
338 fn test_nearest_sync_after() {
339 let mut st = sample_table_30fps();
340 let mut syncs = BTreeSet::new();
341 syncs.insert(1);
342 syncs.insert(30);
343 syncs.insert(60);
344 st.sync_samples = Some(syncs);
345
346 assert_eq!(st.nearest_sync_after(2), Some(30));
347 assert_eq!(st.nearest_sync_after(30), Some(30));
348 assert_eq!(st.nearest_sync_after(61), None);
349 }
350
351 #[test]
352 fn test_sample_at_time() {
353 let st = sample_table_30fps();
354 assert_eq!(st.sample_at_time(0), Some(1));
356 assert_eq!(st.sample_at_time(999), Some(1));
357 assert_eq!(st.sample_at_time(1000), Some(2));
358 assert_eq!(st.sample_at_time(299_999), Some(300));
359 assert_eq!(st.sample_at_time(300_000), None); }
361
362 #[test]
363 fn test_sample_time() {
364 let st = sample_table_30fps();
365 assert_eq!(st.sample_time(1), Some(0));
366 assert_eq!(st.sample_time(2), Some(1000));
367 assert_eq!(st.sample_time(300), Some(299_000));
368 assert_eq!(st.sample_time(0), None);
369 assert_eq!(st.sample_time(301), None);
370 }
371
372 #[test]
373 fn test_total_data_size() {
374 let st = sample_table_30fps();
375 assert_eq!(st.total_data_size(), 300 * 4096);
376 }
377
378 #[test]
379 fn test_average_sample_size() {
380 let mut st = SampleTable::new(1000);
381 st.sample_sizes = SampleSizeMode::Variable(vec![100, 200, 300]);
382 st.time_to_sample.push(TimeToSampleEntry {
383 sample_count: 3,
384 sample_delta: 1000,
385 });
386 assert!((st.average_sample_size() - 200.0).abs() < f64::EPSILON);
387 }
388
389 #[test]
390 fn test_chunk_count() {
391 let st = sample_table_30fps();
392 assert_eq!(st.chunk_count(), 3);
393 }
394}