Properly convert the f64 framerate to a fraction

This commit is contained in:
Sebastian Dröge 2016-12-05 19:47:10 +02:00
parent f9cd9e128d
commit cc183ea92e
2 changed files with 116 additions and 3 deletions

View file

@ -26,6 +26,7 @@ use error::*;
use rsdemuxer::*;
use buffer::*;
use adapter::*;
use utils;
const AUDIO_STREAM_ID: u32 = 0;
const VIDEO_STREAM_ID: u32 = 1;
@ -354,9 +355,10 @@ impl Metadata {
("height", &flavors::ScriptDataValue::Number(height)) => {
metadata.video_height = Some(height as u32);
}
("framerate", &flavors::ScriptDataValue::Number(framerate)) => {
// FIXME: Convert properly to a fraction
metadata.video_framerate = Some((framerate as u32, 1));
("framerate", &flavors::ScriptDataValue::Number(framerate)) if framerate >= 0.0 => {
if let Some((n, d)) = utils::f64_to_fraction(framerate) {
metadata.video_framerate = Some((n as u32, d as u32));
}
}
("AspectRatioX", &flavors::ScriptDataValue::Number(par_x)) => {
par_n = Some(par_x as u32);

View file

@ -18,6 +18,7 @@
//
use libc::c_char;
use std::ffi::CString;
use std::i32;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -51,3 +52,113 @@ impl GBoolean {
pub unsafe extern "C" fn cstring_drop(ptr: *mut c_char) {
let _ = CString::from_raw(ptr);
}
pub fn f64_to_fraction(val: f64) -> Option<(i32, i32)> {
// Continued fractions algorithm
// http://mathforum.org/dr.math/faq/faq.fractions.html#decfrac
let negative = val < 0.0;
let mut q = val.abs();
let mut n0 = 0;
let mut d0 = 1;
let mut n1 = 1;
let mut d1 = 0;
const MAX_ITERATIONS: usize = 30;
const MAX_ERROR: f64 = 1.0e-20;
// 1/EPSILON > i32::MAX
const EPSILON: f64 = 1.0e-10;
// Overflow
if q > i32::MAX as f64 {
return None;
}
for _ in 0..MAX_ITERATIONS {
let a = q as u32;
let f = q - (a as f64);
// Prevent overflow
if a != 0 &&
(n1 > (i32::MAX as u32) / a || d1 > (i32::MAX as u32) / a ||
a * n1 > (i32::MAX as u32) - n0 || a * d1 > (i32::MAX as u32) - d0) {
break;
}
let n = a * n1 + n0;
let d = a * d1 + d0;
n0 = n1;
d0 = d1;
n1 = n;
d1 = d;
// Prevent division by ~0
if f < EPSILON {
break;
}
let r = 1.0 / f;
// Simplify fraction. Doing so here instead of at the end
// allows us to get closer to the target value without overflows
let g = gcd(n1, d1);
if g != 0 {
n1 /= g;
d1 /= g;
}
// Close enough?
if ((n as f64) / (d as f64) - val).abs() < MAX_ERROR {
break;
}
q = r;
}
// Guaranteed by the overflow check
assert!(n1 <= i32::MAX as u32);
assert!(d1 <= i32::MAX as u32);
// Overflow
if d1 == 0 {
return None;
}
// Make negative again if needed
if negative {
Some((-(n1 as i32), d1 as i32))
} else {
Some((n1 as i32, d1 as i32))
}
}
pub fn gcd(mut a: u32, mut b: u32) -> u32 {
// Euclid's algorithm
while b != 0 {
let tmp = a;
a = b;
b = tmp % b;
}
return a;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gcd() {
assert_eq!(gcd(2 * 2 * 2 * 2, 2 * 2 * 2 * 3), 2 * 2 * 2);
assert_eq!(gcd(2 * 3 * 5 * 5 * 7, 2 * 5 * 7), 2 * 5 * 7);
}
#[test]
fn test_f64_to_fraction() {
assert_eq!(f64_to_fraction(2.0), Some((2, 1)));
assert_eq!(f64_to_fraction(2.5), Some((5, 2)));
assert_eq!(f64_to_fraction(0.127659574), Some((29013539, 227272723)));
assert_eq!(f64_to_fraction(29.97), Some((2997, 100)));
}
}