//! C API for mp4parse module. //! //! Parses ISO Base Media Format aka video/mp4 streams. //! //! # Examples //! //! ```rust //! 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 capi_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); //! let mut file = std::fs::File::open(capi_dir + "/../mp4parse/tests/minimal.mp4").unwrap(); //! let io = mp4parse_capi::Mp4parseIo { //! read: Some(buf_read), //! userdata: &mut file as *mut _ as *mut std::os::raw::c_void //! }; //! let mut parser = std::ptr::null_mut(); //! unsafe { //! let rv = mp4parse_capi::mp4parse_new(&io, &mut parser); //! assert_eq!(rv, mp4parse_capi::Mp4parseStatus::Ok); //! assert!(!parser.is_null()); //! 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/.
use byteorder::WriteBytesExt; use fallible_collections::TryReserveError; use mp4parse::unstable::rational_scale; use std::convert::TryFrom; use std::convert::TryInto; use std::hash::Hash;
use std::io::Read;
// Symbols we need from our rust api. use mp4parse::serialize_opus_header; use mp4parse::unstable::{create_sample_table, CheckedInteger, Indice}; use mp4parse::AV1ConfigBox; use mp4parse::AudioCodecSpecific; use mp4parse::AvifContext; use mp4parse::CodecType; use mp4parse::MediaContext; // Re-exported so consumers don't have to depend on mp4parse as well pubuse mp4parse::ParseStrictness; use mp4parse::SampleEntry; pubuse mp4parse::Status as Mp4parseStatus; use mp4parse::Track; use mp4parse::TrackType; use mp4parse::TryBox; use mp4parse::TryHashMap; use mp4parse::TryVec; use mp4parse::VideoCodecSpecific;
// To ensure we don't use stdlib allocating types by accident #[allow(dead_code)] struct Vec; #[allow(dead_code)] structBox; #[allow(dead_code)] struct HashMap; #[allow(dead_code)] struct String;
#[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[repr(C)] #[derive(PartialEq, Eq, Debug, Default)] pubenum Mp4parseCodec { #[default]
Unknown,
Aac,
Flac,
Opus,
Avc,
Vp9,
Av1,
Mp3,
Mp4v,
Jpeg, // for QT JPEG atom in video track
Ac3,
Ec3,
Alac,
H263,
Hevc, #[cfg(feature = "3gpp")]
AMRNB, #[cfg(feature = "3gpp")]
AMRWB,
XHEAAC, // xHE-AAC (Extended High Efficiency AAC)
}
#[repr(C)] #[derive(PartialEq, Eq, Debug, Default)] pubenum Mp4ParseEncryptionSchemeType { #[default]
None,
Cenc,
Cbc1,
Cens,
Cbcs, // Schemes also have a version component. At the time of writing, this does // not impact handling, so we do not expose it. Note that this may need to // be exposed in future, should the spec change.
}
impl Mp4parseByteData { /// Populate the data pointer and length from a byte slice. /// /// The returned pointer borrows directly from `data`; the caller must /// ensure the backing storage outlives any C code that dereferences it. /// For empty slices we return a null pointer to avoid exposing Rust's /// dangling empty-slice pointer (e.g. 0x1) over FFI. /// See [`Mp4parseParser`] for the caching strategy that guarantees this. fn set_data(&mutself, data: &[u8]) { self.length = data.len(); self.data = if data.is_empty() {
std::ptr::null()
} else {
data.as_ptr()
};
}
/// For empty slices we return a null pointer to avoid exposing Rust's /// dangling empty-slice pointer (e.g. 0x1) over FFI. fn set_indices(&mutself, data: &[Indice]) { self.length = data.len(); self.indices = if data.is_empty() {
std::ptr::null()
} else {
data.as_ptr()
};
}
}
#[repr(C)] #[derive(Default, Debug)] pubstruct Mp4parseSinfInfo { pub original_format: OptionalFourCc, pub scheme_type: Mp4ParseEncryptionSchemeType, pub is_encrypted: u8, pub iv_size: u8, pub kid: Mp4parseByteData, // Members for pattern encryption schemes, may be 0 (u8) or empty // (Mp4parseByteData) if pattern encryption is not in use pub crypt_byte_block: u8, pub skip_byte_block: u8, pub constant_iv: Mp4parseByteData, // End pattern encryption scheme members
}
/// Mastering display colour volume from an `mdcv` box (ISO 14496-12). /// Primary indices are R\[0\], G\[1\], B\[2\]. Divide chromaticity values by 50000.0 /// and luminance values by 10000.0 to obtain physical units (chromaticity, cd/m²). #[repr(C)] #[derive(Default, Debug)] pubstruct Mp4parseMasteringDisplayColourVolume { pub display_primaries_x: [u16; 3], pub display_primaries_y: [u16; 3], pub white_point_x: u16, pub white_point_y: u16, /// In units of 0.0001 cd/m² pub max_display_mastering_luminance: u32, /// In units of 0.0001 cd/m² pub min_display_mastering_luminance: u32,
}
/// Content light level from a `clli` box (ISO 14496-12). #[repr(C)] #[derive(Default, Debug)] pubstruct Mp4parseContentLightLevel { /// Maximum content light level in cd/m² pub max_content_light_level: u16, /// Maximum picture average light level in cd/m² pub max_pic_average_light_level: u16,
}
#[repr(C)] #[derive(Default, Debug)] pubstruct Mp4parseTrackVideoSampleInfo { pub codec_type: Mp4parseCodec, pub image_width: u16, pub image_height: u16, pub extra_data: Mp4parseByteData, pub protected_data: Mp4parseSinfInfo, /// True when a `colr` box with `colour_type = 'nclx'` was present. When false, /// the CICP fields below are all zero and must not be interpreted. pub has_colour_info: bool, /// CICP colour primaries (ISO 23091-2 § 8.1). Valid only when `has_colour_info`. pub colour_primaries: u8, /// CICP transfer characteristics (ISO 23091-2 § 8.2). Valid only when `has_colour_info`. pub transfer_characteristics: u8, /// CICP matrix coefficients (ISO 23091-2 § 8.3). Valid only when `has_colour_info`. /// Note: value 0 is a valid CICP value (Identity/GBR), not an absence indicator. pub matrix_coefficients: u8, /// Full range flag from the colr nclx box. Valid only when `has_colour_info`. pub full_range_flag: bool, /// True when an `mdcv` box was present. When false, `mastering_display` must not be read. pub has_mastering_display: bool, pub mastering_display: Mp4parseMasteringDisplayColourVolume, /// True when a `clli` box was present. When false, `content_light_level` must not be read. pub has_content_light_level: bool, pub content_light_level: Mp4parseContentLightLevel,
}
#[repr(C)] #[derive(Default, Debug)] pubstruct Mp4parseFragmentInfo { pub fragment_duration: u64, // in ticks pub time_scale: u64, // TODO: // info in trex box.
}
/// Parser state for MP4 files, exposed to C callers via raw pointer. /// /// # Pointer stability /// /// Several C API functions return raw pointers into data cached on this /// struct (e.g. sample descriptions, indice tables, PSSH data). Those /// pointers remain valid for the lifetime of the parser, because each /// getter populates its cache **at most once** per key and never replaces /// or reallocates the cached entry afterward. /// /// If you add a new getter that hands a raw pointer to C: /// - Store the backing data on this struct so it lives long enough. /// - Return the existing cached entry when the same key is requested /// again; **never** unconditionally rebuild and re-insert, as the /// `insert` would drop the old value and invalidate the pointer C is /// still holding. /// - Add a `repeated_*` regression test that calls the getter twice and /// asserts pointer equality. #[derive(Default)] pubstruct Mp4parseParser {
context: MediaContext,
opus_header: TryHashMap<(u32, usize), TryVec<u8>>,
pssh_data: Option<TryVec<u8>>,
sample_table: TryHashMap<u32, TryVec<Indice>>, // Store a mapping from track index (not id) to associated sample // descriptions. Because each track has a variable number of sample // descriptions, and because we need the data to live long enough to be // copied out by callers, we store these on the parser struct.
audio_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackAudioSampleInfo>>,
video_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackVideoSampleInfo>>,
}
#[repr(C)] #[derive(Debug)] pubstruct Mp4parseAvifInfo { pub premultiplied_alpha: bool, pub major_brand: [u8; 4], pub unsupported_features_bitfield: u32, /// The size of the image; should never be null unless using permissive parsing pub spatial_extents: *const mp4parse::ImageSpatialExtentsProperty, pub nclx_colour_information: *const mp4parse::NclxColourInformation, pub icc_colour_information: Mp4parseByteData, pub image_rotation: mp4parse::ImageRotation, pub image_mirror: *const mp4parse::ImageMirror, pub pixel_aspect_ratio: *const mp4parse::PixelAspectRatio,
/// Whether there is a `pitm` reference to the color image present. pub has_primary_item: bool, /// Bit depth for the item referenced by `pitm`, or 0 if values are inconsistent. pub primary_item_bit_depth: u8, /// Whether there is an `auxl` reference to the `pitm`-accompanying /// alpha image present. pub has_alpha_item: bool, /// Bit depth for the alpha item used by the `pitm`, or 0 if values are inconsistent. pub alpha_item_bit_depth: u8,
/// Whether there is a sequence. Can be true with no primary image. pub has_sequence: bool, /// Indicates whether the EditListBox requests that the image be looped. pub loop_mode: Mp4parseAvifLoopMode, /// Number of times to loop the animation during playback. /// /// The duration of the animation specified in `elst` must be looped to fill the /// duration of the color track. If the resulting loop count is not an integer, /// then it will be ceiled to play past and fill the entire track's duration. pub loop_count: u64, /// The color track's ID, which must be valid if has_sequence is true. pub color_track_id: u32, pub color_track_bit_depth: u8, /// The track ID of the alpha track, will be 0 if no alpha track is present. pub alpha_track_id: u32, pub alpha_track_bit_depth: u8,
}
#[repr(C)] #[derive(Debug)] pubstruct Mp4parseAvifImage { pub primary_image: Mp4parseByteData, /// If no alpha item exists, members' `.length` will be 0 and `.data` will be null pub alpha_image: Mp4parseByteData,
}
/// A unified interface for the parsers which have different contexts, but /// share the same pattern of construction. This allows unification of /// argument validation from C and minimizes the surface of unsafe code. trait ContextParser where Self: Sized,
{ type Context;
impl Read for Mp4parseIo { fn read(&mutself, buf: &mut [u8]) -> std::io::Result<usize> { if buf.len() > isize::MAX as usize { return Err(std::io::Error::other( "buf length overflow in Mp4parseIo Read impl",
));
} let rv = self.read.unwrap()(buf.as_mut_ptr(), buf.len(), self.userdata); if rv >= 0 {
Ok(rv as usize)
} else {
Err(std::io::Error::other("I/O error in Mp4parseIo Read impl"))
}
}
}
// C API wrapper functions.
/// Allocate an `Mp4parseParser*` to read from the supplied `Mp4parseIo` and /// parse the content from the `Mp4parseIo` argument until EOF or error. /// /// # Safety /// /// This function is unsafe because it dereferences the `io` and `parser_out` /// pointers given to it. The caller should ensure that the `Mp4ParseIo` /// struct passed in is a valid pointer. The caller should also ensure the /// members of io are valid: the `read` function should be sanely implemented, /// and the `userdata` pointer should be valid. The `parser_out` should be a /// valid pointer to a location containing a null pointer. Upon successful /// return (`Mp4parseStatus::Ok`), that location will contain the address of /// an `Mp4parseParser` allocated by this function. /// /// To avoid leaking memory, any successful return of this function must be /// paired with a call to `mp4parse_free`. In the event of error, no memory /// will be allocated and `mp4parse_free` must *not* be called. #[no_mangle] pubunsafeextern"C"fn mp4parse_new(
io: *const Mp4parseIo,
parser_out: *mut *mut Mp4parseParser,
) -> Mp4parseStatus {
mp4parse_new_common(io, ParseStrictness::Normal, parser_out)
}
/// Allocate an `Mp4parseAvifParser*` to read from the supplied `Mp4parseIo`. /// /// See mp4parse_new; this function is identical except that it allocates an /// `Mp4parseAvifParser`, which (when successful) must be paired with a call /// to mp4parse_avif_free. /// /// # Safety /// /// Same as mp4parse_new. #[no_mangle] pubunsafeextern"C"fn mp4parse_avif_new(
io: *const Mp4parseIo,
strictness: ParseStrictness,
parser_out: *mut *mut Mp4parseAvifParser,
) -> Mp4parseStatus {
mp4parse_new_common(io, strictness, parser_out)
}
/// Free an `Mp4parseParser*` allocated by `mp4parse_new()`. /// /// # Safety /// /// This function is unsafe because it creates a box from a raw pointer. /// Callers should ensure that the parser pointer points to a valid /// `Mp4parseParser` created by `mp4parse_new`. #[no_mangle] pubunsafeextern"C"fn mp4parse_free(parser: *mut Mp4parseParser) {
assert!(!parser.is_null()); let _ = TryBox::from_raw(parser);
}
/// Free an `Mp4parseAvifParser*` allocated by `mp4parse_avif_new()`. /// /// # Safety /// /// This function is unsafe because it creates a box from a raw pointer. /// Callers should ensure that the parser pointer points to a valid /// `Mp4parseAvifParser` created by `mp4parse_avif_new`. #[no_mangle] pubunsafeextern"C"fn mp4parse_avif_free(parser: *mut Mp4parseAvifParser) {
assert!(!parser.is_null()); let _ = TryBox::from_raw(parser);
}
/// Return the number of tracks parsed by previous `mp4parse_read()` call. /// /// # Safety /// /// This function is unsafe because it dereferences both the parser and count /// raw pointers passed into it. Callers should ensure the parser pointer /// points to a valid `Mp4parseParser`, and that the count pointer points an /// appropriate memory location to have a `u32` written to. #[no_mangle] pubunsafeextern"C"fn mp4parse_get_track_count(
parser: *const Mp4parseParser,
count: *mut u32,
) -> Mp4parseStatus { // Validate arguments from C. if parser.is_null() || count.is_null() { return Mp4parseStatus::BadArg;
} let context = (*parser).context();
// Make sure the track count fits in a u32. if context.tracks.len() > u32::MAX as usize { return Mp4parseStatus::Invalid;
}
*count = context.tracks.len() as u32;
Mp4parseStatus::Ok
}
/// Fill the supplied `Mp4parseTrackInfo` with metadata for `track`. /// /// # Safety /// /// This function is unsafe because it dereferences the the parser and info raw /// pointers passed to it. Callers should ensure the parser pointer points to a /// valid `Mp4parseParser` and that the info pointer points to a valid /// `Mp4parseTrackInfo`. #[no_mangle] pubunsafeextern"C"fn mp4parse_get_track_info(
parser: *mut Mp4parseParser,
track_index: u32,
info: *mut Mp4parseTrackInfo,
) -> Mp4parseStatus { if parser.is_null() || info.is_null() { return Mp4parseStatus::BadArg;
}
// Initialize fields to default values to ensure all fields are always valid.
*info = Default::default();
let context = (*parser).context_mut(); let track_index: usize = track_index as usize; let info: &mut Mp4parseTrackInfo = &mut *info;
if track_index >= context.tracks.len() { return Mp4parseStatus::BadArg;
}
// Empty duration is in the context's timescale, convert it and return it in the track's // timescale let empty_duration: CheckedInteger<u64> = match track.empty_duration.map_or(Some(0), |empty_duration| {
rational_scale(empty_duration.0, context_timescale.0, timescale.0)
}) {
Some(time) => mp4parse::unstable::CheckedInteger(time),
None => return Mp4parseStatus::Invalid,
};
// If an edited duration (from elst) is present, use that instead of the raw track duration // This is important for files produced by afconvert which use elst to specify the actual // duration iflet Some(edited_duration) = track.edited_duration { // edited_duration is in context timescale, need to convert to track timescale match rational_scale(edited_duration.0, context_timescale.0, timescale.0) {
Some(duration) => info.duration = duration,
None => return Mp4parseStatus::Invalid,
}
} else { match track.duration {
Some(duration) => info.duration = duration.0,
None => { // Duration unknown; stagefright returns 0 for this.
info.duration = 0
}
}
}
} else { return Mp4parseStatus::Invalid;
}
/// Fill the supplied `Mp4parseTrackAudioInfo` with metadata for `track`. /// /// # Safety /// /// This function is unsafe because it dereferences the the parser and info raw /// pointers passed to it. Callers should ensure the parser pointer points to a /// valid `Mp4parseParser` and that the info pointer points to a valid /// `Mp4parseTrackAudioInfo`. #[no_mangle] pubunsafeextern"C"fn mp4parse_get_track_audio_info(
parser: *mut Mp4parseParser,
track_index: u32,
info: *mut Mp4parseTrackAudioInfo,
) -> Mp4parseStatus { if parser.is_null() || info.is_null() { return Mp4parseStatus::BadArg;
}
// Initialize fields to default values to ensure all fields are always valid.
*info = Default::default();
iflet Some(p) = audio
.protection_info
.iter()
.find(|sinf| sinf.tenc.is_some())
{
sample_info.protected_data.original_format =
OptionalFourCc::Some(p.original_format.value);
sample_info.protected_data.scheme_type = match p.scheme_type {
Some(ref scheme_type_box) => { match scheme_type_box.scheme_type.value.as_ref() {
b"cenc" => Mp4ParseEncryptionSchemeType::Cenc,
b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, // We don't support other schemes, and shouldn't reach // this case. Try to gracefully handle by treating as // no encryption case.
_ => Mp4ParseEncryptionSchemeType::None,
}
}
None => Mp4ParseEncryptionSchemeType::None,
}; iflet Some(ref tenc) = p.tenc {
sample_info.protected_data.is_encrypted = tenc.is_encrypted;
sample_info.protected_data.iv_size = tenc.iv_size;
sample_info.protected_data.kid.set_data(&(tenc.kid));
sample_info.protected_data.crypt_byte_block =
tenc.crypt_byte_block_count.unwrap_or(0);
sample_info.protected_data.skip_byte_block =
tenc.skip_byte_block_count.unwrap_or(0); iflet Some(ref iv_vec) = tenc.constant_iv { if iv_vec.len() > u32::MAX as usize { return Err(Mp4parseStatus::Invalid);
}
sample_info.protected_data.constant_iv.set_data(iv_vec);
};
}
}
audio_sample_infos.push(sample_info)?;
}
parser
.audio_track_sample_descriptions
.insert_cache_entry(track_index, audio_sample_infos)?; match parser.audio_track_sample_descriptions.get(&track_index) {
Some(sample_info) => { if sample_info.len() > u32::MAX as usize { // Should never happen due to upper limits on number of sample // descriptions a track can have, but lets be safe. return Err(Mp4parseStatus::Invalid);
}
info.sample_info_count = sample_info.len() as u32;
info.sample_info = if sample_info.is_empty() {
std::ptr::null()
} else {
sample_info.as_ptr()
};
}
None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info!
}
Ok(())
}
/// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`. /// /// # Safety /// /// This function is unsafe because it dereferences the the parser and info raw /// pointers passed to it. Callers should ensure the parser pointer points to a /// valid `Mp4parseParser` and that the info pointer points to a valid /// `Mp4parseTrackVideoInfo`. #[no_mangle] pubunsafeextern"C"fn mp4parse_get_track_video_info(
parser: *mut Mp4parseParser,
track_index: u32,
info: *mut Mp4parseTrackVideoInfo,
) -> Mp4parseStatus { if parser.is_null() || info.is_null() { return Mp4parseStatus::BadArg;
}
// Initialize fields to default values to ensure all fields are always valid.
*info = Default::default();
parser
.video_track_sample_descriptions
.insert_cache_entry(track_index, video_sample_infos)?; match parser.video_track_sample_descriptions.get(&track_index) {
Some(sample_info) => { if sample_info.len() > u32::MAX as usize { // Should never happen due to upper limits on number of sample // descriptions a track can have, but lets be safe. return Err(Mp4parseStatus::Invalid);
}
info.sample_info_count = sample_info.len() as u32;
info.sample_info = if sample_info.is_empty() {
std::ptr::null()
} else {
sample_info.as_ptr()
};
}
None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info!
}
Ok(())
}
/// Return a struct containing meta information read by previous /// `mp4parse_avif_new()` call. /// /// `color_track_id`and `alpha_track_id` will be 0 if has_sequence is false. /// `alpha_track_id` will be 0 if no alpha aux track is present. /// /// # Safety /// /// This function is unsafe because it dereferences both the parser and /// avif_info raw pointers passed into it. Callers should ensure the parser /// pointer points to a valid `Mp4parseAvifParser`, and that the avif_info /// pointer points to a valid `Mp4parseAvifInfo`. #[no_mangle] pubunsafeextern"C"fn mp4parse_avif_get_info(
parser: *const Mp4parseAvifParser,
avif_info: *mut Mp4parseAvifInfo,
) -> Mp4parseStatus { if parser.is_null() || avif_info.is_null() { return Mp4parseStatus::BadArg;
}
fn get_bit_depth(data: &[u8]) -> u8 { if !data.is_empty() && data.iter().all(|v| *v == data[0]) {
data[0]
} else { 0
}
} let primary_item_bit_depth =
get_bit_depth(context.primary_item_bits_per_channel().unwrap_or(Ok(&[]))?); let alpha_item_bit_depth =
get_bit_depth(context.alpha_item_bits_per_channel().unwrap_or(Ok(&[]))?);
iflet Some(sequence) = &context.sequence { // Tracks must have track_id and samples fn get_track<T>(tracks: &TryVec<Track>, pred: T) -> Option<&Track> where
T: Fn(&Track) -> bool,
{
tracks.iter().find(|track| { if track.track_id.is_none() { returnfalse;
} match &track.stsc {
Some(stsc) => { if stsc.samples.is_empty() { returnfalse;
} if !pred(track) { returnfalse;
}
stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
}
_ => false,
}
})
}
// Color track will be the first track found let color_track = match get_track(&sequence.tracks, |_| true) {
Some(v) => v,
_ => return Ok(info),
};
// Alpha track will be the first track found with auxl.aux_for_track_id set to color_track's id let alpha_track = get_track(&sequence.tracks, |track| match &track.tref {
Some(tref) => tref.has_auxl_reference(color_track.track_id.unwrap()),
_ => false,
});
/// Return a pointer to the primary item parsed by previous `mp4parse_avif_new()` call. /// /// # Safety /// /// This function is unsafe because it dereferences both the parser and /// avif_image raw pointers passed into it. Callers should ensure the parser /// pointer points to a valid `Mp4parseAvifParser`, and that the avif_image /// pointer points to a valid `Mp4parseAvifImage`. If there was not a previous /// successful call to `mp4parse_avif_read()`, no guarantees are made as to /// the state of `avif_image`. If `avif_image.alpha_image.coded_data` is set to /// a positive `length` and non-null `data`, then the `avif_image` contains a /// valid alpha channel data. Otherwise, the image is opaque. #[no_mangle] pubunsafeextern"C"fn mp4parse_avif_get_image(
parser: *const Mp4parseAvifParser,
avif_image: *mut Mp4parseAvifImage,
) -> Mp4parseStatus { if parser.is_null() || avif_image.is_null() { return Mp4parseStatus::BadArg;
}
/// Fill the supplied `Mp4parseByteData` with index information from `track`. /// /// # Safety /// /// This function is unsafe because it dereferences the the parser and indices /// raw pointers passed to it. Callers should ensure the parser pointer points /// to a valid `Mp4parseParser` and that the indices pointer points to a valid /// `Mp4parseByteData`. #[no_mangle] pubunsafeextern"C"fn mp4parse_get_indice_table(
parser: *mut Mp4parseParser,
track_id: u32,
indices: *mut Mp4parseByteData,
) -> Mp4parseStatus { if parser.is_null() { return Mp4parseStatus::BadArg;
}
// Initialize fields to default values to ensure all fields are always valid.
*indices = Default::default();
/// Fill the supplied `Mp4parseByteData` with index information from `track`. /// /// # Safety /// /// This function is unsafe because it dereferences both the parser and /// indices raw pointers passed to it. Callers should ensure the parser /// points to a valid `Mp4parseAvifParser` and indices points to a valid /// `Mp4parseByteData`. #[no_mangle] pubunsafeextern"C"fn mp4parse_avif_get_indice_table(
parser: *mut Mp4parseAvifParser,
track_id: u32,
indices: *mut Mp4parseByteData,
timescale: *mut u64,
) -> Mp4parseStatus { if parser.is_null() { return Mp4parseStatus::BadArg;
}
if indices.is_null() { return Mp4parseStatus::BadArg;
}
if timescale.is_null() { return Mp4parseStatus::BadArg;
}
// Initialize fields to default values to ensure all fields are always valid.
*indices = Default::default();
iflet Some(sequence) = &(*parser).context.sequence { // Use the top level timescale, and the track timescale if present. letmut found_timescale = false; iflet Some(context_timescale) = sequence.timescale {
*timescale = context_timescale.0;
found_timescale = true;
} let maybe_track_timescale = match sequence
.tracks
.iter()
.find(|track| track.track_id == Some(track_id))
{
Some(track) => track.timescale,
_ => None,
}; iflet Some(track_timescale) = maybe_track_timescale {
found_timescale = true;
*timescale = track_timescale.0;
} if !found_timescale { return Mp4parseStatus::Invalid;
} return get_indice_table(
sequence,
&mut (*parser).sample_table,
track_id,
&mut *indices,
)
.into();
}
let media_time = match &track.media_time {
&Some(t) => i64::try_from(t.0).ok().map(Into::into),
_ => None,
};
let empty_duration: Option<CheckedInteger<_>> = match &track.empty_duration {
&Some(e) => i64::try_from(e.0).ok().map(Into::into),
_ => None,
};
// Find the track start offset time from 'elst'. // 'media_time' maps start time onward, 'empty_duration' adds time offset // before first frame is displayed. let offset_time = match (empty_duration, media_time) {
(Some(e), Some(m)) => (e - m).ok_or(Err(Mp4parseStatus::Invalid))?,
(Some(e), None) => e,
(None, Some(m)) => m,
_ => 0.into(),
};
/// Fill the supplied `Mp4parseFragmentInfo` with metadata from fragmented file. /// /// # Safety /// /// This function is unsafe because it dereferences the the parser and /// info raw pointers passed to it. Callers should ensure the parser /// pointer points to a valid `Mp4parseParser` and that the info pointer points /// to a valid `Mp4parseFragmentInfo`. #[no_mangle] pubunsafeextern"C"fn mp4parse_get_fragment_info(
parser: *mut Mp4parseParser,
info: *mut Mp4parseFragmentInfo,
) -> Mp4parseStatus { if parser.is_null() || info.is_null() { return Mp4parseStatus::BadArg;
}
// Initialize fields to default values to ensure all fields are always valid.
*info = Default::default();
let context = (*parser).context(); let info: &mut Mp4parseFragmentInfo = &mut *info;
info.fragment_duration = 0;
let duration = match context.mvex {
Some(ref mvex) => mvex.fragment_duration,
None => return Mp4parseStatus::Invalid,
};
/// Determine if an mp4 file is fragmented. A fragmented file needs mvex table /// and contains no data in stts, stsc, and stco boxes. /// /// # Safety /// /// This function is unsafe because it dereferences the the parser and /// fragmented raw pointers passed to it. Callers should ensure the parser /// pointer points to a valid `Mp4parseParser` and that the fragmented pointer /// points to an appropriate memory location to have a `u8` written to. #[no_mangle] pubunsafeextern"C"fn mp4parse_is_fragmented(
parser: *mut Mp4parseParser,
track_id: u32,
fragmented: *mut u8,
) -> Mp4parseStatus { if parser.is_null() { return Mp4parseStatus::BadArg;
}
let context = (*parser).context_mut(); let tracks = &context.tracks;
(*fragmented) = falseas u8;
if context.mvex.is_none() { return Mp4parseStatus::Ok;
}
/// Get 'pssh' system id and 'pssh' box content for eme playback. /// /// The data format of the `info` struct passed to gecko is: /// /// - system id (16 byte uuid) /// - pssh box size (32-bit native endian) /// - pssh box content (including header) /// /// # Safety /// /// This function is unsafe because it dereferences the the parser and /// info raw pointers passed to it. Callers should ensure the parser /// pointer points to a valid `Mp4parseParser` and that the fragmented pointer /// points to a valid `Mp4parsePsshInfo`. #[no_mangle] pubunsafeextern"C"fn mp4parse_get_pssh_info(
parser: *mut Mp4parseParser,
info: *mut Mp4parsePsshInfo,
) -> Mp4parseStatus { if parser.is_null() || info.is_null() { return Mp4parseStatus::BadArg;
}
// Initialize fields to default values to ensure all fields are always valid.
*info = Default::default();
// Passing a null Mp4parseIo is an error. letmut parser = std::ptr::null_mut(); let rv = mp4parse_new(std::ptr::null(), &mut parser);
assert_eq!(rv, Mp4parseStatus::BadArg);
assert!(parser.is_null());
let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut();
// Passing an Mp4parseIo with null members is an error. let io = Mp4parseIo {
read: None,
userdata: null_mut,
}; letmut parser = std::ptr::null_mut(); let rv = mp4parse_new(&io, &mut parser);
assert_eq!(rv, Mp4parseStatus::BadArg);
assert!(parser.is_null());
letmut dummy_value = 42; let io = Mp4parseIo {
read: None,
userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
}; letmut parser = std::ptr::null_mut(); let rv = mp4parse_new(&io, &mut parser);
assert_eq!(rv, Mp4parseStatus::BadArg);
assert!(parser.is_null());
assert_eq!(Mp4parseStatus::Ok, unsafe {
mp4parse_get_track_info(parser, 1, &mut info)
});
assert_eq!(info.track_type, Mp4parseTrackType::Audio);
assert_eq!(info.track_id, 2); // Note: this file has an elst, and so this duration is from elst
assert_eq!(info.duration, 1920);
assert_eq!(info.media_time, 1024);
unsafe {
mp4parse_free(parser);
}
}
#[test] fn minimal_mp4_get_track_video_info() { let parser = parse_minimal_mp4();
// Query the same track again — must return the cached pointer, // not rebuild (which would drop the old Vec and invalidate ptr1). letmut audio2 = Mp4parseTrackAudioInfo::default();
assert_eq!(
Mp4parseStatus::Ok,
mp4parse_get_track_audio_info(parser, 1, &mut audio2)
);
assert_eq!(audio2.sample_info_count, 1); let ptr2 = audio2.sample_info;
// Pointer must be stable across calls.
assert_eq!(ptr1, ptr2);
// Data behind the first pointer must still be valid.
assert_eq!((*audio1.sample_info).channels, 1);
assert_eq!((*audio1.sample_info).bit_depth, 16);
assert_eq!((*audio1.sample_info).sample_rate, 48000);
mp4parse_free(parser);
}
}
// Test file: tests/opus_audioinit_two_desc.mp4 // // Generated from opus_audioinit.mp4 by duplicating the Opus stsd entry so // the track has two sample descriptions. The second entry has // channelcount = 2 and dOps output_channel_count = 2 (vs 1 in the // original) so the two descriptions are distinguishable. #[test] fn multi_opus_description_codec_specific_pointers_are_valid() { letmut file = std::fs::File::open("tests/opus_audioinit_two_desc.mp4").expect("Unknown file"); let io = Mp4parseIo {
read: Some(valid_read),
userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
};
let si0 = &*audio.sample_info; let si1 = &*audio.sample_info.add(1);
// Both descriptions must be Opus.
assert_eq!(si0.codec_type, Mp4parseCodec::Opus);
assert_eq!(si1.codec_type, Mp4parseCodec::Opus);
// Distinguish the two entries by channel count.
assert_eq!(si0.channels, 1);
assert_eq!(si1.channels, 2);
// Both must have non-empty, non-null codec_specific_config.
assert!(si0.codec_specific_config.length > 0);
assert!(!si0.codec_specific_config.data.is_null());
assert!(si1.codec_specific_config.length > 0);
assert!(!si1.codec_specific_config.data.is_null());
// The opus_header map must have a separate entry per description. let parser_ref = &*parser;
assert_eq!(
parser_ref.opus_header.len(), 2, "opus_header must have one entry per stsd description, not per track"
);
// Pointers must be distinct (backed by separate opus_header entries).
assert_ne!(
si0.codec_specific_config.data,
si1.codec_specific_config.data
);
// Dereference the data to exercise the UAF under ASAN/valgrind. let config0 = std::slice::from_raw_parts(
si0.codec_specific_config.data,
si0.codec_specific_config.length,
); let config1 = std::slice::from_raw_parts(
si1.codec_specific_config.data,
si1.codec_specific_config.length,
);
// Serialized opus headers differ because channel counts differ.
assert_eq!(config0.len(), config1.len());
assert_ne!(config0, config1);
mp4parse_free(parser);
}
}
#[test] fn repeated_get_track_video_info_returns_stable_pointer() { let parser = parse_minimal_mp4();
// Query the same track again — must return the cached pointer. letmut video2 = Mp4parseTrackVideoInfo::default();
assert_eq!(
Mp4parseStatus::Ok,
mp4parse_get_track_video_info(parser, 0, &mut video2)
);
assert_eq!(video2.sample_info_count, 1); let ptr2 = video2.sample_info;
// Pointer must be stable across calls.
assert_eq!(ptr1, ptr2);
// Data behind the first pointer must still be valid.
assert_eq!((*video1.sample_info).image_width, 320);
assert_eq!((*video1.sample_info).image_height, 240);
// tkhd-derived fields must be populated on the cached path too // (the caller zeroes info before each call).
assert_eq!(video2.display_width, 320);
assert_eq!(video2.display_height, 240);
mp4parse_free(parser);
}
}
#[test] fn parse_no_timescale() { letmut file = std::fs::File::open("tests/no_timescale.mp4").expect("Unknown file"); let io = Mp4parseIo {
read: Some(valid_read),
userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
};
// The file has a video track, but the track has a timescale of 0, so. letmut track_info = Mp4parseTrackInfo::default();
rv = mp4parse_get_track_info(parser, 0, &mut track_info);
assert_eq!(rv, Mp4parseStatus::Invalid);
};
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.