extern crate byteorder;
extern crate log;
extern crate mp4parse;
extern crate num_traits;
use byteorder::WriteBytesExt;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::io::Read;
use mp4parse::serialize_opus_header;
use mp4parse::unstable::{
create_sample_table, media_time_to_us, track_time_to_us, CheckedInteger, Indice, Microseconds,
};
use mp4parse::AudioCodecSpecific;
use mp4parse::AvifContext;
use mp4parse::CodecType;
use mp4parse::MediaContext;
pub use mp4parse::ParseStrictness;
use mp4parse::SampleEntry;
pub use mp4parse::Status as Mp4parseStatus;
use mp4parse::TrackType;
use mp4parse::TryBox;
use mp4parse::TryHashMap;
use mp4parse::TryVec;
use mp4parse::VideoCodecSpecific;
#[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 Mp4parseTrackType {
Video = 0,
Audio = 1,
Metadata = 2,
}
impl Default for Mp4parseTrackType {
fn default() -> Self {
Mp4parseTrackType::Video
}
}
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(C)]
#[derive(PartialEq, Debug)]
pub enum Mp4parseCodec {
Unknown,
Aac,
Flac,
Opus,
Avc,
Vp9,
Av1,
Mp3,
Mp4v,
Jpeg, Ac3,
Ec3,
Alac,
H263,
#[cfg(feature = "3gpp")]
AMRNB,
#[cfg(feature = "3gpp")]
AMRWB,
}
impl Default for Mp4parseCodec {
fn default() -> Self {
Mp4parseCodec::Unknown
}
}
#[repr(C)]
#[derive(PartialEq, Debug)]
pub enum Mp4ParseEncryptionSchemeType {
None,
Cenc,
Cbc1,
Cens,
Cbcs,
}
impl Default for Mp4ParseEncryptionSchemeType {
fn default() -> Self {
Mp4ParseEncryptionSchemeType::None
}
}
#[repr(C)]
#[derive(Default, Debug)]
pub struct Mp4parseTrackInfo {
pub track_type: Mp4parseTrackType,
pub track_id: u32,
pub duration: u64,
pub media_time: CheckedInteger<i64>, }
#[repr(C)]
#[derive(Debug)]
pub struct Mp4parseByteData {
pub length: usize,
pub data: *const u8,
pub indices: *const Indice,
}
impl Mp4parseByteData {
fn with_data(slice: &[u8]) -> Self {
Self {
length: slice.len(),
data: if !slice.is_empty() {
slice.as_ptr()
} else {
std::ptr::null()
},
indices: std::ptr::null(),
}
}
}
impl Default for Mp4parseByteData {
fn default() -> Self {
Self {
length: 0,
data: std::ptr::null(),
indices: std::ptr::null(),
}
}
}
impl Mp4parseByteData {
fn set_data(&mut self, data: &[u8]) {
self.length = data.len();
self.data = data.as_ptr();
}
fn set_indices(&mut self, data: &[Indice]) {
self.length = data.len();
self.indices = data.as_ptr();
}
}
#[repr(C)]
#[derive(Default)]
pub struct Mp4parsePsshInfo {
pub data: Mp4parseByteData,
}
#[repr(u8)]
#[derive(Debug, PartialEq)]
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,
pub crypt_byte_block: u8,
pub skip_byte_block: u8,
pub constant_iv: Mp4parseByteData,
}
#[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,
}
#[derive(Default)]
pub struct Mp4parseParser {
context: MediaContext,
opus_header: TryHashMap<u32, TryVec<u8>>,
pssh_data: TryVec<u8>,
sample_table: TryHashMap<u32, TryVec<Indice>>,
audio_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackAudioSampleInfo>>,
video_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackVideoSampleInfo>>,
}
#[repr(C)]
#[derive(Debug)]
pub struct Mp4parseAvifImageItem {
pub coded_data: Mp4parseByteData,
pub bits_per_channel: Mp4parseByteData,
}
#[repr(C)]
#[derive(Debug)]
pub struct Mp4parseAvifImage {
pub primary_image: Mp4parseAvifImageItem,
pub spatial_extents: *const mp4parse::ImageSpatialExtentsProperty,
pub nclx_colour_information: *const mp4parse::NclxColourInformation,
pub icc_colour_information: Mp4parseByteData,
pub image_rotation: mp4parse::ImageRotation,
pub image_mirror: *const mp4parse::ImageMirror,
pub alpha_image: Mp4parseAvifImageItem,
pub premultiplied_alpha: bool,
}
trait ContextParser
where
Self: Sized,
{
type Context;
fn with_context(context: Self::Context) -> Self;
fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context>;
}
impl Mp4parseParser {
fn context(&self) -> &MediaContext {
&self.context
}
fn context_mut(&mut self) -> &mut MediaContext {
&mut self.context
}
}
impl ContextParser for Mp4parseParser {
type Context = MediaContext;
fn with_context(context: Self::Context) -> Self {
Self {
context,
..Default::default()
}
}
fn read<T: Read>(io: &mut T, _strictness: ParseStrictness) -> mp4parse::Result<Self::Context> {
let r = mp4parse::read_mp4(io);
log::debug!("mp4parse::read_mp4 -> {:?}", r);
r
}
}
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, strictness: ParseStrictness) -> mp4parse::Result<Self::Context> {
let r = mp4parse::read_avif(io, strictness);
if r.is_err() {
log::debug!("{:?}", r);
}
log::trace!("mp4parse::read_avif -> {:?}", r);
r
}
}
#[repr(C)]
#[derive(Clone)]
pub struct Mp4parseIo {
pub read: Option<
extern "C" fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize,
>,
pub userdata: *mut std::os::raw::c_void,
}
impl Read for Mp4parseIo {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if buf.len() > isize::max_value() as usize {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"buf length overflow in Mp4parseIo Read impl",
));
}
let rv = self.read.unwrap()(buf.as_mut_ptr(), buf.len(), self.userdata);
if rv >= 0 {
Ok(rv as usize)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"I/O error in Mp4parseIo Read impl",
))
}
}
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_new(
io: *const Mp4parseIo,
parser_out: *mut *mut Mp4parseParser,
) -> Mp4parseStatus {
mp4parse_new_common(io, ParseStrictness::Normal, parser_out)
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_avif_new(
io: *const Mp4parseIo,
strictness: ParseStrictness,
parser_out: *mut *mut Mp4parseAvifParser,
) -> Mp4parseStatus {
mp4parse_new_common(io, strictness, parser_out)
}
unsafe fn mp4parse_new_common<P: ContextParser>(
io: *const Mp4parseIo,
strictness: ParseStrictness,
parser_out: *mut *mut P,
) -> Mp4parseStatus {
if io.is_null()
|| (*io).userdata.is_null()
|| (*io).read.is_none()
|| parser_out.is_null()
|| !(*parser_out).is_null()
{
Mp4parseStatus::BadArg
} else {
match mp4parse_new_common_safe(&mut (*io).clone(), strictness) {
Ok(parser) => {
*parser_out = parser;
Mp4parseStatus::Ok
}
Err(status) => status,
}
}
}
fn mp4parse_new_common_safe<T: Read, P: ContextParser>(
io: &mut T,
strictness: ParseStrictness,
) -> Result<*mut P, Mp4parseStatus> {
P::read(io, strictness)
.map(P::with_context)
.and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
.map(TryBox::into_raw)
.map_err(Mp4parseStatus::from)
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) {
assert!(!parser.is_null());
let _ = TryBox::from_raw(parser);
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_avif_free(parser: *mut Mp4parseAvifParser) {
assert!(!parser.is_null());
let _ = TryBox::from_raw(parser);
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_get_track_count(
parser: *const Mp4parseParser,
count: *mut u32,
) -> Mp4parseStatus {
if parser.is_null() || count.is_null() {
return Mp4parseStatus::BadArg;
}
let context = (*parser).context();
if context.tracks.len() > u32::max_value() as usize {
return Mp4parseStatus::Invalid;
}
*count = context.tracks.len() as u32;
Mp4parseStatus::Ok
}
#[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;
}
*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(Microseconds(0)), |media_time| {
track_time_to_us(media_time, track_timescale)
}) {
Some(time) => time.0.into(),
None => return Mp4parseStatus::Invalid,
};
let empty_duration: CheckedInteger<_> = match track
.empty_duration
.map_or(Some(Microseconds(0)), |empty_duration| {
media_time_to_us(empty_duration, context_timescale)
}) {
Some(time) => time.0.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.0,
None => return Mp4parseStatus::Invalid,
}
} else {
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
}
#[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;
}
*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);
}
let stsd = match track.stsd {
Some(ref stsd) => stsd,
None => return Err(Mp4parseStatus::Invalid), };
if stsd.descriptions.is_empty() {
return Err(Mp4parseStatus::Invalid); }
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),
};
sample_info.codec_type = match audio.codec_specific {
AudioCodecSpecific::OpusSpecificBox(_) => Mp4parseCodec::Opus,
AudioCodecSpecific::FLACSpecificBox(_) => Mp4parseCodec::Flac,
AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => {
Mp4parseCodec::Aac
}
AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => {
Mp4parseCodec::Mp3
}
AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM => {
Mp4parseCodec::Unknown
}
AudioCodecSpecific::MP3 => Mp4parseCodec::Mp3,
AudioCodecSpecific::ALACSpecificBox(_) => Mp4parseCodec::Alac,
#[cfg(feature = "3gpp")]
AudioCodecSpecific::AMRSpecificBox(_) => {
if audio.codec_type == CodecType::AMRNB {
Mp4parseCodec::AMRNB
} else {
Mp4parseCodec::AMRWB
}
}
};
sample_info.channels = audio.channelcount as u16;
sample_info.bit_depth = audio.samplesize;
sample_info.sample_rate = audio.samplerate as u32;
match audio.codec_specific {
AudioCodecSpecific::ES_Descriptor(ref esds) => {
if esds.codec_esds.len() > std::u32::MAX as usize {
return Err(Mp4parseStatus::Invalid);
}
sample_info.extra_data.length = esds.codec_esds.len();
sample_info.extra_data.data = esds.codec_esds.as_ptr();
sample_info.codec_specific_config.length = esds.decoder_specific_data.len();
sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr();
if let Some(rate) = esds.audio_sample_rate {
sample_info.sample_rate = rate;
}
if let Some(channels) = esds.audio_channel_count {
sample_info.channels = channels;
}
if let Some(profile) = esds.audio_object_type {
sample_info.profile = profile;
}
sample_info.extended_profile = match esds.extended_audio_object_type {
Some(extended_profile) => extended_profile,
_ => sample_info.profile,
};
}
AudioCodecSpecific::FLACSpecificBox(ref flac) => {
let streaminfo = &flac.blocks[0];
if streaminfo.block_type != 0 || streaminfo.data.len() != 34 {
return Err(Mp4parseStatus::Invalid);
}
sample_info.codec_specific_config.length = streaminfo.data.len();
sample_info.codec_specific_config.data = streaminfo.data.as_ptr();
}
AudioCodecSpecific::OpusSpecificBox(ref opus) => {
let mut v = TryVec::new();
match serialize_opus_header(opus, &mut v) {
Err(_) => {
return Err(Mp4parseStatus::Invalid);
}
Ok(_) => {
opus_header.insert(track_index, v)?;
if let Some(v) = opus_header.get(&track_index) {
if v.len() > std::u32::MAX as usize {
return Err(Mp4parseStatus::Invalid);
}
sample_info.codec_specific_config.length = v.len();
sample_info.codec_specific_config.data = v.as_ptr();
}
}
}
}
AudioCodecSpecific::ALACSpecificBox(ref alac) => {
sample_info.codec_specific_config.length = alac.data.len();
sample_info.codec_specific_config.data = alac.data.as_ptr();
}
AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
#[cfg(feature = "3gpp")]
AudioCodecSpecific::AMRSpecificBox(_) => (),
}
if let Some(p) = audio
.protection_info
.iter()
.find(|sinf| sinf.tenc.is_some())
{
sample_info.protected_data.original_format =
OptionalFourCc::Some(p.original_format.value);
sample_info.protected_data.scheme_type = match p.scheme_type {
Some(ref scheme_type_box) => {
match scheme_type_box.scheme_type.value.as_ref() {
b"cenc" => Mp4ParseEncryptionSchemeType::Cenc,
b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs,
_ => Mp4ParseEncryptionSchemeType::None,
}
}
None => Mp4ParseEncryptionSchemeType::None,
};
if let Some(ref tenc) = p.tenc {
sample_info.protected_data.is_encrypted = tenc.is_encrypted;
sample_info.protected_data.iv_size = tenc.iv_size;
sample_info.protected_data.kid.set_data(&(tenc.kid));
sample_info.protected_data.crypt_byte_block =
tenc.crypt_byte_block_count.unwrap_or(0);
sample_info.protected_data.skip_byte_block =
tenc.skip_byte_block_count.unwrap_or(0);
if let Some(ref iv_vec) = tenc.constant_iv {
if iv_vec.len() > std::u32::MAX as usize {
return Err(Mp4parseStatus::Invalid);
}
sample_info.protected_data.constant_iv.set_data(iv_vec);
};
}
}
audio_sample_infos.push(sample_info)?;
}
parser
.audio_track_sample_descriptions
.insert(track_index, audio_sample_infos)?;
match parser.audio_track_sample_descriptions.get(&track_index) {
Some(sample_info) => {
if sample_info.len() > std::u32::MAX as usize {
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), }
Ok(())
}
#[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;
}
*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);
}
if let Some(ref tkhd) = track.tkhd {
info.display_width = tkhd.width >> 16; info.display_height = tkhd.height >> 16; 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, (-1, 0, 0, -1) => 180, (0, -1, 1, 0) => 270, _ => 0,
};
} else {
return Err(Mp4parseStatus::Invalid);
}
let stsd = match track.stsd {
Some(ref stsd) => stsd,
None => return Err(Mp4parseStatus::Invalid), };
if stsd.descriptions.is_empty() {
return Err(Mp4parseStatus::Invalid); }
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),
};
sample_info.codec_type = match video.codec_specific {
VideoCodecSpecific::VPxConfig(_) => Mp4parseCodec::Vp9,
VideoCodecSpecific::AV1Config(_) => Mp4parseCodec::Av1,
VideoCodecSpecific::AVCConfig(_) => Mp4parseCodec::Avc,
VideoCodecSpecific::H263Config(_) => Mp4parseCodec::H263,
#[cfg(feature = "mp4v")]
VideoCodecSpecific::ESDSConfig(_) => Mp4parseCodec::Mp4v,
#[cfg(not(feature = "mp4v"))]
VideoCodecSpecific::ESDSConfig(_) =>
{
Mp4parseCodec::Unknown
}
};
sample_info.image_width = video.width;
sample_info.image_height = video.height;
match video.codec_specific {
VideoCodecSpecific::AV1Config(ref config) => {
sample_info.extra_data.set_data(&config.raw_config);
}
VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => {
sample_info.extra_data.set_data(data);
}
_ => {}
}
if let Some(p) = video
.protection_info
.iter()
.find(|sinf| sinf.tenc.is_some())
{
sample_info.protected_data.original_format =
OptionalFourCc::Some(p.original_format.value);
sample_info.protected_data.scheme_type = match p.scheme_type {
Some(ref scheme_type_box) => {
match scheme_type_box.scheme_type.value.as_ref() {
b"cenc" => Mp4ParseEncryptionSchemeType::Cenc,
b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs,
_ => Mp4ParseEncryptionSchemeType::None,
}
}
None => Mp4ParseEncryptionSchemeType::None,
};
if let Some(ref tenc) = p.tenc {
sample_info.protected_data.is_encrypted = tenc.is_encrypted;
sample_info.protected_data.iv_size = tenc.iv_size;
sample_info.protected_data.kid.set_data(&(tenc.kid));
sample_info.protected_data.crypt_byte_block =
tenc.crypt_byte_block_count.unwrap_or(0);
sample_info.protected_data.skip_byte_block =
tenc.skip_byte_block_count.unwrap_or(0);
if let Some(ref iv_vec) = tenc.constant_iv {
if iv_vec.len() > std::u32::MAX as usize {
return Err(Mp4parseStatus::Invalid);
}
sample_info.protected_data.constant_iv.set_data(iv_vec);
};
}
}
video_sample_infos.push(sample_info)?;
}
parser
.video_track_sample_descriptions
.insert(track_index, video_sample_infos)?;
match parser.video_track_sample_descriptions.get(&track_index) {
Some(sample_info) => {
if sample_info.len() > std::u32::MAX as usize {
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), }
Ok(())
}
#[no_mangle]
pub unsafe extern "C" fn mp4parse_avif_get_image(
parser: *const Mp4parseAvifParser,
avif_image: *mut Mp4parseAvifImage,
) -> Mp4parseStatus {
if parser.is_null() || avif_image.is_null() {
return Mp4parseStatus::BadArg;
}
if let Ok(image) = mp4parse_avif_get_image_safe(&*parser) {
*avif_image = image;
Mp4parseStatus::Ok
} else {
Mp4parseStatus::Invalid
}
}
pub fn mp4parse_avif_get_image_safe(
parser: &Mp4parseAvifParser,
) -> mp4parse::Result<Mp4parseAvifImage> {
let context = parser.context();
let primary_image = Mp4parseAvifImageItem {
coded_data: Mp4parseByteData::with_data(context.primary_item_coded_data()),
bits_per_channel: Mp4parseByteData::with_data(context.primary_item_bits_per_channel()?),
};
let alpha_image = Mp4parseAvifImageItem {
coded_data: Mp4parseByteData::with_data(context.alpha_item_coded_data()),
bits_per_channel: Mp4parseByteData::with_data(context.alpha_item_bits_per_channel()?),
};
Ok(Mp4parseAvifImage {
primary_image,
spatial_extents: context.spatial_extents_ptr()?,
nclx_colour_information: context.nclx_colour_information_ptr()?,
icc_colour_information: Mp4parseByteData::with_data(context.icc_colour_information()?),
image_rotation: context.image_rotation()?,
image_mirror: context.image_mirror_ptr()?,
alpha_image,
premultiplied_alpha: context.premultiplied_alpha,
})
}
#[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;
}
*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.0).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.0).ok())
.map(Into::into),
_ => None,
};
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)
}
#[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;
}
*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.0 as u64,
None => return Mp4parseStatus::Invalid,
}
}
Mp4parseStatus::Ok
}
#[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;
}
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
})
}
#[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;
}
*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 buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
match input.read(buf) {
Ok(n) => n as isize,
Err(_) => -1,
}
}
#[test]
fn get_track_count_null_parser() {
unsafe {
let mut count: u32 = 0;
let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut());
assert_eq!(rv, Mp4parseStatus::BadArg);
let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
assert_eq!(rv, Mp4parseStatus::BadArg);
}
}
#[test]
fn arg_validation() {
unsafe {
let rv = mp4parse_new(std::ptr::null(), std::ptr::null_mut());
assert_eq!(rv, Mp4parseStatus::BadArg);
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();
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());
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);
}
}