Skip to main content

pixelflux/encoders/
frame_rate.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7//! A frame rate as the fraction of whole numbers that encoder parameters and bitstream timing
8//! take, read from the `f64` a capture carries.
9//!
10//! A caller names a rate by its fraction, carried as the `f64` of that fraction: the NTSC rates
11//! are `N * 1000 / 1001` (59.94 is 60000/1001), which no whole number or decimal names. The
12//! fraction nearest the `f64` with a denominator up to `MAX_DENOMINATOR` is that fraction
13//! exactly, since any other in reach lies at least a millionth of a frame per second away, far
14//! past an `f64`'s error. A rate written with up to three decimals reads as its own decimal
15//! fraction, and any other rate as the nearest fraction in reach, within a thousandth of a frame
16//! per second.
17
18/// The largest denominator a rate is read with: 1001 holds every NTSC rate and every rate
19/// written with up to three decimals.
20const MAX_DENOMINATOR: u128 = 1001;
21
22/// The span of rates a capture runs at, in frames per second.
23const MIN_FPS: f64 = 1.0;
24const MAX_FPS: f64 = 1000.0;
25
26/// A frame rate of `num / den` frames per second, in lowest terms.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct FrameRate {
29    pub num: u32,
30    pub den: u32,
31}
32
33impl FrameRate {
34    /// The fraction `fps` names (see the module docs), within one to 1000 frames per second; a
35    /// rate that is not a number reads as one.
36    pub fn of(fps: f64) -> Self {
37        let fps = if fps.is_nan() {
38            MIN_FPS
39        } else {
40            fps.clamp(MIN_FPS, MAX_FPS)
41        };
42        let bits = fps.to_bits();
43        let mantissa = u128::from((bits & ((1 << 52) - 1)) | (1 << 52));
44        let shift = 1075 - ((bits >> 52) & 0x7ff) as i32;
45        let (num, den) = limit_denominator(mantissa, 1 << shift, MAX_DENOMINATOR);
46        Self {
47            num: num as u32,
48            den: den as u32,
49        }
50    }
51
52    /// The rate in frames per second.
53    pub fn fps(self) -> f64 {
54        f64::from(self.num) / f64::from(self.den)
55    }
56
57    /// The whole frames per second a level has to admit: the rate, rounded up.
58    pub fn ceil(self) -> u32 {
59        self.num.div_ceil(self.den)
60    }
61
62    /// The fraction nearest the rate whose terms both fit `max`, for a field narrower than 32
63    /// bits (VA-API packs each into 16).
64    pub fn within(self, max: u32) -> Self {
65        let mut bound =
66            u128::from((u64::from(max) * u64::from(self.den) / u64::from(self.num)).max(1));
67        loop {
68            let (num, den) = limit_denominator(u128::from(self.num), u128::from(self.den), bound);
69            if num <= u128::from(max) || bound == 1 {
70                return Self {
71                    num: num.min(u128::from(max)) as u32,
72                    den: den as u32,
73                };
74            }
75            bound -= 1;
76        }
77    }
78}
79
80/// The fraction nearest `num / den` whose denominator is at most `max_den`, in lowest terms:
81/// the last continued-fraction convergent within the bound, or the semiconvergent past it where
82/// that lies nearer.
83fn limit_denominator(num: u128, den: u128, max_den: u128) -> (u128, u128) {
84    let g = gcd(num, den);
85    let (num, den) = (num / g, den / g);
86    if den <= max_den {
87        return (num, den);
88    }
89    let (mut p0, mut q0, mut p1, mut q1) = (0u128, 1u128, 1u128, 0u128);
90    let (mut n, mut d) = (num, den);
91    loop {
92        let a = n / d;
93        let q2 = q0 + a * q1;
94        if q2 > max_den {
95            break;
96        }
97        (p0, q0, p1, q1) = (p1, q1, p0 + a * p1, q2);
98        (n, d) = (d, n - a * d);
99    }
100    let k = (max_den - q0) / q1;
101    if 2 * d * (q0 + k * q1) <= den {
102        (p1, q1)
103    } else {
104        (p0 + k * p1, q0 + k * q1)
105    }
106}
107
108fn gcd(mut a: u128, mut b: u128) -> u128 {
109    while b != 0 {
110        (a, b) = (b, a % b);
111    }
112    a.max(1)
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    fn rate(num: u32, den: u32) -> FrameRate {
120        FrameRate { num, den }
121    }
122
123    /// Every rate a client's display runs at comes back as the fraction it was sent as, whether
124    /// the page computed it as `N * 1000 / 1001` or snapped it to a whole number.
125    #[test]
126    fn a_rate_carried_as_its_fraction_reads_back_exactly() {
127        for n in [24, 25, 30, 48, 50, 60, 72, 75, 90, 100, 120, 144, 165, 240] {
128            assert_eq!(FrameRate::of(f64::from(n)), rate(n, 1));
129            let g = gcd(u128::from(n) * 1000, 1001) as u32;
130            assert_eq!(
131                FrameRate::of(f64::from(n) * 1000.0 / 1001.0),
132                rate(n * 1000 / g, 1001 / g),
133                "{n}000/1001"
134            );
135        }
136        assert_eq!(FrameRate::of(60000.0 / 1001.0).fps(), 60000.0 / 1001.0);
137    }
138
139    /// A rate written as a decimal names its own fraction; the NTSC rates spelled that way lie
140    /// within a millionth of the NTSC fraction.
141    #[test]
142    fn a_decimal_rate_reads_as_its_decimal_fraction() {
143        assert_eq!(FrameRate::of(59.94), rate(2997, 50));
144        assert_eq!(FrameRate::of(119.88), rate(2997, 25));
145        assert_eq!(FrameRate::of(143.856), rate(17982, 125));
146        assert_eq!(FrameRate::of(23.976), rate(2997, 125));
147        assert_eq!(FrameRate::of(29.97), rate(2997, 100));
148        assert_eq!(FrameRate::of(12.5), rate(25, 2));
149        for (decimal, n) in [(59.94, 60), (119.88, 120), (143.856, 144)] {
150            let ntsc = f64::from(n) * 1000.0 / 1001.0;
151            assert!((FrameRate::of(decimal).fps() - ntsc).abs() / ntsc < 1.1e-6);
152        }
153    }
154
155    /// A rate no small fraction names -- a measured display, a rate scaled by a factor -- reads
156    /// as the nearest fraction in reach, within a thousandth of a frame per second, and one just
157    /// off an NTSC fraction lands on it.
158    #[test]
159    fn any_other_rate_reads_within_a_thousandth() {
160        for fps in [
161            53.28900001,
162            59.9512345,
163            143.98123,
164            7.3,
165            999.9999,
166            1.0000001,
167            164.987654321,
168            60.0005,
169            1.0004999,
170        ] {
171            let r = FrameRate::of(fps);
172            assert!(r.den <= 1001, "{fps}: {r:?}");
173            assert!((r.fps() - fps).abs() < 1e-3, "{fps}: {r:?} = {}", r.fps());
174        }
175        assert_eq!(FrameRate::of(59.9400599), rate(60000, 1001));
176        assert_eq!(FrameRate::of(119.880120), rate(120000, 1001));
177    }
178
179    #[test]
180    fn a_rate_outside_the_span_reads_at_its_edge() {
181        assert_eq!(FrameRate::of(0.0), rate(1, 1));
182        assert_eq!(FrameRate::of(-5.0), rate(1, 1));
183        assert_eq!(FrameRate::of(f64::NAN), rate(1, 1));
184        assert_eq!(FrameRate::of(f64::INFINITY), rate(1000, 1));
185        assert_eq!(FrameRate::of(5000.0), rate(1000, 1));
186    }
187
188    /// A level admits the whole frames per second at or above the rate.
189    #[test]
190    fn a_level_is_sized_for_the_rate_rounded_up() {
191        assert_eq!(rate(60000, 1001).ceil(), 60);
192        assert_eq!(rate(144000, 1001).ceil(), 144);
193        assert_eq!(rate(60, 1).ceil(), 60);
194        assert_eq!(rate(2997, 50).ceil(), 60);
195    }
196
197    /// A field of 16 bits takes the NTSC rates whose terms fit it as they are, and the others
198    /// as the nearest fraction that fits, within a tenth of a millionth.
199    #[test]
200    fn a_narrow_field_takes_the_nearest_fraction_that_fits() {
201        assert_eq!(rate(60000, 1001).within(0xffff), rate(60000, 1001));
202        assert_eq!(rate(60, 1).within(0xffff), rate(60, 1));
203        for n in [120, 144, 240] {
204            let exact = rate(n * 1000, 1001);
205            let fit = exact.within(0xffff);
206            assert!(fit.num <= 0xffff && fit.den <= 0xffff, "{fit:?}");
207            assert!(
208                (fit.fps() - exact.fps()).abs() / exact.fps() < 1e-7,
209                "{n}: {fit:?}"
210            );
211        }
212        assert_eq!(rate(1000, 1).within(0xffff), rate(1000, 1));
213    }
214}