Media: tests for the averaged MP4 frame rate (#1442)

This commit is contained in:
tylerjenningsw
2026-10-14 11:33:49 -04:00
parent eb95f732e5
commit 36a905293b
+45
View File
@@ -306,3 +306,48 @@ fn averaged_rate(timescale: u32, durations: impl Iterator<Item = u32>) -> Option
.map(|(r, _)| r)
}
#[cfg(test)]
mod tests {
use super::*;
/// Durations (in 1/`timescale` s) of `n` frames at `rate` whose times are rounded to the timescale.
fn rounded(rate: FrameRate, timescale: i64, n: i64) -> Vec<u32> {
let at = |k: i64| (2 * k * timescale * rate.den + rate.num) / (2 * rate.num);
(0..n).map(|k| (at(k + 1) - at(k)) as u32).collect()
}
#[test]
fn millisecond_rounded_frame_times_average_to_their_rate() {
assert_eq!(rounded(FrameRate::FPS_30, 1000, 6), [33, 34, 33, 33, 34, 33]);
assert_eq!(averaged_rate(1000, rounded(FrameRate::FPS_30, 1000, 120).into_iter()), Some(FrameRate::FPS_30));
assert_eq!(averaged_rate(1000, rounded(FrameRate::FPS_60, 1000, 240).into_iter()), Some(FrameRate::FPS_60));
// NTSC rates stay NTSC, not the round rate next to them.
for r in [FrameRate::FPS_23_976, FrameRate::FPS_29_97, FrameRate::FPS_59_94] {
assert_eq!(averaged_rate(1000, rounded(r, 1000, 240).into_iter()), Some(r), "{r:?}");
}
// A short clip: its few frames average to the rate as well.
assert_eq!(averaged_rate(1000, [33, 34, 33].into_iter()), Some(FrameRate::FPS_30));
}
#[test]
fn exact_and_variable_rates_keep_the_readers_estimate() {
// A median that is a standard rate already reads right.
assert_eq!(averaged_rate(30000, rounded(FrameRate::FPS_29_97, 30000, 240).into_iter()), None);
assert_eq!(averaged_rate(90000, [3000; 10].into_iter()), None);
assert_eq!(averaged_rate(600, [20, 20, 19, 21, 20].into_iter()), None);
// Durations averaging to no standard rate (22.2 fps).
assert_eq!(averaged_rate(1000, [40, 45, 50].repeat(20).into_iter()), None);
// A truly 30.3 fps clip is not pulled to 30 (1 % off).
assert_eq!(averaged_rate(1000, [33; 30].into_iter()), None);
}
#[test]
fn degenerate_durations_have_no_rate() {
assert_eq!(averaged_rate(1000, std::iter::empty()), None);
assert_eq!(averaged_rate(1000, [0, 0, 0].into_iter()), None);
assert_eq!(averaged_rate(0, [33, 34, 33].into_iter()), None);
assert_eq!(averaged_rate(u32::MAX, [u32::MAX, u32::MAX - 1, 1, 0].into_iter()), None);
assert_eq!(averaged_rate(u32::MAX, [u32::MAX; 300].into_iter()), None);
assert_eq!(video_rate(b"not a movie"), None);
}
}