use std;
use std::io::Read;
use std::collections::HashMap;
use MediaContext;
use TrackType;
use read_mp4;
use Error;
use SampleEntry;
use AudioCodecSpecific;
use VideoCodecSpecific;
use MediaTimeScale;
use MediaScaledTime;
use TrackTimeScale;
use TrackScaledTime;
use serialize_opus_header;
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_OPUS,
MP4PARSE_CODEC_AVC,
MP4PARSE_CODEC_VP9,
}
#[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, }
#[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,
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,
}
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"))
}
}
}
#[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();
}
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)
}
#[no_mangle]
pub unsafe extern fn mp4parse_free(parser: *mut mp4parse_parser) {
assert!(!parser.is_null());
let _ = Box::from_raw(parser);
}
#[no_mangle]
pub unsafe extern fn mp4parse_read(parser: *mut mp4parse_parser) -> mp4parse_error {
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(_)) => {
(*parser).set_poisoned(true);
MP4PARSE_ERROR_INVALID
}
Err(Error::Unsupported(_)) => MP4PARSE_ERROR_UNSUPPORTED,
Err(Error::UnexpectedEOF) => MP4PARSE_ERROR_EOF,
Err(Error::Io(_)) => {
(*parser).set_poisoned(true);
MP4PARSE_ERROR_IO
}
}
}
#[no_mangle]
pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, count: *mut u32) -> mp4parse_error {
if parser.is_null() || count.is_null() || (*parser).poisoned() {
return MP4PARSE_ERROR_BADARG;
}
let context = (*parser).context();
if context.tracks.len() > u32::max_value() as usize {
return MP4PARSE_ERROR_INVALID;
}
*count = context.tracks.len() as u32;
MP4PARSE_OK
}
fn media_time_to_ms(time: MediaScaledTime, scale: MediaTimeScale) -> u64 {
assert!(scale.0 != 0);
time.0 * 1000000 / scale.0
}
fn track_time_to_ms(time: TrackScaledTime, scale: TrackTimeScale) -> u64 {
assert!(time.1 == scale.1);
assert!(scale.0 != 0);
time.0 * 1000000 / scale.0
}
#[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::ES_Descriptor(_) =>
mp4parse_codec::MP4PARSE_CODEC_AAC,
},
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),
Some(track_duration)) = (track.timescale,
context.timescale,
track.duration) {
info.media_time = track.media_time.map_or(0, |media_time| {
track_time_to_ms(media_time, track_timescale) as i64
}) - track.empty_duration.map_or(0, |empty_duration| {
media_time_to_ms(empty_duration, context_timescale) as i64
});
info.duration = track_time_to_ms(track_duration, track_timescale);
} else {
return MP4PARSE_ERROR_INVALID
}
info.track_id = match track.track_id {
Some(track_id) => track_id,
None => return MP4PARSE_ERROR_INVALID,
};
MP4PARSE_OK
}
#[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;
match audio.codec_specific {
AudioCodecSpecific::ES_Descriptor(ref 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();
}
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) => {
(*info).codec_specific_config.length = v.len() as u32;
(*info).codec_specific_config.data = v.as_ptr();
}
}
}
}
}
}
MP4PARSE_OK
}
#[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; (*info).display_height = tkhd.height >> 16; } else {
return MP4PARSE_ERROR_INVALID;
}
(*info).image_width = video.width;
(*info).image_height = video.height;
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 {
let parser = mp4parse_new(std::ptr::null());
assert!(parser.is_null());
let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut();
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());
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());
assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(parser));
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());
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("examples/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, 2);
assert_eq!(audio.bit_depth, 16);
assert_eq!(audio.sample_rate, 48000);
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);
}
}