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 = ((bits & ((1 << 52) - 1)) | (1 << 52)) as u128;
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        self.num as f64 / self.den as f64
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 = (max as u64 * self.den as u64 / self.num as u64).max(1) as u128;
66        loop {
67            let (num, den) = limit_denominator(self.num as u128, self.den as u128, bound);
68            if num <= max as u128 || bound == 1 {
69                return Self {
70                    num: num.min(max as u128) as u32,
71                    den: den as u32,
72                };
73            }
74            bound -= 1;
75        }
76    }
77}
78
79/// The fraction nearest `num / den` whose denominator is at most `max_den`, in lowest terms:
80/// the last continued-fraction convergent within the bound, or the semiconvergent past it where
81/// that lies nearer.
82fn limit_denominator(num: u128, den: u128, max_den: u128) -> (u128, u128) {
83    let g = gcd(num, den);
84    let (num, den) = (num / g, den / g);
85    if den <= max_den {
86        return (num, den);
87    }
88    let (mut p0, mut q0, mut p1, mut q1) = (0u128, 1u128, 1u128, 0u128);
89    let (mut n, mut d) = (num, den);
90    loop {
91        let a = n / d;
92        let q2 = q0 + a * q1;
93        if q2 > max_den {
94            break;
95        }
96        (p0, q0, p1, q1) = (p1, q1, p0 + a * p1, q2);
97        (n, d) = (d, n - a * d);
98    }
99    let k = (max_den - q0) / q1;
100    if 2 * d * (q0 + k * q1) <= den {
101        (p1, q1)
102    } else {
103        (p0 + k * p1, q0 + k * q1)
104    }
105}
106
107fn gcd(mut a: u128, mut b: u128) -> u128 {
108    while b != 0 {
109        (a, b) = (b, a % b);
110    }
111    a.max(1)
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    fn rate(num: u32, den: u32) -> FrameRate {
119        FrameRate { num, den }
120    }
121
122    /// Every rate a client's display runs at comes back as the fraction it was sent as, whether
123    /// the page computed it as `N * 1000 / 1001` or snapped it to a whole number.
124    #[test]
125    fn a_rate_carried_as_its_fraction_reads_back_exactly() {
126        for n in [24, 25, 30, 48, 50, 60, 72, 75, 90, 100, 120, 144, 165, 240] {
127            assert_eq!(FrameRate::of(n as f64), rate(n, 1));
128            let g = gcd(n as u128 * 1000, 1001) as u32;
129            assert_eq!(
130                FrameRate::of(n as f64 * 1000.0 / 1001.0),
131                rate(n * 1000 / g, 1001 / g),
132                "{n}000/1001"
133            );
134        }
135        assert_eq!(FrameRate::of(60000.0 / 1001.0).fps(), 60000.0 / 1001.0);
136    }
137
138    /// A rate written as a decimal names its own fraction; the NTSC rates spelled that way lie
139    /// within a millionth of the NTSC fraction.
140    #[test]
141    fn a_decimal_rate_reads_as_its_decimal_fraction() {
142        assert_eq!(FrameRate::of(59.94), rate(2997, 50));
143        assert_eq!(FrameRate::of(119.88), rate(2997, 25));
144        assert_eq!(FrameRate::of(143.856), rate(17982, 125));
145        assert_eq!(FrameRate::of(23.976), rate(2997, 125));
146        assert_eq!(FrameRate::of(29.97), rate(2997, 100));
147        assert_eq!(FrameRate::of(12.5), rate(25, 2));
148        for (decimal, n) in [(59.94, 60), (119.88, 120), (143.856, 144)] {
149            let ntsc = n as f64 * 1000.0 / 1001.0;
150            assert!((FrameRate::of(decimal).fps() - ntsc).abs() / ntsc < 1.1e-6);
151        }
152    }
153
154    /// A rate no small fraction names -- a measured display, a rate scaled by a factor -- reads
155    /// as the nearest fraction in reach, within a thousandth of a frame per second, and one just
156    /// off an NTSC fraction lands on it.
157    #[test]
158    fn any_other_rate_reads_within_a_thousandth() {
159        for fps in [
160            53.28900001,
161            59.9512345,
162            143.98123,
163            7.3,
164            999.9999,
165            1.0000001,
166            164.987654321,
167            60.0005,
168            1.0004999,
169        ] {
170            let r = FrameRate::of(fps);
171            assert!(r.den <= 1001, "{fps}: {r:?}");
172            assert!((r.fps() - fps).abs() < 1e-3, "{fps}: {r:?} = {}", r.fps());
173        }
174        assert_eq!(FrameRate::of(59.9400599), rate(60000, 1001));
175        assert_eq!(FrameRate::of(119.880120), rate(120000, 1001));
176    }
177
178    #[test]
179    fn a_rate_outside_the_span_reads_at_its_edge() {
180        assert_eq!(FrameRate::of(0.0), rate(1, 1));
181        assert_eq!(FrameRate::of(-5.0), rate(1, 1));
182        assert_eq!(FrameRate::of(f64::NAN), rate(1, 1));
183        assert_eq!(FrameRate::of(f64::INFINITY), rate(1000, 1));
184        assert_eq!(FrameRate::of(5000.0), rate(1000, 1));
185    }
186
187    /// A level admits the whole frames per second at or above the rate.
188    #[test]
189    fn a_level_is_sized_for_the_rate_rounded_up() {
190        assert_eq!(rate(60000, 1001).ceil(), 60);
191        assert_eq!(rate(144000, 1001).ceil(), 144);
192        assert_eq!(rate(60, 1).ceil(), 60);
193        assert_eq!(rate(2997, 50).ceil(), 60);
194    }
195
196    /// A field of 16 bits takes the NTSC rates whose terms fit it as they are, and the others
197    /// as the nearest fraction that fits, within a tenth of a millionth.
198    #[test]
199    fn a_narrow_field_takes_the_nearest_fraction_that_fits() {
200        assert_eq!(rate(60000, 1001).within(0xffff), rate(60000, 1001));
201        assert_eq!(rate(60, 1).within(0xffff), rate(60, 1));
202        for n in [120, 144, 240] {
203            let exact = rate(n * 1000, 1001);
204            let fit = exact.within(0xffff);
205            assert!(fit.num <= 0xffff && fit.den <= 0xffff, "{fit:?}");
206            assert!(
207                (fit.fps() - exact.fps()).abs() / exact.fps() < 1e-7,
208                "{n}: {fit:?}"
209            );
210        }
211        assert_eq!(rate(1000, 1).within(0xffff), rate(1000, 1));
212    }
213}