diff options
Diffstat (limited to 'media/libstagefright/binding/mp4parse_capi/src/lib.rs')
-rw-r--r-- | media/libstagefright/binding/mp4parse_capi/src/lib.rs | 870 |
1 files changed, 870 insertions, 0 deletions
diff --git a/media/libstagefright/binding/mp4parse_capi/src/lib.rs b/media/libstagefright/binding/mp4parse_capi/src/lib.rs new file mode 100644 index 000000000..f52d8b169 --- /dev/null +++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs @@ -0,0 +1,870 @@ +//! C API for mp4parse module. +//! +//! Parses ISO Base Media Format aka video/mp4 streams. +//! +//! # Examples +//! +//! ```rust +//! extern crate mp4parse_capi; +//! use std::io::Read; +//! +//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { +//! let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; +//! let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; +//! match input.read(&mut buf) { +//! Ok(n) => n as isize, +//! Err(_) => -1, +//! } +//! } +//! +//! let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap(); +//! let io = mp4parse_capi::mp4parse_io { +//! read: buf_read, +//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void +//! }; +//! unsafe { +//! let parser = mp4parse_capi::mp4parse_new(&io); +//! let rv = mp4parse_capi::mp4parse_read(parser); +//! assert_eq!(rv, mp4parse_capi::mp4parse_error::MP4PARSE_OK); +//! mp4parse_capi::mp4parse_free(parser); +//! } +//! ``` + +// 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; + +use std::io::Read; +use std::collections::HashMap; + +// 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; +use mp4parse::VideoCodecSpecific; +use mp4parse::MediaTimeScale; +use mp4parse::MediaScaledTime; +use mp4parse::TrackTimeScale; +use mp4parse::TrackScaledTime; +use mp4parse::serialize_opus_header; +use mp4parse::CodecType; + +// rusty-cheddar's C enum generation doesn't namespace enum members by +// prefixing them, so we're forced to do it in our member names until +// https://github.com/Sean1708/rusty-cheddar/pull/35 is fixed. Importing +// the members into the module namespace avoids doubling up on the +// namespacing on the Rust side. +use mp4parse_error::*; +use mp4parse_track_type::*; + +#[repr(C)] +#[derive(PartialEq, Debug)] +pub enum mp4parse_error { + MP4PARSE_OK = 0, + MP4PARSE_ERROR_BADARG = 1, + MP4PARSE_ERROR_INVALID = 2, + MP4PARSE_ERROR_UNSUPPORTED = 3, + MP4PARSE_ERROR_EOF = 4, + MP4PARSE_ERROR_IO = 5, +} + +#[repr(C)] +#[derive(PartialEq, Debug)] +pub enum mp4parse_track_type { + MP4PARSE_TRACK_TYPE_VIDEO = 0, + MP4PARSE_TRACK_TYPE_AUDIO = 1, +} + +#[repr(C)] +#[derive(PartialEq, Debug)] +pub enum mp4parse_codec { + MP4PARSE_CODEC_UNKNOWN, + MP4PARSE_CODEC_AAC, + MP4PARSE_CODEC_FLAC, + MP4PARSE_CODEC_OPUS, + MP4PARSE_CODEC_AVC, + MP4PARSE_CODEC_VP9, + MP4PARSE_CODEC_MP3, +} + +#[repr(C)] +pub struct mp4parse_track_info { + pub track_type: mp4parse_track_type, + pub codec: mp4parse_codec, + pub track_id: u32, + pub duration: u64, + pub media_time: i64, // wants to be u64? understand how elst adjustment works + // TODO(kinetik): include crypto guff +} + +#[repr(C)] +pub struct mp4parse_codec_specific_config { + pub length: u32, + pub data: *const u8, +} + +impl Default for mp4parse_codec_specific_config { + fn default() -> Self { + mp4parse_codec_specific_config { + length: 0, + data: std::ptr::null_mut(), + } + } +} + +#[derive(Default)] +#[repr(C)] +pub struct mp4parse_track_audio_info { + pub channels: u16, + pub bit_depth: u16, + pub sample_rate: u32, + // TODO(kinetik): + // int32_t profile; + // int32_t extended_profile; // check types + codec_specific_config: mp4parse_codec_specific_config, +} + +#[repr(C)] +pub struct mp4parse_track_video_info { + pub display_width: u32, + pub display_height: u32, + pub image_width: u16, + pub image_height: u16, + // TODO(kinetik): + // extra_data + // codec_specific_config +} + +#[repr(C)] +pub struct mp4parse_fragment_info { + pub fragment_duration: u64, + // TODO: + // info in trex box. +} + +// Even though mp4parse_parser is opaque to C, rusty-cheddar won't let us +// use more than one member, so we introduce *another* wrapper. +struct Wrap { + context: MediaContext, + io: mp4parse_io, + poisoned: bool, + opus_header: HashMap<u32, Vec<u8>>, +} + +#[repr(C)] +#[allow(non_camel_case_types)] +pub struct mp4parse_parser(Wrap); + +impl mp4parse_parser { + fn context(&self) -> &MediaContext { + &self.0.context + } + + fn context_mut(&mut self) -> &mut MediaContext { + &mut self.0.context + } + + fn io_mut(&mut self) -> &mut mp4parse_io { + &mut self.0.io + } + + fn poisoned(&self) -> bool { + self.0.poisoned + } + + fn set_poisoned(&mut self, poisoned: bool) { + self.0.poisoned = poisoned; + } + + fn opus_header_mut(&mut self) -> &mut HashMap<u32, Vec<u8>> { + &mut self.0.opus_header + } +} + +#[repr(C)] +#[derive(Clone)] +pub struct mp4parse_io { + pub read: extern fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize, + pub userdata: *mut std::os::raw::c_void, +} + +impl Read for mp4parse_io { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + if buf.len() > isize::max_value() as usize { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "buf length overflow in mp4parse_io Read impl")); + } + let rv = (self.read)(buf.as_mut_ptr(), buf.len(), self.userdata); + if rv >= 0 { + Ok(rv as usize) + } else { + Err(std::io::Error::new(std::io::ErrorKind::Other, "I/O error in mp4parse_io Read impl")) + } + } +} + +// C API wrapper functions. + +/// Allocate an `mp4parse_parser*` to read from the supplied `mp4parse_io`. +#[no_mangle] +pub unsafe extern fn mp4parse_new(io: *const mp4parse_io) -> *mut mp4parse_parser { + if io.is_null() || (*io).userdata.is_null() { + return std::ptr::null_mut(); + } + // is_null() isn't available on a fn type because it can't be null (in + // Rust) by definition. But since this value is coming from the C API, + // it *could* be null. Ideally, we'd wrap it in an Option to represent + // reality, but this causes rusty-cheddar to emit the wrong type + // (https://github.com/Sean1708/rusty-cheddar/issues/30). + if ((*io).read as *mut std::os::raw::c_void).is_null() { + return std::ptr::null_mut(); + } + let parser = Box::new(mp4parse_parser(Wrap { + context: MediaContext::new(), + io: (*io).clone(), + poisoned: false, + opus_header: HashMap::new(), + })); + Box::into_raw(parser) +} + +/// Free an `mp4parse_parser*` allocated by `mp4parse_new()`. +#[no_mangle] +pub unsafe extern fn mp4parse_free(parser: *mut mp4parse_parser) { + assert!(!parser.is_null()); + let _ = Box::from_raw(parser); +} + +/// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error. +#[no_mangle] +pub unsafe extern fn mp4parse_read(parser: *mut mp4parse_parser) -> mp4parse_error { + // Validate arguments from C. + if parser.is_null() || (*parser).poisoned() { + return MP4PARSE_ERROR_BADARG; + } + + let mut context = (*parser).context_mut(); + let mut io = (*parser).io_mut(); + + let r = read_mp4(io, context); + match r { + Ok(_) => MP4PARSE_OK, + Err(Error::NoMoov) | Err(Error::InvalidData(_)) => { + // Block further calls. We've probable lost sync. + (*parser).set_poisoned(true); + MP4PARSE_ERROR_INVALID + } + Err(Error::Unsupported(_)) => MP4PARSE_ERROR_UNSUPPORTED, + Err(Error::UnexpectedEOF) => MP4PARSE_ERROR_EOF, + Err(Error::Io(_)) => { + // Block further calls after a read failure. + // Getting std::io::ErrorKind::UnexpectedEof is normal + // but our From trait implementation should have converted + // those to our Error::UnexpectedEOF variant. + (*parser).set_poisoned(true); + MP4PARSE_ERROR_IO + } + } +} + +/// Return the number of tracks parsed by previous `mp4parse_read()` call. +#[no_mangle] +pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, count: *mut u32) -> mp4parse_error { + // Validate arguments from C. + if parser.is_null() || count.is_null() || (*parser).poisoned() { + return MP4PARSE_ERROR_BADARG; + } + let context = (*parser).context(); + + // Make sure the track count fits in a u32. + if context.tracks.len() > u32::max_value() as usize { + return MP4PARSE_ERROR_INVALID; + } + *count = context.tracks.len() as u32; + MP4PARSE_OK +} + +/// 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 { + 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, + } +} + +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) +} + +fn track_time_to_us(time: TrackScaledTime, scale: TrackTimeScale) -> Option<u64> { + assert!(time.1 == scale.1); + let microseconds_per_second = 1000000; + rational_scale(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; + } + + let context = (*parser).context_mut(); + let track_index: usize = track_index as usize; + let info: &mut mp4parse_track_info = &mut *info; + + if track_index >= context.tracks.len() { + return MP4PARSE_ERROR_BADARG; + } + + info.track_type = match context.tracks[track_index].track_type { + TrackType::Video => MP4PARSE_TRACK_TYPE_VIDEO, + TrackType::Audio => MP4PARSE_TRACK_TYPE_AUDIO, + TrackType::Unknown => return MP4PARSE_ERROR_UNSUPPORTED, + }; + + info.codec = match context.tracks[track_index].data { + Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific { + AudioCodecSpecific::OpusSpecificBox(_) => + mp4parse_codec::MP4PARSE_CODEC_OPUS, + AudioCodecSpecific::FLACSpecificBox(_) => + mp4parse_codec::MP4PARSE_CODEC_FLAC, + AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => + mp4parse_codec::MP4PARSE_CODEC_AAC, + AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => + mp4parse_codec::MP4PARSE_CODEC_MP3, + AudioCodecSpecific::ES_Descriptor(_) => + mp4parse_codec::MP4PARSE_CODEC_UNKNOWN, + }, + Some(SampleEntry::Video(ref video)) => match video.codec_specific { + VideoCodecSpecific::VPxConfig(_) => + mp4parse_codec::MP4PARSE_CODEC_VP9, + VideoCodecSpecific::AVCConfig(_) => + mp4parse_codec::MP4PARSE_CODEC_AVC, + }, + _ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN, + }; + + let track = &context.tracks[track_index]; + + if let (Some(track_timescale), + Some(context_timescale)) = (track.timescale, + context.timescale) { + let media_time = + match track.media_time.map_or(Some(0), |media_time| { + track_time_to_us(media_time, track_timescale) }) { + Some(time) => time as i64, + None => return MP4PARSE_ERROR_INVALID, + }; + let empty_duration = + match track.empty_duration.map_or(Some(0), |empty_duration| { + media_time_to_us(empty_duration, context_timescale) }) { + Some(time) => time as i64, + None => return MP4PARSE_ERROR_INVALID, + }; + info.media_time = media_time - empty_duration; + + if let Some(track_duration) = track.duration { + match track_time_to_us(track_duration, track_timescale) { + Some(duration) => info.duration = duration, + None => return MP4PARSE_ERROR_INVALID, + } + } else { + // Duration unknown; stagefright returns 0 for this. + info.duration = 0 + } + } else { + return MP4PARSE_ERROR_INVALID + } + + info.track_id = match track.track_id { + Some(track_id) => track_id, + None => return MP4PARSE_ERROR_INVALID, + }; + + MP4PARSE_OK +} + +/// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`. +#[no_mangle] +pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_audio_info) -> mp4parse_error { + if parser.is_null() || info.is_null() || (*parser).poisoned() { + return MP4PARSE_ERROR_BADARG; + } + + let context = (*parser).context_mut(); + + if track_index as usize >= context.tracks.len() { + return MP4PARSE_ERROR_BADARG; + } + + let track = &context.tracks[track_index as usize]; + + match track.track_type { + TrackType::Audio => {} + _ => return MP4PARSE_ERROR_INVALID, + }; + + let audio = match track.data { + Some(ref data) => data, + None => return MP4PARSE_ERROR_INVALID, + }; + + let audio = match *audio { + SampleEntry::Audio(ref x) => x, + _ => return MP4PARSE_ERROR_INVALID, + }; + + (*info).channels = audio.channelcount; + (*info).bit_depth = audio.samplesize; + (*info).sample_rate = audio.samplerate >> 16; // 16.16 fixed point + + match audio.codec_specific { + AudioCodecSpecific::ES_Descriptor(ref v) => { + if v.codec_specific_config.len() > std::u32::MAX as usize { + return MP4PARSE_ERROR_INVALID; + } + (*info).codec_specific_config.length = v.codec_specific_config.len() as u32; + (*info).codec_specific_config.data = v.codec_specific_config.as_ptr(); + if let Some(rate) = v.audio_sample_rate { + (*info).sample_rate = rate; + } + if let Some(channels) = v.audio_channel_count { + (*info).channels = channels; + } + } + AudioCodecSpecific::FLACSpecificBox(ref flac) => { + // Return the STREAMINFO metadata block in the codec_specific. + let streaminfo = &flac.blocks[0]; + if streaminfo.block_type != 0 || streaminfo.data.len() != 34 { + return MP4PARSE_ERROR_INVALID; + } + (*info).codec_specific_config.length = streaminfo.data.len() as u32; + (*info).codec_specific_config.data = streaminfo.data.as_ptr(); + } + AudioCodecSpecific::OpusSpecificBox(ref opus) => { + let mut v = Vec::new(); + match serialize_opus_header(opus, &mut v) { + Err(_) => { + return MP4PARSE_ERROR_INVALID; + } + Ok(_) => { + let header = (*parser).opus_header_mut(); + header.insert(track_index, v); + match header.get(&track_index) { + None => {} + Some(v) => { + if v.len() > std::u32::MAX as usize { + return MP4PARSE_ERROR_INVALID; + } + (*info).codec_specific_config.length = v.len() as u32; + (*info).codec_specific_config.data = v.as_ptr(); + } + } + } + } + } + } + + MP4PARSE_OK +} + +/// Fill the supplied `mp4parse_track_video_info` with metadata for `track`. +#[no_mangle] +pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_video_info) -> mp4parse_error { + if parser.is_null() || info.is_null() || (*parser).poisoned() { + return MP4PARSE_ERROR_BADARG; + } + + let context = (*parser).context_mut(); + + if track_index as usize >= context.tracks.len() { + return MP4PARSE_ERROR_BADARG; + } + + let track = &context.tracks[track_index as usize]; + + match track.track_type { + TrackType::Video => {} + _ => return MP4PARSE_ERROR_INVALID, + }; + + let video = match track.data { + Some(ref data) => data, + None => return MP4PARSE_ERROR_INVALID, + }; + + 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 + } else { + return MP4PARSE_ERROR_INVALID; + } + (*info).image_width = video.width; + (*info).image_height = video.height; + + MP4PARSE_OK +} + +#[no_mangle] +pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut mp4parse_parser, info: *mut mp4parse_fragment_info) -> mp4parse_error { + if parser.is_null() || info.is_null() || (*parser).poisoned() { + return MP4PARSE_ERROR_BADARG; + } + + let context = (*parser).context(); + let info: &mut mp4parse_fragment_info = &mut *info; + + info.fragment_duration = 0; + + let duration = match context.mvex { + Some(ref mvex) => mvex.fragment_duration, + None => return MP4PARSE_ERROR_INVALID, + }; + + if let (Some(time), Some(scale)) = (duration, context.timescale) { + info.fragment_duration = match media_time_to_us(time, scale) { + Some(time_us) => time_us as u64, + None => return MP4PARSE_ERROR_INVALID, + } + } + + MP4PARSE_OK +} + +// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes. +#[no_mangle] +pub unsafe extern fn mp4parse_is_fragmented(parser: *mut mp4parse_parser, track_id: u32, fragmented: *mut u8) -> mp4parse_error { + if parser.is_null() || (*parser).poisoned() { + return MP4PARSE_ERROR_BADARG; + } + + let context = (*parser).context_mut(); + let tracks = &context.tracks; + (*fragmented) = false as u8; + + if context.mvex.is_none() { + return MP4PARSE_OK; + } + + // check sample tables. + let mut iter = tracks.iter(); + match iter.find(|track| track.track_id == Some(track_id)) { + Some(track) if track.empty_sample_boxes.all_empty() => (*fragmented) = true as u8, + Some(_) => {}, + None => return MP4PARSE_ERROR_BADARG, + } + + MP4PARSE_OK +} + +#[cfg(test)] +extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize { + panic!("panic_read shouldn't be called in these tests"); +} + +#[cfg(test)] +extern fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize { + -1 +} + +#[cfg(test)] +extern fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + + let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(&mut buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn new_parser() { + let mut dummy_value: u32 = 42; + let io = mp4parse_io { + read: panic_read, + userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, + }; + unsafe { + let parser = mp4parse_new(&io); + assert!(!parser.is_null()); + mp4parse_free(parser); + } +} + +#[test] +#[should_panic(expected = "assertion failed")] +fn free_null_parser() { + unsafe { + mp4parse_free(std::ptr::null_mut()); + } +} + +#[test] +fn get_track_count_null_parser() { + unsafe { + let mut count: u32 = 0; + let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut()); + assert!(rv == MP4PARSE_ERROR_BADARG); + let rv = mp4parse_get_track_count(std::ptr::null(), &mut count); + assert!(rv == MP4PARSE_ERROR_BADARG); + } +} + +#[test] +fn arg_validation() { + unsafe { + // Passing a null mp4parse_io is an error. + let parser = mp4parse_new(std::ptr::null()); + assert!(parser.is_null()); + + let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut(); + + // Passing an mp4parse_io with null members is an error. + let io = mp4parse_io { read: std::mem::transmute(null_mut), + userdata: null_mut }; + let parser = mp4parse_new(&io); + assert!(parser.is_null()); + + let io = mp4parse_io { read: panic_read, + userdata: null_mut }; + let parser = mp4parse_new(&io); + assert!(parser.is_null()); + + let mut dummy_value = 42; + let io = mp4parse_io { + read: std::mem::transmute(null_mut), + userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, + }; + let parser = mp4parse_new(&io); + assert!(parser.is_null()); + + // Passing a null mp4parse_parser is an error. + assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(std::ptr::null_mut())); + + let mut dummy_info = mp4parse_track_info { + track_type: MP4PARSE_TRACK_TYPE_VIDEO, + codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN, + track_id: 0, + duration: 0, + media_time: 0, + }; + 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, + }; + 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)); + } +} + +#[test] +fn arg_validation_with_parser() { + unsafe { + let mut dummy_value = 42; + let io = mp4parse_io { + read: error_read, + userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, + }; + let parser = mp4parse_new(&io); + assert!(!parser.is_null()); + + // Our mp4parse_io read should simply fail with an error. + assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser)); + + // The parser is now poisoned and unusable. + assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(parser)); + + // Null info pointers are an error. + assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, std::ptr::null_mut())); + assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut())); + assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut())); + + let mut dummy_info = mp4parse_track_info { + track_type: MP4PARSE_TRACK_TYPE_VIDEO, + codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN, + track_id: 0, + duration: 0, + media_time: 0, + }; + 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, + }; + 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)); + + mp4parse_free(parser); + } +} + +#[test] +fn get_track_count_poisoned_parser() { + unsafe { + let mut dummy_value = 42; + let io = mp4parse_io { + read: error_read, + userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, + }; + let parser = mp4parse_new(&io); + assert!(!parser.is_null()); + + // Our mp4parse_io read should simply fail with an error. + assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser)); + + let mut count: u32 = 0; + let rv = mp4parse_get_track_count(parser, &mut count); + assert!(rv == MP4PARSE_ERROR_BADARG); + } +} + +#[test] +fn arg_validation_with_data() { + unsafe { + let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap(); + let io = mp4parse_io { read: valid_read, + userdata: &mut file as *mut _ as *mut std::os::raw::c_void }; + let parser = mp4parse_new(&io); + assert!(!parser.is_null()); + + assert_eq!(MP4PARSE_OK, mp4parse_read(parser)); + + let mut count: u32 = 0; + assert_eq!(MP4PARSE_OK, mp4parse_get_track_count(parser, &mut count)); + assert_eq!(2, count); + + let mut info = mp4parse_track_info { + track_type: MP4PARSE_TRACK_TYPE_VIDEO, + codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN, + track_id: 0, + duration: 0, + media_time: 0, + }; + assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 0, &mut info)); + assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO); + assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AVC); + assert_eq!(info.track_id, 1); + assert_eq!(info.duration, 40000); + assert_eq!(info.media_time, 0); + + assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 1, &mut info)); + assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_AUDIO); + assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AAC); + assert_eq!(info.track_id, 2); + 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, + }; + 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); + + let mut audio = Default::default(); + assert_eq!(MP4PARSE_OK, mp4parse_get_track_audio_info(parser, 1, &mut audio)); + assert_eq!(audio.channels, 1); + assert_eq!(audio.bit_depth, 16); + assert_eq!(audio.sample_rate, 48000); + + // Test with an invalid track number. + let mut info = mp4parse_track_info { + track_type: MP4PARSE_TRACK_TYPE_VIDEO, + codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN, + track_id: 0, + duration: 0, + media_time: 0, + }; + assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 3, &mut info)); + assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO); + assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_UNKNOWN); + 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 }; + 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); + + let mut audio = Default::default(); + assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 3, &mut audio)); + assert_eq!(audio.channels, 0); + assert_eq!(audio.bit_depth, 0); + assert_eq!(audio.sample_rate, 0); + + mp4parse_free(parser); + } +} + +#[test] +fn rational_scale_overflow() { + assert_eq!(rational_scale(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); +} + +#[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); + assert_eq!(track_time_to_us(duration, scale), Some(100079991719000000)); +} |