/src/mp4parse-rust/mp4parse/src/unstable.rs
Line | Count | Source |
1 | | // This Source Code Form is subject to the terms of the Mozilla Public |
2 | | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | // file, You can obtain one at https://mozilla.org/MPL/2.0/. |
4 | | use num_traits::{CheckedAdd, CheckedSub, PrimInt, Zero}; |
5 | | use std::ops::{Add, Neg, Sub}; |
6 | | |
7 | | use super::*; |
8 | | |
9 | | /// A zero-overhead wrapper around integer types for the sake of always |
10 | | /// requiring checked arithmetic |
11 | | #[repr(transparent)] |
12 | | #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] |
13 | | pub struct CheckedInteger<T>(pub T); |
14 | | |
15 | | impl<T> From<T> for CheckedInteger<T> { |
16 | 107M | fn from(i: T) -> Self { |
17 | 107M | Self(i) |
18 | 107M | } <mp4parse::unstable::CheckedInteger<i64> as core::convert::From<i64>>::from Line | Count | Source | 16 | 1.55k | fn from(i: T) -> Self { | 17 | 1.55k | Self(i) | 18 | 1.55k | } |
<mp4parse::unstable::CheckedInteger<u64> as core::convert::From<u64>>::from Line | Count | Source | 16 | 107M | fn from(i: T) -> Self { | 17 | 107M | Self(i) | 18 | 107M | } |
|
19 | | } |
20 | | |
21 | | // Orphan rules prevent a more general implementation, but this suffices |
22 | | impl From<CheckedInteger<i64>> for i64 { |
23 | 368 | fn from(checked: CheckedInteger<i64>) -> i64 { |
24 | 368 | checked.0 |
25 | 368 | } |
26 | | } |
27 | | |
28 | | impl<T, U: Into<T>> Add<U> for CheckedInteger<T> |
29 | | where |
30 | | T: CheckedAdd, |
31 | | { |
32 | | type Output = Option<Self>; |
33 | | |
34 | 107M | fn add(self, other: U) -> Self::Output { |
35 | 107M | self.0.checked_add(&other.into()).map(Into::into) |
36 | 107M | } |
37 | | } |
38 | | |
39 | | impl<T, U: Into<T>> Sub<U> for CheckedInteger<T> |
40 | | where |
41 | | T: CheckedSub, |
42 | | { |
43 | | type Output = Option<Self>; |
44 | | |
45 | 368 | fn sub(self, other: U) -> Self::Output { |
46 | 368 | self.0.checked_sub(&other.into()).map(Into::into) |
47 | 368 | } |
48 | | } |
49 | | |
50 | | /// Implement subtraction of checked `u64`s returning i64 |
51 | | // This is necessary for handling Mp4parseTrackInfo::media_time gracefully |
52 | | impl Sub for CheckedInteger<u64> { |
53 | | type Output = Option<CheckedInteger<i64>>; |
54 | | |
55 | 15.7k | fn sub(self, other: Self) -> Self::Output { |
56 | 15.7k | if self >= other { |
57 | 15.6k | self.0 |
58 | 15.6k | .checked_sub(other.0) |
59 | 15.6k | .and_then(|u| i64::try_from(u).ok()) |
60 | 15.6k | .map(CheckedInteger) |
61 | | } else { |
62 | 39 | other |
63 | 39 | .0 |
64 | 39 | .checked_sub(self.0) |
65 | 39 | .and_then(|u| i64::try_from(u).ok()) |
66 | 39 | .map(i64::neg) |
67 | 39 | .map(CheckedInteger) |
68 | | } |
69 | 15.7k | } |
70 | | } |
71 | | |
72 | | #[test] |
73 | | fn u64_subtraction_returning_i64() { |
74 | | // self > other |
75 | | assert_eq!( |
76 | | CheckedInteger(2u64) - CheckedInteger(1u64), |
77 | | Some(CheckedInteger(1i64)) |
78 | | ); |
79 | | |
80 | | // self == other |
81 | | assert_eq!( |
82 | | CheckedInteger(1u64) - CheckedInteger(1u64), |
83 | | Some(CheckedInteger(0i64)) |
84 | | ); |
85 | | |
86 | | // difference too large to store in i64 |
87 | | assert_eq!(CheckedInteger(u64::MAX) - CheckedInteger(1u64), None); |
88 | | |
89 | | // self < other |
90 | | assert_eq!( |
91 | | CheckedInteger(1u64) - CheckedInteger(2u64), |
92 | | Some(CheckedInteger(-1i64)) |
93 | | ); |
94 | | |
95 | | // difference not representable due to overflow |
96 | | assert_eq!(CheckedInteger(1u64) - CheckedInteger(u64::MAX), None); |
97 | | } |
98 | | |
99 | | impl<T: std::cmp::PartialEq> PartialEq<T> for CheckedInteger<T> { |
100 | 107M | fn eq(&self, other: &T) -> bool { |
101 | 107M | self.0 == *other |
102 | 107M | } |
103 | | } |
104 | | |
105 | | /// Provides the following information about a sample in the source file: |
106 | | /// sample data offset (start and end), composition time in microseconds |
107 | | /// (start and end) and whether it is a sync sample |
108 | | #[repr(C)] |
109 | | #[derive(Default, Debug, PartialEq, Eq)] |
110 | | pub struct Indice { |
111 | | /// The byte offset in the file where the indexed sample begins. |
112 | | pub start_offset: CheckedInteger<u64>, |
113 | | /// The byte offset in the file where the indexed sample ends. This is |
114 | | /// equivalent to `start_offset` + the length in bytes of the indexed |
115 | | /// sample. Typically this will be the `start_offset` of the next sample |
116 | | /// in the file. |
117 | | pub end_offset: CheckedInteger<u64>, |
118 | | /// The time in ticks when the indexed sample should be displayed. |
119 | | /// Analogous to the concept of presentation time stamp (pts). |
120 | | pub start_composition: CheckedInteger<i64>, |
121 | | /// The time in ticks when the indexed sample should stop being |
122 | | /// displayed. Typically this would be the `start_composition` time of the |
123 | | /// next sample if samples were ordered by composition time. |
124 | | pub end_composition: CheckedInteger<i64>, |
125 | | /// The time in ticks that the indexed sample should be decoded at. |
126 | | /// Analogous to the concept of decode time stamp (dts). |
127 | | pub start_decode: CheckedInteger<i64>, |
128 | | /// Set if the indexed sample is a sync sample. The meaning of sync is |
129 | | /// somewhat codec specific, but essentially amounts to if the sample is a |
130 | | /// key frame. |
131 | | pub sync: bool, |
132 | | } |
133 | | |
134 | | /// Create a vector of `Indice`s with the information about track samples. |
135 | | /// It uses `stsc`, `stco`, `stsz` and `stts` boxes to construct a list of |
136 | | /// every sample in the file and provides offsets which can be used to read |
137 | | /// raw sample data from the file. |
138 | | #[allow(clippy::reversed_empty_ranges)] |
139 | 817 | pub fn create_sample_table( |
140 | 817 | track: &Track, |
141 | 817 | track_offset_time: CheckedInteger<i64>, |
142 | 817 | ) -> Option<TryVec<Indice>> { |
143 | 817 | let (stsc, stco, stsz, stts) = match (&track.stsc, &track.stco, &track.stsz, &track.stts) { |
144 | 417 | (Some(a), Some(b), Some(c), Some(d)) => (a, b, c, d), |
145 | 400 | _ => return None, |
146 | | }; |
147 | | |
148 | | // According to spec, no sync table means every sample is sync sample. |
149 | 417 | let has_sync_table = track.stss.is_some(); |
150 | | |
151 | 417 | let mut sample_size_iter = stsz.sample_sizes.iter(); |
152 | | |
153 | | // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample |
154 | | // offset address. |
155 | | |
156 | | // With large numbers of samples, the cost of many allocations dominates, |
157 | | // so it's worth iterating twice to allocate sample_table just once. |
158 | 417 | let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets) |
159 | 93.0k | .map(|(_, sample_counts)| sample_counts.to_usize()) |
160 | 417 | .try_fold(0usize, usize::checked_add)?; |
161 | 417 | let mut sample_table = TryVec::with_capacity(total_sample_count).ok()?; |
162 | | |
163 | 79.9k | for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) { |
164 | 79.9k | let chunk_id = i.0 as usize; |
165 | 79.9k | let sample_counts = i.1; |
166 | 79.9k | let mut cur_position = match stco.offsets.get(chunk_id) { |
167 | 79.9k | Some(&i) => i.into(), |
168 | 8 | _ => return None, |
169 | | }; |
170 | 79.9k | for _ in 0..sample_counts { |
171 | 107M | let start_offset = cur_position; |
172 | 107M | let end_offset = match (stsz.sample_size, sample_size_iter.next()) { |
173 | 37.7k | (_, Some(t)) => (start_offset + *t)?, |
174 | 107M | (t, _) if t > 0 => (start_offset + t)?, |
175 | 9 | _ => 0.into(), |
176 | | }; |
177 | 107M | if end_offset == 0 { |
178 | 11 | return None; |
179 | 107M | } |
180 | 107M | cur_position = end_offset; |
181 | | |
182 | 107M | sample_table |
183 | 107M | .push(Indice { |
184 | 107M | start_offset, |
185 | 107M | end_offset, |
186 | 107M | sync: !has_sync_table, |
187 | 107M | ..Default::default() |
188 | 107M | }) |
189 | 107M | .ok()?; |
190 | | } |
191 | | } |
192 | | |
193 | | // Mark the sync sample in sample_table according to 'stss'. |
194 | 385 | if let Some(ref v) = track.stss { |
195 | 2.40k | for iter in &v.samples { |
196 | 2.34k | match iter |
197 | 2.34k | .checked_sub(&1) |
198 | 2.34k | .and_then(|idx| sample_table.get_mut(idx as usize)) |
199 | | { |
200 | 2.32k | Some(elem) => elem.sync = true, |
201 | 15 | _ => return None, |
202 | | } |
203 | | } |
204 | 311 | } |
205 | | |
206 | 370 | let ctts_iter = track.ctts.as_ref().map(|v| v.samples.as_slice().iter()); |
207 | | |
208 | 370 | let mut ctts_offset_iter = TimeOffsetIterator { |
209 | 370 | cur_sample_range: (0..0), |
210 | 370 | cur_offset: 0, |
211 | 370 | ctts_iter, |
212 | 370 | track_id: track.id, |
213 | 370 | }; |
214 | | |
215 | 370 | let mut stts_iter = TimeToSampleIterator { |
216 | 370 | cur_sample_count: (0..0), |
217 | 370 | cur_sample_delta: 0, |
218 | 370 | stts_iter: stts.samples.as_slice().iter(), |
219 | 370 | track_id: track.id, |
220 | 370 | }; |
221 | | |
222 | | // sum_delta is the sum of stts_iter delta. |
223 | | // According to spec: |
224 | | // decode time => DT(n) = DT(n-1) + STTS(n) |
225 | | // composition time => CT(n) = DT(n) + CTTS(n) |
226 | | // Note: |
227 | | // composition time needs to add the track offset time from 'elst' table. |
228 | 370 | let mut sum_delta = TrackScaledTime::<i64>(0, track.id); |
229 | 107M | for sample in sample_table.as_mut_slice() { |
230 | 107M | let decode_time = sum_delta; |
231 | 107M | sum_delta = (sum_delta + stts_iter.next_delta())?; |
232 | | |
233 | | // ctts_offset is the current sample offset time. |
234 | 107M | let ctts_offset = ctts_offset_iter.next_offset_time(); |
235 | | |
236 | 107M | let start_composition = decode_time + ctts_offset; |
237 | | |
238 | 107M | let end_composition = sum_delta + ctts_offset; |
239 | | |
240 | 107M | let start_decode = decode_time; |
241 | | |
242 | 107M | sample.start_composition = CheckedInteger(track_offset_time.0 + start_composition?.0); |
243 | 107M | sample.end_composition = CheckedInteger(track_offset_time.0 + end_composition?.0); |
244 | 107M | sample.start_decode = CheckedInteger(start_decode.0); |
245 | | } |
246 | | |
247 | | // Correct composition end time due to 'ctts' causes composition time re-ordering. |
248 | | // |
249 | | // Composition end time is not in specification. However, gecko needs it, so we need to |
250 | | // calculate to correct the composition end time. |
251 | 370 | if !sample_table.is_empty() { |
252 | | // Create an index table refers to sample_table and sorted by start_composisiton time. |
253 | 72 | let mut sort_table = TryVec::with_capacity(sample_table.len()).ok()?; |
254 | | |
255 | 107M | for i in 0..sample_table.len() { |
256 | 107M | sort_table.push(i).ok()?; |
257 | | } |
258 | | |
259 | 706M | sort_table.sort_by_key(|i| match sample_table.get(*i) { |
260 | 706M | Some(v) => v.start_composition, |
261 | 0 | _ => 0.into(), |
262 | 706M | }); |
263 | | |
264 | 107M | for indices in sort_table.windows(2) { |
265 | 107M | if let [current_index, peek_index] = *indices { |
266 | 107M | let next_start_composition_time = sample_table[peek_index].start_composition; |
267 | 107M | let sample = &mut sample_table[current_index]; |
268 | 107M | sample.end_composition = next_start_composition_time; |
269 | 107M | } |
270 | | } |
271 | 298 | } |
272 | | |
273 | 370 | Some(sample_table) |
274 | 817 | } |
275 | | |
276 | | // Convert a 'ctts' compact table to full table by iterator, |
277 | | // (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ... |
278 | | // |
279 | | // For example: |
280 | | // (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time(). |
281 | | struct TimeOffsetIterator<'a> { |
282 | | cur_sample_range: std::ops::Range<u32>, |
283 | | cur_offset: i64, |
284 | | ctts_iter: Option<std::slice::Iter<'a, TimeOffset>>, |
285 | | track_id: usize, |
286 | | } |
287 | | |
288 | | impl Iterator for TimeOffsetIterator<'_> { |
289 | | type Item = i64; |
290 | | |
291 | | #[allow(clippy::reversed_empty_ranges)] |
292 | 107M | fn next(&mut self) -> Option<i64> { |
293 | 107M | let has_sample = self.cur_sample_range.next().or_else(|| { |
294 | | // At end of current TimeOffset, find the next TimeOffset. |
295 | 95.8k | let iter = match self.ctts_iter { |
296 | 75.4k | Some(ref mut v) => v, |
297 | 20.4k | _ => return None, |
298 | | }; |
299 | | let offset_version; |
300 | 75.4k | self.cur_sample_range = match iter.next() { |
301 | 40.2k | Some(v) => { |
302 | 40.2k | offset_version = v.time_offset; |
303 | 40.2k | 0..v.sample_count |
304 | | } |
305 | | _ => { |
306 | 35.1k | offset_version = TimeOffsetVersion::Version0(0); |
307 | 35.1k | 0..0 |
308 | | } |
309 | | }; |
310 | | |
311 | 75.4k | self.cur_offset = match offset_version { |
312 | 35.1k | TimeOffsetVersion::Version0(i) => i64::from(i), |
313 | 40.2k | TimeOffsetVersion::Version1(i) => i64::from(i), |
314 | | }; |
315 | | |
316 | 75.4k | self.cur_sample_range.next() |
317 | 95.8k | }); |
318 | | |
319 | 107M | has_sample.and(Some(self.cur_offset)) |
320 | 107M | } |
321 | | } |
322 | | |
323 | | impl TimeOffsetIterator<'_> { |
324 | 107M | fn next_offset_time(&mut self) -> TrackScaledTime<i64> { |
325 | 107M | match self.next() { |
326 | 107M | Some(v) => TrackScaledTime::<i64>(v, self.track_id), |
327 | 55.8k | _ => TrackScaledTime::<i64>(0, self.track_id), |
328 | | } |
329 | 107M | } |
330 | | } |
331 | | |
332 | | // Convert 'stts' compact table to full table by iterator, |
333 | | // (sample_count_with_the_same_time, time) => (time, time, time) ... repeats |
334 | | // sample_count_with_the_same_time. |
335 | | // |
336 | | // For example: |
337 | | // (2, 3000), (1, 2999) to (3000, 3000, 2999). |
338 | | struct TimeToSampleIterator<'a> { |
339 | | cur_sample_count: std::ops::Range<u32>, |
340 | | cur_sample_delta: u32, |
341 | | stts_iter: std::slice::Iter<'a, Sample>, |
342 | | track_id: usize, |
343 | | } |
344 | | |
345 | | impl Iterator for TimeToSampleIterator<'_> { |
346 | | type Item = u32; |
347 | | |
348 | | #[allow(clippy::reversed_empty_ranges)] |
349 | 107M | fn next(&mut self) -> Option<u32> { |
350 | 107M | let has_sample = self.cur_sample_count.next().or_else(|| { |
351 | 46.2M | self.cur_sample_count = match self.stts_iter.next() { |
352 | 575 | Some(v) => { |
353 | 575 | self.cur_sample_delta = v.sample_delta; |
354 | 575 | 0..v.sample_count |
355 | | } |
356 | 46.2M | _ => 0..0, |
357 | | }; |
358 | | |
359 | 46.2M | self.cur_sample_count.next() |
360 | 46.2M | }); |
361 | | |
362 | 107M | has_sample.and(Some(self.cur_sample_delta)) |
363 | 107M | } |
364 | | } |
365 | | |
366 | | impl TimeToSampleIterator<'_> { |
367 | 107M | fn next_delta(&mut self) -> TrackScaledTime<i64> { |
368 | 107M | match self.next() { |
369 | 60.7M | Some(v) => TrackScaledTime::<i64>(i64::from(v), self.track_id), |
370 | 46.2M | _ => TrackScaledTime::<i64>(0, self.track_id), |
371 | | } |
372 | 107M | } |
373 | | } |
374 | | |
375 | | // Convert 'stco' compact table to full table by iterator. |
376 | | // (start_chunk_num, sample_number) => (start_chunk_num, sample_number), |
377 | | // (start_chunk_num + 1, sample_number), |
378 | | // (start_chunk_num + 2, sample_number), |
379 | | // ... |
380 | | // (next start_chunk_num, next sample_number), |
381 | | // ... |
382 | | // |
383 | | // For example: |
384 | | // (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10), |
385 | | // (7, 10), (8, 10), (9, 2) |
386 | 832 | fn sample_to_chunk_iter<'a>( |
387 | 832 | stsc_samples: &'a TryVec<SampleToChunk>, |
388 | 832 | stco_offsets: &'a TryVec<u64>, |
389 | 832 | ) -> SampleToChunkIterator<'a> { |
390 | 832 | SampleToChunkIterator { |
391 | 832 | chunks: (0..0), |
392 | 832 | sample_count: 0, |
393 | 832 | stsc_peek_iter: stsc_samples.as_slice().iter().peekable(), |
394 | 832 | remain_chunk_count: stco_offsets |
395 | 832 | .len() |
396 | 832 | .try_into() |
397 | 832 | .expect("stco.entry_count is u32"), |
398 | 832 | } |
399 | 832 | } |
400 | | |
401 | | struct SampleToChunkIterator<'a> { |
402 | | chunks: std::ops::Range<u32>, |
403 | | sample_count: u32, |
404 | | stsc_peek_iter: std::iter::Peekable<std::slice::Iter<'a, SampleToChunk>>, |
405 | | remain_chunk_count: u32, // total chunk number from 'stco'. |
406 | | } |
407 | | |
408 | | impl Iterator for SampleToChunkIterator<'_> { |
409 | | type Item = (u32, u32); |
410 | | |
411 | 173k | fn next(&mut self) -> Option<(u32, u32)> { |
412 | 173k | let has_chunk = self.chunks.next().or_else(|| { |
413 | 7.18k | self.chunks = self.locate(); |
414 | 7.18k | self.remain_chunk_count |
415 | 7.18k | .checked_sub( |
416 | 7.18k | self.chunks |
417 | 7.18k | .len() |
418 | 7.18k | .try_into() |
419 | 7.18k | .expect("len() of a Range<u32> must fit in u32"), |
420 | | ) |
421 | 7.18k | .and_then(|res| { |
422 | 7.10k | self.remain_chunk_count = res; |
423 | 7.10k | self.chunks.next() |
424 | 7.10k | }) |
425 | 7.18k | }); |
426 | | |
427 | 173k | has_chunk.map(|id| (id, self.sample_count)) |
428 | 173k | } |
429 | | } |
430 | | |
431 | | impl SampleToChunkIterator<'_> { |
432 | | #[allow(clippy::reversed_empty_ranges)] |
433 | 7.18k | fn locate(&mut self) -> std::ops::Range<u32> { |
434 | | loop { |
435 | 8.12k | return match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) { |
436 | 7.28k | (Some(next), Some(peek)) if next.first_chunk == peek.first_chunk => { |
437 | | // Invalid entry, skip it and will continue searching at |
438 | | // next loop iteration. |
439 | 942 | continue; |
440 | | } |
441 | 6.34k | (Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => { |
442 | 6.33k | self.sample_count = next.samples_per_chunk; |
443 | 6.33k | (next.first_chunk - 1)..(peek.first_chunk - 1) |
444 | | } |
445 | 153 | (Some(next), None) if next.first_chunk > 0 => { |
446 | 153 | self.sample_count = next.samples_per_chunk; |
447 | | // Total chunk number in 'stsc' could be different to 'stco', |
448 | | // there could be more chunks at the last 'stsc' record. |
449 | 153 | match next.first_chunk.checked_add(self.remain_chunk_count) { |
450 | 153 | Some(r) => (next.first_chunk - 1)..r - 1, |
451 | 0 | _ => 0..0, |
452 | | } |
453 | | } |
454 | 693 | _ => 0..0, |
455 | | }; |
456 | | } |
457 | 7.18k | } |
458 | | } |
459 | | |
460 | | /// Calculate numerator * scale / denominator, if possible. |
461 | | /// |
462 | | /// Applying the associativity of integer arithmetic, we divide first |
463 | | /// and add the remainder after multiplying each term separately |
464 | | /// to preserve precision while leaving more headroom. That is, |
465 | | /// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d. |
466 | | /// |
467 | | /// Return None on overflow or if the denominator is zero. |
468 | 4.17k | pub fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T> |
469 | 4.17k | where |
470 | 4.17k | T: PrimInt + Zero, |
471 | 4.17k | S: PrimInt, |
472 | | { |
473 | 4.17k | if denominator.is_zero() { |
474 | 0 | return None; |
475 | 4.17k | } |
476 | | |
477 | 4.17k | let integer = numerator / denominator; |
478 | 4.17k | let remainder = numerator % denominator; |
479 | 4.17k | num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { |
480 | 4.17k | Some(integer) => remainder |
481 | 4.17k | .checked_mul(&s) |
482 | 4.17k | .and_then(|remainder| (remainder / denominator).checked_add(&integer)), mp4parse::unstable::rational_scale::<u64, u64>::{closure#0}::{closure#0}Line | Count | Source | 482 | 4.17k | .and_then(|remainder| (remainder / denominator).checked_add(&integer)), |
Unexecuted instantiation: mp4parse::unstable::rational_scale::<u64, i32>::{closure#0}::{closure#0} |
483 | 0 | None => None, |
484 | 4.17k | }) mp4parse::unstable::rational_scale::<u64, u64>::{closure#0}Line | Count | Source | 479 | 4.17k | num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { | 480 | 4.17k | Some(integer) => remainder | 481 | 4.17k | .checked_mul(&s) | 482 | 4.17k | .and_then(|remainder| (remainder / denominator).checked_add(&integer)), | 483 | 0 | None => None, | 484 | 4.17k | }) |
Unexecuted instantiation: mp4parse::unstable::rational_scale::<u64, i32>::{closure#0} |
485 | 4.17k | } mp4parse::unstable::rational_scale::<u64, u64> Line | Count | Source | 468 | 4.17k | pub fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T> | 469 | 4.17k | where | 470 | 4.17k | T: PrimInt + Zero, | 471 | 4.17k | S: PrimInt, | 472 | | { | 473 | 4.17k | if denominator.is_zero() { | 474 | 0 | return None; | 475 | 4.17k | } | 476 | | | 477 | 4.17k | let integer = numerator / denominator; | 478 | 4.17k | let remainder = numerator % denominator; | 479 | 4.17k | num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { | 480 | | Some(integer) => remainder | 481 | | .checked_mul(&s) | 482 | | .and_then(|remainder| (remainder / denominator).checked_add(&integer)), | 483 | | None => None, | 484 | | }) | 485 | 4.17k | } |
Unexecuted instantiation: mp4parse::unstable::rational_scale::<u64, i32> |
486 | | |
487 | | #[derive(Debug, PartialEq, Eq)] |
488 | | pub struct Microseconds<T>(pub T); |
489 | | |
490 | | /// Convert `time` in media's global (mvhd) timescale to microseconds, |
491 | | /// using provided `MediaTimeScale` |
492 | 0 | pub fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<Microseconds<u64>> { |
493 | 0 | let microseconds_per_second = 1_000_000; |
494 | 0 | rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) |
495 | 0 | } |
496 | | |
497 | | /// Convert `time` in track's local (mdhd) timescale to microseconds, |
498 | | /// using provided `TrackTimeScale<T>` |
499 | | pub fn track_time_to_us<T>( |
500 | | time: TrackScaledTime<T>, |
501 | | scale: TrackTimeScale<T>, |
502 | | ) -> Option<Microseconds<T>> |
503 | | where |
504 | | T: PrimInt + Zero, |
505 | | { |
506 | | assert_eq!(time.1, scale.1); |
507 | | let microseconds_per_second = 1_000_000; |
508 | | rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) |
509 | | } |
510 | | |
511 | | #[test] |
512 | | fn rational_scale_overflow() { |
513 | | assert_eq!(rational_scale::<u64, u64>(17, 3, 1000), Some(5666)); |
514 | | let large = 0x4000_0000_0000_0000; |
515 | | assert_eq!(rational_scale::<u64, u64>(large, 2, 2), Some(large)); |
516 | | assert_eq!(rational_scale::<u64, u64>(large, 4, 4), Some(large)); |
517 | | assert_eq!(rational_scale::<u64, u64>(large, 2, 8), None); |
518 | | assert_eq!(rational_scale::<u64, u64>(large, 8, 4), Some(large / 2)); |
519 | | assert_eq!(rational_scale::<u64, u64>(large + 1, 4, 4), Some(large + 1)); |
520 | | assert_eq!(rational_scale::<u64, u64>(large, 40, 1000), None); |
521 | | } |
522 | | |
523 | | #[test] |
524 | | fn media_time_overflow() { |
525 | | let scale = MediaTimeScale(90000); |
526 | | let duration = MediaScaledTime(9_007_199_254_710_000); |
527 | | assert_eq!( |
528 | | media_time_to_us(duration, scale), |
529 | | Some(Microseconds(100_079_991_719_000_000u64)) |
530 | | ); |
531 | | } |
532 | | |
533 | | #[test] |
534 | | fn track_time_overflow() { |
535 | | let scale = TrackTimeScale(44100u64, 0); |
536 | | let duration = TrackScaledTime(4_413_527_634_807_900u64, 0); |
537 | | assert_eq!( |
538 | | track_time_to_us(duration, scale), |
539 | | Some(Microseconds(100_079_991_719_000_000u64)) |
540 | | ); |
541 | | } |