summaryrefslogtreecommitdiffstats
path: root/third_party/rust/mp4parse_capi/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/mp4parse_capi/src')
-rw-r--r--third_party/rust/mp4parse_capi/src/lib.rs1863
1 files changed, 1863 insertions, 0 deletions
diff --git a/third_party/rust/mp4parse_capi/src/lib.rs b/third_party/rust/mp4parse_capi/src/lib.rs
new file mode 100644
index 0000000000..5b362f303f
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/src/lib.rs
@@ -0,0 +1,1863 @@
+//! 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 std::convert::TryFrom;
+use std::convert::TryInto;
+
+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
+pub use mp4parse::ParseStrictness;
+use mp4parse::SampleEntry;
+pub use 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)]
+struct Box;
+#[allow(dead_code)]
+struct HashMap;
+#[allow(dead_code)]
+struct String;
+
+#[repr(C)]
+#[derive(PartialEq, Eq, Debug, Default)]
+pub enum Mp4parseTrackType {
+ #[default]
+ Video = 0,
+ Picture = 1,
+ AuxiliaryVideo = 2,
+ Audio = 3,
+ Metadata = 4,
+}
+
+#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
+#[repr(C)]
+#[derive(PartialEq, Eq, Debug, Default)]
+pub enum Mp4parseCodec {
+ #[default]
+ Unknown,
+ Aac,
+ Flac,
+ Opus,
+ Avc,
+ Vp9,
+ Av1,
+ Mp3,
+ Mp4v,
+ Jpeg, // for QT JPEG atom in video track
+ Ac3,
+ Ec3,
+ Alac,
+ H263,
+ #[cfg(feature = "3gpp")]
+ AMRNB,
+ #[cfg(feature = "3gpp")]
+ AMRWB,
+}
+
+#[repr(C)]
+#[derive(PartialEq, Eq, Debug, Default)]
+pub enum 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.
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseTrackInfo {
+ pub track_type: Mp4parseTrackType,
+ pub track_id: u32,
+ pub duration: u64,
+ pub media_time: CheckedInteger<i64>,
+ pub time_scale: u32,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseByteData {
+ pub length: usize,
+ // cheddar can't handle generic type, so it needs to be multiple data types here.
+ pub data: *const u8,
+ pub indices: *const Indice,
+}
+
+impl Mp4parseByteData {
+ fn with_data(slice: &[u8]) -> Self {
+ Self {
+ length: slice.len(),
+ data: if !slice.is_empty() {
+ slice.as_ptr()
+ } else {
+ std::ptr::null()
+ },
+ indices: std::ptr::null(),
+ }
+ }
+}
+
+impl Default for Mp4parseByteData {
+ fn default() -> Self {
+ Self {
+ length: 0,
+ data: std::ptr::null(),
+ indices: std::ptr::null(),
+ }
+ }
+}
+
+impl Mp4parseByteData {
+ fn set_data(&mut self, data: &[u8]) {
+ self.length = data.len();
+ self.data = data.as_ptr();
+ }
+
+ fn set_indices(&mut self, data: &[Indice]) {
+ self.length = data.len();
+ self.indices = data.as_ptr();
+ }
+}
+
+#[repr(C)]
+#[derive(Default)]
+pub struct Mp4parsePsshInfo {
+ pub data: Mp4parseByteData,
+}
+
+#[repr(u8)]
+#[derive(Debug, PartialEq, Eq)]
+pub enum OptionalFourCc {
+ None,
+ Some([u8; 4]),
+}
+
+impl Default for OptionalFourCc {
+ fn default() -> Self {
+ Self::None
+ }
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct 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
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseTrackAudioSampleInfo {
+ pub codec_type: Mp4parseCodec,
+ pub channels: u16,
+ pub bit_depth: u16,
+ pub sample_rate: u32,
+ pub profile: u16,
+ pub extended_profile: u16,
+ pub codec_specific_config: Mp4parseByteData,
+ pub extra_data: Mp4parseByteData,
+ pub protected_data: Mp4parseSinfInfo,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseTrackAudioInfo {
+ pub sample_info_count: u32,
+ pub sample_info: *const Mp4parseTrackAudioSampleInfo,
+}
+
+impl Default for Mp4parseTrackAudioInfo {
+ fn default() -> Self {
+ Self {
+ sample_info_count: 0,
+ sample_info: std::ptr::null(),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseTrackVideoSampleInfo {
+ pub codec_type: Mp4parseCodec,
+ pub image_width: u16,
+ pub image_height: u16,
+ pub extra_data: Mp4parseByteData,
+ pub protected_data: Mp4parseSinfInfo,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseTrackVideoInfo {
+ pub display_width: u32,
+ pub display_height: u32,
+ pub rotation: u16,
+ pub sample_info_count: u32,
+ pub sample_info: *const Mp4parseTrackVideoSampleInfo,
+}
+
+impl Default for Mp4parseTrackVideoInfo {
+ fn default() -> Self {
+ Self {
+ display_width: 0,
+ display_height: 0,
+ rotation: 0,
+ sample_info_count: 0,
+ sample_info: std::ptr::null(),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseFragmentInfo {
+ pub fragment_duration: u64, // in ticks
+ pub time_scale: u64,
+ // TODO:
+ // info in trex box.
+}
+
+#[derive(Default)]
+pub struct Mp4parseParser {
+ context: MediaContext,
+ opus_header: TryHashMap<u32, TryVec<u8>>,
+ pssh_data: 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, Default)]
+pub enum Mp4parseAvifLoopMode {
+ #[default]
+ NoEdits,
+ LoopByCount,
+ LoopInfinitely,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct 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)]
+pub struct 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;
+
+ fn with_context(context: Self::Context) -> Self;
+
+ fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context>;
+}
+
+impl Mp4parseParser {
+ fn context(&self) -> &MediaContext {
+ &self.context
+ }
+
+ fn context_mut(&mut self) -> &mut MediaContext {
+ &mut self.context
+ }
+}
+
+impl ContextParser for Mp4parseParser {
+ type Context = MediaContext;
+
+ fn with_context(context: Self::Context) -> Self {
+ Self {
+ context,
+ ..Default::default()
+ }
+ }
+
+ fn read<T: Read>(io: &mut T, _strictness: ParseStrictness) -> mp4parse::Result<Self::Context> {
+ let r = mp4parse::read_mp4(io);
+ log::debug!("mp4parse::read_mp4 -> {:?}", r);
+ r
+ }
+}
+
+#[derive(Default)]
+pub struct Mp4parseAvifParser {
+ context: AvifContext,
+ sample_table: TryHashMap<u32, TryVec<Indice>>,
+}
+
+impl Mp4parseAvifParser {
+ fn context(&self) -> &AvifContext {
+ &self.context
+ }
+}
+
+impl ContextParser for Mp4parseAvifParser {
+ type Context = AvifContext;
+
+ fn with_context(context: Self::Context) -> Self {
+ Self {
+ context,
+ ..Default::default()
+ }
+ }
+
+ fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context> {
+ let r = mp4parse::read_avif(io, strictness);
+ if r.is_err() {
+ log::debug!("{:?}", r);
+ }
+ log::trace!("mp4parse::read_avif -> {:?}", r);
+ r
+ }
+}
+
+#[repr(C)]
+#[derive(Clone)]
+pub struct Mp4parseIo {
+ pub read: Option<
+ extern "C" 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 Mp4parseIo {
+ 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 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::new(
+ std::io::ErrorKind::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]
+pub unsafe extern "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]
+pub unsafe extern "C" fn mp4parse_avif_new(
+ io: *const Mp4parseIo,
+ strictness: ParseStrictness,
+ parser_out: *mut *mut Mp4parseAvifParser,
+) -> Mp4parseStatus {
+ mp4parse_new_common(io, strictness, parser_out)
+}
+
+unsafe fn mp4parse_new_common<P: ContextParser>(
+ io: *const Mp4parseIo,
+ strictness: ParseStrictness,
+ parser_out: *mut *mut P,
+) -> Mp4parseStatus {
+ // Validate arguments from C.
+ if io.is_null()
+ || (*io).userdata.is_null()
+ || (*io).read.is_none()
+ || parser_out.is_null()
+ || !(*parser_out).is_null()
+ {
+ Mp4parseStatus::BadArg
+ } else {
+ match mp4parse_new_common_safe(&mut (*io).clone(), strictness) {
+ Ok(parser) => {
+ *parser_out = parser;
+ Mp4parseStatus::Ok
+ }
+ Err(status) => status,
+ }
+ }
+}
+
+fn mp4parse_new_common_safe<T: Read, P: ContextParser>(
+ io: &mut T,
+ strictness: ParseStrictness,
+) -> Result<*mut P, Mp4parseStatus> {
+ P::read(io, strictness)
+ .map(P::with_context)
+ .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
+ .map(TryBox::into_raw)
+ .map_err(Mp4parseStatus::from)
+}
+
+/// 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]
+pub unsafe extern "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]
+pub unsafe extern "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]
+pub unsafe extern "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_value() 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]
+pub unsafe extern "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;
+ }
+
+ info.track_type = match context.tracks[track_index].track_type {
+ TrackType::Video => Mp4parseTrackType::Video,
+ TrackType::Picture => Mp4parseTrackType::Picture,
+ TrackType::AuxiliaryVideo => Mp4parseTrackType::AuxiliaryVideo,
+ TrackType::Audio => Mp4parseTrackType::Audio,
+ TrackType::Metadata => Mp4parseTrackType::Metadata,
+ TrackType::Unknown => return Mp4parseStatus::Unsupported,
+ };
+
+ let track = &context.tracks[track_index];
+
+ if let (Some(timescale), Some(_)) = (track.timescale, context.timescale) {
+ info.time_scale = timescale.0 as u32;
+ let media_time: CheckedInteger<u64> = track
+ .media_time
+ .map_or(0.into(), |media_time| media_time.0.into());
+
+ let empty_duration: CheckedInteger<u64> = track
+ .empty_duration
+ .map_or(0.into(), |empty_duration| empty_duration.0.into());
+
+ info.media_time = match media_time - empty_duration {
+ Some(difference) => difference,
+ None => return Mp4parseStatus::Invalid,
+ };
+
+ match track.duration {
+ Some(duration) => info.duration = duration.0,
+ None => {
+ // Duration unknown; stagefright returns 0 for this.
+ info.duration = 0
+ }
+ }
+ } else {
+ return Mp4parseStatus::Invalid;
+ }
+
+ info.track_id = match track.track_id {
+ Some(track_id) => track_id,
+ None => return Mp4parseStatus::Invalid,
+ };
+ Mp4parseStatus::Ok
+}
+
+/// 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]
+pub unsafe extern "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();
+
+ get_track_audio_info(&mut *parser, track_index, &mut *info).into()
+}
+
+fn get_track_audio_info(
+ parser: &mut Mp4parseParser,
+ track_index: u32,
+ info: &mut Mp4parseTrackAudioInfo,
+) -> Result<(), Mp4parseStatus> {
+ let Mp4parseParser {
+ context,
+ opus_header,
+ ..
+ } = parser;
+
+ if track_index as usize >= context.tracks.len() {
+ return Err(Mp4parseStatus::BadArg);
+ }
+
+ let track = &context.tracks[track_index as usize];
+
+ if track.track_type != TrackType::Audio {
+ return Err(Mp4parseStatus::Invalid);
+ }
+
+ // Handle track.stsd
+ let stsd = match track.stsd {
+ Some(ref stsd) => stsd,
+ None => return Err(Mp4parseStatus::Invalid), // Stsd should be present
+ };
+
+ if stsd.descriptions.is_empty() {
+ return Err(Mp4parseStatus::Invalid); // Should have at least 1 description
+ }
+
+ let mut audio_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?;
+ for description in stsd.descriptions.iter() {
+ let mut sample_info = Mp4parseTrackAudioSampleInfo::default();
+ let audio = match description {
+ SampleEntry::Audio(a) => a,
+ _ => return Err(Mp4parseStatus::Invalid),
+ };
+
+ // UNKNOWN for unsupported format.
+ sample_info.codec_type = match audio.codec_specific {
+ AudioCodecSpecific::OpusSpecificBox(_) => Mp4parseCodec::Opus,
+ AudioCodecSpecific::FLACSpecificBox(_) => Mp4parseCodec::Flac,
+ AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => {
+ Mp4parseCodec::Aac
+ }
+ AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => {
+ Mp4parseCodec::Mp3
+ }
+ AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM => {
+ Mp4parseCodec::Unknown
+ }
+ AudioCodecSpecific::MP3 => Mp4parseCodec::Mp3,
+ AudioCodecSpecific::ALACSpecificBox(_) => Mp4parseCodec::Alac,
+ #[cfg(feature = "3gpp")]
+ AudioCodecSpecific::AMRSpecificBox(_) => {
+ if audio.codec_type == CodecType::AMRNB {
+ Mp4parseCodec::AMRNB
+ } else {
+ Mp4parseCodec::AMRWB
+ }
+ }
+ };
+ sample_info.channels = audio.channelcount as u16;
+ sample_info.bit_depth = audio.samplesize;
+ sample_info.sample_rate = audio.samplerate as u32;
+ // sample_info.profile is handled below on a per case basis
+
+ match audio.codec_specific {
+ AudioCodecSpecific::ES_Descriptor(ref esds) => {
+ if esds.codec_esds.len() > std::u32::MAX as usize {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.extra_data.length = esds.codec_esds.len();
+ sample_info.extra_data.data = esds.codec_esds.as_ptr();
+ sample_info.codec_specific_config.length = esds.decoder_specific_data.len();
+ sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr();
+ if let Some(rate) = esds.audio_sample_rate {
+ sample_info.sample_rate = rate;
+ }
+ if let Some(channels) = esds.audio_channel_count {
+ sample_info.channels = channels;
+ }
+ if let Some(profile) = esds.audio_object_type {
+ sample_info.profile = profile;
+ }
+ sample_info.extended_profile = match esds.extended_audio_object_type {
+ Some(extended_profile) => extended_profile,
+ _ => sample_info.profile,
+ };
+ }
+ 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 Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.codec_specific_config.length = streaminfo.data.len();
+ sample_info.codec_specific_config.data = streaminfo.data.as_ptr();
+ }
+ AudioCodecSpecific::OpusSpecificBox(ref opus) => {
+ let mut v = TryVec::new();
+ match serialize_opus_header(opus, &mut v) {
+ Err(_) => {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ Ok(_) => {
+ opus_header.insert(track_index, v)?;
+ if let Some(v) = opus_header.get(&track_index) {
+ if v.len() > std::u32::MAX as usize {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.codec_specific_config.length = v.len();
+ sample_info.codec_specific_config.data = v.as_ptr();
+ }
+ }
+ }
+ }
+ AudioCodecSpecific::ALACSpecificBox(ref alac) => {
+ sample_info.codec_specific_config.length = alac.data.len();
+ sample_info.codec_specific_config.data = alac.data.as_ptr();
+ }
+ AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
+ #[cfg(feature = "3gpp")]
+ AudioCodecSpecific::AMRSpecificBox(_) => (),
+ }
+
+ if let 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,
+ };
+ if let 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);
+ if let Some(ref iv_vec) = tenc.constant_iv {
+ if iv_vec.len() > std::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(track_index, audio_sample_infos)?;
+ match parser.audio_track_sample_descriptions.get(&track_index) {
+ Some(sample_info) => {
+ if sample_info.len() > std::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 = 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]
+pub unsafe extern "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();
+
+ mp4parse_get_track_video_info_safe(&mut *parser, track_index, &mut *info).into()
+}
+
+fn mp4parse_get_track_video_info_safe(
+ parser: &mut Mp4parseParser,
+ track_index: u32,
+ info: &mut Mp4parseTrackVideoInfo,
+) -> Result<(), Mp4parseStatus> {
+ let context = parser.context();
+
+ if track_index as usize >= context.tracks.len() {
+ return Err(Mp4parseStatus::BadArg);
+ }
+
+ let track = &context.tracks[track_index as usize];
+
+ if track.track_type != TrackType::Video {
+ return Err(Mp4parseStatus::Invalid);
+ }
+
+ // Handle track.tkhd
+ 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 Err(Mp4parseStatus::Invalid);
+ }
+
+ // Handle track.stsd
+ let stsd = match track.stsd {
+ Some(ref stsd) => stsd,
+ None => return Err(Mp4parseStatus::Invalid), // Stsd should be present
+ };
+
+ if stsd.descriptions.is_empty() {
+ return Err(Mp4parseStatus::Invalid); // Should have at least 1 description
+ }
+
+ let mut video_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?;
+ for description in stsd.descriptions.iter() {
+ let mut sample_info = Mp4parseTrackVideoSampleInfo::default();
+ let video = match description {
+ SampleEntry::Video(v) => v,
+ _ => return Err(Mp4parseStatus::Invalid),
+ };
+
+ // UNKNOWN for unsupported format.
+ sample_info.codec_type = match video.codec_specific {
+ VideoCodecSpecific::VPxConfig(_) => Mp4parseCodec::Vp9,
+ VideoCodecSpecific::AV1Config(_) => Mp4parseCodec::Av1,
+ VideoCodecSpecific::AVCConfig(_) => Mp4parseCodec::Avc,
+ VideoCodecSpecific::H263Config(_) => Mp4parseCodec::H263,
+ #[cfg(feature = "mp4v")]
+ VideoCodecSpecific::ESDSConfig(_) => Mp4parseCodec::Mp4v,
+ #[cfg(not(feature = "mp4v"))]
+ VideoCodecSpecific::ESDSConfig(_) =>
+ // MP4V (14496-2) video is unsupported.
+ {
+ Mp4parseCodec::Unknown
+ }
+ };
+ sample_info.image_width = video.width;
+ sample_info.image_height = video.height;
+
+ match video.codec_specific {
+ VideoCodecSpecific::AV1Config(ref config) => {
+ sample_info.extra_data.set_data(&config.raw_config);
+ }
+ VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => {
+ sample_info.extra_data.set_data(data);
+ }
+ _ => {}
+ }
+
+ if let Some(p) = video
+ .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,
+ };
+ if let 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);
+ if let Some(ref iv_vec) = tenc.constant_iv {
+ if iv_vec.len() > std::u32::MAX as usize {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.protected_data.constant_iv.set_data(iv_vec);
+ };
+ }
+ }
+ video_sample_infos.push(sample_info)?;
+ }
+
+ parser
+ .video_track_sample_descriptions
+ .insert(track_index, video_sample_infos)?;
+ match parser.video_track_sample_descriptions.get(&track_index) {
+ Some(sample_info) => {
+ if sample_info.len() > std::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 = 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]
+pub unsafe extern "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;
+ }
+
+ if let Ok(info) = mp4parse_avif_get_info_safe((*parser).context()) {
+ *avif_info = info;
+ Mp4parseStatus::Ok
+ } else {
+ Mp4parseStatus::Invalid
+ }
+}
+
+fn mp4parse_avif_get_info_safe(context: &AvifContext) -> mp4parse::Result<Mp4parseAvifInfo> {
+ let info = Mp4parseAvifInfo {
+ premultiplied_alpha: context.premultiplied_alpha,
+ major_brand: context.major_brand.value,
+ unsupported_features_bitfield: context.unsupported_features.into_bitfield(),
+ spatial_extents: context.spatial_extents_ptr()?,
+ nclx_colour_information: context
+ .nclx_colour_information_ptr()
+ .unwrap_or(Ok(std::ptr::null()))?,
+ icc_colour_information: Mp4parseByteData::with_data(
+ context.icc_colour_information().unwrap_or(Ok(&[]))?,
+ ),
+ image_rotation: context.image_rotation()?,
+ image_mirror: context.image_mirror_ptr()?,
+ pixel_aspect_ratio: context.pixel_aspect_ratio_ptr()?,
+
+ has_primary_item: context.primary_item_is_present(),
+ primary_item_bit_depth: 0,
+ has_alpha_item: context.alpha_item_is_present(),
+ alpha_item_bit_depth: 0,
+
+ has_sequence: false,
+ loop_mode: Mp4parseAvifLoopMode::NoEdits,
+ loop_count: 0,
+ color_track_id: 0,
+ color_track_bit_depth: 0,
+ alpha_track_id: 0,
+ alpha_track_bit_depth: 0,
+ };
+
+ 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(&[]))?);
+
+ if let 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() {
+ return false;
+ }
+ match &track.stsc {
+ Some(stsc) => {
+ if stsc.samples.is_empty() {
+ return false;
+ }
+ if !pred(track) {
+ return false;
+ }
+ 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,
+ });
+
+ fn get_av1c(track: &Track) -> Option<&AV1ConfigBox> {
+ if let Some(stsd) = &track.stsd {
+ for entry in &stsd.descriptions {
+ if let SampleEntry::Video(video_entry) = entry {
+ if let VideoCodecSpecific::AV1Config(av1c) = &video_entry.codec_specific {
+ return Some(av1c);
+ }
+ }
+ }
+ }
+
+ None
+ }
+
+ let color_track_id = color_track.track_id.unwrap();
+ let color_track_bit_depth = match get_av1c(color_track) {
+ Some(av1c) => av1c.bit_depth,
+ _ => return Ok(info),
+ };
+
+ let (alpha_track_id, alpha_track_bit_depth) = match alpha_track {
+ Some(track) => (
+ track.track_id.unwrap(),
+ match get_av1c(track) {
+ Some(av1c) => av1c.bit_depth,
+ _ => return Ok(info),
+ },
+ ),
+ _ => (0, 0),
+ };
+
+ let (loop_mode, loop_count) = match color_track.tkhd.as_ref().map(|tkhd| tkhd.duration) {
+ Some(movie_duration) if movie_duration == std::u64::MAX => {
+ (Mp4parseAvifLoopMode::LoopInfinitely, 0)
+ }
+ Some(movie_duration) => match color_track.looped {
+ Some(true) => match color_track.edited_duration.map(|v| v.0) {
+ Some(segment_duration) => {
+ match movie_duration.checked_div(segment_duration).and_then(|n| {
+ match movie_duration.checked_rem(segment_duration) {
+ Some(0) => Some(n.saturating_sub(1)),
+ Some(_) => Some(n),
+ None => None,
+ }
+ }) {
+ Some(n) => (Mp4parseAvifLoopMode::LoopByCount, n),
+ None => (Mp4parseAvifLoopMode::LoopInfinitely, 0),
+ }
+ }
+ None => (Mp4parseAvifLoopMode::NoEdits, 0),
+ },
+ Some(false) => (Mp4parseAvifLoopMode::LoopByCount, 0),
+ None => (Mp4parseAvifLoopMode::NoEdits, 0),
+ },
+ None => (Mp4parseAvifLoopMode::LoopInfinitely, 0),
+ };
+
+ return Ok(Mp4parseAvifInfo {
+ primary_item_bit_depth,
+ alpha_item_bit_depth,
+ has_sequence: true,
+ loop_mode,
+ loop_count,
+ color_track_id,
+ color_track_bit_depth,
+ alpha_track_id,
+ alpha_track_bit_depth,
+ ..info
+ });
+ }
+
+ Ok(info)
+}
+
+/// 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]
+pub unsafe extern "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;
+ }
+
+ if let Ok(image) = mp4parse_avif_get_image_safe(&*parser) {
+ *avif_image = image;
+ Mp4parseStatus::Ok
+ } else {
+ Mp4parseStatus::Invalid
+ }
+}
+
+pub fn mp4parse_avif_get_image_safe(
+ parser: &Mp4parseAvifParser,
+) -> mp4parse::Result<Mp4parseAvifImage> {
+ let context = parser.context();
+ Ok(Mp4parseAvifImage {
+ primary_image: Mp4parseByteData::with_data(
+ context.primary_item_coded_data().unwrap_or(&[]),
+ ),
+ alpha_image: Mp4parseByteData::with_data(context.alpha_item_coded_data().unwrap_or(&[])),
+ })
+}
+
+/// 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]
+pub unsafe extern "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();
+
+ get_indice_table(
+ &(*parser).context,
+ &mut (*parser).sample_table,
+ track_id,
+ &mut *indices,
+ )
+ .into()
+}
+
+/// 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]
+pub unsafe extern "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();
+
+ if let Some(sequence) = &(*parser).context.sequence {
+ // Use the top level timescale, and the track timescale if present.
+ let mut found_timescale = false;
+ if let 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,
+ };
+ if let 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();
+ }
+
+ Mp4parseStatus::BadArg
+}
+
+fn get_indice_table(
+ context: &MediaContext,
+ sample_table_cache: &mut TryHashMap<u32, TryVec<Indice>>,
+ track_id: u32,
+ indices: &mut Mp4parseByteData,
+) -> Result<(), Mp4parseStatus> {
+ let tracks = &context.tracks;
+ let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) {
+ Some(t) => t,
+ _ => return Err(Mp4parseStatus::Invalid),
+ };
+
+ if let Some(v) = sample_table_cache.get(&track_id) {
+ indices.set_indices(v);
+ return Ok(());
+ }
+
+ 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(),
+ };
+
+ if let Some(v) = create_sample_table(track, offset_time) {
+ indices.set_indices(&v);
+ sample_table_cache.insert(track_id, v)?;
+ return Ok(());
+ }
+
+ Err(Mp4parseStatus::Invalid)
+}
+
+/// 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]
+pub unsafe extern "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,
+ };
+
+ if let (Some(time), Some(scale)) = (duration, context.timescale) {
+ info.fragment_duration = time.0;
+ info.time_scale = scale.0;
+ return Mp4parseStatus::Ok;
+ };
+ 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]
+pub unsafe extern "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) = false as u8;
+
+ if context.mvex.is_none() {
+ return Mp4parseStatus::Ok;
+ }
+
+ // check sample tables.
+ let mut iter = tracks.iter();
+ iter.find(|track| track.track_id == Some(track_id))
+ .map_or(Mp4parseStatus::BadArg, |track| {
+ match (&track.stsc, &track.stco, &track.stts) {
+ (Some(stsc), Some(stco), Some(stts))
+ if stsc.samples.is_empty()
+ && stco.offsets.is_empty()
+ && stts.samples.is_empty() =>
+ {
+ (*fragmented) = true as u8
+ }
+ _ => {}
+ };
+ 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]
+pub unsafe extern "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();
+
+ get_pssh_info(&mut *parser, &mut *info).into()
+}
+
+fn get_pssh_info(
+ parser: &mut Mp4parseParser,
+ info: &mut Mp4parsePsshInfo,
+) -> Result<(), Mp4parseStatus> {
+ let Mp4parseParser {
+ context, pssh_data, ..
+ } = parser;
+
+ pssh_data.clear();
+ for pssh in &context.psshs {
+ let content_len = pssh
+ .box_content
+ .len()
+ .try_into()
+ .map_err(|_| Mp4parseStatus::Invalid)?;
+ let mut data_len = TryVec::new();
+ data_len.write_u32::<byteorder::NativeEndian>(content_len)?;
+ pssh_data.extend_from_slice(pssh.system_id.as_slice())?;
+ pssh_data.extend_from_slice(data_len.as_slice())?;
+ pssh_data.extend_from_slice(pssh.box_content.as_slice())?;
+ }
+
+ info.data.set_data(pssh_data);
+
+ Ok(())
+}
+
+#[cfg(test)]
+extern "C" fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
+ -1
+}
+
+#[cfg(test)]
+extern "C" fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[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_eq!(rv, Mp4parseStatus::BadArg);
+ let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+ }
+}
+
+#[test]
+fn arg_validation() {
+ unsafe {
+ let rv = mp4parse_new(std::ptr::null(), std::ptr::null_mut());
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+
+ // Passing a null Mp4parseIo is an error.
+ let mut 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,
+ };
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+ assert!(parser.is_null());
+
+ let mut dummy_value = 42;
+ let io = Mp4parseIo {
+ read: None,
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+ assert!(parser.is_null());
+
+ let mut dummy_info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Video,
+ ..Default::default()
+ };
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info)
+ );
+
+ let mut dummy_video = Mp4parseTrackVideoInfo {
+ display_width: 0,
+ display_height: 0,
+ rotation: 0,
+ sample_info_count: 0,
+ sample_info: std::ptr::null(),
+ };
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video)
+ );
+
+ let mut dummy_audio = Default::default();
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio)
+ );
+ }
+}
+
+#[test]
+fn parser_input_must_be_null() {
+ let mut dummy_value = 42;
+ let io = Mp4parseIo {
+ read: Some(error_read),
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let mut parser = 0xDEAD_BEEF as *mut _;
+ let rv = unsafe { mp4parse_new(&io, &mut parser) };
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+}
+
+#[test]
+fn arg_validation_with_parser() {
+ unsafe {
+ let mut dummy_value = 42;
+ let io = Mp4parseIo {
+ read: Some(error_read),
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Io);
+ assert!(parser.is_null());
+
+ // Null info pointers are an error.
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_info(parser, 0, std::ptr::null_mut())
+ );
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut())
+ );
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut())
+ );
+
+ let mut dummy_info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Video,
+ ..Default::default()
+ };
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_info(parser, 0, &mut dummy_info)
+ );
+
+ let mut dummy_video = Mp4parseTrackVideoInfo {
+ display_width: 0,
+ display_height: 0,
+ rotation: 0,
+ sample_info_count: 0,
+ sample_info: std::ptr::null(),
+ };
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_video_info(parser, 0, &mut dummy_video)
+ );
+
+ let mut dummy_audio = Default::default();
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio)
+ );
+ }
+}
+
+#[cfg(test)]
+fn parse_minimal_mp4() -> *mut Mp4parseParser {
+ let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
+ let io = Mp4parseIo {
+ read: Some(valid_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+ let mut parser = std::ptr::null_mut();
+ let rv = unsafe { mp4parse_new(&io, &mut parser) };
+ assert_eq!(Mp4parseStatus::Ok, rv);
+ parser
+}
+
+#[test]
+fn minimal_mp4_parse_ok() {
+ let parser = parse_minimal_mp4();
+
+ assert!(!parser.is_null());
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_cout() {
+ let parser = parse_minimal_mp4();
+
+ let mut count: u32 = 0;
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_count(parser, &mut count)
+ });
+ assert_eq!(2, count);
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_info() {
+ let parser = parse_minimal_mp4();
+
+ let mut info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Video,
+ ..Default::default()
+ };
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_info(parser, 0, &mut info)
+ });
+ assert_eq!(info.track_type, Mp4parseTrackType::Video);
+ assert_eq!(info.track_id, 1);
+ assert_eq!(info.duration, 512);
+ assert_eq!(info.media_time, 0);
+
+ 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);
+ assert_eq!(info.duration, 2944);
+ assert_eq!(info.media_time, 1024);
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_video_info() {
+ let parser = parse_minimal_mp4();
+
+ let mut video = Mp4parseTrackVideoInfo::default();
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_video_info(parser, 0, &mut video)
+ });
+ assert_eq!(video.display_width, 320);
+ assert_eq!(video.display_height, 240);
+ assert_eq!(video.sample_info_count, 1);
+
+ unsafe {
+ assert_eq!((*video.sample_info).image_width, 320);
+ assert_eq!((*video.sample_info).image_height, 240);
+ }
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_audio_info() {
+ let parser = parse_minimal_mp4();
+
+ let mut audio = Mp4parseTrackAudioInfo::default();
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_audio_info(parser, 1, &mut audio)
+ });
+ assert_eq!(audio.sample_info_count, 1);
+
+ unsafe {
+ assert_eq!((*audio.sample_info).channels, 1);
+ assert_eq!((*audio.sample_info).bit_depth, 16);
+ assert_eq!((*audio.sample_info).sample_rate, 48000);
+ }
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_info_invalid_track_number() {
+ let parser = parse_minimal_mp4();
+
+ let mut info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Video,
+ ..Default::default()
+ };
+ assert_eq!(Mp4parseStatus::BadArg, unsafe {
+ mp4parse_get_track_info(parser, 3, &mut info)
+ });
+ assert_eq!(info.track_type, Mp4parseTrackType::Video);
+ assert_eq!(info.track_id, 0);
+ assert_eq!(info.duration, 0);
+ assert_eq!(info.media_time, 0);
+
+ let mut video = Mp4parseTrackVideoInfo::default();
+ assert_eq!(Mp4parseStatus::BadArg, unsafe {
+ mp4parse_get_track_video_info(parser, 3, &mut video)
+ });
+ assert_eq!(video.display_width, 0);
+ assert_eq!(video.display_height, 0);
+ assert_eq!(video.sample_info_count, 0);
+
+ let mut audio = Default::default();
+ assert_eq!(Mp4parseStatus::BadArg, unsafe {
+ mp4parse_get_track_audio_info(parser, 3, &mut audio)
+ });
+ assert_eq!(audio.sample_info_count, 0);
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn parse_no_timescale() {
+ let mut 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,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ // The file has a video track, but the track has a timescale of 0, so.
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Invalid);
+ };
+}