summaryrefslogtreecommitdiffstats
path: root/third_party/rust/mp4parse_capi
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/mp4parse_capi')
-rw-r--r--third_party/rust/mp4parse_capi/.cargo-checksum.json1
-rw-r--r--third_party/rust/mp4parse_capi/Cargo.toml33
-rw-r--r--third_party/rust/mp4parse_capi/cbindgen.toml17
-rw-r--r--third_party/rust/mp4parse_capi/examples/dump.rs166
-rw-r--r--third_party/rust/mp4parse_capi/src/lib.rs2111
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs45
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_encryption.rs213
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_fragment.rs117
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_rotation.rs41
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_sample_table.rs278
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs46
11 files changed, 3068 insertions, 0 deletions
diff --git a/third_party/rust/mp4parse_capi/.cargo-checksum.json b/third_party/rust/mp4parse_capi/.cargo-checksum.json
new file mode 100644
index 0000000000..e59d87b754
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"ba144343a4d38fe6918dd61bb6723e46ac297f506a27370c1d10b49bd8123572","cbindgen.toml":"5c9429f271d6e914d81b63e6509c04ffe84cab11ed3a53a2ed4715e5d5ace80e","examples/dump.rs":"83462422315c22e496960bae922edb23105c0aa272d2b106edd7574ff068513a","src/lib.rs":"d25c2e010186ada3524801e8f1ecdbe42cc4f961880ccca11ba9bdddd42363f7","tests/test_chunk_out_of_range.rs":"b5da583218d98027ed973a29c67434a91a1306f2d2fb39ec4d640d4824c308ce","tests/test_encryption.rs":"ca98516ff423c03b5fcc17b05f993f13b32485e4cf3ba86faf1bea72681d75ce","tests/test_fragment.rs":"e90eb5a4418d30002655466c0c4b3125c7fd70a74b6871471eaa172f1def9db8","tests/test_rotation.rs":"fb43c2f2dfa496d151c33bdd46c0fd3252387c23cc71e2cac9ed0234de715a81","tests/test_sample_table.rs":"185755909b2f4e0ea99604bb423a07623d614a180accdaebd1c98aef2c2e3ae6","tests/test_workaround_stsc.rs":"7dd419f3d55b9a3a039cac57e58a9240a9c8166bcd4356c24f69f731c3ced83b"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/mp4parse_capi/Cargo.toml b/third_party/rust/mp4parse_capi/Cargo.toml
new file mode 100644
index 0000000000..b4ede98c93
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "mp4parse_capi"
+version = "0.11.4"
+authors = [
+ "Ralph Giles <giles@mozilla.com>",
+ "Matthew Gregan <kinetik@flim.org>",
+ "Alfredo Yang <ayang@mozilla.com>",
+ "Jon Bauman <jbauman@mozilla.com>",
+]
+
+description = "Parser for ISO base media file format (mp4)"
+documentation = "https://docs.rs/mp4parse_capi/"
+license = "MPL-2.0"
+
+repository = "https://github.com/mozilla/mp4parse-rust"
+
+# Avoid complaints about trying to package test files.
+exclude = [
+ "*.mp4",
+]
+
+[badges]
+travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
+
+[dependencies]
+byteorder = "1.2.1"
+fallible_collections = { version = "0.3", features = ["std_io"] }
+log = "0.4"
+mp4parse = {version = "0.11.2", path = "../mp4parse"}
+num-traits = "=0.2.10"
+
+[dev-dependencies]
+env_logger = "0.7.1"
diff --git a/third_party/rust/mp4parse_capi/cbindgen.toml b/third_party/rust/mp4parse_capi/cbindgen.toml
new file mode 100644
index 0000000000..bbaf50b078
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/cbindgen.toml
@@ -0,0 +1,17 @@
+header = """/* 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 http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */
+#ifndef mp4parse_rust_mp4parse_h
+#error "Don't include this file directly, instead include mp4parse.h"
+#endif
+"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C"
+cpp_compat = true
+
+[enum]
+rename_variants = "QualifiedScreamingSnakeCase"
diff --git a/third_party/rust/mp4parse_capi/examples/dump.rs b/third_party/rust/mp4parse_capi/examples/dump.rs
new file mode 100644
index 0000000000..d00fdf5e93
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/examples/dump.rs
@@ -0,0 +1,166 @@
+extern crate mp4parse;
+extern crate mp4parse_capi;
+
+#[macro_use]
+extern crate log;
+
+extern crate env_logger;
+
+use mp4parse_capi::*;
+use std::env;
+use std::fs::File;
+use std::io::Read;
+
+extern "C" fn buf_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 mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+fn dump_file(filename: &str) {
+ let mut file = File::open(filename).expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+
+ match rv {
+ Mp4parseStatus::Ok => (),
+ _ => {
+ println!("-- fail to parse: {:?}, '-v' for more info", rv);
+ return;
+ }
+ }
+
+ let mut frag_info = Mp4parseFragmentInfo::default();
+ match mp4parse_get_fragment_info(parser, &mut frag_info) {
+ Mp4parseStatus::Ok => {
+ println!("-- mp4parse_fragment_info {:?}", frag_info);
+ }
+ rv => {
+ println!("-- mp4parse_fragment_info failed with {:?}", rv);
+ return;
+ }
+ }
+
+ let mut counts: u32 = 0;
+ match mp4parse_get_track_count(parser, &mut counts) {
+ Mp4parseStatus::Ok => (),
+ _ => {
+ println!("-- mp4parse_get_track_count failed");
+ return;
+ }
+ }
+
+ for i in 0..counts {
+ let mut track_info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Audio,
+ ..Default::default()
+ };
+ match mp4parse_get_track_info(parser, i, &mut track_info) {
+ Mp4parseStatus::Ok => {
+ println!("-- mp4parse_get_track_info {:?}", track_info);
+ }
+ _ => {
+ println!("-- mp4parse_get_track_info failed, track id: {}", i);
+ return;
+ }
+ }
+
+ match track_info.track_type {
+ Mp4parseTrackType::Audio => {
+ let mut audio_info = Mp4parseTrackAudioInfo::default();
+ match mp4parse_get_track_audio_info(parser, i, &mut audio_info) {
+ Mp4parseStatus::Ok => {
+ println!("-- mp4parse_get_track_audio_info {:?}", audio_info);
+ for i in 0..audio_info.sample_info_count as isize {
+ let sample_info = audio_info.sample_info.offset(i);
+ println!(
+ " -- mp4parse_get_track_audio_info sample_info[{:?}] {:?}",
+ i, *sample_info
+ );
+ }
+ }
+ _ => {
+ println!("-- mp4parse_get_track_audio_info failed, track id: {}", i);
+ return;
+ }
+ }
+ }
+ Mp4parseTrackType::Video => {
+ let mut video_info = Mp4parseTrackVideoInfo::default();
+ match mp4parse_get_track_video_info(parser, i, &mut video_info) {
+ Mp4parseStatus::Ok => {
+ println!("-- mp4parse_get_track_video_info {:?}", video_info);
+ for i in 0..video_info.sample_info_count as isize {
+ let sample_info = video_info.sample_info.offset(i);
+ println!(
+ " -- mp4parse_get_track_video_info sample_info[{:?}] {:?}",
+ i, *sample_info
+ );
+ }
+ }
+ _ => {
+ println!("-- mp4parse_get_track_video_info failed, track id: {}", i);
+ return;
+ }
+ }
+ }
+ Mp4parseTrackType::Metadata => {
+ println!("TODO metadata track");
+ }
+ }
+
+ let mut indices = Mp4parseByteData::default();
+ match mp4parse_get_indice_table(parser, track_info.track_id, &mut indices) {
+ Mp4parseStatus::Ok => {
+ println!(
+ "-- mp4parse_get_indice_table track_id {} indices {:?}",
+ track_info.track_id, indices
+ );
+ }
+ _ => {
+ println!(
+ "-- mp4parse_get_indice_table failed, track_info.track_id: {}",
+ track_info.track_id
+ );
+ return;
+ }
+ }
+ }
+ mp4parse_free(parser);
+ }
+}
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ if args.len() < 2 {
+ return;
+ }
+
+ // Initialize logging, setting the log level if requested.
+ let (skip, verbose) = if args[1] == "-v" {
+ (2, true)
+ } else {
+ (1, false)
+ };
+ let env = env_logger::Env::default();
+ let mut logger = env_logger::Builder::from_env(env);
+ if verbose {
+ logger.filter(None, log::LevelFilter::Debug);
+ }
+ logger.init();
+
+ for filename in args.iter().skip(skip) {
+ info!("-- dump of '{}' --", filename);
+ dump_file(filename);
+ info!("-- end of '{}' --", filename);
+ }
+}
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..becb5dec1d
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/src/lib.rs
@@ -0,0 +1,2111 @@
+//! C API for mp4parse module.
+//!
+//! Parses ISO Base Media Format aka video/mp4 streams.
+//!
+//! # Examples
+//!
+//! ```rust
+//! extern crate mp4parse_capi;
+//! use std::io::Read;
+//!
+//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+//! let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+//! let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+//! match input.read(&mut buf) {
+//! Ok(n) => n as isize,
+//! Err(_) => -1,
+//! }
+//! }
+//! let 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/.
+
+extern crate byteorder;
+extern crate log;
+extern crate mp4parse;
+extern crate num_traits;
+
+use byteorder::WriteBytesExt;
+use num_traits::{CheckedAdd, CheckedSub};
+use num_traits::{PrimInt, Zero};
+use std::convert::TryFrom;
+use std::convert::TryInto;
+
+use std::io::Read;
+use std::ops::Neg;
+use std::ops::{Add, Sub};
+
+// Symbols we need from our rust api.
+use mp4parse::read_avif;
+use mp4parse::read_mp4;
+use mp4parse::serialize_opus_header;
+use mp4parse::AudioCodecSpecific;
+use mp4parse::AvifContext;
+use mp4parse::CodecType;
+use mp4parse::Error;
+use mp4parse::MediaContext;
+use mp4parse::MediaScaledTime;
+use mp4parse::MediaTimeScale;
+use mp4parse::SampleEntry;
+use mp4parse::ToUsize;
+use mp4parse::Track;
+use mp4parse::TrackScaledTime;
+use mp4parse::TrackTimeScale;
+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, Debug)]
+pub enum Mp4parseStatus {
+ Ok = 0,
+ BadArg = 1,
+ Invalid = 2,
+ Unsupported = 3,
+ Eof = 4,
+ Io = 5,
+ Oom = 6,
+}
+
+#[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum Mp4parseTrackType {
+ Video = 0,
+ Audio = 1,
+ Metadata = 2,
+}
+
+impl Default for Mp4parseTrackType {
+ fn default() -> Self {
+ Mp4parseTrackType::Video
+ }
+}
+
+#[allow(non_camel_case_types)]
+#[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum Mp4parseCodec {
+ Unknown,
+ Aac,
+ Flac,
+ Opus,
+ Avc,
+ Vp9,
+ Av1,
+ Mp3,
+ Mp4v,
+ Jpeg, // for QT JPEG atom in video track
+ Ac3,
+ Ec3,
+ Alac,
+}
+
+impl Default for Mp4parseCodec {
+ fn default() -> Self {
+ Mp4parseCodec::Unknown
+ }
+}
+
+#[repr(C)]
+#[derive(PartialEq, Debug)]
+pub enum Mp4ParseEncryptionSchemeType {
+ 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 Default for Mp4ParseEncryptionSchemeType {
+ fn default() -> Self {
+ Mp4ParseEncryptionSchemeType::None
+ }
+}
+
+/// A zero-overhead wrapper around integer types for the sake of always
+/// requiring checked arithmetic
+#[repr(transparent)]
+#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CheckedInteger<T>(pub T);
+
+impl<T> From<T> for CheckedInteger<T> {
+ fn from(i: T) -> Self {
+ Self(i)
+ }
+}
+
+// Orphan rules prevent a more general implementation, but this suffices
+impl From<CheckedInteger<i64>> for i64 {
+ fn from(checked: CheckedInteger<i64>) -> i64 {
+ checked.0
+ }
+}
+
+impl<T, U: Into<T>> Add<U> for CheckedInteger<T>
+where
+ T: CheckedAdd,
+{
+ type Output = Option<Self>;
+
+ fn add(self, other: U) -> Self::Output {
+ self.0.checked_add(&other.into()).map(Into::into)
+ }
+}
+
+impl<T, U: Into<T>> Sub<U> for CheckedInteger<T>
+where
+ T: CheckedSub,
+{
+ type Output = Option<Self>;
+
+ fn sub(self, other: U) -> Self::Output {
+ self.0.checked_sub(&other.into()).map(Into::into)
+ }
+}
+
+/// Implement subtraction of checked `u64`s returning i64
+// This is necessary for handling Mp4parseTrackInfo::media_time gracefully
+impl Sub for CheckedInteger<u64> {
+ type Output = Option<CheckedInteger<i64>>;
+
+ fn sub(self, other: Self) -> Self::Output {
+ if self >= other {
+ self.0
+ .checked_sub(other.0)
+ .and_then(|u| i64::try_from(u).ok())
+ .map(CheckedInteger)
+ } else {
+ other
+ .0
+ .checked_sub(self.0)
+ .and_then(|u| i64::try_from(u).ok())
+ .map(i64::neg)
+ .map(CheckedInteger)
+ }
+ }
+}
+
+#[test]
+fn u64_subtraction_returning_i64() {
+ // self > other
+ assert_eq!(
+ CheckedInteger(2u64) - CheckedInteger(1u64),
+ Some(CheckedInteger(1i64))
+ );
+
+ // self == other
+ assert_eq!(
+ CheckedInteger(1u64) - CheckedInteger(1u64),
+ Some(CheckedInteger(0i64))
+ );
+
+ // difference too large to store in i64
+ assert_eq!(CheckedInteger(u64::MAX) - CheckedInteger(1u64), None);
+
+ // self < other
+ assert_eq!(
+ CheckedInteger(1u64) - CheckedInteger(2u64),
+ Some(CheckedInteger(-1i64))
+ );
+
+ // difference not representable due to overflow
+ assert_eq!(CheckedInteger(1u64) - CheckedInteger(u64::MAX), None);
+}
+
+impl<T: std::cmp::PartialEq> PartialEq<T> for CheckedInteger<T> {
+ fn eq(&self, other: &T) -> bool {
+ self.0 == *other
+ }
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseTrackInfo {
+ pub track_type: Mp4parseTrackType,
+ pub track_id: u32,
+ pub duration: u64,
+ pub media_time: CheckedInteger<i64>, // wants to be u64? understand how elst adjustment works
+ // TODO(kinetik): include crypto guff
+ // If this changes to u64, we can get rid of the strange
+ // impl Sub for CheckedInteger<u64>
+}
+
+#[repr(C)]
+#[derive(Default, Debug, PartialEq)]
+pub struct Mp4parseIndice {
+ /// The byte offset in the file where the indexed sample begins.
+ pub start_offset: CheckedInteger<u64>,
+ /// The byte offset in the file where the indexed sample ends. This is
+ /// equivalent to `start_offset` + the length in bytes of the indexed
+ /// sample. Typically this will be the `start_offset` of the next sample
+ /// in the file.
+ pub end_offset: CheckedInteger<u64>,
+ /// The time in microseconds when the indexed sample should be displayed.
+ /// Analogous to the concept of presentation time stamp (pts).
+ pub start_composition: CheckedInteger<i64>,
+ /// The time in microseconds when the indexed sample should stop being
+ /// displayed. Typically this would be the `start_composition` time of the
+ /// next sample if samples were ordered by composition time.
+ pub end_composition: CheckedInteger<i64>,
+ /// The time in microseconds that the indexed sample should be decoded at.
+ /// Analogous to the concept of decode time stamp (dts).
+ pub start_decode: CheckedInteger<i64>,
+ /// Set if the indexed sample is a sync sample. The meaning of sync is
+ /// somewhat codec specific, but essentially amounts to if the sample is a
+ /// key frame.
+ pub sync: bool,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseByteData {
+ pub length: u32,
+ // cheddar can't handle generic type, so it needs to be multiple data types here.
+ pub data: *const u8,
+ pub indices: *const Mp4parseIndice,
+}
+
+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() as u32;
+ self.data = data.as_ptr();
+ }
+
+ fn set_indices(&mut self, data: &[Mp4parseIndice]) {
+ self.length = data.len() as u32;
+ self.indices = data.as_ptr();
+ }
+}
+
+#[repr(C)]
+#[derive(Default)]
+pub struct Mp4parsePsshInfo {
+ pub data: Mp4parseByteData,
+}
+
+#[repr(u8)]
+#[derive(Debug, PartialEq)]
+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,
+ // 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<Mp4parseIndice>>,
+ // 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(Default)]
+pub struct AvifImage {
+ pub primary_item: Mp4parseByteData,
+ pub alpha_item: Mp4parseByteData,
+ pub premultiplied_alpha: bool,
+}
+
+/// 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) -> 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) -> mp4parse::Result<Self::Context> {
+ read_mp4(io)
+ }
+}
+
+pub struct Mp4parseAvifParser {
+ context: AvifContext,
+}
+
+impl Mp4parseAvifParser {
+ fn context(&self) -> &AvifContext {
+ &self.context
+ }
+}
+
+impl ContextParser for Mp4parseAvifParser {
+ type Context = AvifContext;
+
+ fn with_context(context: Self::Context) -> Self {
+ Self { context }
+ }
+
+ fn read<T: Read>(io: &mut T) -> mp4parse::Result<Self::Context> {
+ read_avif(io)
+ }
+}
+
+#[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, 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,
+ parser_out: *mut *mut Mp4parseAvifParser,
+) -> Mp4parseStatus {
+ mp4parse_new_common(io, parser_out)
+}
+
+unsafe fn mp4parse_new_common<P: ContextParser>(
+ io: *const Mp4parseIo,
+ 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()) {
+ Ok(parser) => {
+ *parser_out = parser;
+ Mp4parseStatus::Ok
+ }
+ Err(status) => status,
+ }
+ }
+}
+
+fn mp4parse_new_common_safe<T: Read, P: ContextParser>(
+ io: &mut T,
+) -> Result<*mut P, Mp4parseStatus> {
+ P::read(io)
+ .map(P::with_context)
+ .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
+ .map(TryBox::into_raw)
+ .map_err(Mp4parseStatus::from)
+}
+
+impl From<mp4parse::Error> for Mp4parseStatus {
+ fn from(error: mp4parse::Error) -> Self {
+ match error {
+ Error::NoMoov | Error::InvalidData(_) => Mp4parseStatus::Invalid,
+ Error::Unsupported(_) => Mp4parseStatus::Unsupported,
+ Error::UnexpectedEOF => Mp4parseStatus::Eof,
+ Error::Io(_) => {
+ // Getting std::io::ErrorKind::UnexpectedEof is normal
+ // but our From trait implementation should have converted
+ // those to our Error::UnexpectedEOF variant.
+ Mp4parseStatus::Io
+ }
+ Error::OutOfMemory => Mp4parseStatus::Oom,
+ }
+ }
+}
+
+impl From<Result<(), Mp4parseStatus>> for Mp4parseStatus {
+ fn from(result: Result<(), Mp4parseStatus>) -> Self {
+ match result {
+ Ok(()) => Mp4parseStatus::Ok,
+ Err(Mp4parseStatus::Ok) => unreachable!(),
+ Err(e) => e,
+ }
+ }
+}
+
+impl From<fallible_collections::TryReserveError> for Mp4parseStatus {
+ fn from(_: fallible_collections::TryReserveError) -> Self {
+ Mp4parseStatus::Oom
+ }
+}
+
+/// 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
+}
+
+/// Calculate numerator * scale / denominator, if possible.
+///
+/// Applying the associativity of integer arithmetic, we divide first
+/// and add the remainder after multiplying each term separately
+/// to preserve precision while leaving more headroom. That is,
+/// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d.
+///
+/// Return None on overflow or if the denominator is zero.
+fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T>
+where
+ T: PrimInt + Zero,
+ S: PrimInt,
+{
+ if denominator.is_zero() {
+ return None;
+ }
+
+ let integer = numerator / denominator;
+ let remainder = numerator % denominator;
+ num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) {
+ Some(integer) => remainder
+ .checked_mul(&s)
+ .and_then(|remainder| (remainder / denominator).checked_add(&integer)),
+ None => None,
+ })
+}
+
+fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<u64> {
+ let microseconds_per_second = 1_000_000;
+ rational_scale::<u64, u64>(time.0, scale.0, microseconds_per_second)
+}
+
+fn track_time_to_us<T>(time: TrackScaledTime<T>, scale: TrackTimeScale<T>) -> Option<T>
+where
+ T: PrimInt + Zero,
+{
+ assert_eq!(time.1, scale.1);
+ let microseconds_per_second = 1_000_000;
+ rational_scale::<T, u64>(time.0, scale.0, microseconds_per_second)
+}
+
+/// 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::Audio => Mp4parseTrackType::Audio,
+ TrackType::Metadata => Mp4parseTrackType::Metadata,
+ TrackType::Unknown => return Mp4parseStatus::Unsupported,
+ };
+
+ let track = &context.tracks[track_index];
+
+ if let (Some(track_timescale), Some(context_timescale)) = (track.timescale, context.timescale) {
+ let media_time: CheckedInteger<_> = match track.media_time.map_or(Some(0), |media_time| {
+ track_time_to_us(media_time, track_timescale)
+ }) {
+ Some(time) => time.into(),
+ None => return Mp4parseStatus::Invalid,
+ };
+ let empty_duration: CheckedInteger<_> =
+ match track.empty_duration.map_or(Some(0), |empty_duration| {
+ media_time_to_us(empty_duration, context_timescale)
+ }) {
+ Some(time) => time.into(),
+ None => return Mp4parseStatus::Invalid,
+ };
+ info.media_time = match media_time - empty_duration {
+ Some(difference) => difference,
+ None => return Mp4parseStatus::Invalid,
+ };
+
+ if let Some(track_duration) = track.duration {
+ match track_time_to_us(track_duration, track_timescale) {
+ Some(duration) => info.duration = duration,
+ None => return Mp4parseStatus::Invalid,
+ }
+ } else {
+ // 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,
+ };
+ 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() as u32;
+ sample_info.extra_data.data = esds.codec_esds.as_ptr();
+ sample_info.codec_specific_config.length = esds.decoder_specific_data.len() as u32;
+ 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() as u32;
+ 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() as u32;
+ sample_info.codec_specific_config.data = v.as_ptr();
+ }
+ }
+ }
+ }
+ AudioCodecSpecific::ALACSpecificBox(ref alac) => {
+ sample_info.codec_specific_config.length = alac.data.len() as u32;
+ sample_info.codec_specific_config.data = alac.data.as_ptr();
+ }
+ AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
+ }
+
+ 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 = match tenc.crypt_byte_block_count {
+ Some(n) => n,
+ None => 0,
+ };
+ sample_info.protected_data.skip_byte_block = match tenc.skip_byte_block_count {
+ Some(n) => n,
+ None => 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::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::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 = match tenc.crypt_byte_block_count {
+ Some(n) => n,
+ None => 0,
+ };
+ sample_info.protected_data.skip_byte_block = match tenc.skip_byte_block_count {
+ Some(n) => n,
+ None => 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 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 `AvifImage`. 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_item` is set to a
+/// positive `length` and non-null `data`, then the `avif_image` contains an
+/// valid alpha channel data. Otherwise, the image is opaque.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_avif_get_image(
+ parser: *mut Mp4parseAvifParser,
+ avif_image: *mut AvifImage,
+) -> Mp4parseStatus {
+ if parser.is_null() || avif_image.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ // Initialize fields to default values to ensure all fields are always valid.
+ *avif_image = Default::default();
+ let context = (*parser).context();
+
+ (*avif_image).primary_item.set_data(context.primary_item());
+ if let Some(context_alpha_item) = context.alpha_item() {
+ (*avif_image).alpha_item.set_data(context_alpha_item);
+ (*avif_image).premultiplied_alpha = context.premultiplied_alpha;
+ }
+
+ Mp4parseStatus::Ok
+}
+
+/// 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(&mut *parser, track_id, &mut *indices).into()
+}
+
+fn get_indice_table(
+ parser: &mut Mp4parseParser,
+ track_id: u32,
+ indices: &mut Mp4parseByteData,
+) -> Result<(), Mp4parseStatus> {
+ let Mp4parseParser {
+ context,
+ sample_table: index_table,
+ ..
+ } = parser;
+ 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) = index_table.get(&track_id) {
+ indices.set_indices(v);
+ return Ok(());
+ }
+
+ let media_time = match (&track.media_time, &track.timescale) {
+ (&Some(t), &Some(s)) => track_time_to_us(t, s)
+ .and_then(|v| i64::try_from(v).ok())
+ .map(Into::into),
+ _ => None,
+ };
+
+ let empty_duration: Option<CheckedInteger<_>> =
+ match (&track.empty_duration, &context.timescale) {
+ (&Some(e), &Some(s)) => media_time_to_us(e, s)
+ .and_then(|v| i64::try_from(v).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);
+ index_table.insert(track_id, v)?;
+ return Ok(());
+ }
+
+ Err(Mp4parseStatus::Invalid)
+}
+
+// Convert a 'ctts' compact table to full table by iterator,
+// (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ...
+//
+// For example:
+// (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time().
+struct TimeOffsetIterator<'a> {
+ cur_sample_range: std::ops::Range<u32>,
+ cur_offset: i64,
+ ctts_iter: Option<std::slice::Iter<'a, mp4parse::TimeOffset>>,
+ track_id: usize,
+}
+
+impl<'a> Iterator for TimeOffsetIterator<'a> {
+ type Item = i64;
+
+ #[allow(clippy::reversed_empty_ranges)]
+ fn next(&mut self) -> Option<i64> {
+ let has_sample = self.cur_sample_range.next().or_else(|| {
+ // At end of current TimeOffset, find the next TimeOffset.
+ let iter = match self.ctts_iter {
+ Some(ref mut v) => v,
+ _ => return None,
+ };
+ let offset_version;
+ self.cur_sample_range = match iter.next() {
+ Some(v) => {
+ offset_version = v.time_offset;
+ 0..v.sample_count
+ }
+ _ => {
+ offset_version = mp4parse::TimeOffsetVersion::Version0(0);
+ 0..0
+ }
+ };
+
+ self.cur_offset = match offset_version {
+ mp4parse::TimeOffsetVersion::Version0(i) => i64::from(i),
+ mp4parse::TimeOffsetVersion::Version1(i) => i64::from(i),
+ };
+
+ self.cur_sample_range.next()
+ });
+
+ has_sample.and(Some(self.cur_offset))
+ }
+}
+
+impl<'a> TimeOffsetIterator<'a> {
+ fn next_offset_time(&mut self) -> TrackScaledTime<i64> {
+ match self.next() {
+ Some(v) => TrackScaledTime::<i64>(v as i64, self.track_id),
+ _ => TrackScaledTime::<i64>(0, self.track_id),
+ }
+ }
+}
+
+// Convert 'stts' compact table to full table by iterator,
+// (sample_count_with_the_same_time, time) => (time, time, time) ... repeats
+// sample_count_with_the_same_time.
+//
+// For example:
+// (2, 3000), (1, 2999) to (3000, 3000, 2999).
+struct TimeToSampleIterator<'a> {
+ cur_sample_count: std::ops::Range<u32>,
+ cur_sample_delta: u32,
+ stts_iter: std::slice::Iter<'a, mp4parse::Sample>,
+ track_id: usize,
+}
+
+impl<'a> Iterator for TimeToSampleIterator<'a> {
+ type Item = u32;
+
+ #[allow(clippy::reversed_empty_ranges)]
+ fn next(&mut self) -> Option<u32> {
+ let has_sample = self.cur_sample_count.next().or_else(|| {
+ self.cur_sample_count = match self.stts_iter.next() {
+ Some(v) => {
+ self.cur_sample_delta = v.sample_delta;
+ 0..v.sample_count
+ }
+ _ => 0..0,
+ };
+
+ self.cur_sample_count.next()
+ });
+
+ has_sample.and(Some(self.cur_sample_delta))
+ }
+}
+
+impl<'a> TimeToSampleIterator<'a> {
+ fn next_delta(&mut self) -> TrackScaledTime<i64> {
+ match self.next() {
+ Some(v) => TrackScaledTime::<i64>(i64::from(v), self.track_id),
+ _ => TrackScaledTime::<i64>(0, self.track_id),
+ }
+ }
+}
+
+// Convert 'stco' compact table to full table by iterator.
+// (start_chunk_num, sample_number) => (start_chunk_num, sample_number),
+// (start_chunk_num + 1, sample_number),
+// (start_chunk_num + 2, sample_number),
+// ...
+// (next start_chunk_num, next sample_number),
+// ...
+//
+// For example:
+// (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10),
+// (7, 10), (8, 10), (9, 2)
+fn sample_to_chunk_iter<'a>(
+ stsc_samples: &'a TryVec<mp4parse::SampleToChunk>,
+ stco_offsets: &'a TryVec<u64>,
+) -> SampleToChunkIterator<'a> {
+ SampleToChunkIterator {
+ chunks: (0..0),
+ sample_count: 0,
+ stsc_peek_iter: stsc_samples.as_slice().iter().peekable(),
+ remain_chunk_count: stco_offsets
+ .len()
+ .try_into()
+ .expect("stco.entry_count is u32"),
+ }
+}
+
+struct SampleToChunkIterator<'a> {
+ chunks: std::ops::Range<u32>,
+ sample_count: u32,
+ stsc_peek_iter: std::iter::Peekable<std::slice::Iter<'a, mp4parse::SampleToChunk>>,
+ remain_chunk_count: u32, // total chunk number from 'stco'.
+}
+
+impl<'a> Iterator for SampleToChunkIterator<'a> {
+ type Item = (u32, u32);
+
+ fn next(&mut self) -> Option<(u32, u32)> {
+ let has_chunk = self.chunks.next().or_else(|| {
+ self.chunks = self.locate();
+ self.remain_chunk_count
+ .checked_sub(
+ self.chunks
+ .len()
+ .try_into()
+ .expect("len() of a Range<u32> must fit in u32"),
+ )
+ .and_then(|res| {
+ self.remain_chunk_count = res;
+ self.chunks.next()
+ })
+ });
+
+ has_chunk.map(|id| (id, self.sample_count))
+ }
+}
+
+impl<'a> SampleToChunkIterator<'a> {
+ #[allow(clippy::reversed_empty_ranges)]
+ fn locate(&mut self) -> std::ops::Range<u32> {
+ loop {
+ return match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) {
+ (Some(next), Some(peek)) if next.first_chunk == peek.first_chunk => {
+ // Invalid entry, skip it and will continue searching at
+ // next loop iteration.
+ continue;
+ }
+ (Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => {
+ self.sample_count = next.samples_per_chunk;
+ (next.first_chunk - 1)..(peek.first_chunk - 1)
+ }
+ (Some(next), None) if next.first_chunk > 0 => {
+ self.sample_count = next.samples_per_chunk;
+ // Total chunk number in 'stsc' could be different to 'stco',
+ // there could be more chunks at the last 'stsc' record.
+ match next.first_chunk.checked_add(self.remain_chunk_count) {
+ Some(r) => (next.first_chunk - 1)..r - 1,
+ _ => 0..0,
+ }
+ }
+ _ => 0..0,
+ };
+ }
+ }
+}
+
+#[allow(clippy::reversed_empty_ranges)]
+fn create_sample_table(
+ track: &Track,
+ track_offset_time: CheckedInteger<i64>,
+) -> Option<TryVec<Mp4parseIndice>> {
+ let timescale = match track.timescale {
+ Some(ref t) => TrackTimeScale::<i64>(t.0 as i64, t.1),
+ _ => return None,
+ };
+
+ let (stsc, stco, stsz, stts) = match (&track.stsc, &track.stco, &track.stsz, &track.stts) {
+ (&Some(ref a), &Some(ref b), &Some(ref c), &Some(ref d)) => (a, b, c, d),
+ _ => return None,
+ };
+
+ // According to spec, no sync table means every sample is sync sample.
+ let has_sync_table = match track.stss {
+ Some(_) => true,
+ _ => false,
+ };
+
+ let mut sample_size_iter = stsz.sample_sizes.iter();
+
+ // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample
+ // offset address.
+
+ // With large numbers of samples, the cost of many allocations dominates,
+ // so it's worth iterating twice to allocate sample_table just once.
+ let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets)
+ .by_ref()
+ .map(|(_, sample_counts)| sample_counts.to_usize())
+ .sum();
+ let mut sample_table = TryVec::with_capacity(total_sample_count).ok()?;
+
+ for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) {
+ let chunk_id = i.0 as usize;
+ let sample_counts = i.1;
+ let mut cur_position = match stco.offsets.get(chunk_id) {
+ Some(&i) => i.into(),
+ _ => return None,
+ };
+ for _ in 0..sample_counts {
+ let start_offset = cur_position;
+ let end_offset = match (stsz.sample_size, sample_size_iter.next()) {
+ (_, Some(t)) => (start_offset + *t)?,
+ (t, _) if t > 0 => (start_offset + t)?,
+ _ => 0.into(),
+ };
+ if end_offset == 0 {
+ return None;
+ }
+ cur_position = end_offset;
+
+ sample_table
+ .push(Mp4parseIndice {
+ start_offset,
+ end_offset,
+ sync: !has_sync_table,
+ ..Default::default()
+ })
+ .ok()?;
+ }
+ }
+
+ // Mark the sync sample in sample_table according to 'stss'.
+ if let Some(ref v) = track.stss {
+ for iter in &v.samples {
+ match iter
+ .checked_sub(&1)
+ .and_then(|idx| sample_table.get_mut(idx as usize))
+ {
+ Some(elem) => elem.sync = true,
+ _ => return None,
+ }
+ }
+ }
+
+ let ctts_iter = match track.ctts {
+ Some(ref v) => Some(v.samples.as_slice().iter()),
+ _ => None,
+ };
+
+ let mut ctts_offset_iter = TimeOffsetIterator {
+ cur_sample_range: (0..0),
+ cur_offset: 0,
+ ctts_iter,
+ track_id: track.id,
+ };
+
+ let mut stts_iter = TimeToSampleIterator {
+ cur_sample_count: (0..0),
+ cur_sample_delta: 0,
+ stts_iter: stts.samples.as_slice().iter(),
+ track_id: track.id,
+ };
+
+ // sum_delta is the sum of stts_iter delta.
+ // According to sepc:
+ // decode time => DT(n) = DT(n-1) + STTS(n)
+ // composition time => CT(n) = DT(n) + CTTS(n)
+ // Note:
+ // composition time needs to add the track offset time from 'elst' table.
+ let mut sum_delta = TrackScaledTime::<i64>(0, track.id);
+ for sample in sample_table.as_mut_slice() {
+ let decode_time = sum_delta;
+ sum_delta = (sum_delta + stts_iter.next_delta())?;
+
+ // ctts_offset is the current sample offset time.
+ let ctts_offset = ctts_offset_iter.next_offset_time();
+
+ let start_composition = track_time_to_us((decode_time + ctts_offset)?, timescale)?;
+
+ let end_composition = track_time_to_us((sum_delta + ctts_offset)?, timescale)?;
+
+ let start_decode = track_time_to_us(decode_time, timescale)?;
+
+ sample.start_composition = (track_offset_time + start_composition)?;
+ sample.end_composition = (track_offset_time + end_composition)?;
+ sample.start_decode = start_decode.into();
+ }
+
+ // Correct composition end time due to 'ctts' causes composition time re-ordering.
+ //
+ // Composition end time is not in specification. However, gecko needs it, so we need to
+ // calculate to correct the composition end time.
+ if !sample_table.is_empty() {
+ // Create an index table refers to sample_table and sorted by start_composisiton time.
+ let mut sort_table = TryVec::with_capacity(sample_table.len()).ok()?;
+
+ for i in 0..sample_table.len() {
+ sort_table.push(i).ok()?;
+ }
+
+ sort_table.sort_by_key(|i| match sample_table.get(*i) {
+ Some(v) => v.start_composition,
+ _ => 0.into(),
+ });
+
+ for indices in sort_table.windows(2) {
+ if let [current_index, peek_index] = *indices {
+ let next_start_composition_time = sample_table[peek_index].start_composition;
+ let sample = &mut sample_table[current_index];
+ sample.end_composition = next_start_composition_time;
+ }
+ }
+ }
+
+ Some(sample_table)
+}
+
+/// 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 = match media_time_to_us(time, scale) {
+ Some(time_us) => time_us as u64,
+ None => return Mp4parseStatus::Invalid,
+ }
+ }
+
+ Mp4parseStatus::Ok
+}
+
+/// 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(ref stsc), &Some(ref stco), &Some(ref 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();
+ if data_len
+ .write_u32::<byteorder::NativeEndian>(content_len)
+ .is_err()
+ {
+ return Err(Mp4parseStatus::Io);
+ }
+ 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 mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn 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, 40000);
+ 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, 61333);
+ assert_eq!(info.media_time, 21333);
+
+ 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 rational_scale_overflow() {
+ assert_eq!(rational_scale::<u64, u64>(17, 3, 1000), Some(5666));
+ let large = 0x4000_0000_0000_0000;
+ assert_eq!(rational_scale::<u64, u64>(large, 2, 2), Some(large));
+ assert_eq!(rational_scale::<u64, u64>(large, 4, 4), Some(large));
+ assert_eq!(rational_scale::<u64, u64>(large, 2, 8), None);
+ assert_eq!(rational_scale::<u64, u64>(large, 8, 4), Some(large / 2));
+ assert_eq!(rational_scale::<u64, u64>(large + 1, 4, 4), Some(large + 1));
+ assert_eq!(rational_scale::<u64, u64>(large, 40, 1000), None);
+}
+
+#[test]
+fn media_time_overflow() {
+ let scale = MediaTimeScale(90000);
+ let duration = MediaScaledTime(9_007_199_254_710_000);
+ assert_eq!(
+ media_time_to_us(duration, scale),
+ Some(100_079_991_719_000_000)
+ );
+}
+
+#[test]
+fn track_time_overflow() {
+ let scale = TrackTimeScale(44100u64, 0);
+ let duration = TrackScaledTime(4_413_527_634_807_900u64, 0);
+ assert_eq!(
+ track_time_to_us(duration, scale),
+ Some(100_079_991_719_000_000)
+ );
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs
new file mode 100644
index 0000000000..5529c92182
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs
@@ -0,0 +1,45 @@
+extern crate mp4parse_capi;
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_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 mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_out_of_chunk_range() {
+ let mut file = std::fs::File::open("tests/chunk_out_of_range.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ // its first chunk is out of range.
+ // <SampleToChunkBox EntryCount="1">
+ // <BoxInfo Size="28" Type="stsc"/>
+ // <FullBoxInfo Version="0" Flags="0x0"/>
+ // <SampleToChunkEntry FirstChunk="16777217" SamplesPerChunk="17" SampleDescriptionIndex="1"/>
+ //
+ let mut indice = Mp4parseByteData::default();
+ let rv = mp4parse_get_indice_table(parser, 1, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Invalid);
+
+ mp4parse_free(parser);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_encryption.rs b/third_party/rust/mp4parse_capi/tests/test_encryption.rs
new file mode 100644
index 0000000000..33c9c6d9f9
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_encryption.rs
@@ -0,0 +1,213 @@
+extern crate mp4parse_capi;
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_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 mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+#[allow(clippy::cognitive_complexity)] // TODO: Consider simplifying this
+fn parse_cenc() {
+ let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 2);
+
+ // Make sure we have a video track and it's at index 0
+ let mut video_track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut video_track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video);
+
+ // Make sure we have a audio track and it's at index 1
+ let mut audio_track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 1, &mut audio_track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio_track_info.track_type, Mp4parseTrackType::Audio);
+
+ // Verify video track and crypto information
+ let mut video = Mp4parseTrackVideoInfo::default();
+ rv = mp4parse_get_track_video_info(parser, 0, &mut video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video.sample_info_count, 1);
+ assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Avc);
+ assert_eq!((*video.sample_info).image_width, 320);
+ assert_eq!((*video.sample_info).image_height, 240);
+ let protected_data = &(*video.sample_info).protected_data;
+ assert_eq!(
+ protected_data.original_format,
+ OptionalFourCC::Some(*b"avc1")
+ );
+ assert_eq!(
+ protected_data.scheme_type,
+ Mp4ParseEncryptionSchemeType::Cenc
+ );
+ assert_eq!(protected_data.is_encrypted, 0x01);
+ assert_eq!(protected_data.iv_size, 16);
+ assert_eq!(protected_data.kid.length, 16);
+ let expected_kid = [
+ 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57,
+ 0x1d, 0x01,
+ ];
+ for (i, expected_byte) in expected_kid.iter().enumerate() {
+ assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte);
+ }
+ assert_eq!(protected_data.crypt_byte_block, 0);
+ assert_eq!(protected_data.skip_byte_block, 0);
+ assert_eq!(protected_data.constant_iv.length, 0);
+
+ // Verify audio track and crypto information
+ let mut audio = Mp4parseTrackAudioInfo::default();
+ rv = mp4parse_get_track_audio_info(parser, 1, &mut audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio.sample_info_count, 1);
+ assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Aac);
+ assert_eq!((*audio.sample_info).channels, 2);
+ assert_eq!((*audio.sample_info).bit_depth, 16);
+ assert_eq!((*audio.sample_info).sample_rate, 44100);
+ let protected_data = &(*audio.sample_info).protected_data;
+ assert_eq!(
+ protected_data.original_format,
+ OptionalFourCC::Some(*b"mp4a")
+ );
+ assert_eq!(protected_data.is_encrypted, 0x01);
+ assert_eq!(protected_data.iv_size, 16);
+ assert_eq!(protected_data.kid.length, 16);
+ let expected_kid = [
+ 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57,
+ 0x1d, 0x02,
+ ];
+ for (i, expected_byte) in expected_kid.iter().enumerate() {
+ assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte);
+ }
+ assert_eq!(protected_data.crypt_byte_block, 0);
+ assert_eq!(protected_data.skip_byte_block, 0);
+ assert_eq!(protected_data.constant_iv.length, 0);
+ }
+}
+
+#[test]
+fn parse_cbcs() {
+ let mut file = std::fs::File::open("tests/bipbop_cbcs_video_init.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ // Make sure we have a video track
+ let mut video_track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut video_track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video);
+
+ // Verify video track and crypto information
+ let mut video = Mp4parseTrackVideoInfo::default();
+ rv = mp4parse_get_track_video_info(parser, 0, &mut video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video.sample_info_count, 2);
+ assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Avc);
+ assert_eq!((*video.sample_info).image_width, 400);
+ assert_eq!((*video.sample_info).image_height, 300);
+ let protected_data = &(*video.sample_info).protected_data;
+ assert_eq!(
+ protected_data.original_format,
+ OptionalFourCC::Some(*b"avc1")
+ );
+ assert_eq!(
+ protected_data.scheme_type,
+ Mp4ParseEncryptionSchemeType::Cbcs
+ );
+ assert_eq!(protected_data.is_encrypted, 0x01);
+ assert_eq!(protected_data.iv_size, 0);
+ assert_eq!(protected_data.kid.length, 16);
+ let expected_kid = [
+ 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57,
+ 0x1d, 0x21,
+ ];
+ for (i, expected_byte) in expected_kid.iter().enumerate() {
+ assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte);
+ }
+ assert_eq!(protected_data.crypt_byte_block, 1);
+ assert_eq!(protected_data.skip_byte_block, 9);
+ assert_eq!(protected_data.constant_iv.length, 16);
+ let expected_iv = [
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66,
+ ];
+ for (i, expected_byte) in expected_iv.iter().enumerate() {
+ assert_eq!(&(*protected_data.constant_iv.data.add(i)), expected_byte);
+ }
+ }
+}
+
+#[test]
+fn parse_unencrypted() {
+ // Ensure the encryption related data is not populated for files without
+ // encryption metadata.
+ let mut file = std::fs::File::open("tests/opus_audioinit.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+
+ let mut audio = Mp4parseTrackAudioInfo::default();
+ rv = mp4parse_get_track_audio_info(parser, 0, &mut audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio.sample_info_count, 1);
+ let protected_data = &(*audio.sample_info).protected_data;
+ assert_eq!(protected_data.original_format, OptionalFourCC::None);
+ assert_eq!(
+ protected_data.scheme_type,
+ Mp4ParseEncryptionSchemeType::None
+ );
+ assert_eq!(protected_data.is_encrypted, 0x00);
+ assert_eq!(protected_data.iv_size, 0);
+ assert_eq!(protected_data.kid.length, 0);
+ assert_eq!(protected_data.crypt_byte_block, 0);
+ assert_eq!(protected_data.skip_byte_block, 0);
+ assert_eq!(protected_data.constant_iv.length, 0);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_fragment.rs b/third_party/rust/mp4parse_capi/tests/test_fragment.rs
new file mode 100644
index 0000000000..5ded45fd3d
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_fragment.rs
@@ -0,0 +1,117 @@
+extern crate mp4parse_capi;
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_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 mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_fragment() {
+ let mut file = std::fs::File::open("tests/bipbop_audioinit.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+ assert_eq!(track_info.track_id, 1);
+ assert_eq!(track_info.duration, 0);
+ assert_eq!(track_info.media_time, 0);
+
+ let mut audio = Default::default();
+ rv = mp4parse_get_track_audio_info(parser, 0, &mut audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio.sample_info_count, 1);
+
+ assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Aac);
+ assert_eq!((*audio.sample_info).channels, 2);
+ assert_eq!((*audio.sample_info).bit_depth, 16);
+ assert_eq!((*audio.sample_info).sample_rate, 22050);
+ assert_eq!((*audio.sample_info).extra_data.length, 27);
+ assert_eq!((*audio.sample_info).codec_specific_config.length, 2);
+
+ let mut is_fragmented_file: u8 = 0;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 1);
+
+ let mut fragment_info = Mp4parseFragmentInfo::default();
+ rv = mp4parse_get_fragment_info(parser, &mut fragment_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(fragment_info.fragment_duration, 10_032_000);
+
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn parse_opus_fragment() {
+ let mut file = std::fs::File::open("tests/opus_audioinit.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+ assert_eq!(track_info.track_id, 1);
+ assert_eq!(track_info.duration, 0);
+ assert_eq!(track_info.media_time, 0);
+
+ let mut audio = Default::default();
+ rv = mp4parse_get_track_audio_info(parser, 0, &mut audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio.sample_info_count, 1);
+
+ assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Opus);
+ assert_eq!((*audio.sample_info).channels, 1);
+ assert_eq!((*audio.sample_info).bit_depth, 16);
+ assert_eq!((*audio.sample_info).sample_rate, 48000);
+ assert_eq!((*audio.sample_info).extra_data.length, 0);
+ assert_eq!((*audio.sample_info).codec_specific_config.length, 19);
+
+ let mut is_fragmented_file: u8 = 0;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 1);
+
+ let mut fragment_info = Mp4parseFragmentInfo::default();
+ rv = mp4parse_get_fragment_info(parser, &mut fragment_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ mp4parse_free(parser);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_rotation.rs b/third_party/rust/mp4parse_capi/tests/test_rotation.rs
new file mode 100644
index 0000000000..45991c6ada
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_rotation.rs
@@ -0,0 +1,41 @@
+extern crate mp4parse_capi;
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_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 mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_rotation() {
+ let mut file = std::fs::File::open("tests/video_rotation_90.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut video = Mp4parseTrackVideoInfo::default();
+
+ let rv = mp4parse_get_track_video_info(parser, 0, &mut video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video.rotation, 90);
+
+ mp4parse_free(parser);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_sample_table.rs b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs
new file mode 100644
index 0000000000..018bfc8e9b
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs
@@ -0,0 +1,278 @@
+extern crate mp4parse_capi;
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_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 mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_sample_table() {
+ let mut file =
+ std::fs::File::open("tests/bipbop_nonfragment_header.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 2);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 1, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+
+ // Check audio smaple table
+ let mut is_fragmented_file: u8 = 0;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 0);
+
+ let mut indice = Mp4parseByteData::default();
+ rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ // Compare the value from stagefright.
+ let audio_indice_0 = Mp4parseIndice {
+ start_offset: 27_046.into(),
+ end_offset: 27_052.into(),
+ start_composition: 0.into(),
+ end_composition: 46_439.into(),
+ start_decode: 0.into(),
+ sync: true,
+ };
+ let audio_indice_215 = Mp4parseIndice {
+ start_offset: 283_550.into(),
+ end_offset: 283_556.into(),
+ start_composition: 9_984_580.into(),
+ end_composition: 10_031_020.into(),
+ start_decode: 9_984_580.into(),
+ sync: true,
+ };
+ assert_eq!(indice.length, 216);
+ assert_eq!(*indice.indices.offset(0), audio_indice_0);
+ assert_eq!(*indice.indices.offset(215), audio_indice_215);
+
+ // Check video smaple table
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Video);
+
+ let mut is_fragmented_file: u8 = 0;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 0);
+
+ let mut indice = Mp4parseByteData::default();
+ rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ // Compare the last few data from stagefright.
+ let video_indice_291 = Mp4parseIndice {
+ start_offset: 280_226.into(),
+ end_offset: 280_855.into(),
+ start_composition: 9_838_333.into(),
+ end_composition: 9_871_677.into(),
+ start_decode: 9_710_000.into(),
+ sync: false,
+ };
+ let video_indice_292 = Mp4parseIndice {
+ start_offset: 280_855.into(),
+ end_offset: 281_297.into(),
+ start_composition: 9_805_011.into(),
+ end_composition: 9_838_333.into(),
+ start_decode: 9_710_011.into(),
+ sync: false,
+ };
+ // TODO: start_composition time in stagefright is 9905000, but it is 9904999 in parser, it
+ // could be rounding error.
+ //let video_indice_293 = Mp4parseIndice { start_offset: 281_297, end_offset: 281_919, start_composition: 9_905_000, end_composition: 9_938_344, start_decode: 9_776_666, sync: false };
+ //let video_indice_294 = Mp4parseIndice { start_offset: 281_919, end_offset: 282_391, start_composition: 9_871_677, end_composition: 9_905_000, start_decode: 9_776_677, sync: false };
+ let video_indice_295 = Mp4parseIndice {
+ start_offset: 282_391.into(),
+ end_offset: 283_032.into(),
+ start_composition: 9_971_666.into(),
+ end_composition: 9_971_677.into(),
+ start_decode: 9_843_333.into(),
+ sync: false,
+ };
+ let video_indice_296 = Mp4parseIndice {
+ start_offset: 283_092.into(),
+ end_offset: 283_526.into(),
+ start_composition: 9_938_344.into(),
+ end_composition: 9_971_666.into(),
+ start_decode: 9_843_344.into(),
+ sync: false,
+ };
+
+ assert_eq!(indice.length, 297);
+ assert_eq!(*indice.indices.offset(291), video_indice_291);
+ assert_eq!(*indice.indices.offset(292), video_indice_292);
+ //assert_eq!(*indice.indices.offset(293), video_indice_293);
+ //assert_eq!(*indice.indices.offset(294), video_indice_294);
+ assert_eq!(*indice.indices.offset(295), video_indice_295);
+ assert_eq!(*indice.indices.offset(296), video_indice_296);
+
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn parse_sample_table_with_elst() {
+ let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 2);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 1, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+
+ // Check audio sample table
+ let mut is_fragmented_file: u8 = std::u8::MAX;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 0);
+
+ let mut indice = Mp4parseByteData::default();
+ rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ // Compare the value from stagefright.
+ // Due to 'elst', the start_composition and end_composition are negative
+ // at first two samples.
+ let audio_indice_0 = Mp4parseIndice {
+ start_offset: 6992.into(),
+ end_offset: 7363.into(),
+ start_composition: (-36281).into(),
+ end_composition: (-13062).into(),
+ start_decode: 0.into(),
+ sync: true,
+ };
+ let audio_indice_1 = Mp4parseIndice {
+ start_offset: 7363.into(),
+ end_offset: 7735.into(),
+ start_composition: (-13062).into(),
+ end_composition: 10158.into(),
+ start_decode: 23219.into(),
+ sync: true,
+ };
+ let audio_indice_2 = Mp4parseIndice {
+ start_offset: 7735.into(),
+ end_offset: 8106.into(),
+ start_composition: 10158.into(),
+ end_composition: 33378.into(),
+ start_decode: 46439.into(),
+ sync: true,
+ };
+ assert_eq!(indice.length, 21);
+ assert_eq!(*indice.indices.offset(0), audio_indice_0);
+ assert_eq!(*indice.indices.offset(1), audio_indice_1);
+ assert_eq!(*indice.indices.offset(2), audio_indice_2);
+
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn parse_sample_table_with_negative_ctts() {
+ let mut file = std::fs::File::open("tests/white.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_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());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Video);
+
+ let mut is_fragmented_file: u8 = std::u8::MAX;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 0);
+
+ let mut indice = Mp4parseByteData::default();
+ rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ // There are negative value in 'ctts' table.
+ let video_indice_0 = Mp4parseIndice {
+ start_offset: 48.into(),
+ end_offset: 890.into(),
+ start_composition: 0.into(),
+ end_composition: 33_333.into(),
+ start_decode: 0.into(),
+ sync: true,
+ };
+ let video_indice_1 = Mp4parseIndice {
+ start_offset: 890.into(),
+ end_offset: 913.into(),
+ start_composition: 133_333.into(),
+ end_composition: 166_666.into(),
+ start_decode: 33_333.into(),
+ sync: false,
+ };
+ let video_indice_2 = Mp4parseIndice {
+ start_offset: 913.into(),
+ end_offset: 934.into(),
+ start_composition: 66_666.into(),
+ end_composition: 100_000.into(),
+ start_decode: 66_666.into(),
+ sync: false,
+ };
+ let video_indice_3 = Mp4parseIndice {
+ start_offset: 934.into(),
+ end_offset: 955.into(),
+ start_composition: 33_333.into(),
+ end_composition: 66_666.into(),
+ start_decode: 100_000.into(),
+ sync: false,
+ };
+ assert_eq!(indice.length, 300);
+ assert_eq!(*indice.indices.offset(0), video_indice_0);
+ assert_eq!(*indice.indices.offset(1), video_indice_1);
+ assert_eq!(*indice.indices.offset(2), video_indice_2);
+ assert_eq!(*indice.indices.offset(3), video_indice_3);
+
+ mp4parse_free(parser);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs
new file mode 100644
index 0000000000..b77da23b6d
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs
@@ -0,0 +1,46 @@
+extern crate mp4parse_capi;
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_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 mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(&mut buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_invalid_stsc_table() {
+ let mut file = std::fs::File::open("tests/zero_empty_stsc.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ let rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 2);
+
+ let mut indice_video = Mp4parseByteData::default();
+ let rv = mp4parse_get_indice_table(parser, 1, &mut indice_video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(indice_video.length, 1040);
+
+ let mut indice_audio = Mp4parseByteData::default();
+ let rv = mp4parse_get_indice_table(parser, 2, &mut indice_audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(indice_audio.length, 1952);
+
+ mp4parse_free(parser);
+ }
+}