diff --git a/examples/mp4dump.rs b/examples/mp4dump.rs index 4e4a7dd..48537d2 100644 --- a/examples/mp4dump.rs +++ b/examples/mp4dump.rs @@ -107,6 +107,17 @@ fn get_boxes(file: File) -> Result> { boxes.push(build_box(co64)); } } + + // If fragmented, add moof boxes. + for moof in mp4.moofs.iter() { + boxes.push(build_box(moof)); + boxes.push(build_box(&moof.mfhd)); + for traf in moof.trafs.iter() { + boxes.push(build_box(traf)); + boxes.push(build_box(&traf.tfhd)); + } + } + Ok(boxes) } diff --git a/examples/mp4info.rs b/examples/mp4info.rs index 234548a..1d98c19 100644 --- a/examples/mp4info.rs +++ b/examples/mp4info.rs @@ -40,6 +40,7 @@ fn info>(filename: &P) -> Result<()> { println!(" version: {}", mp4.moov.mvhd.version); println!(" creation time: {}", creation_time(mp4.moov.mvhd.creation_time)); println!(" duration: {:?}", mp4.duration()); + println!(" fragments: {:?}", mp4.is_fragmented()); println!(" timescale: {:?}\n", mp4.timescale()); println!("Found {} Tracks", mp4.tracks().len()); @@ -56,7 +57,6 @@ fn info>(filename: &P) -> Result<()> { media_info ); } - Ok(()) } @@ -70,7 +70,7 @@ fn video_info(track: &Mp4Track) -> Result { track.width(), track.height(), track.bitrate() / 1000, - track.frame_rate_f64() + track.frame_rate() )) } else { Ok(format!( @@ -80,7 +80,7 @@ fn video_info(track: &Mp4Track) -> Result { track.width(), track.height(), track.bitrate() / 1000, - track.frame_rate_f64() + track.frame_rate() )) } } diff --git a/src/mp4box/mfhd.rs b/src/mp4box/mfhd.rs new file mode 100644 index 0000000..cbb4abc --- /dev/null +++ b/src/mp4box/mfhd.rs @@ -0,0 +1,97 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct MfhdBox { + pub version: u8, + pub flags: u32, + pub sequence_number: u32, +} + +impl Default for MfhdBox { + fn default() -> Self { + MfhdBox { + version: 0, + flags: 0, + sequence_number: 1, + } + } +} + +impl MfhdBox { + pub fn get_type(&self) -> BoxType { + BoxType::MfhdBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + } +} + +impl Mp4Box for MfhdBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } +} + +impl ReadBox<&mut R> for MfhdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + let sequence_number = reader.read_u32::()?; + + skip_bytes_to(reader, start + size)?; + + Ok(MfhdBox { + version, + flags, + sequence_number, + }) + } +} + +impl WriteBox<&mut W> for MfhdBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + write_box_header_ext(writer, self.version, self.flags)?; + writer.write_u32::(self.sequence_number)?; + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_mfhd() { + let src_box = MfhdBox { + version: 0, + flags: 0, + sequence_number: 1, + }; + let mut buf = Vec::new(); + src_box.write_box(&mut buf).unwrap(); + assert_eq!(buf.len(), src_box.box_size() as usize); + + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::MfhdBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = MfhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index a82aa5f..b0053cf 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -16,8 +16,10 @@ pub(crate) mod mdhd; pub(crate) mod mdia; pub(crate) mod minf; pub(crate) mod moov; +pub(crate) mod moof; pub(crate) mod mp4a; pub(crate) mod mvhd; +pub(crate) mod mfhd; pub(crate) mod smhd; pub(crate) mod stbl; pub(crate) mod stco; @@ -27,11 +29,14 @@ pub(crate) mod stss; pub(crate) mod stsz; pub(crate) mod stts; pub(crate) mod tkhd; +pub(crate) mod tfhd; pub(crate) mod trak; +pub(crate) mod traf; pub(crate) mod vmhd; pub use ftyp::FtypBox; pub use moov::MoovBox; +pub use moof::MoofBox; pub const HEADER_SIZE: u64 = 8; // const HEADER_LARGE_SIZE: u64 = 16; @@ -68,11 +73,13 @@ macro_rules! boxtype { boxtype! { FtypBox => 0x66747970, MvhdBox => 0x6d766864, + MfhdBox => 0x6d666864, FreeBox => 0x66726565, MdatBox => 0x6d646174, MoovBox => 0x6d6f6f76, MoofBox => 0x6d6f6f66, TkhdBox => 0x746b6864, + TfhdBox => 0x74666864, EdtsBox => 0x65647473, MdiaBox => 0x6d646961, ElstBox => 0x656c7374, @@ -90,6 +97,7 @@ boxtype! { StcoBox => 0x7374636F, Co64Box => 0x636F3634, TrakBox => 0x7472616b, + TrafBox => 0x74726166, UdtaBox => 0x75647461, DinfBox => 0x64696e66, SmhdBox => 0x736d6864, diff --git a/src/mp4box/moof.rs b/src/mp4box/moof.rs new file mode 100644 index 0000000..f9d9201 --- /dev/null +++ b/src/mp4box/moof.rs @@ -0,0 +1,90 @@ +use std::io::{Read, Seek, SeekFrom, Write}; + +use crate::mp4box::*; +use crate::mp4box::{mfhd::MfhdBox, traf::TrafBox}; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct MoofBox { + pub mfhd: MfhdBox, + pub trafs: Vec, +} + +impl MoofBox { + pub fn get_type(&self) -> BoxType { + BoxType::MoofBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + self.mfhd.box_size(); + for traf in self.trafs.iter() { + size += traf.box_size(); + } + size + } +} + +impl Mp4Box for MoofBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } +} + +impl ReadBox<&mut R> for MoofBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut mfhd = None; + let mut trafs = Vec::new(); + + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + + match name { + BoxType::MfhdBox => { + mfhd = Some(MfhdBox::read_box(reader, s)?); + } + BoxType::TrafBox => { + let traf = TrafBox::read_box(reader, s)?; + trafs.push(traf); + } + _ => { + // XXX warn!() + skip_box(reader, s)?; + } + } + current = reader.seek(SeekFrom::Current(0))?; + } + + if mfhd.is_none() { + return Err(Error::BoxNotFound(BoxType::MfhdBox)); + } + + skip_bytes_to(reader, start + size)?; + + Ok(MoofBox { + mfhd: mfhd.unwrap(), + trafs, + }) + } +} + +impl WriteBox<&mut W> for MoofBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + self.mfhd.write_box(writer)?; + for traf in self.trafs.iter() { + traf.write_box(writer)?; + } + Ok(0) + } +} diff --git a/src/mp4box/tfhd.rs b/src/mp4box/tfhd.rs new file mode 100644 index 0000000..cdc1464 --- /dev/null +++ b/src/mp4box/tfhd.rs @@ -0,0 +1,97 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct TfhdBox { + pub version: u8, + pub flags: u32, + pub track_id: u32, +} + +impl Default for TfhdBox { + fn default() -> Self { + TfhdBox { + version: 0, + flags: 0, + track_id: 0, + } + } +} + +impl TfhdBox { + pub fn get_type(&self) -> BoxType { + BoxType::TfhdBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + } +} + +impl Mp4Box for TfhdBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } +} + +impl ReadBox<&mut R> for TfhdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + let track_id = reader.read_u32::()?; + + skip_bytes_to(reader, start + size)?; + + Ok(TfhdBox { + version, + flags, + track_id, + }) + } +} + +impl WriteBox<&mut W> for TfhdBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + write_box_header_ext(writer, self.version, self.flags)?; + writer.write_u32::(self.track_id)?; + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_tfhd() { + let src_box = TfhdBox { + version: 0, + flags: 0, + track_id: 1, + }; + let mut buf = Vec::new(); + src_box.write_box(&mut buf).unwrap(); + assert_eq!(buf.len(), src_box.box_size() as usize); + + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::TfhdBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = TfhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/mp4box/traf.rs b/src/mp4box/traf.rs new file mode 100644 index 0000000..c966dfe --- /dev/null +++ b/src/mp4box/traf.rs @@ -0,0 +1,80 @@ +use std::io::{Read, Seek, SeekFrom, Write}; + +use crate::mp4box::*; +use crate::mp4box::{tfhd::TfhdBox}; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct TrafBox { + pub tfhd: TfhdBox, +} + +impl TrafBox { + pub fn get_type(&self) -> BoxType { + BoxType::TrafBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE; + size += self.tfhd.box_size(); + size + } +} + +impl Mp4Box for TrafBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } +} + +impl ReadBox<&mut R> for TrafBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut tfhd = None; + + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + + match name { + BoxType::TfhdBox => { + tfhd = Some(TfhdBox::read_box(reader, s)?); + } + _ => { + // XXX warn!() + skip_box(reader, s)?; + } + } + + current = reader.seek(SeekFrom::Current(0))?; + } + + if tfhd.is_none() { + return Err(Error::BoxNotFound(BoxType::TfhdBox)); + } + + skip_bytes_to(reader, start + size)?; + + Ok(TrafBox { + tfhd: tfhd.unwrap(), + }) + } +} + +impl WriteBox<&mut W> for TrafBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + self.tfhd.write_box(writer)?; + + Ok(size) + } +} diff --git a/src/reader.rs b/src/reader.rs index 7a58a9f..c2c46ba 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -9,6 +9,7 @@ pub struct Mp4Reader { reader: R, pub ftyp: FtypBox, pub moov: MoovBox, + pub moofs: Vec, tracks: Vec, size: u64, @@ -20,6 +21,7 @@ impl Mp4Reader { let mut ftyp = None; let mut moov = None; + let mut moofs = Vec::new(); let mut current = start; while current < size { @@ -42,7 +44,8 @@ impl Mp4Reader { moov = Some(MoovBox::read_box(&mut reader, s)?); } BoxType::MoofBox => { - skip_box(&mut reader, s)?; + let moof = MoofBox::read_box(&mut reader, s)?; + moofs.push(moof); } _ => { // XXX warn!() @@ -75,6 +78,7 @@ impl Mp4Reader { reader, ftyp: ftyp.unwrap(), moov: moov.unwrap(), + moofs, size, tracks, }) @@ -104,6 +108,14 @@ impl Mp4Reader { self.moov.mvhd.timescale } + pub fn is_fragmented(&self) -> bool { + if self.moofs.len() != 0 { + true + } else { + false + } + } + pub fn tracks(&self) -> &[Mp4Track] { &self.tracks } diff --git a/src/track.rs b/src/track.rs index bee3854..7800c46 100644 --- a/src/track.rs +++ b/src/track.rs @@ -131,19 +131,10 @@ impl Mp4Track { } } - pub fn frame_rate(&self) -> Ratio { + pub fn frame_rate(&self) -> f64 { let dur_msec = self.duration().as_millis() as u64; if dur_msec > 0 { - Ratio::new(self.sample_count() as u64 * 1_000, dur_msec) - } else { - Ratio::new(0, 0) - } - } - - pub fn frame_rate_f64(&self) -> f64 { - let fr = self.frame_rate(); - if fr.to_integer() > 0 { - *fr.numer() as f64 / *fr.denom() as f64 + ((self.sample_count() as u64 * 1000) / dur_msec) as f64 } else { 0.0 } diff --git a/tests/lib.rs b/tests/lib.rs index 1cc8a48..4208e3e 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -102,7 +102,7 @@ fn test_read_mp4() { assert_eq!(track1.width(), 320); assert_eq!(track1.height(), 240); assert_eq!(track1.bitrate(), 0); // XXX - assert_eq!(track1.frame_rate().to_integer(), 25); // XXX + assert_eq!(track1.frame_rate(), 25.00); // XXX // track #2 let track2 = mp4.tracks().get(1).unwrap();