--- a/media/libstagefright/binding/mp4parse/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse/src/lib.rs
@@ -5,21 +5,23 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
#![cfg_attr(feature = "fuzz", feature(plugin))]
#![cfg_attr(feature = "fuzz", plugin(afl_plugin))]
#[cfg(feature = "fuzz")]
extern crate afl;
extern crate byteorder;
extern crate bitreader;
+extern crate num_traits;
use byteorder::{ReadBytesExt, WriteBytesExt};
use bitreader::{BitReader, ReadInto};
use std::io::{Read, Take};
use std::io::Cursor;
use std::cmp;
+use num_traits::Num;
mod boxes;
use boxes::{BoxType, FourCC};
// Unit tests.
#[cfg(test)]
mod tests;
@@ -113,24 +115,38 @@ struct FileTypeBox {
/// Movie header box 'mvhd'.
#[derive(Debug)]
struct MovieHeaderBox {
pub timescale: u32,
duration: u64,
}
+#[derive(Debug, Clone, Copy)]
+pub struct Matrix {
+ pub a: i32, // 16.16 fix point
+ pub b: i32, // 16.16 fix point
+ pub u: i32, // 2.30 fix point
+ pub c: i32, // 16.16 fix point
+ pub d: i32, // 16.16 fix point
+ pub v: i32, // 2.30 fix point
+ pub x: i32, // 16.16 fix point
+ pub y: i32, // 16.16 fix point
+ pub w: i32, // 2.30 fix point
+}
+
/// Track header box 'tkhd'
#[derive(Debug, Clone)]
pub struct TrackHeaderBox {
track_id: u32,
pub disabled: bool,
pub duration: u64,
pub width: u32,
pub height: u32,
+ pub matrix: Matrix,
}
/// Edit list box 'elst'
#[derive(Debug)]
struct EditListBox {
edits: Vec<Edit>,
}
@@ -401,22 +417,30 @@ pub struct MediaTimeScale(pub u64);
/// A time to be scaled by the media's global (mvhd) timescale.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct MediaScaledTime(pub u64);
/// The track's local (mdhd) timescale.
/// Members are timescale units per second and the track id.
#[derive(Debug, Copy, Clone, PartialEq)]
-pub struct TrackTimeScale(pub u64, pub usize);
+pub struct TrackTimeScale<T: Num>(pub T, pub usize);
/// A time to be scaled by the track's local (mdhd) timescale.
/// Members are time in scale units and the track id.
#[derive(Debug, Copy, Clone, PartialEq)]
-pub struct TrackScaledTime(pub u64, pub usize);
+pub struct TrackScaledTime<T: Num>(pub T, pub usize);
+
+impl <T> std::ops::Add for TrackScaledTime<T> where T: Num {
+ type Output = TrackScaledTime<T>;
+
+ fn add(self, other: TrackScaledTime<T>) -> TrackScaledTime<T> {
+ TrackScaledTime::<T>(self.0 + other.0, self.1)
+ }
+}
/// A fragmented file contains no sample data in stts, stsc, and stco.
#[derive(Debug, Default)]
pub struct EmptySampleTableBoxes {
// TODO: Track has stts, stsc and stco, this structure can be discarded.
pub empty_stts: bool,
pub empty_stsc: bool,
pub empty_stco: bool,
@@ -426,22 +450,22 @@ pub struct EmptySampleTableBoxes {
impl EmptySampleTableBoxes {
pub fn all_empty(&self) -> bool {
self.empty_stts & self.empty_stsc & self.empty_stco
}
}
#[derive(Debug, Default)]
pub struct Track {
- id: usize,
+ pub id: usize,
pub track_type: TrackType,
pub empty_duration: Option<MediaScaledTime>,
- pub media_time: Option<TrackScaledTime>,
- pub timescale: Option<TrackTimeScale>,
- pub duration: Option<TrackScaledTime>,
+ pub media_time: Option<TrackScaledTime<u64>>,
+ pub timescale: Option<TrackTimeScale<u64>>,
+ pub duration: Option<TrackScaledTime<u64>>,
pub track_id: Option<u32>,
pub codec_type: CodecType,
pub empty_sample_boxes: EmptySampleTableBoxes,
pub data: Option<SampleEntry>,
pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
pub stts: Option<TimeToSampleBox>,
pub stsc: Option<SampleToChunkBox>,
pub stsz: Option<SampleSizeBox>,
@@ -788,37 +812,37 @@ fn read_edts<T: Read>(f: &mut BMFFBox<T>
return Err(Error::InvalidData("expected additional edit"));
}
idx += 1;
}
track.empty_duration = Some(MediaScaledTime(empty_duration));
if elst.edits[idx].media_time < 0 {
return Err(Error::InvalidData("unexpected negative media time in edit"));
}
- track.media_time = Some(TrackScaledTime(elst.edits[idx].media_time as u64,
+ track.media_time = Some(TrackScaledTime::<u64>(elst.edits[idx].media_time as u64,
track.id));
log!("{:?}", elst);
}
_ => skip_box_content(&mut b)?,
};
check_parser_state!(b.content);
}
Ok(())
}
-fn parse_mdhd<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<(MediaHeaderBox, Option<TrackScaledTime>, Option<TrackTimeScale>)> {
+fn parse_mdhd<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<(MediaHeaderBox, Option<TrackScaledTime<u64>>, Option<TrackTimeScale<u64>>)> {
let mdhd = read_mdhd(f)?;
let duration = match mdhd.duration {
std::u64::MAX => None,
- duration => Some(TrackScaledTime(duration, track.id)),
+ duration => Some(TrackScaledTime::<u64>(duration, track.id)),
};
if mdhd.timescale == 0 {
return Err(Error::InvalidData("zero timescale in mdhd"));
}
- let timescale = Some(TrackTimeScale(mdhd.timescale as u64, track.id));
+ let timescale = Some(TrackTimeScale::<u64>(mdhd.timescale as u64, track.id));
Ok((mdhd, duration, timescale))
}
fn read_mdia<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
let mut iter = f.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::MediaHeaderBox => {
@@ -984,25 +1008,33 @@ fn read_tkhd<T: Read>(src: &mut BMFFBox<
let track_id = be_u32(src)?;
skip(src, 4)?;
let duration = match version {
1 => be_u64(src)?,
0 => be_u32(src)? as u64,
_ => return Err(Error::InvalidData("unhandled tkhd version")),
};
// Skip uninteresting fields.
- skip(src, 52)?;
+ skip(src, 16)?;
+
+ let matrix = Matrix{
+ a: be_i32(src)?, b: be_i32(src)?, u: be_i32(src)?,
+ c: be_i32(src)?, d: be_i32(src)?, v: be_i32(src)?,
+ x: be_i32(src)?, y: be_i32(src)?, w: be_i32(src)?,
+ };
+
let width = be_u32(src)?;
let height = be_u32(src)?;
Ok(TrackHeaderBox {
track_id: track_id,
disabled: disabled,
duration: duration,
width: width,
height: height,
+ matrix: matrix,
})
}
/// Parse a elst box.
fn read_elst<T: Read>(src: &mut BMFFBox<T>) -> Result<EditListBox> {
let (version, _) = read_fullbox_extra(src)?;
let edit_count = be_u32(src)?;
if edit_count == 0 {
@@ -1160,22 +1192,20 @@ fn read_ctts<T: Read>(src: &mut BMFFBox<
if src.bytes_left() < (counts as usize * 8) {
return Err(Error::InvalidData("insufficient data in 'ctts' box"));
}
let mut offsets = Vec::new();
for _ in 0..counts {
let (sample_count, time_offset) = match version {
- 0 => {
- let count = be_u32(src)?;
- let offset = TimeOffsetVersion::Version0(be_u32(src)?);
- (count, offset)
- },
- 1 => {
+ // According to spec, Version0 shoule be used when version == 0;
+ // however, some buggy contents have negative value when version == 0.
+ // So we always use Version1 here.
+ 0...1 => {
let count = be_u32(src)?;
let offset = TimeOffsetVersion::Version1(be_i32(src)?);
(count, offset)
},
_ => {
return Err(Error::InvalidData("unsupported version in 'ctts' box"));
}
};
--- a/media/libstagefright/binding/mp4parse_capi/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
@@ -31,20 +31,22 @@
//! ```
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
extern crate mp4parse;
extern crate byteorder;
+extern crate num_traits;
use std::io::Read;
use std::collections::HashMap;
use byteorder::WriteBytesExt;
+use num_traits::{PrimInt, Zero};
// Symbols we need from our rust api.
use mp4parse::MediaContext;
use mp4parse::TrackType;
use mp4parse::read_mp4;
use mp4parse::Error;
use mp4parse::SampleEntry;
use mp4parse::AudioCodecSpecific;
@@ -189,16 +191,17 @@ pub struct mp4parse_track_audio_info {
#[repr(C)]
#[derive(Default)]
pub struct mp4parse_track_video_info {
pub display_width: u32,
pub display_height: u32,
pub image_width: u16,
pub image_height: u16,
+ pub rotation: u16,
pub extra_data: mp4parse_byte_data,
pub protected_data: mp4parse_sinf_info,
}
#[repr(C)]
#[derive(Default)]
pub struct mp4parse_fragment_info {
pub fragment_duration: u64,
@@ -369,38 +372,43 @@ pub unsafe extern fn mp4parse_get_track_
/// Calculate numerator * scale / denominator, if possible.
///
/// Applying the associativity of integer arithmetic, we divide first
/// and add the remainder after multiplying each term separately
/// to preserve precision while leaving more headroom. That is,
/// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d.
///
/// Return None on overflow or if the denominator is zero.
-fn rational_scale(numerator: u64, denominator: u64, scale: u64) -> Option<u64> {
- if denominator == 0 {
+fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T>
+ where T: PrimInt + Zero, S: PrimInt {
+ if denominator.is_zero() {
return None;
}
+
let integer = numerator / denominator;
let remainder = numerator % denominator;
- match integer.checked_mul(scale) {
- Some(integer) => remainder.checked_mul(scale)
- .and_then(|remainder| (remainder/denominator).checked_add(integer)),
- None => None,
- }
+ num_traits::cast(scale2).and_then(|s| {
+ match integer.checked_mul(&s) {
+ Some(integer) => remainder.checked_mul(&s)
+ .and_then(|remainder| (remainder/denominator).checked_add(&integer)),
+ None => None,
+ }
+ })
}
fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<u64> {
let microseconds_per_second = 1000000;
- rational_scale(time.0, scale.0, microseconds_per_second)
+ rational_scale::<u64, u64>(time.0, scale.0, microseconds_per_second)
}
-fn track_time_to_us(time: TrackScaledTime, scale: TrackTimeScale) -> Option<u64> {
+fn track_time_to_us<T>(time: TrackScaledTime<T>, scale: TrackTimeScale<T>) -> Option<T>
+ where T: PrimInt + Zero {
assert_eq!(time.1, scale.1);
let microseconds_per_second = 1000000;
- rational_scale(time.0, scale.0, microseconds_per_second)
+ rational_scale::<T, u64>(time.0, scale.0, microseconds_per_second)
}
/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
#[no_mangle]
pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_error {
if parser.is_null() || info.is_null() || (*parser).poisoned() {
return MP4PARSE_ERROR_BADARG;
}
@@ -621,16 +629,24 @@ pub unsafe extern fn mp4parse_get_track_
let video = match *video {
SampleEntry::Video(ref x) => x,
_ => return MP4PARSE_ERROR_INVALID,
};
if let Some(ref tkhd) = track.tkhd {
(*info).display_width = tkhd.width >> 16; // 16.16 fixed point
(*info).display_height = tkhd.height >> 16; // 16.16 fixed point
+ let matrix = (tkhd.matrix.a >> 16, tkhd.matrix.b >> 16,
+ tkhd.matrix.c >> 16, tkhd.matrix.d >> 16);
+ (*info).rotation = match matrix {
+ ( 0, 1, -1, 0) => 90, // rotate 90 degrees
+ (-1, 0, 0, -1) => 180, // rotate 180 degrees
+ ( 0, -1, 1, 0) => 270, // rotate 270 degrees
+ _ => 0,
+ };
} else {
return MP4PARSE_ERROR_INVALID;
}
(*info).image_width = video.width;
(*info).image_height = video.height;
match video.codec_specific {
VideoCodecSpecific::AVCConfig(ref avc) => {
@@ -718,16 +734,17 @@ pub unsafe extern fn mp4parse_get_indice
// (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ...
//
// For example:
// (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time().
struct TimeOffsetIterator<'a> {
cur_sample_range: std::ops::Range<u32>,
cur_offset: i64,
ctts_iter: Option<std::slice::Iter<'a, mp4parse::TimeOffset>>,
+ track_id: usize,
}
impl<'a> Iterator for TimeOffsetIterator<'a> {
type Item = i64;
fn next(&mut self) -> Option<i64> {
let has_sample = self.cur_sample_range.next()
.or_else(|| {
@@ -756,37 +773,38 @@ impl<'a> Iterator for TimeOffsetIterator
self.cur_sample_range.next()
});
has_sample.and(Some(self.cur_offset))
}
}
impl<'a> TimeOffsetIterator<'a> {
- fn next_offset_time(&mut self) -> i64 {
+ fn next_offset_time(&mut self) -> TrackScaledTime<i64> {
match self.next() {
- Some(v) => v as i64,
- _ => 0,
+ Some(v) => TrackScaledTime::<i64>(v as i64, self.track_id),
+ _ => TrackScaledTime::<i64>(0, self.track_id),
}
}
}
// Convert 'stts' compact table to full table by iterator,
// (sample_count_with_the_same_time, time) => (time, time, time) ... repeats
// sample_count_with_the_same_time.
//
// For example:
// (2, 3000), (1, 2999) to (3000, 3000, 2999).
-struct TimeToSampleIteraor<'a> {
+struct TimeToSampleIterator<'a> {
cur_sample_count: std::ops::Range<u32>,
cur_sample_delta: u32,
stts_iter: std::slice::Iter<'a, mp4parse::Sample>,
+ track_id: usize,
}
-impl<'a> Iterator for TimeToSampleIteraor<'a> {
+impl<'a> Iterator for TimeToSampleIterator<'a> {
type Item = u32;
fn next(&mut self) -> Option<u32> {
let has_sample = self.cur_sample_count.next()
.or_else(|| {
self.cur_sample_count = match self.stts_iter.next() {
Some(v) => {
self.cur_sample_delta = v.sample_delta;
@@ -797,21 +815,21 @@ impl<'a> Iterator for TimeToSampleIterao
self.cur_sample_count.next()
});
has_sample.and(Some(self.cur_sample_delta))
}
}
-impl<'a> TimeToSampleIteraor<'a> {
- fn next_delta(&mut self) -> u32 {
+impl<'a> TimeToSampleIterator<'a> {
+ fn next_delta(&mut self) -> TrackScaledTime<i64> {
match self.next() {
- Some(v) => v,
- _ => 0,
+ Some(v) => TrackScaledTime::<i64>(v as i64, self.track_id),
+ _ => TrackScaledTime::<i64>(0, self.track_id),
}
}
}
// Convert 'stco' compact table to full table by iterator.
// (start_chunk_num, sample_number) => (start_chunk_num, sample_number),
// (start_chunk_num + 1, sample_number),
// (start_chunk_num + 2, sample_number),
@@ -851,43 +869,20 @@ impl<'a> Iterator for SampleToChunkItera
self.remain_chunk_count -= self.chunks.len() as u32;
self.chunks.next()
});
has_chunk.map_or(None, |id| { Some((id, self.sample_count)) })
}
}
-// A helper struct to convert track time to us.
-struct PresentationTime {
- time: i64,
- scale: TrackTimeScale
-}
-
-impl PresentationTime {
- fn new(time: i64, scale: TrackTimeScale) -> PresentationTime {
- PresentationTime {
- time: time,
- scale: scale,
- }
- }
-
- fn to_us(&self) -> i64 {
- let track_time = TrackScaledTime(self.time as u64, self.scale.1);
- match track_time_to_us(track_time, self.scale) {
- Some(v) => v as i64,
- _ => 0,
- }
- }
-}
-
fn create_sample_table(track: &Track, track_offset_time: i64) -> Option<Vec<mp4parse_indice>> {
let timescale = match track.timescale {
- Some(t) => t,
- _ => return None,
+ Some(ref t) => TrackTimeScale::<i64>(t.0 as i64, t.1),
+ _ => TrackTimeScale::<i64>(0, 0),
};
let (stsc, stco, stsz, stts) =
match (&track.stsc, &track.stco, &track.stsz, &track.stts) {
(&Some(ref a), &Some(ref b), &Some(ref c), &Some(ref d)) => (a, b, c, d),
_ => return None,
};
@@ -952,44 +947,64 @@ fn create_sample_table(track: &Track, tr
Some(ref v) => Some(v.samples.as_slice().iter()),
_ => None,
};
let mut ctts_offset_iter = TimeOffsetIterator {
cur_sample_range: (0 .. 0),
cur_offset: 0,
ctts_iter: ctts_iter,
+ track_id: track.id,
};
- let mut stts_iter = TimeToSampleIteraor {
+ let mut stts_iter = TimeToSampleIterator {
cur_sample_count: (0 .. 0),
cur_sample_delta: 0,
stts_iter: stts.samples.as_slice().iter(),
+ track_id: track.id,
};
// sum_delta is the sum of stts_iter delta.
// According to sepc:
// decode time => DT(n) = DT(n-1) + STTS(n)
// composition time => CT(n) = DT(n) + CTTS(n)
// Note:
// composition time needs to add the track offset time from 'elst' table.
- let mut sum_delta = PresentationTime::new(0, timescale);
+ let mut sum_delta = TrackScaledTime::<i64>(0, track.id);
for sample in sample_table.as_mut_slice() {
- let decode_time = sum_delta.to_us();
- sum_delta.time += stts_iter.next_delta() as i64;
+ let decode_time = sum_delta;
+ sum_delta = sum_delta + stts_iter.next_delta();
// ctts_offset is the current sample offset time.
- let ctts_offset = PresentationTime::new(ctts_offset_iter.next_offset_time(), timescale);
+ let ctts_offset = ctts_offset_iter.next_offset_time();
+
+ // ctts_offset could be negative but (decode_time + ctts_offset) should always be positive
+ // value.
+ let start_composition = track_time_to_us(decode_time + ctts_offset, timescale).and_then(|t| {
+ if t < 0 { return None; }
+ Some(t)
+ });
- let start_composition = decode_time + ctts_offset.to_us() + track_offset_time;
- let end_composition = sum_delta.to_us() + ctts_offset.to_us() + track_offset_time;
+ // ctts_offset could be negative but (sum_delta + ctts_offset) should always be positive
+ // value.
+ let end_composition = track_time_to_us(sum_delta + ctts_offset, timescale).and_then(|t| {
+ if t < 0 { return None; }
+ Some(t)
+ });
+
+ let start_decode = track_time_to_us(decode_time, timescale);
- sample.start_decode = decode_time;
- sample.start_composition = start_composition;
- sample.end_composition = end_composition;
+ match (start_composition, end_composition, start_decode) {
+ (Some(s_c), Some(e_c), Some(s_d)) => {
+ sample.start_composition = s_c + track_offset_time;
+ sample.end_composition = e_c + track_offset_time;
+ sample.start_decode = s_d;
+ },
+ _ => return None,
+ }
}
// Correct composition end time due to 'ctts' causes composition time re-ordering.
//
// Composition end time is not in specification. However, gecko needs it, so we need to
// calculate to correct the composition end time.
if track.ctts.is_some() {
// Create an index table refers to sample_table and sorted by start_composisiton time.
@@ -1210,16 +1225,17 @@ fn arg_validation() {
};
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
let mut dummy_video = mp4parse_track_video_info {
display_width: 0,
display_height: 0,
image_width: 0,
image_height: 0,
+ rotation: 0,
extra_data: mp4parse_byte_data::default(),
protected_data: Default::default(),
};
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
let mut dummy_audio = Default::default();
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
}
@@ -1256,16 +1272,17 @@ fn arg_validation_with_parser() {
};
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, &mut dummy_info));
let mut dummy_video = mp4parse_track_video_info {
display_width: 0,
display_height: 0,
image_width: 0,
image_height: 0,
+ rotation: 0,
extra_data: mp4parse_byte_data::default(),
protected_data: Default::default(),
};
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
let mut dummy_audio = Default::default();
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
@@ -1329,16 +1346,17 @@ fn arg_validation_with_data() {
assert_eq!(info.duration, 61333);
assert_eq!(info.media_time, 21333);
let mut video = mp4parse_track_video_info {
display_width: 0,
display_height: 0,
image_width: 0,
image_height: 0,
+ rotation: 0,
extra_data: mp4parse_byte_data::default(),
protected_data: Default::default(),
};
assert_eq!(MP4PARSE_OK, mp4parse_get_track_video_info(parser, 0, &mut video));
assert_eq!(video.display_width, 320);
assert_eq!(video.display_height, 240);
assert_eq!(video.image_width, 320);
assert_eq!(video.image_height, 240);
@@ -1363,16 +1381,17 @@ fn arg_validation_with_data() {
assert_eq!(info.track_id, 0);
assert_eq!(info.duration, 0);
assert_eq!(info.media_time, 0);
let mut video = mp4parse_track_video_info { display_width: 0,
display_height: 0,
image_width: 0,
image_height: 0,
+ rotation: 0,
extra_data: mp4parse_byte_data::default(),
protected_data: Default::default(),
};
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 3, &mut video));
assert_eq!(video.display_width, 0);
assert_eq!(video.display_height, 0);
assert_eq!(video.image_width, 0);
assert_eq!(video.image_height, 0);
@@ -1384,31 +1403,31 @@ fn arg_validation_with_data() {
assert_eq!(audio.sample_rate, 0);
mp4parse_free(parser);
}
}
#[test]
fn rational_scale_overflow() {
- assert_eq!(rational_scale(17, 3, 1000), Some(5666));
+ assert_eq!(rational_scale::<u64, u64>(17, 3, 1000), Some(5666));
let large = 0x4000_0000_0000_0000;
- assert_eq!(rational_scale(large, 2, 2), Some(large));
- assert_eq!(rational_scale(large, 4, 4), Some(large));
- assert_eq!(rational_scale(large, 2, 8), None);
- assert_eq!(rational_scale(large, 8, 4), Some(large/2));
- assert_eq!(rational_scale(large + 1, 4, 4), Some(large+1));
- assert_eq!(rational_scale(large, 40, 1000), None);
+ assert_eq!(rational_scale::<u64, u64>(large, 2, 2), Some(large));
+ assert_eq!(rational_scale::<u64, u64>(large, 4, 4), Some(large));
+ assert_eq!(rational_scale::<u64, u64>(large, 2, 8), None);
+ assert_eq!(rational_scale::<u64, u64>(large, 8, 4), Some(large/2));
+ assert_eq!(rational_scale::<u64, u64>(large + 1, 4, 4), Some(large+1));
+ assert_eq!(rational_scale::<u64, u64>(large, 40, 1000), None);
}
#[test]
fn media_time_overflow() {
let scale = MediaTimeScale(90000);
let duration = MediaScaledTime(9007199254710000);
assert_eq!(media_time_to_us(duration, scale), Some(100079991719000000));
}
#[test]
fn track_time_overflow() {
- let scale = TrackTimeScale(44100, 0);
- let duration = TrackScaledTime(4413527634807900, 0);
+ let scale = TrackTimeScale(44100u64, 0);
+ let duration = TrackScaledTime(4413527634807900u64, 0);
assert_eq!(track_time_to_us(duration, scale), Some(100079991719000000));
}