security(codecs): avoid quadratic MPEG timestamp lookup (#2883)

This commit is contained in:
Earl Co
2026-10-23 10:52:03 -04:00
committed by GitHub
parent 19ff23cba3
commit 8fa72cf18d
+30 -9
View File
@@ -369,15 +369,7 @@ impl MpegSource {
let raw: Vec<Option<i64>> = v.order.iter().map(|&i| units[v.units[i]].pts).collect();
let rate = self.info.frame_rate();
let dur = |k: i64| (k as i128 * 90_000 * rate.den as i128 / rate.num.max(1) as i128) as i64;
let known: Vec<usize> = (0..raw.len()).filter(|&i| raw[i].is_some()).collect();
(0..raw.len())
.map(|i| {
raw[i].or_else(|| {
let j = known.iter().rev().find(|&&j| j < i).or_else(|| known.iter().find(|&&j| j > i))?;
Some(raw[*j]? + dur(i as i64 - *j as i64))
})
})
.collect()
interpolate_pts(&raw, dur)
}
fn read_unit(&self, v: &VideoTrack, i: usize) -> crate::Result<Vec<u8>> {
@@ -690,6 +682,23 @@ impl MpegSource {
}
}
/// Fill missing timestamps from their closest preceding timestamp, or the next one when there is
/// no predecessor. The known positions are sorted, so binary search avoids rescanning them for
/// every missing frame.
fn interpolate_pts(raw: &[Option<i64>], dur: impl Fn(i64) -> i64) -> Vec<Option<i64>> {
let known: Vec<usize> = (0..raw.len()).filter(|&i| raw[i].is_some()).collect();
raw.iter()
.enumerate()
.map(|(i, &pts)| {
pts.or_else(|| {
let next = known.partition_point(|&j| j < i);
let j = next.checked_sub(1).or_else(|| known.get(next).map(|_| next))?;
Some(raw[known[j]]? + dur(i as i64 - known[j] as i64))
})
})
.collect()
}
fn chroma_name(c: filmcraft_frame::Chroma) -> &'static str {
match c {
filmcraft_frame::Chroma::C420 => "4:2:0",
@@ -1015,4 +1024,16 @@ mod tests {
assert_eq!(order, vec![0, 4, 5, 3]);
assert_eq!(pts, vec![0, -2, -3, 3, 1, 2]);
}
#[test]
fn timestamp_interpolation_handles_long_missing_runs_without_rescanning() {
let mut raw = vec![None; 10_000];
raw[4_000] = Some(40_000);
raw[9_999] = Some(99_990);
let pts = interpolate_pts(&raw, |frames| frames * 10);
assert_eq!(pts[0], Some(0));
assert_eq!(pts[3_999], Some(39_990));
assert_eq!(pts[4_001], Some(40_010));
assert_eq!(pts[9_998], Some(99_980));
}
}