/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 | 189M | fn from(i: T) -> Self { |
17 | 189M | Self(i) |
18 | 189M | } <mp4parse::unstable::CheckedInteger<i64> as core::convert::From<i64>>::from Line | Count | Source | 16 | 2.17k | fn from(i: T) -> Self { | 17 | 2.17k | Self(i) | 18 | 2.17k | } |
<mp4parse::unstable::CheckedInteger<u64> as core::convert::From<u64>>::from Line | Count | Source | 16 | 189M | fn from(i: T) -> Self { | 17 | 189M | Self(i) | 18 | 189M | } |
|
19 | | } |
20 | | |
21 | | // Orphan rules prevent a more general implementation, but this suffices |
22 | | impl From<CheckedInteger<i64>> for i64 { |
23 | 397 | fn from(checked: CheckedInteger<i64>) -> i64 { |
24 | 397 | checked.0 |
25 | 397 | } |
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 | 189M | fn add(self, other: U) -> Self::Output { |
35 | 189M | self.0.checked_add(&other.into()).map(Into::into) |
36 | 189M | } |
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 | 397 | fn sub(self, other: U) -> Self::Output { |
46 | 397 | self.0.checked_sub(&other.into()).map(Into::into) |
47 | 397 | } <mp4parse::unstable::CheckedInteger<i64> as core::ops::arith::Sub>::sub Line | Count | Source | 45 | 397 | fn sub(self, other: U) -> Self::Output { | 46 | 397 | self.0.checked_sub(&other.into()).map(Into::into) | 47 | 397 | } |
Unexecuted instantiation: <mp4parse::unstable::CheckedInteger<_> as core::ops::arith::Sub<_>>::sub |
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 | 32.0k | fn sub(self, other: Self) -> Self::Output { |
56 | 32.0k | if self >= other { |
57 | 31.9k | self.0 |
58 | 31.9k | .checked_sub(other.0) |
59 | 31.9k | .and_then(|u| i64::try_from(u).ok()) |
60 | 31.9k | .map(CheckedInteger) |
61 | | } else { |
62 | 81 | other |
63 | 81 | .0 |
64 | 81 | .checked_sub(self.0) |
65 | 81 | .and_then(|u| i64::try_from(u).ok()) |
66 | 81 | .map(i64::neg) |
67 | 81 | .map(CheckedInteger) |
68 | | } |
69 | 32.0k | } |
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 | 189M | fn eq(&self, other: &T) -> bool { |
101 | 189M | self.0 == *other |
102 | 189M | } |
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 | 1.38k | pub fn create_sample_table( |
140 | 1.38k | track: &Track, |
141 | 1.38k | track_offset_time: CheckedInteger<i64>, |
142 | 1.38k | ) -> Option<TryVec<Indice>> { |
143 | 1.38k | let (stsc, stco, stsz, stts) = match (&track.stsc, &track.stco, &track.stsz, &track.stts) { |
144 | 580 | (Some(a), Some(b), Some(c), Some(d)) => (a, b, c, d), |
145 | 802 | _ => return None, |
146 | | }; |
147 | | |
148 | | // According to spec, no sync table means every sample is sync sample. |
149 | 580 | let has_sync_table = track.stss.is_some(); |
150 | | |
151 | 580 | 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 | 580 | let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets) |
159 | 87.3k | .map(|(_, sample_counts)| sample_counts.to_usize()) |
160 | 580 | .try_fold(0usize, usize::checked_add)?; |
161 | 580 | let mut sample_table = TryVec::with_capacity(total_sample_count).ok()?; |
162 | | |
163 | 81.5k | for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) { |
164 | 81.5k | let chunk_id = i.0 as usize; |
165 | 81.5k | let sample_counts = i.1; |
166 | 81.5k | let mut cur_position = match stco.offsets.get(chunk_id) { |
167 | 81.5k | Some(&i) => i.into(), |
168 | 12 | _ => return None, |
169 | | }; |
170 | 81.5k | for _ in 0..sample_counts { |
171 | 189M | let start_offset = cur_position; |
172 | 189M | let end_offset = match (stsz.sample_size, sample_size_iter.next()) { |
173 | 20.0k | (_, Some(t)) => (start_offset + *t)?, |
174 | 189M | (t, _) if t > 0 => (start_offset + t)?, |
175 | 1 | _ => 0.into(), |
176 | | }; |
177 | 189M | if end_offset == 0 { |
178 | 4 | return None; |
179 | 189M | } |
180 | 189M | cur_position = end_offset; |
181 | | |
182 | 189M | sample_table |
183 | 189M | .push(Indice { |
184 | 189M | start_offset, |
185 | 189M | end_offset, |
186 | 189M | sync: !has_sync_table, |
187 | 189M | ..Default::default() |
188 | 189M | }) |
189 | 189M | .ok()?; |
190 | | } |
191 | | } |
192 | | |
193 | | // Mark the sync sample in sample_table according to 'stss'. |
194 | 537 | if let Some(ref v) = track.stss { |
195 | 3.01k | for iter in &v.samples { |
196 | 2.93k | match iter |
197 | 2.93k | .checked_sub(&1) |
198 | 2.93k | .and_then(|idx| sample_table.get_mut(idx as usize)) |
199 | | { |
200 | 2.92k | Some(elem) => elem.sync = true, |
201 | 7 | _ => return None, |
202 | | } |
203 | | } |
204 | 449 | } |
205 | | |
206 | 530 | let ctts_iter = track.ctts.as_ref().map(|v| v.samples.as_slice().iter()); |
207 | | |
208 | 530 | let mut ctts_offset_iter = TimeOffsetIterator { |
209 | 530 | cur_sample_range: (0..0), |
210 | 530 | cur_offset: 0, |
211 | 530 | ctts_iter, |
212 | 530 | track_id: track.id, |
213 | 530 | }; |
214 | | |
215 | 530 | let mut stts_iter = TimeToSampleIterator { |
216 | 530 | cur_sample_count: (0..0), |
217 | 530 | cur_sample_delta: 0, |
218 | 530 | stts_iter: stts.samples.as_slice().iter(), |
219 | 530 | track_id: track.id, |
220 | 530 | }; |
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 | 530 | let mut sum_delta = TrackScaledTime::<i64>(0, track.id); |
229 | 189M | for sample in sample_table.as_mut_slice() { |
230 | 189M | let decode_time = sum_delta; |
231 | 189M | sum_delta = (sum_delta + stts_iter.next_delta())?; |
232 | | |
233 | | // ctts_offset is the current sample offset time. |
234 | 189M | let ctts_offset = ctts_offset_iter.next_offset_time(); |
235 | | |
236 | 189M | let start_composition = decode_time + ctts_offset; |
237 | | |
238 | 189M | let end_composition = sum_delta + ctts_offset; |
239 | | |
240 | 189M | let start_decode = decode_time; |
241 | | |
242 | 189M | let start_composition_val: i64 = start_composition?.0; |
243 | 189M | let end_composition_val: i64 = end_composition?.0; |
244 | | |
245 | 189M | let track_offset: i64 = track_offset_time.0; |
246 | | |
247 | 189M | sample.start_composition = CheckedInteger(track_offset.checked_add(start_composition_val)?); |
248 | 189M | sample.end_composition = CheckedInteger(track_offset.checked_add(end_composition_val)?); |
249 | 189M | sample.start_decode = CheckedInteger(start_decode.0); |
250 | | } |
251 | | |
252 | | // Correct composition end time due to 'ctts' causes composition time re-ordering. |
253 | | // |
254 | | // Composition end time is not in specification. However, gecko needs it, so we need to |
255 | | // calculate to correct the composition end time. |
256 | 530 | if !sample_table.is_empty() { |
257 | | // Create an index table refers to sample_table and sorted by start_composisiton time. |
258 | 115 | let mut sort_table = TryVec::with_capacity(sample_table.len()).ok()?; |
259 | | |
260 | 189M | for i in 0..sample_table.len() { |
261 | 189M | sort_table.push(i).ok()?; |
262 | | } |
263 | | |
264 | 1.03G | sort_table.sort_by_key(|i| match sample_table.get(*i) { |
265 | 1.03G | Some(v) => v.start_composition, |
266 | 0 | _ => 0.into(), |
267 | 1.03G | }); |
268 | | |
269 | 189M | for indices in sort_table.windows(2) { |
270 | 189M | if let [current_index, peek_index] = *indices { |
271 | 189M | let next_start_composition_time = sample_table[peek_index].start_composition; |
272 | 189M | let sample = &mut sample_table[current_index]; |
273 | 189M | sample.end_composition = next_start_composition_time; |
274 | 189M | } |
275 | | } |
276 | 415 | } |
277 | | |
278 | 530 | Some(sample_table) |
279 | 1.38k | } |
280 | | |
281 | | // Convert a 'ctts' compact table to full table by iterator, |
282 | | // (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ... |
283 | | // |
284 | | // For example: |
285 | | // (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time(). |
286 | | struct TimeOffsetIterator<'a> { |
287 | | cur_sample_range: std::ops::Range<u32>, |
288 | | cur_offset: i64, |
289 | | ctts_iter: Option<std::slice::Iter<'a, TimeOffset>>, |
290 | | track_id: usize, |
291 | | } |
292 | | |
293 | | impl Iterator for TimeOffsetIterator<'_> { |
294 | | type Item = i64; |
295 | | |
296 | | #[allow(clippy::reversed_empty_ranges)] |
297 | 189M | fn next(&mut self) -> Option<i64> { |
298 | 189M | let has_sample = self.cur_sample_range.next().or_else(|| { |
299 | | // At end of current TimeOffset, find the next TimeOffset. |
300 | 13.1M | let iter = match self.ctts_iter { |
301 | 3.21M | Some(ref mut v) => v, |
302 | 9.98M | _ => return None, |
303 | | }; |
304 | | let offset_version; |
305 | 3.21M | self.cur_sample_range = match iter.next() { |
306 | 52.9k | Some(v) => { |
307 | 52.9k | offset_version = v.time_offset; |
308 | 52.9k | 0..v.sample_count |
309 | | } |
310 | | _ => { |
311 | 3.15M | offset_version = TimeOffsetVersion::Version0(0); |
312 | 3.15M | 0..0 |
313 | | } |
314 | | }; |
315 | | |
316 | 3.21M | self.cur_offset = match offset_version { |
317 | 3.15M | TimeOffsetVersion::Version0(i) => i64::from(i), |
318 | 52.9k | TimeOffsetVersion::Version1(i) => i64::from(i), |
319 | | }; |
320 | | |
321 | 3.21M | self.cur_sample_range.next() |
322 | 13.1M | }); |
323 | | |
324 | 189M | has_sample.and(Some(self.cur_offset)) |
325 | 189M | } |
326 | | } |
327 | | |
328 | | impl TimeOffsetIterator<'_> { |
329 | 189M | fn next_offset_time(&mut self) -> TrackScaledTime<i64> { |
330 | 189M | match self.next() { |
331 | 176M | Some(v) => TrackScaledTime::<i64>(v, self.track_id), |
332 | 13.1M | _ => TrackScaledTime::<i64>(0, self.track_id), |
333 | | } |
334 | 189M | } |
335 | | } |
336 | | |
337 | | // Convert 'stts' compact table to full table by iterator, |
338 | | // (sample_count_with_the_same_time, time) => (time, time, time) ... repeats |
339 | | // sample_count_with_the_same_time. |
340 | | // |
341 | | // For example: |
342 | | // (2, 3000), (1, 2999) to (3000, 3000, 2999). |
343 | | struct TimeToSampleIterator<'a> { |
344 | | cur_sample_count: std::ops::Range<u32>, |
345 | | cur_sample_delta: u32, |
346 | | stts_iter: std::slice::Iter<'a, Sample>, |
347 | | track_id: usize, |
348 | | } |
349 | | |
350 | | impl Iterator for TimeToSampleIterator<'_> { |
351 | | type Item = u32; |
352 | | |
353 | | #[allow(clippy::reversed_empty_ranges)] |
354 | 189M | fn next(&mut self) -> Option<u32> { |
355 | 189M | let has_sample = self.cur_sample_count.next().or_else(|| { |
356 | 49.6M | self.cur_sample_count = match self.stts_iter.next() { |
357 | 1.70k | Some(v) => { |
358 | 1.70k | self.cur_sample_delta = v.sample_delta; |
359 | 1.70k | 0..v.sample_count |
360 | | } |
361 | 49.6M | _ => 0..0, |
362 | | }; |
363 | | |
364 | 49.6M | self.cur_sample_count.next() |
365 | 49.6M | }); |
366 | | |
367 | 189M | has_sample.and(Some(self.cur_sample_delta)) |
368 | 189M | } |
369 | | } |
370 | | |
371 | | impl TimeToSampleIterator<'_> { |
372 | 189M | fn next_delta(&mut self) -> TrackScaledTime<i64> { |
373 | 189M | match self.next() { |
374 | 139M | Some(v) => TrackScaledTime::<i64>(i64::from(v), self.track_id), |
375 | 49.6M | _ => TrackScaledTime::<i64>(0, self.track_id), |
376 | | } |
377 | 189M | } |
378 | | } |
379 | | |
380 | | // Convert 'stco' compact table to full table by iterator. |
381 | | // (start_chunk_num, sample_number) => (start_chunk_num, sample_number), |
382 | | // (start_chunk_num + 1, sample_number), |
383 | | // (start_chunk_num + 2, sample_number), |
384 | | // ... |
385 | | // (next start_chunk_num, next sample_number), |
386 | | // ... |
387 | | // |
388 | | // For example: |
389 | | // (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10), |
390 | | // (7, 10), (8, 10), (9, 2) |
391 | 1.16k | fn sample_to_chunk_iter<'a>( |
392 | 1.16k | stsc_samples: &'a TryVec<SampleToChunk>, |
393 | 1.16k | stco_offsets: &'a TryVec<u64>, |
394 | 1.16k | ) -> SampleToChunkIterator<'a> { |
395 | 1.16k | SampleToChunkIterator { |
396 | 1.16k | chunks: (0..0), |
397 | 1.16k | sample_count: 0, |
398 | 1.16k | stsc_peek_iter: stsc_samples.as_slice().iter().peekable(), |
399 | 1.16k | remain_chunk_count: stco_offsets |
400 | 1.16k | .len() |
401 | 1.16k | .try_into() |
402 | 1.16k | .expect("stco.entry_count is u32"), |
403 | 1.16k | } |
404 | 1.16k | } |
405 | | |
406 | | struct SampleToChunkIterator<'a> { |
407 | | chunks: std::ops::Range<u32>, |
408 | | sample_count: u32, |
409 | | stsc_peek_iter: std::iter::Peekable<std::slice::Iter<'a, SampleToChunk>>, |
410 | | remain_chunk_count: u32, // total chunk number from 'stco'. |
411 | | } |
412 | | |
413 | | impl Iterator for SampleToChunkIterator<'_> { |
414 | | type Item = (u32, u32); |
415 | | |
416 | 169k | fn next(&mut self) -> Option<(u32, u32)> { |
417 | 169k | let has_chunk = self.chunks.next().or_else(|| { |
418 | 8.48k | self.chunks = self.locate(); |
419 | 8.48k | self.remain_chunk_count |
420 | 8.48k | .checked_sub( |
421 | 8.48k | self.chunks |
422 | 8.48k | .len() |
423 | 8.48k | .try_into() |
424 | 8.48k | .expect("len() of a Range<u32> must fit in u32"), |
425 | | ) |
426 | 8.48k | .and_then(|res| { |
427 | 8.34k | self.remain_chunk_count = res; |
428 | 8.34k | self.chunks.next() |
429 | 8.34k | }) |
430 | 8.48k | }); |
431 | | |
432 | 169k | has_chunk.map(|id| (id, self.sample_count)) |
433 | 169k | } |
434 | | } |
435 | | |
436 | | impl SampleToChunkIterator<'_> { |
437 | | #[allow(clippy::reversed_empty_ranges)] |
438 | 8.48k | fn locate(&mut self) -> std::ops::Range<u32> { |
439 | | loop { |
440 | 9.61k | return match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) { |
441 | 8.51k | (Some(next), Some(peek)) if next.first_chunk == peek.first_chunk => { |
442 | | // Invalid entry, skip it and will continue searching at |
443 | | // next loop iteration. |
444 | 1.12k | continue; |
445 | | } |
446 | 7.39k | (Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => { |
447 | 7.38k | self.sample_count = next.samples_per_chunk; |
448 | 7.38k | (next.first_chunk - 1)..(peek.first_chunk - 1) |
449 | | } |
450 | 148 | (Some(next), None) if next.first_chunk > 0 => { |
451 | 148 | self.sample_count = next.samples_per_chunk; |
452 | | // Total chunk number in 'stsc' could be different to 'stco', |
453 | | // there could be more chunks at the last 'stsc' record. |
454 | 148 | match next.first_chunk.checked_add(self.remain_chunk_count) { |
455 | 148 | Some(r) => (next.first_chunk - 1)..r - 1, |
456 | 0 | _ => 0..0, |
457 | | } |
458 | | } |
459 | 954 | _ => 0..0, |
460 | | }; |
461 | | } |
462 | 8.48k | } |
463 | | } |
464 | | |
465 | | /// Calculate numerator * scale / denominator, if possible. |
466 | | /// |
467 | | /// Applying the associativity of integer arithmetic, we divide first |
468 | | /// and add the remainder after multiplying each term separately |
469 | | /// to preserve precision while leaving more headroom. That is, |
470 | | /// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d. |
471 | | /// |
472 | | /// Return None on overflow or if the denominator is zero. |
473 | 2.53k | pub fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T> |
474 | 2.53k | where |
475 | 2.53k | T: PrimInt + Zero, |
476 | 2.53k | S: PrimInt, |
477 | | { |
478 | 2.53k | if denominator.is_zero() { |
479 | 0 | return None; |
480 | 2.53k | } |
481 | | |
482 | 2.53k | let integer = numerator / denominator; |
483 | 2.53k | let remainder = numerator % denominator; |
484 | 2.53k | num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { |
485 | 2.53k | Some(integer) => remainder |
486 | 2.53k | .checked_mul(&s) |
487 | 2.53k | .and_then(|remainder| (remainder / denominator).checked_add(&integer)), mp4parse::unstable::rational_scale::<u64, u64>::{closure#0}::{closure#0}Line | Count | Source | 487 | 2.53k | .and_then(|remainder| (remainder / denominator).checked_add(&integer)), |
Unexecuted instantiation: mp4parse::unstable::rational_scale::<u64, i32>::{closure#0}::{closure#0} |
488 | 0 | None => None, |
489 | 2.53k | }) mp4parse::unstable::rational_scale::<u64, u64>::{closure#0}Line | Count | Source | 484 | 2.53k | num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { | 485 | 2.53k | Some(integer) => remainder | 486 | 2.53k | .checked_mul(&s) | 487 | 2.53k | .and_then(|remainder| (remainder / denominator).checked_add(&integer)), | 488 | 0 | None => None, | 489 | 2.53k | }) |
Unexecuted instantiation: mp4parse::unstable::rational_scale::<u64, i32>::{closure#0} |
490 | 2.53k | } mp4parse::unstable::rational_scale::<u64, u64> Line | Count | Source | 473 | 2.53k | pub fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T> | 474 | 2.53k | where | 475 | 2.53k | T: PrimInt + Zero, | 476 | 2.53k | S: PrimInt, | 477 | | { | 478 | 2.53k | if denominator.is_zero() { | 479 | 0 | return None; | 480 | 2.53k | } | 481 | | | 482 | 2.53k | let integer = numerator / denominator; | 483 | 2.53k | let remainder = numerator % denominator; | 484 | 2.53k | num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { | 485 | | Some(integer) => remainder | 486 | | .checked_mul(&s) | 487 | | .and_then(|remainder| (remainder / denominator).checked_add(&integer)), | 488 | | None => None, | 489 | | }) | 490 | 2.53k | } |
Unexecuted instantiation: mp4parse::unstable::rational_scale::<u64, i32> |
491 | | |
492 | | #[derive(Debug, PartialEq, Eq)] |
493 | | pub struct Microseconds<T>(pub T); |
494 | | |
495 | | /// Convert `time` in media's global (mvhd) timescale to microseconds, |
496 | | /// using provided `MediaTimeScale` |
497 | 0 | pub fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<Microseconds<u64>> { |
498 | 0 | let microseconds_per_second = 1_000_000; |
499 | 0 | rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) |
500 | 0 | } |
501 | | |
502 | | /// Convert `time` in track's local (mdhd) timescale to microseconds, |
503 | | /// using provided `TrackTimeScale<T>` |
504 | 0 | pub fn track_time_to_us<T>( |
505 | 0 | time: TrackScaledTime<T>, |
506 | 0 | scale: TrackTimeScale<T>, |
507 | 0 | ) -> Option<Microseconds<T>> |
508 | 0 | where |
509 | 0 | T: PrimInt + Zero, |
510 | | { |
511 | 0 | assert_eq!(time.1, scale.1); |
512 | 0 | let microseconds_per_second = 1_000_000; |
513 | 0 | rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) |
514 | 0 | } |
515 | | |
516 | | #[test] |
517 | | fn rational_scale_overflow() { |
518 | | assert_eq!(rational_scale::<u64, u64>(17, 3, 1000), Some(5666)); |
519 | | let large = 0x4000_0000_0000_0000; |
520 | | assert_eq!(rational_scale::<u64, u64>(large, 2, 2), Some(large)); |
521 | | assert_eq!(rational_scale::<u64, u64>(large, 4, 4), Some(large)); |
522 | | assert_eq!(rational_scale::<u64, u64>(large, 2, 8), None); |
523 | | assert_eq!(rational_scale::<u64, u64>(large, 8, 4), Some(large / 2)); |
524 | | assert_eq!(rational_scale::<u64, u64>(large + 1, 4, 4), Some(large + 1)); |
525 | | assert_eq!(rational_scale::<u64, u64>(large, 40, 1000), None); |
526 | | } |
527 | | |
528 | | #[test] |
529 | | fn media_time_overflow() { |
530 | | let scale = MediaTimeScale(90000); |
531 | | let duration = MediaScaledTime(9_007_199_254_710_000); |
532 | | assert_eq!( |
533 | | media_time_to_us(duration, scale), |
534 | | Some(Microseconds(100_079_991_719_000_000u64)) |
535 | | ); |
536 | | } |
537 | | |
538 | | #[test] |
539 | | fn track_time_overflow() { |
540 | | let scale = TrackTimeScale(44100u64, 0); |
541 | | let duration = TrackScaledTime(4_413_527_634_807_900u64, 0); |
542 | | assert_eq!( |
543 | | track_time_to_us(duration, scale), |
544 | | Some(Microseconds(100_079_991_719_000_000u64)) |
545 | | ); |
546 | | } |