// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use num_traits::{CheckedAdd, CheckedSub, PrimInt, Zero}; use std::ops::{Add, Neg, Sub};
usesuper::*;
/// A zero-overhead wrapper around integer types for the sake of always /// requiring checked arithmetic #[repr(transparent)] #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pubstruct CheckedInteger<T>(pub T);
// Orphan rules prevent a more general implementation, but this suffices impl From<CheckedInteger<i64>> for i64 { fn from(checked: CheckedInteger<i64>) -> i64 {
checked.0
}
}
impl<T, U: Into<T>> Add<U> for CheckedInteger<T> where
T: CheckedAdd,
{ type Output = Option<Self>;
/// Implement subtraction of checked `u64`s returning i64 // This is necessary for handling Mp4parseTrackInfo::media_time gracefully impl Sub for CheckedInteger<u64> { type Output = Option<CheckedInteger<i64>>;
/// Provides the following information about a sample in the source file: /// sample data offset (start and end), composition time in microseconds /// (start and end) and whether it is a sync sample #[repr(C)] #[derive(Default, Debug, PartialEq, Eq)] pubstruct Indice { /// The byte offset in the file where the indexed sample begins. pub start_offset: CheckedInteger<u64>, /// The byte offset in the file where the indexed sample ends. This is /// equivalent to `start_offset` + the length in bytes of the indexed /// sample. Typically this will be the `start_offset` of the next sample /// in the file. pub end_offset: CheckedInteger<u64>, /// The time in ticks when the indexed sample should be displayed. /// Analogous to the concept of presentation time stamp (pts). pub start_composition: CheckedInteger<i64>, /// The time in ticks when the indexed sample should stop being /// displayed. Typically this would be the `start_composition` time of the /// next sample if samples were ordered by composition time. pub end_composition: CheckedInteger<i64>, /// The time in ticks that the indexed sample should be decoded at. /// Analogous to the concept of decode time stamp (dts). pub start_decode: CheckedInteger<i64>, /// Set if the indexed sample is a sync sample. The meaning of sync is /// somewhat codec specific, but essentially amounts to if the sample is a /// key frame. pub sync: bool,
}
/// Create a vector of `Indice`s with the information about track samples. /// It uses `stsc`, `stco`, `stsz` and `stts` boxes to construct a list of /// every sample in the file and provides offsets which can be used to read /// raw sample data from the file. #[allow(clippy::reversed_empty_ranges)] pubfn create_sample_table(
track: &Track,
track_offset_time: CheckedInteger<i64>,
) -> Option<TryVec<Indice>> { let (stsc, stco, stsz, stts) = match (&track.stsc, &track.stco, &track.stsz, &track.stts) {
(Some(a), Some(b), Some(c), Some(d)) => (a, b, c, d),
_ => return None,
};
// According to spec, no sync table means every sample is sync sample. let has_sync_table = track.stss.is_some();
// Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample // offset address.
// With large numbers of samples, the cost of many allocations dominates, // so it's worth iterating twice to allocate sample_table just once. let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets)
.map(|(_, sample_counts)| sample_counts.to_usize())
.try_fold(0usize, usize::checked_add)?; letmut sample_table = TryVec::with_capacity(total_sample_count).ok()?;
for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) { let chunk_id = i.0as usize; let sample_counts = i.1; letmut cur_position = match stco.offsets.get(chunk_id) {
Some(&i) => i.into(),
_ => return None,
}; for _ in0..sample_counts { let start_offset = cur_position; let end_offset = match (stsz.sample_size, sample_size_iter.next()) {
(_, Some(t)) => (start_offset + *t)?,
(t, _) if t > 0 => (start_offset + t)?,
_ => 0.into(),
}; if end_offset == 0 { return None;
}
cur_position = end_offset;
// Mark the sync sample in sample_table according to 'stss'. iflet Some(ref v) = track.stss { for iter in &v.samples { match iter
.checked_sub(&1)
.and_then(|idx| sample_table.get_mut(idx as usize))
{
Some(elem) => elem.sync = true,
_ => return None,
}
}
}
let ctts_iter = track.ctts.as_ref().map(|v| v.samples.as_slice().iter());
// sum_delta is the sum of stts_iter delta. // According to spec: // decode time => DT(n) = DT(n-1) + STTS(n) // composition time => CT(n) = DT(n) + CTTS(n) // Note: // composition time needs to add the track offset time from 'elst' table. letmut sum_delta = TrackScaledTime::<i64>(0, track.id); for sample in sample_table.as_mut_slice() { let decode_time = sum_delta;
sum_delta = (sum_delta + stts_iter.next_delta())?;
// ctts_offset is the current sample offset time. let ctts_offset = ctts_offset_iter.next_offset_time();
let start_composition = decode_time + ctts_offset;
// Correct composition end time due to 'ctts' causes composition time re-ordering. // // Composition end time is not in specification. However, gecko needs it, so we need to // calculate to correct the composition end time. if !sample_table.is_empty() { // Create an index table refers to sample_table and sorted by start_composisiton time. letmut sort_table = TryVec::with_capacity(sample_table.len()).ok()?;
for i in0..sample_table.len() {
sort_table.push(i).ok()?;
}
struct SampleToChunkIterator<'a> {
chunks: std::ops::Range<u32>,
sample_count: u32,
stsc_peek_iter: std::iter::Peekable<std::slice::Iter<'a, SampleToChunk>>,
remain_chunk_count: u32, // total chunk number from 'stco'.
}
impl<'a> Iterator for SampleToChunkIterator<'a> { type Item = (u32, u32);
fn next(&mutself) -> Option<(u32, u32)> { let has_chunk = self.chunks.next().or_else(|| { self.chunks = self.locate(); self.remain_chunk_count
.checked_sub( self.chunks
.len()
.try_into()
.expect("len() of a Range<u32> must fit in u32"),
)
.and_then(|res| { self.remain_chunk_count = res; self.chunks.next()
})
});
has_chunk.map(|id| (id, self.sample_count))
}
}
impl<'a> SampleToChunkIterator<'a> { #[allow(clippy::reversed_empty_ranges)] fn locate(&mutself) -> std::ops::Range<u32> { loop { returnmatch (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) {
(Some(next), Some(peek)) if next.first_chunk == peek.first_chunk => { // Invalid entry, skip it and will continue searching at // next loop iteration. continue;
}
(Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => { self.sample_count = next.samples_per_chunk;
(next.first_chunk - 1)..(peek.first_chunk - 1)
}
(Some(next), None) if next.first_chunk > 0 => { self.sample_count = next.samples_per_chunk; // Total chunk number in 'stsc' could be different to 'stco', // there could be more chunks at the last 'stsc' record. match next.first_chunk.checked_add(self.remain_chunk_count) {
Some(r) => (next.first_chunk - 1)..r - 1,
_ => 0..0,
}
}
_ => 0..0,
};
}
}
}
/// Calculate numerator * scale / denominator, if possible. /// /// Applying the associativity of integer arithmetic, we divide first /// and add the remainder after multiplying each term separately /// to preserve precision while leaving more headroom. That is, /// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d. /// /// Return None on overflow or if the denominator is zero. pubfn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T> where
T: PrimInt + Zero,
S: PrimInt,
{ if denominator.is_zero() { return None;
}
let integer = numerator / denominator; let remainder = numerator % denominator;
num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) {
Some(integer) => remainder
.checked_mul(&s)
.and_then(|remainder| (remainder / denominator).checked_add(&integer)),
None => None,
})
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.