From f8f767dc0792512ee3a8d3788072be43fe36576b Mon Sep 17 00:00:00 2001 From: Alfred Gutierrez Date: Sun, 13 Sep 2020 00:25:08 -0700 Subject: [PATCH] Fragmented boxes (#30) * Add trun box. * Adding Movie Extends Box and subboxes (mvex, mehd, trex). --- src/mp4box/mehd.rs | 147 +++++++++++++++++++++++++++++++++++++++++++++ src/mp4box/mod.rs | 8 +++ src/mp4box/moof.rs | 2 + src/mp4box/moov.rs | 12 +++- src/mp4box/mvex.rs | 98 ++++++++++++++++++++++++++++++ src/mp4box/tfhd.rs | 8 ++- src/mp4box/traf.rs | 11 +++- src/mp4box/trex.rs | 122 +++++++++++++++++++++++++++++++++++++ src/mp4box/trun.rs | 142 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 547 insertions(+), 3 deletions(-) create mode 100644 src/mp4box/mehd.rs create mode 100644 src/mp4box/mvex.rs create mode 100644 src/mp4box/trex.rs create mode 100644 src/mp4box/trun.rs diff --git a/src/mp4box/mehd.rs b/src/mp4box/mehd.rs new file mode 100644 index 0000000..7a4627b --- /dev/null +++ b/src/mp4box/mehd.rs @@ -0,0 +1,147 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; +use serde::{Serialize}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct MehdBox { + pub version: u8, + pub flags: u32, + pub fragment_duration: u64, +} + +impl MehdBox { + pub fn get_type(&self) -> BoxType { + BoxType::MehdBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + HEADER_EXT_SIZE; + + if self.version == 1 { + size += 8; + } else { + assert_eq!(self.version, 0); + size += 4; + } + size + } +} + +impl Default for MehdBox { + fn default() -> Self { + MehdBox { + version: 0, + flags: 0, + fragment_duration: 0, + } + } +} + +impl Mp4Box for MehdBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!("fragment_duration={}", self.fragment_duration); + Ok(s) + } +} + +impl ReadBox<&mut R> for MehdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let fragment_duration = if version == 1 { + reader.read_u64::()? + } else { + assert_eq!(version, 0); + reader.read_u32::()? as u64 + }; + skip_bytes_to(reader, start + size)?; + + Ok(MehdBox { + version, + flags, + fragment_duration, + }) + } +} + +impl WriteBox<&mut W> for MehdBox { + 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)?; + + if self.version == 1 { + writer.write_u64::(self.fragment_duration)?; + } else { + assert_eq!(self.version, 0); + writer.write_u32::(self.fragment_duration as u32)?; + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + + #[test] + fn test_mehd32() { + let src_box = MehdBox { + version: 0, + flags: 0, + fragment_duration: 32, + }; + 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::MehdBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = MehdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } + + #[test] + fn test_mehd64() { + let src_box = MehdBox { + version: 0, + flags: 0, + fragment_duration: 30439936, + }; + 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::MehdBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = MehdBox::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 c3f02d1..f05c4b8 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -16,6 +16,9 @@ pub(crate) mod mdhd; pub(crate) mod mdia; pub(crate) mod minf; pub(crate) mod moov; +pub(crate) mod mvex; +pub(crate) mod mehd; +pub(crate) mod trex; pub(crate) mod moof; pub(crate) mod mp4a; pub(crate) mod mvhd; @@ -32,6 +35,7 @@ pub(crate) mod tkhd; pub(crate) mod tfhd; pub(crate) mod trak; pub(crate) mod traf; +pub(crate) mod trun; pub(crate) mod tx3g; pub(crate) mod vmhd; @@ -78,6 +82,9 @@ boxtype! { FreeBox => 0x66726565, MdatBox => 0x6d646174, MoovBox => 0x6d6f6f76, + MvexBox => 0x6d766578, + MehdBox => 0x6d656864, + TrexBox => 0x74726578, MoofBox => 0x6d6f6f66, TkhdBox => 0x746b6864, TfhdBox => 0x74666864, @@ -99,6 +106,7 @@ boxtype! { Co64Box => 0x636F3634, TrakBox => 0x7472616b, TrafBox => 0x74726166, + TrunBox => 0x7472756E, UdtaBox => 0x75647461, DinfBox => 0x64696e66, SmhdBox => 0x736d6864, diff --git a/src/mp4box/moof.rs b/src/mp4box/moof.rs index a949510..d6533f3 100644 --- a/src/mp4box/moof.rs +++ b/src/mp4box/moof.rs @@ -7,6 +7,8 @@ use crate::mp4box::{mfhd::MfhdBox, traf::TrafBox}; #[derive(Debug, Clone, PartialEq, Default, Serialize)] pub struct MoofBox { pub mfhd: MfhdBox, + + #[serde(rename = "traf")] pub trafs: Vec, } diff --git a/src/mp4box/moov.rs b/src/mp4box/moov.rs index 6cb476a..c7243ca 100644 --- a/src/mp4box/moov.rs +++ b/src/mp4box/moov.rs @@ -2,11 +2,16 @@ use std::io::{Read, Seek, SeekFrom, Write}; use serde::{Serialize}; use crate::mp4box::*; -use crate::mp4box::{mvhd::MvhdBox, trak::TrakBox}; +use crate::mp4box::{mvhd::MvhdBox, mvex::MvexBox, trak::TrakBox}; #[derive(Debug, Clone, PartialEq, Default, Serialize)] pub struct MoovBox { pub mvhd: MvhdBox, + + #[serde(skip_serializing_if = "Option::is_none")] + pub mvex: Option, + + #[serde(rename = "trak")] pub traks: Vec, } @@ -48,6 +53,7 @@ impl ReadBox<&mut R> for MoovBox { let start = box_start(reader)?; let mut mvhd = None; + let mut mvex = None; let mut traks = Vec::new(); let mut current = reader.seek(SeekFrom::Current(0))?; @@ -61,6 +67,9 @@ impl ReadBox<&mut R> for MoovBox { BoxType::MvhdBox => { mvhd = Some(MvhdBox::read_box(reader, s)?); } + BoxType::MvexBox => { + mvex = Some(MvexBox::read_box(reader, s)?); + } BoxType::TrakBox => { let trak = TrakBox::read_box(reader, s)?; traks.push(trak); @@ -86,6 +95,7 @@ impl ReadBox<&mut R> for MoovBox { Ok(MoovBox { mvhd: mvhd.unwrap(), + mvex, traks, }) } diff --git a/src/mp4box/mvex.rs b/src/mp4box/mvex.rs new file mode 100644 index 0000000..c5eedf2 --- /dev/null +++ b/src/mp4box/mvex.rs @@ -0,0 +1,98 @@ +use std::io::{Read, Seek, SeekFrom, Write}; +use serde::{Serialize}; + +use crate::mp4box::*; +use crate::mp4box::{mehd::MehdBox, trex::TrexBox}; + +#[derive(Debug, Clone, PartialEq, Default, Serialize)] +pub struct MvexBox { + pub mehd: MehdBox, + pub trex: TrexBox, +} + +impl MvexBox { + pub fn get_type(&self) -> BoxType { + BoxType::MdiaBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + self.mehd.box_size() + self.trex.box_size() + } +} + +impl Mp4Box for MvexBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!(""); + Ok(s) + } +} + +impl ReadBox<&mut R> for MvexBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut mehd = None; + let mut trex = 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::MehdBox => { + mehd = Some(MehdBox::read_box(reader, s)?); + } + BoxType::TrexBox => { + trex = Some(TrexBox::read_box(reader, s)?); + } + _ => { + // XXX warn!() + skip_box(reader, s)?; + } + } + + current = reader.seek(SeekFrom::Current(0))?; + } + + if mehd.is_none() { + return Err(Error::BoxNotFound(BoxType::MehdBox)); + } + if trex.is_none() { + return Err(Error::BoxNotFound(BoxType::TrexBox)); + } + + skip_bytes_to(reader, start + size)?; + + Ok(MvexBox { + mehd: mehd.unwrap(), + trex: trex.unwrap(), + }) + } +} + +impl WriteBox<&mut W> for MvexBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + self.mehd.write_box(writer)?; + self.trex.write_box(writer)?; + + Ok(size) + } +} diff --git a/src/mp4box/tfhd.rs b/src/mp4box/tfhd.rs index 0f3ccfb..bf7169c 100644 --- a/src/mp4box/tfhd.rs +++ b/src/mp4box/tfhd.rs @@ -9,6 +9,7 @@ pub struct TfhdBox { pub version: u8, pub flags: u32, pub track_id: u32, + pub base_data_offset: u64, } impl Default for TfhdBox { @@ -17,6 +18,7 @@ impl Default for TfhdBox { version: 0, flags: 0, track_id: 0, + base_data_offset: 0, } } } @@ -27,7 +29,7 @@ impl TfhdBox { } pub fn get_size(&self) -> u64 { - HEADER_SIZE + HEADER_EXT_SIZE + 4 + HEADER_SIZE + HEADER_EXT_SIZE + 4 + 8 } } @@ -56,6 +58,7 @@ impl ReadBox<&mut R> for TfhdBox { let (version, flags) = read_box_header_ext(reader)?; let track_id = reader.read_u32::()?; + let base_data_offset = reader.read_u64::()?; skip_bytes_to(reader, start + size)?; @@ -63,6 +66,7 @@ impl ReadBox<&mut R> for TfhdBox { version, flags, track_id, + base_data_offset, }) } } @@ -74,6 +78,7 @@ impl WriteBox<&mut W> for TfhdBox { write_box_header_ext(writer, self.version, self.flags)?; writer.write_u32::(self.track_id)?; + writer.write_u64::(self.base_data_offset)?; Ok(size) } @@ -91,6 +96,7 @@ mod tests { version: 0, flags: 0, track_id: 1, + base_data_offset: 0, }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); diff --git a/src/mp4box/traf.rs b/src/mp4box/traf.rs index dc89846..e4c1ad4 100644 --- a/src/mp4box/traf.rs +++ b/src/mp4box/traf.rs @@ -2,11 +2,12 @@ use std::io::{Read, Seek, SeekFrom, Write}; use serde::{Serialize}; use crate::mp4box::*; -use crate::mp4box::{tfhd::TfhdBox}; +use crate::mp4box::{tfhd::TfhdBox, trun::TrunBox}; #[derive(Debug, Clone, PartialEq, Default, Serialize)] pub struct TrafBox { pub tfhd: TfhdBox, + pub trun: Option, } impl TrafBox { @@ -17,6 +18,9 @@ impl TrafBox { pub fn get_size(&self) -> u64 { let mut size = HEADER_SIZE; size += self.tfhd.box_size(); + if let Some(ref trun) = self.trun { + size += trun.box_size(); + } size } } @@ -45,6 +49,7 @@ impl ReadBox<&mut R> for TrafBox { let start = box_start(reader)?; let mut tfhd = None; + let mut trun = None; let mut current = reader.seek(SeekFrom::Current(0))?; let end = start + size; @@ -57,6 +62,9 @@ impl ReadBox<&mut R> for TrafBox { BoxType::TfhdBox => { tfhd = Some(TfhdBox::read_box(reader, s)?); } + BoxType::TrunBox => { + trun = Some(TrunBox::read_box(reader, s)?); + } _ => { // XXX warn!() skip_box(reader, s)?; @@ -74,6 +82,7 @@ impl ReadBox<&mut R> for TrafBox { Ok(TrafBox { tfhd: tfhd.unwrap(), + trun, }) } } diff --git a/src/mp4box/trex.rs b/src/mp4box/trex.rs new file mode 100644 index 0000000..fbedc2e --- /dev/null +++ b/src/mp4box/trex.rs @@ -0,0 +1,122 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; +use serde::{Serialize}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Default, Serialize)] +pub struct TrexBox { + pub version: u8, + pub flags: u32, + pub track_id: u32, + pub default_sample_description_index: u32, + pub default_sample_duration: u32, + pub default_sample_size: u32, + pub default_sample_flags: u32, +} + +impl TrexBox { + pub fn get_type(&self) -> BoxType { + BoxType::TrexBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + 20 + } +} + +impl Mp4Box for TrexBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!("track_id={} default_sample_duration={}", + self.track_id, self.default_sample_duration); + Ok(s) + } +} + +impl ReadBox<&mut R> for TrexBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + 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::()?; + let default_sample_size = reader.read_u32::()?; + let default_sample_flags = reader.read_u32::()?; + + skip_bytes_to(reader, start + size)?; + + Ok(TrexBox { + version, + flags, + track_id, + default_sample_description_index, + default_sample_duration, + default_sample_size, + default_sample_flags, + }) + } +} + +impl WriteBox<&mut W> for TrexBox { + 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::(0)?; // pre-defined + writer.write_u32::(self.track_id)?; + writer.write_u32::(self.default_sample_description_index)?; + writer.write_u32::(self.default_sample_duration)?; + writer.write_u32::(self.default_sample_size)?; + writer.write_u32::(self.default_sample_flags)?; + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_trex() { + let src_box = TrexBox { + version: 0, + flags: 0, + track_id: 1, + default_sample_description_index: 1, + default_sample_duration: 1000, + default_sample_size: 0, + default_sample_flags: 65536, + }; + 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::TrexBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = TrexBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/mp4box/trun.rs b/src/mp4box/trun.rs new file mode 100644 index 0000000..ab9a77a --- /dev/null +++ b/src/mp4box/trun.rs @@ -0,0 +1,142 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; +use serde::{Serialize}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Default, Serialize)] +pub struct TrunBox { + pub version: u8, + pub flags: u32, + pub sample_count: u32, + pub data_offset: i32, + + // #[serde(skip_serializing)] + pub sample_sizes: Vec, +} + +impl TrunBox { + pub fn get_type(&self) -> BoxType { + BoxType::TrunBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 8 + (4 * self.sample_sizes.len() as u64) + } +} + +impl Mp4Box for TrunBox { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!("sample_size={}", + self.sample_count); + Ok(s) + } +} + +impl ReadBox<&mut R> for TrunBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let sample_count = reader.read_u32::()?; + let data_offset = reader.read_i32::()?; + + 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); + } + + skip_bytes_to(reader, start + size)?; + + Ok(TrunBox { + version, + flags, + sample_count, + data_offset, + sample_sizes, + }) + } +} + +impl WriteBox<&mut W> for TrunBox { + 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.sample_count)?; + writer.write_i32::(self.data_offset)?; + assert_eq!(self.sample_count, self.sample_sizes.len() as u32); + for sample_number in self.sample_sizes.iter() { + writer.write_u32::(*sample_number)?; + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_trun_same_size() { + let src_box = TrunBox { + version: 0, + flags: 0, + data_offset: 0, + sample_count: 0, + sample_sizes: vec![], + }; + 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::TrunBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = TrunBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } + + #[test] + fn test_trun_many_sizes() { + let src_box = TrunBox { + version: 0, + flags: 0, + data_offset: 0, + sample_count: 9, + sample_sizes: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730], + }; + 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::TrunBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = TrunBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +}