diff --git a/README.md b/README.md index 4714330..6e532eb 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ * [ISO/IEC 14496-14](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - MP4 file format * ISO/IEC 14496-17 - Streaming text format +https://crates.io/crates/mp4 + [![Crates.io](https://img.shields.io/crates/v/mp4)](https://crates.io/crates/mp4) [![Crates.io](https://img.shields.io/crates/d/mp4)](https://crates.io/crates/mp4) [![Build Status](https://travis-ci.org/alfg/mp4rs.svg?branch=master)](https://travis-ci.org/alfg/mp4rs) @@ -55,6 +57,12 @@ fn main() -> Result<()> { See [examples/](examples/) for more examples. +#### Install +Add to your `Cargo.toml`: +``` +mp4 = "0.6.0" +``` + #### Documentation * https://docs.rs/mp4/ @@ -99,7 +107,7 @@ View HTML report at `target/criterion/report/index.html` ## Web Assembly See the [mp4-inspector](https://github.com/alfg/mp4-inspector) project as a reference for using this library in Javascript via Web Assembly. -## Resources +## Related Projects * https://github.com/mozilla/mp4parse-rust * https://github.com/pcwalton/rust-media * https://github.com/alfg/mp4 diff --git a/examples/mp4dump.rs b/examples/mp4dump.rs index b8aa837..f8ee462 100644 --- a/examples/mp4dump.rs +++ b/examples/mp4dump.rs @@ -52,6 +52,12 @@ fn get_boxes(file: File) -> Result> { boxes.push(build_box(&mp4.moov)); boxes.push(build_box(&mp4.moov.mvhd)); + if let Some(ref mvex) = &mp4.moov.mvex { + boxes.push(build_box(mvex)); + boxes.push(build_box(&mvex.mehd)); + boxes.push(build_box(&mvex.trex)); + } + // trak. for track in mp4.tracks().iter() { boxes.push(build_box(&track.trak)); @@ -116,6 +122,9 @@ fn get_boxes(file: File) -> Result> { for traf in moof.trafs.iter() { boxes.push(build_box(traf)); boxes.push(build_box(&traf.tfhd)); + if let Some(ref trun) = &traf.trun { + boxes.push(build_box(trun)); + } } } diff --git a/examples/mp4sample.rs b/examples/mp4sample.rs new file mode 100644 index 0000000..b6e9b77 --- /dev/null +++ b/examples/mp4sample.rs @@ -0,0 +1,50 @@ +use std::env; +use std::fs::File; +use std::io::prelude::*; +use std::io::{self, BufReader}; +use std::path::Path; + +use mp4::{Result}; + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() < 2 { + println!("Usage: mp4sample "); + std::process::exit(1); + } + + if let Err(err) = samples(&args[1]) { + let _ = writeln!(io::stderr(), "{}", err); + } +} + +fn samples>(filename: &P) -> Result<()> { + let f = File::open(filename)?; + let size = f.metadata()?.len(); + let reader = BufReader::new(f); + + let mut mp4 = mp4::Mp4Reader::read_header(reader, size)?; + + for track_idx in 0..mp4.tracks().len() { + let track_id = track_idx as u32 + 1; + let sample_count = mp4.sample_count(track_id).unwrap(); + + for sample_idx in 0..sample_count { + let sample_id = sample_idx + 1; + let sample = mp4.read_sample(track_id, sample_id); + + if let Some(ref samp) = sample.unwrap() { + println!("[{}] start_time={} duration={} rendering_offset={} size={} is_sync={}", + sample_id, + samp.start_time, + samp.duration, + samp.rendering_offset, + samp.bytes.len(), + samp.is_sync, + ); + } + } + } + Ok(()) +} diff --git a/src/error.rs b/src/error.rs index d549d9b..f90605a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,8 +16,12 @@ pub enum Error { TrakNotFound(u32), #[error("trak[{0}].{1} not found")] BoxInTrakNotFound(u32, BoxType), + #[error("traf[{0}].{1} not found")] + BoxInTrafNotFound(u32, BoxType), #[error("trak[{0}].stbl.{1} not found")] BoxInStblNotFound(u32, BoxType), #[error("trak[{0}].stbl.{1}.entry[{2}] not found")] EntryInStblNotFound(u32, BoxType, u32), + #[error("traf[{0}].trun.{1}.entry[{2}] not found")] + EntryInTrunNotFound(u32, BoxType, u32), } diff --git a/src/mp4box/trex.rs b/src/mp4box/trex.rs index fbedc2e..943265f 100644 --- a/src/mp4box/trex.rs +++ b/src/mp4box/trex.rs @@ -21,7 +21,7 @@ impl TrexBox { } pub fn get_size(&self) -> u64 { - HEADER_SIZE + HEADER_EXT_SIZE + 4 + 20 + HEADER_SIZE + HEADER_EXT_SIZE + 20 } } @@ -51,7 +51,6 @@ impl ReadBox<&mut R> for TrexBox { let (version, flags) = read_box_header_ext(reader)?; - reader.read_u32::()?; // pre-defined let track_id = reader.read_u32::()?; let default_sample_description_index = reader.read_u32::()?; let default_sample_duration = reader.read_u32::()?; @@ -79,7 +78,6 @@ impl WriteBox<&mut W> for TrexBox { write_box_header_ext(writer, self.version, self.flags)?; - writer.write_u32::(0)?; // pre-defined writer.write_u32::(self.track_id)?; writer.write_u32::(self.default_sample_description_index)?; writer.write_u32::(self.default_sample_duration)?; diff --git a/src/mp4box/trun.rs b/src/mp4box/trun.rs index ab9a77a..88adfe0 100644 --- a/src/mp4box/trun.rs +++ b/src/mp4box/trun.rs @@ -11,7 +11,7 @@ pub struct TrunBox { pub sample_count: u32, pub data_offset: i32, - // #[serde(skip_serializing)] + #[serde(skip_serializing)] pub sample_sizes: Vec, } @@ -56,8 +56,8 @@ impl ReadBox<&mut R> for TrunBox { let mut sample_sizes = Vec::with_capacity(sample_count as usize); for _ in 0..sample_count { - let sample_duration = reader.read_u32::()?; - sample_sizes.push(sample_duration); + let sample_size = reader.read_u32::()?; + sample_sizes.push(sample_size); } skip_bytes_to(reader, start + size)?; diff --git a/src/reader.rs b/src/reader.rs index 568fa14..b610942 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -63,7 +63,7 @@ impl Mp4Reader { } let size = current - start; - let tracks = if let Some(ref moov) = moov { + let mut tracks = if let Some(ref moov) = moov { let mut tracks = Vec::with_capacity(moov.traks.len()); for (i, trak) in moov.traks.iter().enumerate() { assert_eq!(trak.tkhd.track_id, i as u32 + 1); @@ -74,6 +74,24 @@ impl Mp4Reader { Vec::new() }; + // Update tracks if any fragmented (moof) boxes are found. + if moofs.len() > 0 { + let mut default_sample_duration = 0; + if let Some(ref moov) = moov { + if let Some(ref mvex) = &moov.mvex { + default_sample_duration = mvex.trex.default_sample_duration + } + } + + for moof in moofs.iter() { + for traf in moof.trafs.iter() { + let track_id = traf.tfhd.track_id as usize - 1; + tracks[track_id].default_sample_duration = default_sample_duration; + tracks[track_id].trafs.push(traf.clone()); + } + } + } + Ok(Mp4Reader { reader, ftyp: ftyp.unwrap(), diff --git a/src/track.rs b/src/track.rs index 4644533..2802c6a 100644 --- a/src/track.rs +++ b/src/track.rs @@ -5,6 +5,7 @@ use std::io::{Read, Seek, SeekFrom, Write}; use std::time::Duration; use crate::mp4box::trak::TrakBox; +use crate::mp4box::traf::TrafBox; use crate::mp4box::*; use crate::mp4box::{ avc1::Avc1Box, @@ -88,12 +89,16 @@ impl From for TrackConfig { #[derive(Debug)] pub struct Mp4Track { pub trak: TrakBox, + pub trafs: Vec, + + // Fragmented Tracks Defaults. + pub default_sample_duration: u32, } impl Mp4Track { pub(crate) fn from(trak: &TrakBox) -> Self { let trak = trak.clone(); - Self { trak } + Self { trak, trafs: Vec::new(), default_sample_duration: 0, } } pub fn track_id(&self) -> u32 { @@ -215,7 +220,17 @@ impl Mp4Track { } pub fn sample_count(&self) -> u32 { - self.trak.mdia.minf.stbl.stsz.sample_count + if self.trafs.len() > 0 { + let mut sample_count = 0u32; + for traf in self.trafs.iter() { + if let Some(ref trun) = traf.trun { + sample_count += trun.sample_count; + } + } + sample_count + } else { + self.trak.mdia.minf.stbl.stsz.sample_count + } } pub fn video_profile(&self) -> Result { @@ -330,18 +345,39 @@ impl Mp4Track { } fn sample_size(&self, sample_id: u32) -> Result { - let stsz = &self.trak.mdia.minf.stbl.stsz; - if stsz.sample_size > 0 { - return Ok(stsz.sample_size); - } - if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) { - Ok(*size) + if self.trafs.len() > 0 { + let sample_sizes_count = self.sample_count() / self.trafs.len() as u32; + let traf_idx = (sample_id - 1) / sample_sizes_count; + if let Some(trun) = &self.trafs[traf_idx as usize].trun { + if let Some(size) = trun.sample_sizes.get((sample_id - (sample_sizes_count * traf_idx)) as usize - 1) { + Ok(*size) + } else { + return Err(Error::EntryInTrunNotFound( + self.track_id(), + BoxType::TrunBox, + sample_id, + )); + } + } else { + return Err(Error::BoxInTrafNotFound( + self.track_id(), + BoxType::TrafBox, + )); + } } else { - return Err(Error::EntryInStblNotFound( - self.track_id(), - BoxType::StszBox, - sample_id, - )); + let stsz = &self.trak.mdia.minf.stbl.stsz; + if stsz.sample_size > 0 { + return Ok(stsz.sample_size); + } + if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) { + Ok(*size) + } else { + return Err(Error::EntryInStblNotFound( + self.track_id(), + BoxType::StszBox, + sample_id, + )); + } } } @@ -359,27 +395,33 @@ impl Mp4Track { } fn sample_offset(&self, sample_id: u32) -> Result { - let stsc_index = self.stsc_index(sample_id); + if self.trafs.len() > 0 { + let sample_sizes_count = self.sample_count() / self.trafs.len() as u32; + let traf_idx = (sample_id - 1) / sample_sizes_count; + Ok(self.trafs[(sample_id - (sample_sizes_count * traf_idx)) as usize].tfhd.base_data_offset as u64) + } else { + let stsc_index = self.stsc_index(sample_id); - let stsc = &self.trak.mdia.minf.stbl.stsc; - let stsc_entry = stsc.entries.get(stsc_index).unwrap(); + let stsc = &self.trak.mdia.minf.stbl.stsc; + let stsc_entry = stsc.entries.get(stsc_index).unwrap(); - let first_chunk = stsc_entry.first_chunk; - let first_sample = stsc_entry.first_sample; - let samples_per_chunk = stsc_entry.samples_per_chunk; + let first_chunk = stsc_entry.first_chunk; + let first_sample = stsc_entry.first_sample; + let samples_per_chunk = stsc_entry.samples_per_chunk; - let chunk_id = first_chunk + (sample_id - first_sample) / samples_per_chunk; + let chunk_id = first_chunk + (sample_id - first_sample) / samples_per_chunk; - let chunk_offset = self.chunk_offset(chunk_id)?; + let chunk_offset = self.chunk_offset(chunk_id)?; - let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk; + let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk; - let mut sample_offset = 0; - for i in first_sample_in_chunk..sample_id { - sample_offset += self.sample_size(i)?; + let mut sample_offset = 0; + for i in first_sample_in_chunk..sample_id { + sample_offset += self.sample_size(i)?; + } + + Ok(chunk_offset + sample_offset as u64) } - - Ok(chunk_offset + sample_offset as u64) } fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> { @@ -388,22 +430,27 @@ impl Mp4Track { let mut sample_count = 1; let mut elapsed = 0; - for entry in stts.entries.iter() { - if sample_id <= sample_count + entry.sample_count - 1 { - let start_time = - (sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed; - return Ok((start_time, entry.sample_delta)); + if self.trafs.len() > 0 { + let start_time = ((sample_id - 1) * self.default_sample_duration) as u64; + return Ok((start_time, self.default_sample_duration)) + } else { + for entry in stts.entries.iter() { + if sample_id <= sample_count + entry.sample_count - 1 { + let start_time = + (sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed; + return Ok((start_time, entry.sample_delta)); + } + + sample_count += entry.sample_count; + elapsed += entry.sample_count as u64 * entry.sample_delta as u64; } - sample_count += entry.sample_count; - elapsed += entry.sample_count as u64 * entry.sample_delta as u64; + return Err(Error::EntryInStblNotFound( + self.track_id(), + BoxType::SttsBox, + sample_id, + )); } - - return Err(Error::EntryInStblNotFound( - self.track_id(), - BoxType::SttsBox, - sample_id, - )); } fn sample_rendering_offset(&self, sample_id: u32) -> i32 { @@ -417,6 +464,11 @@ impl Mp4Track { } fn is_sync_sample(&self, sample_id: u32) -> bool { + if self.trafs.len() > 0 { + let sample_sizes_count = self.sample_count() / self.trafs.len() as u32; + return sample_id == 1 || sample_id % sample_sizes_count == 0 + } + if let Some(ref stss) = self.trak.mdia.minf.stbl.stss { match stss.entries.binary_search(&sample_id) { Ok(_) => true,