mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-14 15:30:07 +00:00
273 lines
7.3 KiB
Rust
273 lines
7.3 KiB
Rust
//! C API for mp4parse module.
|
|
//!
|
|
//! Parses ISO Base Media Format aka video/mp4 streams.
|
|
//!
|
|
//! # Examples
|
|
//!
|
|
//! ```rust
|
|
//! extern crate mp4parse;
|
|
//!
|
|
//! // Minimal valid mp4 containing no tracks.
|
|
//! let data = b"\0\0\0\x0cftypmp42";
|
|
//!
|
|
//! let context = mp4parse::mp4parse_new();
|
|
//! unsafe {
|
|
//! let rv = mp4parse::mp4parse_read(context, data.as_ptr(), data.len());
|
|
//! assert_eq!(0, rv);
|
|
//! mp4parse::mp4parse_free(context);
|
|
//! }
|
|
//! ```
|
|
|
|
// 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/.
|
|
|
|
use std;
|
|
use std::io::Cursor;
|
|
|
|
// Symbols we need from our rust api.
|
|
use MediaContext;
|
|
use TrackType;
|
|
use read_mp4;
|
|
use Error;
|
|
use media_time_to_ms;
|
|
use track_time_to_ms;
|
|
use SampleEntry;
|
|
|
|
const TRACK_TYPE_H264: u32 = 0;
|
|
const TRACK_TYPE_AAC: u32 = 1;
|
|
|
|
// These structs *must* match those declared in include/mp4parse.h.
|
|
#[repr(C)]
|
|
pub struct TrackInfo {
|
|
track_type: u32,
|
|
track_id: u32,
|
|
duration: u64,
|
|
media_time: i64, // wants to be u64? understand how elst adjustment works
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct TrackAudioInfo {
|
|
channels: u16,
|
|
bit_depth: u16,
|
|
sample_rate: u32,
|
|
// profile: i32,
|
|
// extended_profile: i32, // check types
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct TrackVideoInfo {
|
|
display_width: u32,
|
|
display_height: u32,
|
|
image_width: u16,
|
|
image_height: u16,
|
|
}
|
|
|
|
/// Allocate an opaque rust-side parser context.
|
|
#[no_mangle]
|
|
pub extern "C" fn mp4parse_new() -> *mut MediaContext {
|
|
let context = Box::new(MediaContext::new());
|
|
Box::into_raw(context)
|
|
}
|
|
|
|
/// Free a rust-side parser context.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
|
|
assert!(!context.is_null());
|
|
let _ = Box::from_raw(context);
|
|
}
|
|
|
|
/// Feed a buffer through `read_mp4()` with the given rust-side
|
|
/// parser context, returning the number of detected tracks.
|
|
///
|
|
/// This is safe to call with NULL arguments but will crash
|
|
/// if given invalid pointers, as is usual for C.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *const u8, size: usize) -> i32 {
|
|
// Validate arguments from C.
|
|
if context.is_null() || buffer.is_null() || size < 8 {
|
|
return -1;
|
|
}
|
|
|
|
let mut context: &mut MediaContext = &mut *context;
|
|
|
|
// Wrap the buffer we've been give in a slice.
|
|
let b = std::slice::from_raw_parts(buffer, size);
|
|
let mut c = Cursor::new(b);
|
|
|
|
// Parse in a subthread to catch any panics.
|
|
let task = std::thread::spawn(move || {
|
|
match read_mp4(&mut c, &mut context) {
|
|
Ok(_) => {},
|
|
Err(Error::UnexpectedEOF) => {},
|
|
Err(e) => { panic!(e); },
|
|
}
|
|
// Make sure the track count fits in an i32 so we can use
|
|
// negative values for failure.
|
|
assert!(context.tracks.len() < i32::max_value() as usize);
|
|
context.tracks.len() as i32
|
|
});
|
|
task.join().unwrap_or(-1)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mp4parse_get_track_info(context: *mut MediaContext, track: i32, info: *mut TrackInfo) -> i32 {
|
|
if context.is_null() || track < 0 || info.is_null() {
|
|
return -1;
|
|
}
|
|
|
|
let context: &mut MediaContext = &mut *context;
|
|
let track_index: usize = track as usize;
|
|
let info: &mut TrackInfo = &mut *info;
|
|
|
|
if track_index >= context.tracks.len() {
|
|
return -1;
|
|
}
|
|
|
|
info.track_type = match context.tracks[track_index].track_type {
|
|
TrackType::Video => TRACK_TYPE_H264,
|
|
TrackType::Audio => TRACK_TYPE_AAC,
|
|
TrackType::Unknown => return -1,
|
|
};
|
|
|
|
// Maybe context & track should just have a single simple is_valid() instead?
|
|
if context.timescale.is_none() ||
|
|
context.tracks[track_index].timescale.is_none() ||
|
|
context.tracks[track_index].duration.is_none() ||
|
|
context.tracks[track_index].track_id.is_none() {
|
|
return -1;
|
|
}
|
|
|
|
std::thread::spawn(move || {
|
|
let track = &context.tracks[track_index];
|
|
let empty_duration = if track.empty_duration.is_some() {
|
|
media_time_to_ms(track.empty_duration.unwrap(), context.timescale.unwrap())
|
|
} else {
|
|
0
|
|
};
|
|
info.media_time = if track.media_time.is_some() {
|
|
track_time_to_ms(track.media_time.unwrap(), track.timescale.unwrap()) as i64 - empty_duration as i64
|
|
} else {
|
|
0
|
|
};
|
|
info.duration = track_time_to_ms(track.duration.unwrap(), track.timescale.unwrap());
|
|
info.track_id = track.track_id.unwrap();
|
|
0
|
|
}).join().unwrap_or(-1)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mp4parse_get_track_audio_info(context: *mut MediaContext, track: i32, info: *mut TrackAudioInfo) -> i32 {
|
|
if context.is_null() || track < 0 || info.is_null() {
|
|
return -1;
|
|
}
|
|
|
|
let context: &mut MediaContext = &mut *context;
|
|
|
|
if track as usize >= context.tracks.len() {
|
|
return -1;
|
|
}
|
|
|
|
let track = &context.tracks[track as usize];
|
|
|
|
match track.track_type {
|
|
TrackType::Audio => {},
|
|
_ => return -1,
|
|
};
|
|
|
|
let audio = match track.data {
|
|
Some(ref data) => data,
|
|
None => return -1,
|
|
};
|
|
|
|
let audio = match audio {
|
|
&SampleEntry::Audio(ref x) => x,
|
|
_ => return -1,
|
|
};
|
|
|
|
(*info).channels = audio.channelcount;
|
|
(*info).bit_depth = audio.samplesize;
|
|
(*info).sample_rate = audio.samplerate >> 16; // 16.16 fixed point
|
|
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn mp4parse_get_track_video_info(context: *mut MediaContext, track: i32, info: *mut TrackVideoInfo) -> i32 {
|
|
if context.is_null() || track < 0 || info.is_null() {
|
|
return -1;
|
|
}
|
|
|
|
let context: &mut MediaContext = &mut *context;
|
|
|
|
if track as usize >= context.tracks.len() {
|
|
return -1;
|
|
}
|
|
|
|
let track = &context.tracks[track as usize];
|
|
|
|
match track.track_type {
|
|
TrackType::Video => {},
|
|
_ => return -1,
|
|
};
|
|
|
|
let video = match track.data {
|
|
Some(ref data) => data,
|
|
None => return -1,
|
|
};
|
|
|
|
let video = match video {
|
|
&SampleEntry::Video(ref x) => x,
|
|
_ => return -1,
|
|
};
|
|
|
|
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 -1
|
|
}
|
|
(*info).image_width = video.width;
|
|
(*info).image_width = video.height;
|
|
|
|
0
|
|
}
|
|
|
|
#[test]
|
|
fn new_context() {
|
|
let context = mp4parse_new();
|
|
assert!(!context.is_null());
|
|
unsafe { mp4parse_free(context); }
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "assertion failed")]
|
|
fn free_null_context() {
|
|
unsafe { mp4parse_free(std::ptr::null_mut()); }
|
|
}
|
|
|
|
#[test]
|
|
fn arg_validation() {
|
|
let null_buffer = std::ptr::null();
|
|
let null_context = std::ptr::null_mut();
|
|
|
|
let context = mp4parse_new();
|
|
assert!(!context.is_null());
|
|
|
|
let buffer = vec![0u8; 8];
|
|
|
|
unsafe {
|
|
assert_eq!(-1, mp4parse_read(null_context, null_buffer, 0));
|
|
assert_eq!(-1, mp4parse_read(context, null_buffer, 0));
|
|
}
|
|
|
|
for size in 0..buffer.len() {
|
|
println!("testing buffer length {}", size);
|
|
unsafe {
|
|
assert_eq!(-1, mp4parse_read(context, buffer.as_ptr(), size));
|
|
}
|
|
}
|
|
|
|
unsafe { mp4parse_free(context); }
|
|
}
|