diff --git a/Cargo.toml b/Cargo.toml index d9779aa..693e336 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,5 @@ license = "MIT" [dependencies] thiserror = "^1.0" byteorder = "1" +bytes = "0.5" num-rational = "0.3" \ No newline at end of file diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs new file mode 100644 index 0000000..2ec9eba --- /dev/null +++ b/examples/mp4copy.rs @@ -0,0 +1,41 @@ +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() < 3 { + println!("Usage: mp4copy "); + std::process::exit(1); + } + + if let Err(err) = copy(&args[1], &args[2]) { + let _ = writeln!(io::stderr(), "{}", err); + } +} + +fn copy>(src_filename: &P, _dst_filename: &P) -> Result<()> { + let src_file = File::open(src_filename)?; + let size = src_file.metadata()?.len(); + let reader = BufReader::new(src_file); + + let mut mp4 = mp4::Mp4Reader::new(reader); + mp4.read(size)?; + + for tix in 0..mp4.track_count()? { + let track_id = tix + 1; + let sample_count = mp4.sample_count(track_id)?; + for six in 0..sample_count { + let sample_id = six + 1; + let sample = mp4.read_sample(track_id, sample_id)?.unwrap(); + println!("sample_id: {}, {}", sample_id, sample); + } + } + + Ok(()) +} diff --git a/examples/mp4info.rs b/examples/mp4info.rs index 39177eb..daf0e46 100644 --- a/examples/mp4info.rs +++ b/examples/mp4info.rs @@ -1,97 +1,98 @@ -use mp4; use std::env; use std::fs::File; +use std::io::prelude::*; +use std::io::{self, BufReader}; +use std::path::Path; + +use mp4::{Result, Mp4Reader, TrackType}; fn main() { let args: Vec = env::args().collect(); - match args.len() { - 2 => { - let filename = &args[1]; - let f = File::open(filename).unwrap(); + if args.len() < 2 { + println!("Usage: mp4info "); + std::process::exit(1); + } - let bmff = mp4::read_mp4(f).unwrap(); - let moov = bmff.moov.unwrap(); + if let Err(err) = info(&args[1]) { + let _ = writeln!(io::stderr(), "{}", err); + } +} - // Print results. - println!("File:"); - println!(" file size: {}", bmff.size); - println!( - " brands: {:?} {:?}\n", - bmff.ftyp.major_brand, bmff.ftyp.compatible_brands - ); +fn info>(filename: &P) -> Result<()> { + let f = File::open(filename)?; + let size = f.metadata()?.len(); + let reader = BufReader::new(f); - println!("Movie:"); - println!(" version: {:?}", moov.mvhd.version); - println!( - " creation time: {}", - creation_time(moov.mvhd.creation_time) - ); - println!(" duration: {:?}", moov.mvhd.duration); - println!(" timescale: {:?}\n", moov.mvhd.timescale); + let mut mp4 = Mp4Reader::new(reader); + mp4.read(size)?; - println!("Found {} Tracks", moov.traks.len()); - for trak in moov.traks.iter() { - let tkhd = trak.tkhd.as_ref().unwrap(); - println!("Track: {:?}", tkhd.track_id); - println!(" flags: {:?}", tkhd.flags); - println!(" id: {:?}", tkhd.track_id); - println!(" duration: {:?}", tkhd.duration); - if tkhd.width != 0 && tkhd.height != 0 { - println!(" width: {:?}", tkhd.width); - println!(" height: {:?}", tkhd.height); + println!("File:"); + println!(" size: {}", mp4.size()); + println!(" brands: {:?} {:?}\n", + mp4.ftyp.major_brand, mp4.ftyp.compatible_brands); + + if let Some(ref moov) = mp4.moov { + println!("Movie:"); + println!(" version: {:?}", moov.mvhd.version); + println!(" creation time: {}", + creation_time(moov.mvhd.creation_time)); + println!(" duration: {:?}", moov.mvhd.duration); + println!(" timescale: {:?}\n", moov.mvhd.timescale); + + println!("Found {} Tracks", moov.traks.len()); + for trak in moov.traks.iter() { + let tkhd = trak.tkhd.as_ref().unwrap(); + println!("Track: {:?}", tkhd.track_id); + println!(" flags: {:?}", tkhd.flags); + println!(" id: {:?}", tkhd.track_id); + println!(" duration: {:?}", tkhd.duration); + if tkhd.width != 0 && tkhd.height != 0 { + println!(" width: {:?}", tkhd.width); + println!(" height: {:?}", tkhd.height); + } + if let Some(ref mdia) = trak.mdia { + let hdlr = mdia.hdlr.as_ref().unwrap(); + let mdhd = mdia.mdhd.as_ref().unwrap(); + let stts = mdia + .minf + .as_ref() + .map(|m| m.stbl.as_ref().map(|s| s.stts.as_ref()).flatten()) + .flatten(); + + println!(" type: {:?}", + get_handler_type(hdlr.handler_type.value.as_ref())); + println!(" language: {:?}", mdhd.language); + + println!(" media:"); + if let Some(ref s) = stts { + println!(" sample count: {:?}", s.entries[0].sample_count); } - if let Some(ref mdia) = trak.mdia { - let hdlr = mdia.hdlr.as_ref().unwrap(); - let mdhd = mdia.mdhd.as_ref().unwrap(); - let stts = mdia - .minf - .as_ref() - .map(|m| m.stbl.as_ref().map(|s| s.stts.as_ref()).flatten()) - .flatten(); - - println!( - " type: {:?}", - get_handler_type(hdlr.handler_type.value.as_ref()) - ); - println!(" language: {:?}", mdhd.language); - - println!(" media:"); + println!(" timescale: {:?}", mdhd.timescale); + println!(" duration: {:?} (media timescale units)", + mdhd.duration); + println!(" duration: {:?} (ms)", + get_duration_ms(mdhd.duration, mdhd.timescale)); + if get_handler_type(hdlr.handler_type.value.as_ref()) == TrackType::Video { if let Some(ref s) = stts { - println!(" sample count: {:?}", s.entries[0].sample_count); - } - println!(" timescale: {:?}", mdhd.timescale); - println!( - " duration: {:?} (media timescale units)", - mdhd.duration - ); - println!( - " duration: {:?} (ms)", - get_duration_ms(mdhd.duration, mdhd.timescale) - ); - if get_handler_type(hdlr.handler_type.value.as_ref()) == mp4::TrackType::Video { - if let Some(ref s) = stts { - println!( - " frame rate: (computed): {:?}", - get_framerate(s.entries[0].sample_count, mdhd.duration, mdhd.timescale) - ); - } + println!(" frame rate: (computed): {:?}", + get_framerate(s.entries[0].sample_count, + mdhd.duration, mdhd.timescale)); } } } } - _ => { - println!("Usage: mp4info "); - } } + + Ok(()) } -fn get_handler_type(handler: &str) -> mp4::TrackType { - let mut typ: mp4::TrackType = mp4::TrackType::Unknown; +fn get_handler_type(handler: &str) -> TrackType { + let mut typ: TrackType = TrackType::Unknown; match handler { - "vide" => typ = mp4::TrackType::Video, - "soun" => typ = mp4::TrackType::Audio, - "meta" => typ = mp4::TrackType::Unknown, + "vide" => typ = TrackType::Video, + "soun" => typ = TrackType::Audio, + "meta" => typ = TrackType::Unknown, _ => (), } return typ; diff --git a/src/atoms/avc.rs b/src/atoms/avc.rs new file mode 100644 index 0000000..c6cbc81 --- /dev/null +++ b/src/atoms/avc.rs @@ -0,0 +1,235 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use num_rational::Ratio; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, PartialEq)] +pub struct Avc1Box { + pub data_reference_index: u16, + pub width: u16, + pub height: u16, + pub horizresolution: Ratio, + pub vertresolution: Ratio, + pub frame_count: u16, + pub depth: u16, + pub avcc: AvcCBox, +} + +impl Default for Avc1Box { + fn default() -> Self { + Avc1Box { + data_reference_index: 0, + width: 0, + height: 0, + horizresolution: Ratio::new_raw(0x00480000, 0x10000), + vertresolution: Ratio::new_raw(0x00480000, 0x10000), + frame_count: 1, + depth: 0x0018, + avcc: AvcCBox::default(), + } + } +} + +impl Mp4Box for Avc1Box { + fn box_type() -> BoxType { + BoxType::Avc1Box + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + 8 + 74 + self.avcc.box_size() + } +} + +impl ReadBox<&mut R> for Avc1Box { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + reader.read_u32::()?; // reserved + reader.read_u16::()?; // reserved + let data_reference_index = reader.read_u16::()?; + + reader.read_u32::()?; // pre-defined, reserved + reader.read_u64::()?; // pre-defined + reader.read_u32::()?; // pre-defined + let width = reader.read_u16::()?; + let height = reader.read_u16::()?; + let horiznumer = reader.read_u32::()?; + let horizresolution = Ratio::new_raw(horiznumer, 0x10000); + let vertnumer = reader.read_u32::()?; + let vertresolution = Ratio::new_raw(vertnumer, 0x10000); + reader.read_u32::()?; // reserved + let frame_count = reader.read_u16::()?; + skip_read(reader, 32)?; // compressorname + let depth = reader.read_u16::()?; + reader.read_i16::()?; // pre-defined + + let header = BoxHeader::read(reader)?; + let BoxHeader{ name, size: s } = header; + if name == BoxType::AvcCBox { + let avcc = AvcCBox::read_box(reader, s)?; + + skip_read_to(reader, start + size)?; + + Ok(Avc1Box { + data_reference_index, + width, + height, + horizresolution, + vertresolution, + frame_count, + depth, + avcc, + }) + } else { + Err(Error::InvalidData("avcc not found")) + } + } +} + +impl WriteBox<&mut W> for Avc1Box { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(Self::box_type(), size).write(writer)?; + + writer.write_u32::(0)?; // reserved + writer.write_u16::(0)?; // reserved + writer.write_u16::(self.data_reference_index)?; + + writer.write_u32::(0)?; // pre-defined, reserved + writer.write_u64::(0)?; // pre-defined + writer.write_u32::(0)?; // pre-defined + writer.write_u16::(self.width)?; + writer.write_u16::(self.height)?; + writer.write_u32::(*self.horizresolution.numer())?; + writer.write_u32::(*self.vertresolution.numer())?; + writer.write_u32::(0)?; // reserved + writer.write_u16::(self.frame_count)?; + // skip compressorname + for _ in 0..4 { + writer.write_u64::(0)?; + } + writer.write_u16::(self.depth)?; + writer.write_i16::(-1)?; // pre-defined + + self.avcc.write_box(writer)?; + + Ok(size) + } +} + + +#[derive(Debug, Default, PartialEq)] +pub struct AvcCBox { + pub configuration_version: u8, + pub avc_profile_indication: u8, + pub profile_compatibility: u8, + pub avc_level_indication: u8, + pub length_size_minus_one: u8, + pub sequence_parameter_sets: Vec, + pub picture_parameter_sets: Vec, +} + +impl Mp4Box for AvcCBox { + fn box_type() -> BoxType { + BoxType::AvcCBox + } + + fn box_size(&self) -> u64 { + let mut size = HEADER_SIZE + 7; + for sps in self.sequence_parameter_sets.iter() { + size += sps.size() as u64; + } + for pps in self.picture_parameter_sets.iter() { + size += pps.size() as u64; + } + size + } +} + +impl ReadBox<&mut R> for AvcCBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let configuration_version = reader.read_u8()?; + let avc_profile_indication = reader.read_u8()?; + let profile_compatibility = reader.read_u8()?; + let avc_level_indication = reader.read_u8()?; + let length_size_minus_one = reader.read_u8()? & 0x3; + let num_of_spss = reader.read_u8()? & 0x1F; + let mut sequence_parameter_sets = Vec::with_capacity(num_of_spss as usize); + for _ in 0..num_of_spss { + let nal_unit = NalUnit::read(reader)?; + sequence_parameter_sets.push(nal_unit); + } + let num_of_ppss = reader.read_u8()?; + let mut picture_parameter_sets = Vec::with_capacity(num_of_ppss as usize); + for _ in 0..num_of_ppss { + let nal_unit = NalUnit::read(reader)?; + picture_parameter_sets.push(nal_unit); + } + + skip_read_to(reader, start + size)?; + + Ok(AvcCBox { + configuration_version, + avc_profile_indication, + profile_compatibility, + avc_level_indication, + length_size_minus_one, + sequence_parameter_sets, + picture_parameter_sets, + }) + } +} + +impl WriteBox<&mut W> for AvcCBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(Self::box_type(), size).write(writer)?; + + writer.write_u8(self.configuration_version)?; + writer.write_u8(self.avc_profile_indication)?; + writer.write_u8(self.profile_compatibility)?; + writer.write_u8(self.avc_level_indication)?; + writer.write_u8(self.length_size_minus_one | 0xFC)?; + writer.write_u8(self.sequence_parameter_sets.len() as u8 | 0xE0)?; + for sps in self.sequence_parameter_sets.iter() { + sps.write(writer)?; + } + writer.write_u8(self.picture_parameter_sets.len() as u8)?; + for pps in self.picture_parameter_sets.iter() { + pps.write(writer)?; + } + Ok(size) + } +} + + +#[derive(Debug, Default, PartialEq)] +pub struct NalUnit { + pub bytes: Vec, +} + +impl NalUnit { + pub fn size(&self) -> usize { + 2 + self.bytes.len() + } + + pub fn read(reader: &mut R) -> Result { + let length = reader.read_u16::()? as usize; + let mut bytes = vec![0u8; length]; + reader.read(&mut bytes)?; + Ok(NalUnit { + bytes, + }) + } + + pub fn write(&self, writer: &mut W) -> Result { + writer.write_u16::(self.bytes.len() as u16)?; + writer.write(&self.bytes)?; + Ok(self.size() as u64) + } +} diff --git a/src/atoms/co64.rs b/src/atoms/co64.rs new file mode 100644 index 0000000..a578bc4 --- /dev/null +++ b/src/atoms/co64.rs @@ -0,0 +1,89 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, Default, PartialEq)] +pub struct Co64Box { + pub version: u8, + pub flags: u32, + pub entries: Vec, +} + +impl Mp4Box for Co64Box { + fn box_type() -> BoxType { + BoxType::Co64Box + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64) + } +} + +impl ReadBox<&mut R> for Co64Box { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let entry_count = reader.read_u32::()?; + let mut entries = Vec::with_capacity(entry_count as usize); + for _i in 0..entry_count { + let chunk_offset = reader.read_u64::()?; + entries.push(chunk_offset); + } + + skip_read_to(reader, start + size)?; + + Ok(Co64Box { + version, + flags, + entries, + }) + } +} + +impl WriteBox<&mut W> for Co64Box { + 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.entries.len() as u32)?; + for chunk_offset in self.entries.iter() { + writer.write_u64::(*chunk_offset)?; + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::atoms::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_co64() { + let src_box = Co64Box { + version: 0, + flags: 0, + entries: vec![267, 1970, 2535, 2803, 11843, 22223, 33584], + }; + 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::Co64Box); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = Co64Box::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/ctts.rs b/src/atoms/ctts.rs new file mode 100644 index 0000000..4db657a --- /dev/null +++ b/src/atoms/ctts.rs @@ -0,0 +1,102 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, Default, PartialEq)] +pub struct CttsBox { + pub version: u8, + pub flags: u32, + pub entries: Vec, +} + +#[derive(Debug, Default, PartialEq)] +pub struct CttsEntry { + pub sample_count: u32, + pub sample_offset: i32, +} + +impl Mp4Box for CttsBox { + fn box_type() -> BoxType { + BoxType::CttsBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64) + } +} + +impl ReadBox<&mut R> for CttsBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let entry_count = reader.read_u32::()?; + let mut entries = Vec::with_capacity(entry_count as usize); + for _ in 0..entry_count { + let entry = CttsEntry { + sample_count: reader.read_u32::()?, + sample_offset: reader.read_i32::()?, + }; + entries.push(entry); + } + + skip_read_to(reader, start + size)?; + + Ok(CttsBox { + version, + flags, + entries, + }) + } +} + +impl WriteBox<&mut W> for CttsBox { + 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.entries.len() as u32)?; + for entry in self.entries.iter() { + writer.write_u32::(entry.sample_count)?; + writer.write_i32::(entry.sample_offset)?; + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::atoms::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_ctts() { + let src_box = CttsBox { + version: 0, + flags: 0, + entries: vec![ + CttsEntry {sample_count: 1, sample_offset: 200}, + CttsEntry {sample_count: 2, sample_offset: -100}, + ], + }; + 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::CttsBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = CttsBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/edts.rs b/src/atoms/edts.rs index 193a3d4..f8bf46b 100644 --- a/src/atoms/edts.rs +++ b/src/atoms/edts.rs @@ -1,6 +1,7 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use crate::*; +use crate::atoms::*; use crate::atoms::elst::ElstBox; @@ -16,50 +17,48 @@ impl EdtsBox { } impl Mp4Box for EdtsBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::EdtsBox } fn box_size(&self) -> u64 { let mut size = HEADER_SIZE; - if let Some(elst) = &self.elst { + if let Some(ref elst) = self.elst { size += elst.box_size(); } size } } -impl ReadBox<&mut BufReader> for EdtsBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for EdtsBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + let mut edts = EdtsBox::new(); - let start = 0u64; - while start < size { - // Get box header. - let header = read_box_header(reader, start)?; - let BoxHeader{ name, size: s } = header; + let header = BoxHeader::read(reader)?; + let BoxHeader{ name, size: s } = header; - match name { - BoxType::ElstBox => { - let elst = ElstBox::read_box(reader, s)?; - edts.elst = Some(elst); - } - _ => break + match name { + BoxType::ElstBox => { + let elst = ElstBox::read_box(reader, s)?; + edts.elst = Some(elst); } + _ => {} } - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(edts) } } -impl WriteBox<&mut BufWriter> for EdtsBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for EdtsBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; - if let Some(elst) = &self.elst { + if let Some(ref elst) = self.elst { elst.write_box(writer)?; } diff --git a/src/atoms/elst.rs b/src/atoms/elst.rs index 7ed9ca6..018194a 100644 --- a/src/atoms/elst.rs +++ b/src/atoms/elst.rs @@ -1,7 +1,8 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::*; +use crate::atoms::*; #[derive(Debug, Default, PartialEq)] @@ -20,7 +21,7 @@ pub struct ElstEntry { } impl Mp4Box for ElstBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::ElstBox } @@ -36,9 +37,9 @@ impl Mp4Box for ElstBox { } } -impl ReadBox<&mut BufReader> for ElstBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for ElstBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -66,7 +67,8 @@ impl ReadBox<&mut BufReader> for ElstBox { }; entries.push(entry); } - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(ElstBox { version, @@ -76,10 +78,10 @@ impl ReadBox<&mut BufReader> for ElstBox { } } -impl WriteBox<&mut BufWriter> for ElstBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for ElstBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; write_box_header_ext(writer, self.version, self.flags)?; @@ -103,7 +105,7 @@ impl WriteBox<&mut BufWriter> for ElstBox { #[cfg(test)] mod tests { use super::*; - use crate::read_box_header; + use crate::atoms::BoxHeader; use std::io::Cursor; #[test] @@ -119,22 +121,16 @@ mod tests { }], }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::ElstBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::ElstBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } #[test] @@ -150,21 +146,15 @@ mod tests { }], }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::ElstBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::ElstBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } } diff --git a/src/atoms/ftyp.rs b/src/atoms/ftyp.rs index 53c83cc..5e1af83 100644 --- a/src/atoms/ftyp.rs +++ b/src/atoms/ftyp.rs @@ -1,7 +1,8 @@ -use std::io::{BufReader, Seek, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::*; +use crate::atoms::*; #[derive(Debug, Default, PartialEq)] @@ -12,7 +13,7 @@ pub struct FtypBox { } impl Mp4Box for FtypBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::FtypBox } @@ -21,8 +22,10 @@ impl Mp4Box for FtypBox { } } -impl ReadBox<&mut BufReader> for FtypBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { +impl ReadBox<&mut R> for FtypBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + let major = reader.read_u32::()?; let minor = reader.read_u32::()?; if size % 4 != 0 { @@ -36,6 +39,8 @@ impl ReadBox<&mut BufReader> for FtypBox { brands.push(From::from(b)); } + skip_read_to(reader, start + size)?; + Ok(FtypBox { major_brand: From::from(major), minor_version: minor, @@ -44,10 +49,10 @@ impl ReadBox<&mut BufReader> for FtypBox { } } -impl WriteBox<&mut BufWriter> for FtypBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for FtypBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; writer.write_u32::((&self.major_brand).into())?; writer.write_u32::(self.minor_version)?; @@ -61,7 +66,7 @@ impl WriteBox<&mut BufWriter> for FtypBox { #[cfg(test)] mod tests { use super::*; - use crate::read_box_header; + use crate::atoms::BoxHeader; use std::io::Cursor; #[test] @@ -77,21 +82,15 @@ mod tests { ] }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::FtypBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::FtypBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = FtypBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = FtypBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } } diff --git a/src/atoms/hdlr.rs b/src/atoms/hdlr.rs index e7bdec3..3d7736e 100644 --- a/src/atoms/hdlr.rs +++ b/src/atoms/hdlr.rs @@ -1,7 +1,8 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::*; +use crate::atoms::*; #[derive(Debug, Default, PartialEq)] @@ -13,7 +14,7 @@ pub struct HdlrBox { } impl Mp4Box for HdlrBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::HdlrBox } @@ -22,18 +23,18 @@ impl Mp4Box for HdlrBox { } } -impl ReadBox<&mut BufReader> for HdlrBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for HdlrBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; reader.read_u32::()?; // pre-defined let handler = reader.read_u32::()?; - let n = reader.seek(SeekFrom::Current(12))?; // 12 bytes reserved. + skip_read(reader, 12)?; // reserved - let buf_size = (size - (n - current)) - HEADER_SIZE - 1; + let buf_size = size - HEADER_SIZE - HEADER_EXT_SIZE - 20 - 1; let mut buf = vec![0u8; buf_size as usize]; reader.read_exact(&mut buf)?; @@ -45,7 +46,7 @@ impl ReadBox<&mut BufReader> for HdlrBox { _ => String::from("null"), }; - skip_read(reader, current, size)?; + skip_read_to(reader, start + size)?; Ok(HdlrBox { version, @@ -56,10 +57,10 @@ impl ReadBox<&mut BufReader> for HdlrBox { } } -impl WriteBox<&mut BufWriter> for HdlrBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for HdlrBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; write_box_header_ext(writer, self.version, self.flags)?; @@ -81,7 +82,7 @@ impl WriteBox<&mut BufWriter> for HdlrBox { #[cfg(test)] mod tests { use super::*; - use crate::read_box_header; + use crate::atoms::BoxHeader; use std::io::Cursor; #[test] @@ -93,21 +94,15 @@ mod tests { name: String::from("VideoHandler"), }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::HdlrBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::HdlrBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } } diff --git a/src/atoms/mdhd.rs b/src/atoms/mdhd.rs index 1b7f1c4..03b3445 100644 --- a/src/atoms/mdhd.rs +++ b/src/atoms/mdhd.rs @@ -1,8 +1,9 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use std::char::{decode_utf16, REPLACEMENT_CHARACTER}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::*; +use crate::atoms::*; #[derive(Debug, PartialEq)] @@ -31,7 +32,7 @@ impl Default for MdhdBox { } impl Mp4Box for MdhdBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::MdhdBox } @@ -49,9 +50,9 @@ impl Mp4Box for MdhdBox { } } -impl ReadBox<&mut BufReader> for MdhdBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for MdhdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -74,7 +75,8 @@ impl ReadBox<&mut BufReader> for MdhdBox { }; let language_code = reader.read_u16::()?; let language = get_language_string(language_code); - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(MdhdBox { version, @@ -88,10 +90,10 @@ impl ReadBox<&mut BufReader> for MdhdBox { } } -impl WriteBox<&mut BufWriter> for MdhdBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for MdhdBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; write_box_header_ext(writer, self.version, self.flags)?; @@ -142,7 +144,7 @@ fn get_language_code(language: &str) -> u16 { #[cfg(test)] mod tests { use super::*; - use crate::read_box_header; + use crate::atoms::BoxHeader; use std::io::Cursor; fn test_language_code(lang: &str) { @@ -170,22 +172,16 @@ mod tests { language: String::from("und"), }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::MdhdBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::MdhdBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } #[test] @@ -200,21 +196,15 @@ mod tests { language: String::from("eng"), }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::MdhdBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::MdhdBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } } diff --git a/src/atoms/mdia.rs b/src/atoms/mdia.rs index ed3b359..7367d5b 100644 --- a/src/atoms/mdia.rs +++ b/src/atoms/mdia.rs @@ -1,6 +1,7 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, SeekFrom, Read, Write}; use crate::*; +use crate::atoms::*; use crate::atoms::{mdhd::MdhdBox, hdlr::HdlrBox, minf::MinfBox}; @@ -18,34 +19,36 @@ impl MdiaBox { } impl Mp4Box for MdiaBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::MdiaBox } fn box_size(&self) -> u64 { let mut size = HEADER_SIZE; - if let Some(mdhd) = &self.mdhd { + if let Some(ref mdhd) = self.mdhd { size += mdhd.box_size(); } - if let Some(hdlr) = &self.hdlr { + if let Some(ref hdlr) = self.hdlr { size += hdlr.box_size(); } - if let Some(minf) = &self.minf { + if let Some(ref minf) = self.minf { size += minf.box_size(); } size } } -impl ReadBox<&mut BufReader> for MdiaBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for MdiaBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + let mut mdia = MdiaBox::new(); - let start = 0u64; - while start < size { + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { // Get box header. - let header = read_box_header(reader, start)?; + let header = BoxHeader::read(reader)?; let BoxHeader{ name, size: s } = header; match name { @@ -61,27 +64,33 @@ impl ReadBox<&mut BufReader> for MdiaBox { let minf = MinfBox::read_box(reader, s)?; mdia.minf = Some(minf); } - _ => break + _ => { + // XXX warn!() + skip_box(reader, s)?; + } } + + current = reader.seek(SeekFrom::Current(0))?; } - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(mdia) } } -impl WriteBox<&mut BufWriter> for MdiaBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for MdiaBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; - if let Some(mdhd) = &self.mdhd { + if let Some(ref mdhd) = self.mdhd { mdhd.write_box(writer)?; } - if let Some(hdlr) = &self.hdlr { + if let Some(ref hdlr) = self.hdlr { hdlr.write_box(writer)?; } - if let Some(minf) = &self.minf { + if let Some(ref minf) = self.minf { minf.write_box(writer)?; } diff --git a/src/atoms/minf.rs b/src/atoms/minf.rs index 5df6356..0187741 100644 --- a/src/atoms/minf.rs +++ b/src/atoms/minf.rs @@ -1,12 +1,14 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, SeekFrom, Read, Write}; use crate::*; -use crate::atoms::{vmhd::VmhdBox, stbl::StblBox}; +use crate::atoms::*; +use crate::atoms::{vmhd::VmhdBox, smhd::SmhdBox, stbl::StblBox}; #[derive(Debug, Default)] pub struct MinfBox { pub vmhd: Option, + pub smhd: Option, pub stbl: Option, } @@ -17,31 +19,36 @@ impl MinfBox { } impl Mp4Box for MinfBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::MinfBox } fn box_size(&self) -> u64 { let mut size = HEADER_SIZE; - if let Some(vmhd) = &self.vmhd { + if let Some(ref vmhd) = self.vmhd { size += vmhd.box_size(); } - if let Some(stbl) = &self.stbl { + if let Some(ref smhd) = self.smhd { + size += smhd.box_size(); + } + if let Some(ref stbl) = self.stbl { size += stbl.box_size(); } size } } -impl ReadBox<&mut BufReader> for MinfBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for MinfBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + let mut minf = MinfBox::new(); - let mut start = 0u64; - while start < size { + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { // Get box header. - let header = read_box_header(reader, start)?; + let header = BoxHeader::read(reader)?; let BoxHeader{ name, size: s } = header; match name { @@ -50,33 +57,43 @@ impl ReadBox<&mut BufReader> for MinfBox { minf.vmhd = Some(vmhd); } BoxType::SmhdBox => { - start = s - HEADER_SIZE; + let smhd = SmhdBox::read_box(reader, s)?; + minf.smhd = Some(smhd); } - BoxType::DinfBox => { - start = s - HEADER_SIZE; + BoxType::DinfBox => {// XXX warn!() + skip_box(reader, s)?; } BoxType::StblBox => { let stbl = StblBox::read_box(reader, s)?; minf.stbl = Some(stbl); } - _ => break + _ => { + // XXX warn!() + skip_box(reader, s)?; + } } + + current = reader.seek(SeekFrom::Current(0))?; } - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(minf) } } -impl WriteBox<&mut BufWriter> for MinfBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for MinfBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; - if let Some(vmhd) = &self.vmhd { + if let Some(ref vmhd) = self.vmhd { vmhd.write_box(writer)?; } - if let Some(stbl) = &self.stbl { + if let Some(ref smhd) = self.smhd { + smhd.write_box(writer)?; + } + if let Some(ref stbl) = self.stbl { stbl.write_box(writer)?; } diff --git a/src/atoms/mod.rs b/src/atoms/mod.rs index 8dcd740..b61e229 100644 --- a/src/atoms/mod.rs +++ b/src/atoms/mod.rs @@ -1,5 +1,5 @@ use std::fmt; -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, SeekFrom, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::*; @@ -16,13 +16,24 @@ mod mdhd; mod hdlr; mod minf; mod vmhd; +mod smhd; mod stbl; -mod stts; mod stsd; +mod stts; +mod ctts; +mod stss; +mod stsc; +mod stsz; +mod stco; +mod co64; +mod avc; +mod mp4a; pub use ftyp::FtypBox; pub use moov::MoovBox; +const HEADER_SIZE: u64 = 8; +// const HEADER_LARGE_SIZE: u64 = 16; pub const HEADER_EXT_SIZE: u64 = 4; macro_rules! boxtype { @@ -71,12 +82,20 @@ boxtype!{ StblBox => 0x7374626c, StsdBox => 0x73747364, SttsBox => 0x73747473, + CttsBox => 0x63747473, + StssBox => 0x73747373, + StscBox => 0x73747363, + StszBox => 0x7374737A, + StcoBox => 0x7374636F, + Co64Box => 0x636F3634, TrakBox => 0x7472616b, UdtaBox => 0x75647461, DinfBox => 0x64696e66, SmhdBox => 0x736d6864, Avc1Box => 0x61766331, - Mp4aBox => 0x6d703461 + AvcCBox => 0x61766343, + Mp4aBox => 0x6d703461, + EsdsBox => 0x65736473 } impl fmt::Debug for BoxType { @@ -86,6 +105,13 @@ impl fmt::Debug for BoxType { } } +impl fmt::Display for BoxType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let fourcc: FourCC = From::from(self.clone()); + write!(f, "{}", fourcc) + } +} + #[derive(Default, PartialEq, Clone)] pub struct FourCC { pub value: String @@ -167,7 +193,7 @@ impl fmt::Display for FourCC { } pub trait Mp4Box: Sized { - fn box_type(&self) -> BoxType; + fn box_type() -> BoxType; fn box_size(&self) -> u64; } @@ -179,23 +205,50 @@ pub trait WriteBox: Sized { fn write_box(&self, _: T) -> Result; } -pub fn read_box_header_ext(reader: &mut BufReader) -> Result<(u8, u32)> { - let version = reader.read_u8()?; - let flags_a = reader.read_u8()?; - let flags_b = reader.read_u8()?; - let flags_c = reader.read_u8()?; - let flags = u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c); - Ok((version, flags)) +#[derive(Debug, Clone, Copy)] +pub struct BoxHeader { + pub name: BoxType, + pub size: u64, } -pub fn write_box_header_ext(w: &mut BufWriter, v: u8, f: u32) -> Result { - let d = u32::from(v) << 24 | f; - w.write_u32::(d)?; - Ok(4) -} +impl BoxHeader { + fn new(name: BoxType, size: u64) -> Self { + Self { name, size } + } -impl WriteBox<&mut BufWriter> for BoxHeader { - fn write_box(&self, writer: &mut BufWriter) -> Result { + // TODO: if size is 0, then this box is the last one in the file + pub fn read(reader: &mut R) -> Result { + // Create and read to buf. + let mut buf = [0u8;8]; // 8 bytes for box header. + reader.read(&mut buf)?; + + // Get size. + let s = buf[0..4].try_into().unwrap(); + let size = u32::from_be_bytes(s); + + // Get box type string. + let t = buf[4..8].try_into().unwrap(); + let typ = u32::from_be_bytes(t); + + // Get largesize if size is 1 + if size == 1 { + reader.read(&mut buf)?; + let s = buf.try_into().unwrap(); + let largesize = u64::from_be_bytes(s); + + Ok(BoxHeader { + name: BoxType::from(typ), + size: largesize, + }) + } else { + Ok(BoxHeader { + name: BoxType::from(typ), + size: size as u64, + }) + } + } + + fn write(&self, writer: &mut W) -> Result { if self.size > u32::MAX as u64 { writer.write_u32::(1)?; writer.write_u32::(self.name.into())?; @@ -209,19 +262,44 @@ impl WriteBox<&mut BufWriter> for BoxHeader { } } -pub fn skip_read(reader: &mut BufReader, current: u64, size: u64) -> Result { - let after = reader.seek(SeekFrom::Current(0))?; - let remaining_bytes = (size - (after - current)) as i64; - let size = remaining_bytes - HEADER_SIZE as i64; - reader.seek(SeekFrom::Current(size))?; - Ok(size) +pub fn read_box_header_ext(reader: &mut R) -> Result<(u8, u32)> { + let version = reader.read_u8()?; + let flags = reader.read_u24::()?; + Ok((version, flags)) } -pub fn skip_write(writer: &mut BufWriter, size: u64) -> Result { +pub fn write_box_header_ext(w: &mut W, v: u8, f: u32) -> Result { + w.write_u8(v)?; + w.write_u24::(f)?; + Ok(4) +} + +pub fn get_box_start(reader: &mut R) -> Result { + Ok(reader.seek(SeekFrom::Current(0))? - HEADER_SIZE) +} + +pub fn skip_read(reader: &mut R, size: i64) -> Result<()> { + assert!(size >= 0); + reader.seek(SeekFrom::Current(size))?; + Ok(()) +} + +pub fn skip_read_to(reader: &mut R, pos: u64) -> Result<()> { + reader.seek(SeekFrom::Start(pos))?; + Ok(()) +} + +pub fn skip_box(reader: &mut R, size: u64) -> Result<()> { + let start = get_box_start(reader)?; + skip_read_to(reader, start + size)?; + Ok(()) +} + +pub fn skip_write(writer: &mut W, size: u64) -> Result<()> { for _ in 0..size { writer.write_u8(0)?; } - Ok(size) + Ok(()) } #[cfg(test)] diff --git a/src/atoms/moov.rs b/src/atoms/moov.rs index 3a5c045..5e7bd74 100644 --- a/src/atoms/moov.rs +++ b/src/atoms/moov.rs @@ -1,6 +1,7 @@ -use std::io::{BufReader, Seek, Read, BufWriter, Write}; +use std::io::{Seek, SeekFrom, Read, Write}; use crate::*; +use crate::atoms::*; use crate::atoms::{mvhd::MvhdBox, trak::TrakBox}; @@ -17,7 +18,7 @@ impl MoovBox { } impl Mp4Box for MoovBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::MoovBox } @@ -30,15 +31,17 @@ impl Mp4Box for MoovBox { } } -impl ReadBox<&mut BufReader> for MoovBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { +impl ReadBox<&mut R> for MoovBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + let mut moov = MoovBox::new(); - let mut start = 0u64; - while start < size { - + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { // Get box header. - let header = read_box_header(reader, start)?; + let header = BoxHeader::read(reader)?; let BoxHeader{ name, size: s } = header; match name { @@ -46,23 +49,33 @@ impl ReadBox<&mut BufReader> for MoovBox { moov.mvhd = MvhdBox::read_box(reader, s)?; } BoxType::TrakBox => { - let trak = TrakBox::read_box(reader, s)?; + let mut trak = TrakBox::read_box(reader, s)?; + trak.id = moov.traks.len() as u32 + 1; moov.traks.push(trak); } BoxType::UdtaBox => { - start = s - HEADER_SIZE; + // XXX warn!() + skip_box(reader, s)?; + } + _ => { + // XXX warn!() + skip_box(reader, s)?; } - _ => break } + + current = reader.seek(SeekFrom::Current(0))?; } + + skip_read_to(reader, start + size)?; + Ok(moov) } } -impl WriteBox<&mut BufWriter> for MoovBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for MoovBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; self.mvhd.write_box(writer)?; for trak in self.traks.iter() { diff --git a/src/atoms/mp4a.rs b/src/atoms/mp4a.rs new file mode 100644 index 0000000..c1fa804 --- /dev/null +++ b/src/atoms/mp4a.rs @@ -0,0 +1,411 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, PartialEq)] +pub struct Mp4aBox { + pub data_reference_index: u16, + pub channel_count: u16, + pub samplesize: u16, + pub samplerate: u32, + pub esds: EsdsBox, +} + +impl Default for Mp4aBox { + fn default() -> Self { + Mp4aBox { + data_reference_index: 0, + channel_count: 2, + samplesize: 16, + samplerate: 0, // XXX + esds: EsdsBox::default(), + } + } +} + +impl Mp4Box for Mp4aBox { + fn box_type() -> BoxType { + BoxType::Mp4aBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + 8 + 74 + self.esds.box_size() + } +} + +impl ReadBox<&mut R> for Mp4aBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + reader.read_u32::()?; // reserved + reader.read_u16::()?; // reserved + let data_reference_index = reader.read_u16::()?; + + reader.read_u64::()?; // reserved + let channel_count = reader.read_u16::()?; + let samplesize = reader.read_u16::()?; + reader.read_u32::()?; // pre-defined, reserved + let samplerate = reader.read_u32::()?; + + let header = BoxHeader::read(reader)?; + let BoxHeader{ name, size: s } = header; + if name == BoxType::EsdsBox { + let esds = EsdsBox::read_box(reader, s)?; + + skip_read_to(reader, start + size)?; + + Ok(Mp4aBox { + data_reference_index, + channel_count, + samplesize, + samplerate, + esds, + }) + } else { + Err(Error::InvalidData("esds not found")) + } + } +} + +impl WriteBox<&mut W> for Mp4aBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(Self::box_type(), size).write(writer)?; + + writer.write_u32::(0)?; // reserved + writer.write_u16::(0)?; // reserved + writer.write_u16::(self.data_reference_index)?; + + writer.write_u64::(0)?; // reserved + writer.write_u16::(self.channel_count)?; + writer.write_u16::(self.samplesize)?; + writer.write_u32::(0)?; // reserved + writer.write_u32::(self.samplerate)?; + + self.esds.write_box(writer)?; + + Ok(size) + } +} + + +#[derive(Debug, Default, PartialEq)] +pub struct EsdsBox { + pub version: u8, + pub flags: u32, + pub es_desc: ESDescriptor, +} + +impl Mp4Box for EsdsBox { + fn box_type() -> BoxType { + BoxType::EsdsBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + } +} + +impl ReadBox<&mut R> for EsdsBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let es_desc = ESDescriptor::read_desc(reader)?; + + skip_read_to(reader, start + size)?; + + Ok(EsdsBox { + version, + flags, + es_desc, + }) + } +} + +impl WriteBox<&mut W> for EsdsBox { + 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)?; + + Ok(size) + } +} + + +trait Descriptor: Sized { + fn desc_tag() -> u8; + fn desc_size() -> u32; +} + +trait ReadDesc: Sized { + fn read_desc(_: T) -> Result; +} + +trait WriteDesc: Sized { + fn write_desc(&self, _: T) -> Result; +} + +fn read_desc(reader: &mut R) -> Result<(u8, u32)> { + let tag = reader.read_u8()?; + + let mut size: u32 = 0; + for _ in 0..4 { + let b = reader.read_u8()?; + size = (size << 7) | (b & 0x7F) as u32; + if b & 0x80 == 0 { + break; + } + } + + Ok((tag, size)) +} + +fn write_desc(writer: &mut W, tag: u8, size: u32) -> Result { + writer.write_u8(tag)?; + + if size > 0x0FFFFFFF { + return Err(Error::InvalidData("invalid descriptor length range")); + } + + let nbytes = match size { + 0x0..=0x7F => 1, + 0x80..=0x3FFF => 2, + 0x4000..=0x1FFFFF => 3, + _ => 4, + }; + + for i in 0..nbytes { + let mut b = (size >> ((3 - i) * 7)) as u8 & 0x7F; + if i < nbytes - 1 { + b |= 0x80; + } + writer.write_u8(b)?; + } + + Ok(1 + nbytes) +} + + +#[derive(Debug, Default, PartialEq)] +pub struct ESDescriptor { + pub tag: u8, + pub size: u32, + + pub es_id: u16, + + pub dec_config: DecoderConfigDescriptor, + pub sl_config: SLConfigDescriptor, +} + +impl Descriptor for ESDescriptor { + fn desc_tag() -> u8 { + 0x03 + } + + // XXX size > 0x7F + fn desc_size() -> u32 { + 2 + 3 + + DecoderConfigDescriptor::desc_size() + + SLConfigDescriptor::desc_size() + } +} + +impl ReadDesc<&mut R> for ESDescriptor { + fn read_desc(reader: &mut R) -> Result { + let (tag, size) = read_desc(reader)?; + if tag != Self::desc_tag() { + return Err(Error::InvalidData("ESDescriptor not found")); + } + + let es_id = reader.read_u16::()?; + reader.read_u8()?; // XXX flags must be 0 + + let dec_config = DecoderConfigDescriptor::read_desc(reader)?; + let sl_config = SLConfigDescriptor::read_desc(reader)?; + + Ok(ESDescriptor { + tag, + size, + es_id, + dec_config, + sl_config, + }) + } +} + +impl WriteDesc<&mut W> for ESDescriptor { + fn write_desc(&self, writer: &mut W) -> Result { + write_desc(writer, self.tag, self.size)?; + + Ok(self.size) + } +} + +#[derive(Debug, Default, PartialEq)] +pub struct DecoderConfigDescriptor { + pub tag: u8, + pub size: u32, + + pub object_type_indication: u8, + pub stream_type: u8, + pub up_stream: u8, + pub buffer_size_db: u32, + pub max_bitrate: u32, + pub avg_bitrate: u32, + + pub dec_specific: DecoderSpecificDescriptor, +} + +impl Descriptor for DecoderConfigDescriptor { + fn desc_tag() -> u8 { + 0x04 + } + + // XXX size > 0x7F + fn desc_size() -> u32 { + 2 + 13 + DecoderSpecificDescriptor::desc_size() + } +} + +impl ReadDesc<&mut R> for DecoderConfigDescriptor { + fn read_desc(reader: &mut R) -> Result { + let (tag, size) = read_desc(reader)?; + if tag != Self::desc_tag() { + return Err(Error::InvalidData("DecoderConfigDescriptor not found")); + } + + let object_type_indication = reader.read_u8()?; + let byte_a = reader.read_u8()?; + let stream_type = byte_a & 0xFC; + let up_stream = byte_a & 0x02; + let buffer_size_db = reader.read_u24::()?; + let max_bitrate = reader.read_u32::()?; + let avg_bitrate = reader.read_u32::()?; + + let dec_specific = DecoderSpecificDescriptor::read_desc(reader)?; + + // XXX skip_read + for _ in DecoderConfigDescriptor::desc_size()..size-1 { + reader.read_u8()?; + } + + Ok(DecoderConfigDescriptor { + tag, + size, + object_type_indication, + stream_type, + up_stream, + buffer_size_db, + max_bitrate, + avg_bitrate, + dec_specific, + }) + } +} + +impl WriteDesc<&mut W> for DecoderConfigDescriptor { + fn write_desc(&self, writer: &mut W) -> Result { + write_desc(writer, self.tag, self.size)?; + + Ok(self.size) + } +} + +#[derive(Debug, Default, PartialEq)] +pub struct DecoderSpecificDescriptor { + pub tag: u8, + pub size: u32, + pub profile: u8, + pub freq_index: u8, + pub chan_conf: u8, +} + +impl Descriptor for DecoderSpecificDescriptor { + fn desc_tag() -> u8 { + 0x05 + } + + // XXX size > 0x7F + fn desc_size() -> u32 { + 2 + 2 + } +} + +impl ReadDesc<&mut R> for DecoderSpecificDescriptor { + fn read_desc(reader: &mut R) -> Result { + let (tag, size) = read_desc(reader)?; + if tag != Self::desc_tag() { + return Err(Error::InvalidData("DecoderSpecificDescriptor not found")); + } + + let byte_a = reader.read_u8()?; + let byte_b = reader.read_u8()?; + let profile = byte_a >> 3; + let freq_index = ((byte_a & 0x07) << 1) + (byte_b >> 7); + let chan_conf = (byte_b >> 3) & 0x0F; + + Ok(DecoderSpecificDescriptor { + tag, + size, + profile, + freq_index, + chan_conf, + }) + } +} + +impl WriteDesc<&mut W> for DecoderSpecificDescriptor { + fn write_desc(&self, writer: &mut W) -> Result { + write_desc(writer, self.tag, self.size)?; + + Ok(self.size) + } +} + +#[derive(Debug, Default, PartialEq)] +pub struct SLConfigDescriptor { + pub tag: u8, + pub size: u32, +} + +impl Descriptor for SLConfigDescriptor { + fn desc_tag() -> u8 { + 0x06 + } + + // XXX size > 0x7F + fn desc_size() -> u32 { + 2 + 1 + } +} + +impl ReadDesc<&mut R> for SLConfigDescriptor { + fn read_desc(reader: &mut R) -> Result { + let (tag, size) = read_desc(reader)?; + if tag != Self::desc_tag() { + return Err(Error::InvalidData("SLConfigDescriptor not found")); + } + + reader.read_u8()?; // pre-defined + + Ok(SLConfigDescriptor { + tag, + size, + }) + } +} + +impl WriteDesc<&mut W> for SLConfigDescriptor { + fn write_desc(&self, writer: &mut W) -> Result { + write_desc(writer, self.tag, self.size)?; + + Ok(self.size) + } +} diff --git a/src/atoms/mvhd.rs b/src/atoms/mvhd.rs index 0a53628..ed88fae 100644 --- a/src/atoms/mvhd.rs +++ b/src/atoms/mvhd.rs @@ -1,8 +1,9 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use num_rational::Ratio; use crate::*; +use crate::atoms::*; #[derive(Debug, PartialEq)] @@ -31,7 +32,7 @@ impl Default for MvhdBox { } impl Mp4Box for MvhdBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::MvhdBox } @@ -48,9 +49,9 @@ impl Mp4Box for MvhdBox { } } -impl ReadBox<&mut BufReader> for MvhdBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for MvhdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -73,7 +74,8 @@ impl ReadBox<&mut BufReader> for MvhdBox { }; let numer = reader.read_u32::()?; let rate = Ratio::new_raw(numer, 0x10000); - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(MvhdBox{ version, @@ -87,10 +89,10 @@ impl ReadBox<&mut BufReader> for MvhdBox { } } -impl WriteBox<&mut BufWriter> for MvhdBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for MvhdBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; write_box_header_ext(writer, self.version, self.flags)?; @@ -118,7 +120,7 @@ impl WriteBox<&mut BufWriter> for MvhdBox { #[cfg(test)] mod tests { use super::*; - use crate::read_box_header; + use crate::atoms::BoxHeader; use std::io::Cursor; #[test] @@ -133,22 +135,16 @@ mod tests { rate: Ratio::new_raw(0x00010000, 0x10000), }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::MvhdBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::MvhdBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } #[test] @@ -163,21 +159,15 @@ mod tests { rate: Ratio::new_raw(0x00010000, 0x10000), }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::MvhdBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::MvhdBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } } diff --git a/src/atoms/smhd.rs b/src/atoms/smhd.rs new file mode 100644 index 0000000..07416bb --- /dev/null +++ b/src/atoms/smhd.rs @@ -0,0 +1,95 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use num_rational::Ratio; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, PartialEq)] +pub struct SmhdBox { + pub version: u8, + pub flags: u32, + pub balance: Ratio, +} + +impl Default for SmhdBox { + fn default() -> Self { + SmhdBox { + version: 0, + flags: 0, + balance: Ratio::new_raw(0, 0x100), + } + } +} + +impl Mp4Box for SmhdBox { + fn box_type() -> BoxType { + BoxType::SmhdBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + } +} + +impl ReadBox<&mut R> for SmhdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let balance_numer = reader.read_i16::()?; + let balance = Ratio::new_raw(balance_numer, 0x100); + + skip_read_to(reader, start + size)?; + + Ok(SmhdBox { + version, + flags, + balance, + }) + } +} + +impl WriteBox<&mut W> for SmhdBox { + 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_i16::(*self.balance.numer())?; + writer.write_u16::(0)?; // reserved + + Ok(size) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use crate::atoms::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_smhd() { + let src_box = SmhdBox { + version: 0, + flags: 0, + balance: Ratio::new_raw(-0x100, 0x100), + }; + 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::SmhdBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = SmhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/stbl.rs b/src/atoms/stbl.rs index ab46bf8..bb5aaa5 100644 --- a/src/atoms/stbl.rs +++ b/src/atoms/stbl.rs @@ -1,13 +1,29 @@ -use std::io::{BufReader, Seek, Read, BufWriter, Write}; +use std::io::{Seek, SeekFrom, Read, Write}; use crate::*; -use crate::atoms::{stts::SttsBox, stsd::StsdBox}; +use crate::atoms::*; +use crate::atoms::{ + stsd::StsdBox, + stts::SttsBox, + ctts::CttsBox, + stss::StssBox, + stsc::StscBox, + stsz::StszBox, + stco::StcoBox, + co64::Co64Box, +}; #[derive(Debug, Default)] pub struct StblBox { - pub stts: Option, pub stsd: Option, + pub stts: Option, + pub ctts: Option, + pub stss: Option, + pub stsc: Option, + pub stsz: Option, + pub stco: Option, + pub co64: Option, } impl StblBox { @@ -17,58 +33,128 @@ impl StblBox { } impl Mp4Box for StblBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::StblBox } fn box_size(&self) -> u64 { let mut size = HEADER_SIZE; - if let Some(stts) = &self.stts { + if let Some(ref stsd) = self.stsd { + size += stsd.box_size(); + } + if let Some(ref stts) = self.stts { size += stts.box_size(); } - if let Some(stsd) = &self.stsd { - size += stsd.box_size(); + if let Some(ref ctts) = self.ctts { + size += ctts.box_size(); + } + if let Some(ref stss) = self.stss { + size += stss.box_size(); + } + if let Some(ref stsc) = self.stsc { + size += stsc.box_size(); + } + if let Some(ref stsz) = self.stsz { + size += stsz.box_size(); + } + if let Some(ref stco) = self.stco { + size += stco.box_size(); + } + if let Some(ref co64) = self.co64 { + size += co64.box_size(); } size } } -impl ReadBox<&mut BufReader> for StblBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { +impl ReadBox<&mut R> for StblBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + let mut stbl = StblBox::new(); - let start = 0u64; - while start < size { + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { // Get box header. - let header = read_box_header(reader, start)?; + let header = BoxHeader::read(reader)?; let BoxHeader{ name, size: s } = header; match name { - BoxType::SttsBox => { - let stts = SttsBox::read_box(reader, s)?; - stbl.stts = Some(stts); - } BoxType::StsdBox => { let stsd = StsdBox::read_box(reader, s)?; stbl.stsd = Some(stsd); } - _ => break + BoxType::SttsBox => { + let stts = SttsBox::read_box(reader, s)?; + stbl.stts = Some(stts); + } + BoxType::CttsBox => { + let ctts = CttsBox::read_box(reader, s)?; + stbl.ctts = Some(ctts); + } + BoxType::StssBox => { + let stss = StssBox::read_box(reader, s)?; + stbl.stss = Some(stss); + } + BoxType::StscBox => { + let stsc = StscBox::read_box(reader, s)?; + stbl.stsc = Some(stsc); + } + BoxType::StszBox => { + let stsz = StszBox::read_box(reader, s)?; + stbl.stsz = Some(stsz); + } + BoxType::StcoBox => { + let stco = StcoBox::read_box(reader, s)?; + stbl.stco = Some(stco); + } + BoxType::Co64Box => { + let co64 = Co64Box::read_box(reader, s)?; + stbl.co64 = Some(co64); + } + _ => { + // XXX warn!() + skip_box(reader, s)?; + } } + current = reader.seek(SeekFrom::Current(0))?; } + + skip_read_to(reader, start + size)?; + Ok(stbl) } } -impl WriteBox<&mut BufWriter> for StblBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for StblBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; - if let Some(stts) = &self.stts { + if let Some(ref stsd) = self.stsd { + stsd.write_box(writer)?; + } + if let Some(ref stts) = self.stts { stts.write_box(writer)?; } - if let Some(stsd) = &self.stsd { - stsd.write_box(writer)?; + if let Some(ref ctts) = self.ctts { + ctts.write_box(writer)?; + } + if let Some(ref stss) = self.stss { + stss.write_box(writer)?; + } + if let Some(ref stsc) = self.stsc { + stsc.write_box(writer)?; + } + if let Some(ref stsz) = self.stsz { + stsz.write_box(writer)?; + } + if let Some(ref stco) = self.stco { + stco.write_box(writer)?; + } + if let Some(ref co64) = self.co64 { + co64.write_box(writer)?; } Ok(size) diff --git a/src/atoms/stco.rs b/src/atoms/stco.rs new file mode 100644 index 0000000..c6a85cc --- /dev/null +++ b/src/atoms/stco.rs @@ -0,0 +1,89 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, Default, PartialEq)] +pub struct StcoBox { + pub version: u8, + pub flags: u32, + pub entries: Vec, +} + +impl Mp4Box for StcoBox { + fn box_type() -> BoxType { + BoxType::StcoBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + (4 * self.entries.len() as u64) + } +} + +impl ReadBox<&mut R> for StcoBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let entry_count = reader.read_u32::()?; + let mut entries = Vec::with_capacity(entry_count as usize); + for _i in 0..entry_count { + let chunk_offset = reader.read_u32::()?; + entries.push(chunk_offset); + } + + skip_read_to(reader, start + size)?; + + Ok(StcoBox { + version, + flags, + entries, + }) + } +} + +impl WriteBox<&mut W> for StcoBox { + 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.entries.len() as u32)?; + for chunk_offset in self.entries.iter() { + writer.write_u32::(*chunk_offset)?; + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::atoms::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_stco() { + let src_box = StcoBox { + version: 0, + flags: 0, + entries: vec![267, 1970, 2535, 2803, 11843, 22223, 33584], + }; + 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::StcoBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = StcoBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/stsc.rs b/src/atoms/stsc.rs new file mode 100644 index 0000000..d6ea620 --- /dev/null +++ b/src/atoms/stsc.rs @@ -0,0 +1,130 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, Default, PartialEq)] +pub struct StscBox { + pub version: u8, + pub flags: u32, + pub entries: Vec, +} + +#[derive(Debug, Default, PartialEq)] +pub struct StscEntry { + pub first_chunk: u32, + pub samples_per_chunk: u32, + pub sample_description_index: u32, + pub first_sample: u32, +} + +impl Mp4Box for StscBox { + fn box_type() -> BoxType { + BoxType::StscBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + (12 * self.entries.len() as u64) + } +} + +impl ReadBox<&mut R> for StscBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let entry_count = reader.read_u32::()?; + let mut entries = Vec::with_capacity(entry_count as usize); + for _ in 0..entry_count { + let entry = StscEntry { + first_chunk: reader.read_u32::()?, + samples_per_chunk: reader.read_u32::()?, + sample_description_index: reader.read_u32::()?, + first_sample: 0, + }; + entries.push(entry); + } + + let mut sample_id = 1; + for i in 0..entry_count { + let (first_chunk, samples_per_chunk) = { + let mut entry = entries.get_mut(i as usize).unwrap(); + entry.first_sample = sample_id; + (entry.first_chunk, entry.samples_per_chunk) + }; + if i < entry_count - 1 { + let next_entry = entries.get(i as usize + 1).unwrap(); + sample_id += (next_entry.first_chunk - first_chunk) * samples_per_chunk; + } + } + + skip_read_to(reader, start + size)?; + + Ok(StscBox { + version, + flags, + entries, + }) + } +} + +impl WriteBox<&mut W> for StscBox { + 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.entries.len() as u32)?; + for entry in self.entries.iter() { + writer.write_u32::(entry.first_chunk)?; + writer.write_u32::(entry.samples_per_chunk)?; + writer.write_u32::(entry.sample_description_index)?; + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::atoms::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_stsc() { + let src_box = StscBox { + version: 0, + flags: 0, + entries: vec![ + StscEntry { + first_chunk: 1, + samples_per_chunk: 1, + sample_description_index: 1, + first_sample: 1, + }, + StscEntry { + first_chunk: 19026, + samples_per_chunk: 14, + sample_description_index: 1, + first_sample: 19026, + }, + ], + }; + 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::StscBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = StscBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/stsd.rs b/src/atoms/stsd.rs index 5c216b7..0817428 100644 --- a/src/atoms/stsd.rs +++ b/src/atoms/stsd.rs @@ -1,59 +1,86 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; -use byteorder::{BigEndian, ReadBytesExt}; +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::*; +use crate::atoms::*; +use crate::atoms::{avc::Avc1Box, mp4a::Mp4aBox}; #[derive(Debug)] pub struct StsdBox { pub version: u8, pub flags: u32, + pub avc1: Option, + pub mp4a: Option, } impl Mp4Box for StsdBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::StsdBox } fn box_size(&self) -> u64 { - // TODO - 0 + let mut size = HEADER_SIZE + HEADER_EXT_SIZE; + if let Some(ref avc1) = self.avc1 { + size += avc1.box_size(); + } else if let Some(ref mp4a) = self.mp4a { + size += mp4a.box_size(); + } + size } } -impl ReadBox<&mut BufReader> for StsdBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for StsdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; - let _entry_count = reader.read_u32::()?; + reader.read_u32::()?; // XXX entry_count - let mut start = 0u64; - while start < size { - // Get box header. - let header = read_box_header(reader, start)?; - let BoxHeader{ name, size: s } = header; + let mut avc1 = None; + let mut mp4a = None; - match name { - BoxType::Avc1Box => {} - BoxType::Mp4aBox => {} - _ => break + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader{ name, size: s } = header; + + match name { + BoxType::Avc1Box => { + avc1 = Some(Avc1Box::read_box(reader, s)?); } - start += s - HEADER_SIZE; + BoxType::Mp4aBox => { + mp4a = Some(Mp4aBox::read_box(reader, s)?); + } + _ => {} } - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(StsdBox { version, flags, + avc1, + mp4a, }) } } -impl WriteBox<&mut BufWriter> for StsdBox { - fn write_box(&self, _writer: &mut BufWriter) -> Result { - // TODO - Ok(0) +impl WriteBox<&mut W> for StsdBox { + 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::(1)?; // entry_count + + if let Some(ref avc1) = self.avc1 { + avc1.write_box(writer)?; + } else if let Some(ref mp4a) = self.mp4a { + mp4a.write_box(writer)?; + } + + Ok(size) } } diff --git a/src/atoms/stss.rs b/src/atoms/stss.rs new file mode 100644 index 0000000..8e001aa --- /dev/null +++ b/src/atoms/stss.rs @@ -0,0 +1,89 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, Default, PartialEq)] +pub struct StssBox { + pub version: u8, + pub flags: u32, + pub entries: Vec, +} + +impl Mp4Box for StssBox { + fn box_type() -> BoxType { + BoxType::StssBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + (4 * self.entries.len() as u64) + } +} + +impl ReadBox<&mut R> for StssBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let entry_count = reader.read_u32::()?; + let mut entries = Vec::with_capacity(entry_count as usize); + for _i in 0..entry_count { + let sample_number = reader.read_u32::()?; + entries.push(sample_number); + } + + skip_read_to(reader, start + size)?; + + Ok(StssBox { + version, + flags, + entries, + }) + } +} + +impl WriteBox<&mut W> for StssBox { + 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.entries.len() as u32)?; + for sample_number in self.entries.iter() { + writer.write_u32::(*sample_number)?; + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::atoms::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_stss() { + let src_box = StssBox { + version: 0, + flags: 0, + entries: vec![1, 61, 121, 181, 241, 301, 361, 421, 481], + }; + 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::StssBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = StssBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/stsz.rs b/src/atoms/stsz.rs new file mode 100644 index 0000000..51c0631 --- /dev/null +++ b/src/atoms/stsz.rs @@ -0,0 +1,119 @@ +use std::io::{Seek, Read, Write}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use crate::*; +use crate::atoms::*; + + +#[derive(Debug, Default, PartialEq)] +pub struct StszBox { + pub version: u8, + pub flags: u32, + pub sample_size: u32, + pub sample_sizes: Vec, +} + +impl Mp4Box for StszBox { + fn box_type() -> BoxType { + BoxType::StszBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 8 + (4 * self.sample_sizes.len() as u64) + } +} + +impl ReadBox<&mut R> for StszBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let sample_size = reader.read_u32::()?; + let sample_count = reader.read_u32::()?; + let mut sample_sizes = Vec::with_capacity(sample_count as usize); + if sample_size == 0 { + for _i in 0..sample_count { + let sample_number = reader.read_u32::()?; + sample_sizes.push(sample_number); + } + } + + skip_read_to(reader, start + size)?; + + Ok(StszBox { + version, + flags, + sample_size, + sample_sizes, + }) + } +} + +impl WriteBox<&mut W> for StszBox { + 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_size)?; + writer.write_u32::(self.sample_sizes.len() as u32)?; + if self.sample_size == 0 { + for sample_number in self.sample_sizes.iter() { + writer.write_u32::(*sample_number)?; + } + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::atoms::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_stsz_same_size() { + let src_box = StszBox { + version: 0, + flags: 0, + sample_size: 1165, + 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::StszBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = StszBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } + + #[test] + fn test_stsz_many_sizes() { + let src_box = StszBox { + version: 0, + flags: 0, + sample_size: 0, + 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::StszBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = StszBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/stts.rs b/src/atoms/stts.rs index 27bf0c3..a9bfeb4 100644 --- a/src/atoms/stts.rs +++ b/src/atoms/stts.rs @@ -1,7 +1,8 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::*; +use crate::atoms::*; #[derive(Debug, Default, PartialEq)] @@ -18,7 +19,7 @@ pub struct SttsEntry { } impl Mp4Box for SttsBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::SttsBox } @@ -27,9 +28,9 @@ impl Mp4Box for SttsBox { } } -impl ReadBox<&mut BufReader> for SttsBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for SttsBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -42,7 +43,8 @@ impl ReadBox<&mut BufReader> for SttsBox { }; entries.push(entry); } - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(SttsBox { version, @@ -52,10 +54,10 @@ impl ReadBox<&mut BufReader> for SttsBox { } } -impl WriteBox<&mut BufWriter> for SttsBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for SttsBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; write_box_header_ext(writer, self.version, self.flags)?; @@ -72,11 +74,11 @@ impl WriteBox<&mut BufWriter> for SttsBox { #[cfg(test)] mod tests { use super::*; - use crate::read_box_header; + use crate::atoms::BoxHeader; use std::io::Cursor; #[test] - fn test_stts32() { + fn test_stts() { let src_box = SttsBox { version: 0, flags: 0, @@ -86,50 +88,15 @@ mod tests { ], }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::SttsBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::SttsBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } - } - - #[test] - fn test_stts64() { - let src_box = SttsBox { - version: 1, - flags: 0, - entries: vec![ - SttsEntry {sample_count: 29726, sample_delta: 1024}, - SttsEntry {sample_count: 1, sample_delta: 512}, - ], - }; - let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } - assert_eq!(buf.len(), src_box.box_size() as usize); - - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::SttsBox); - assert_eq!(src_box.box_size(), header.size); - - let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } } diff --git a/src/atoms/tkhd.rs b/src/atoms/tkhd.rs index 46c44b1..c6b0337 100644 --- a/src/atoms/tkhd.rs +++ b/src/atoms/tkhd.rs @@ -1,8 +1,9 @@ -use std::io::{BufReader, Seek, SeekFrom, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use num_rational::Ratio; use crate::*; +use crate::atoms::*; #[derive(Debug, PartialEq)] @@ -54,7 +55,7 @@ pub struct Matrix { } impl Mp4Box for TkhdBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::TkhdBox } @@ -71,9 +72,9 @@ impl Mp4Box for TkhdBox { } } -impl ReadBox<&mut BufReader> for TkhdBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for TkhdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -118,7 +119,7 @@ impl ReadBox<&mut BufReader> for TkhdBox { let width = reader.read_u32::()? >> 16; let height = reader.read_u32::()? >> 16; - skip_read(reader, current, size)?; + skip_read_to(reader, start + size)?; Ok(TkhdBox { version, @@ -137,10 +138,10 @@ impl ReadBox<&mut BufReader> for TkhdBox { } } -impl WriteBox<&mut BufWriter> for TkhdBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for TkhdBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; write_box_header_ext(writer, self.version, self.flags)?; @@ -186,7 +187,7 @@ impl WriteBox<&mut BufWriter> for TkhdBox { #[cfg(test)] mod tests { use super::*; - use crate::read_box_header; + use crate::atoms::BoxHeader; use std::io::Cursor; #[test] @@ -216,22 +217,16 @@ mod tests { height: 288, }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::TkhdBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::TkhdBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } #[test] @@ -261,21 +256,15 @@ mod tests { height: 288, }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::TkhdBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::TkhdBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } } diff --git a/src/atoms/trak.rs b/src/atoms/trak.rs index 3d4eac2..7e5004f 100644 --- a/src/atoms/trak.rs +++ b/src/atoms/trak.rs @@ -1,11 +1,22 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, SeekFrom, Read, Write}; use crate::*; -use crate::atoms::{tkhd::TkhdBox, edts::EdtsBox, mdia::MdiaBox}; +use crate::atoms::*; +use crate::atoms::{ + tkhd::TkhdBox, + edts::EdtsBox, + mdia::MdiaBox, + stbl::StblBox, + stts::SttsBox, + stsc::StscBox, + stsz::StszBox, +}; #[derive(Debug, Default)] pub struct TrakBox { + pub id: u32, + pub tkhd: Option, pub edts: Option, pub mdia: Option, @@ -15,37 +26,260 @@ impl TrakBox { pub(crate) fn new() -> TrakBox { Default::default() } + + fn stbl(&self) -> Result<&StblBox> { + if let Some(ref mdia) = self.mdia { + if let Some(ref minf) = mdia.minf { + if let Some(ref stbl) = minf.stbl { + Ok(stbl) + } else { + Err(Error::BoxInTrakNotFound(self.id, BoxType::StblBox)) + } + } else { + Err(Error::BoxInTrakNotFound(self.id, BoxType::MinfBox)) + } + } else { + Err(Error::BoxInTrakNotFound(self.id, BoxType::MdiaBox)) + } + } + + fn stts(&self) -> Result<&SttsBox> { + let stbl = self.stbl()?; + + if let Some(ref stts) = stbl.stts { + Ok(stts) + } else { + Err(Error::BoxInStblNotFound(self.id, BoxType::SttsBox)) + } + } + + fn stsc(&self) -> Result<&StscBox> { + let stbl = self.stbl()?; + + if let Some(ref stsc) = stbl.stsc { + Ok(stsc) + } else { + Err(Error::BoxInStblNotFound(self.id, BoxType::StscBox)) + } + } + + fn stsz(&self) -> Result<&StszBox> { + let stbl = self.stbl()?; + + if let Some(ref stsz) = stbl.stsz { + Ok(stsz) + } else { + Err(Error::BoxInStblNotFound(self.id, BoxType::StszBox)) + } + } + + fn stsc_index(&self, sample_id: u32) -> Result { + let stsc = self.stsc()?; + + for (i, entry) in stsc.entries.iter().enumerate() { + if sample_id < entry.first_sample { + assert_ne!(i, 0); + return Ok(i - 1); + } + } + + assert_ne!(stsc.entries.len(), 0); + Ok(stsc.entries.len() - 1) + } + + fn chunk_offset(&self, chunk_id: u32) -> Result { + let stbl = self.stbl()?; + + if let Some(ref stco) = stbl.stco { + if let Some(offset) = stco.entries.get(chunk_id as usize - 1) { + return Ok(*offset as u64); + } else { + return Err(Error::EntryInStblNotFound(self.id, BoxType::StcoBox, + chunk_id)); + } + } else { + if let Some(ref co64) = stbl.co64 { + if let Some(offset) = co64.entries.get(chunk_id as usize - 1) { + return Ok(*offset); + } else { + return Err(Error::EntryInStblNotFound(self.id, BoxType::Co64Box, + chunk_id)); + } + } else { + // XXX BoxType::StcoBox & BoxType::Co64Box + Err(Error::BoxInStblNotFound(self.id, BoxType::Co64Box)) + } + } + } + + fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> { + let stbl = self.stbl()?; + + let ctts = if let Some(ref ctts) = stbl.ctts { + ctts + } else { + return Err(Error::BoxInStblNotFound(self.id, BoxType::CttsBox)); + }; + + let mut sample_count = 1; + for (i, entry) in ctts.entries.iter().enumerate() { + if sample_id <= sample_count + entry.sample_count -1 { + return Ok((i, sample_count)) + } + sample_count += entry.sample_count; + } + + return Err(Error::EntryInStblNotFound(self.id, BoxType::CttsBox, sample_id)); + } + + pub fn sample_count(&self) -> Result { + let stsz = self.stsz()?; + Ok(stsz.sample_sizes.len() as u32) + } + + pub fn sample_size(&self, sample_id: u32) -> Result { + let stsz = self.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.id, BoxType::StszBox, sample_id)); + } + } + + pub fn sample_offset(&self, sample_id: u32) -> Result { + let stsc_index = self.stsc_index(sample_id)?; + + let stsc = self.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 chunk_id = first_chunk + (sample_id - first_sample) / samples_per_chunk; + + let chunk_offset = self.chunk_offset(chunk_id)?; + + 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)?; + } + + Ok(chunk_offset + sample_offset as u64) + } + + pub fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> { + let stts = self.stts()?; + + 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)); + } + + sample_count += entry.sample_count; + elapsed += entry.sample_count as u64 * entry.sample_delta as u64; + } + + return Err(Error::EntryInStblNotFound(self.id, BoxType::SttsBox, sample_id)); + } + + pub fn sample_rendering_offset(&self, sample_id: u32) -> Result { + let stbl = self.stbl()?; + + if let Some(ref ctts) = stbl.ctts { + let (ctts_index, _) = self.ctts_index(sample_id)?; + let ctts_entry = ctts.entries.get(ctts_index).unwrap(); + Ok(ctts_entry.sample_offset) + } else { + Ok(0) + } + } + + pub fn is_sync_sample(&self, sample_id: u32) -> Result { + let stbl = self.stbl()?; + + if let Some(ref stss) = stbl.stss { + match stss.entries.binary_search(&sample_id) { + Ok(_) => Ok(true), + Err(_) => Ok(false) + } + } else { + Ok(true) + } + } + + pub fn read_sample( + &self, + reader: &mut R, + sample_id: u32, + ) -> Result> { + let sample_offset = match self.sample_offset(sample_id) { + Ok(offset) => offset, + Err(Error::EntryInStblNotFound(_,_,_)) => return Ok(None), + Err(err) => return Err(err) + }; + let sample_size = self.sample_size(sample_id)?; + + let mut buffer = vec![0x0u8; sample_size as usize]; + reader.seek(SeekFrom::Start(sample_offset))?; + reader.read_exact(&mut buffer)?; + + let (start_time, duration) = self.sample_time(sample_id)?; + let rendering_offset = self.sample_rendering_offset(sample_id)?; + let is_sync = self.is_sync_sample(sample_id)?; + + Ok(Some(Mp4Sample { + start_time, + duration, + rendering_offset, + is_sync, + bytes: Bytes::from(buffer), + })) + } } impl Mp4Box for TrakBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::TrakBox } fn box_size(&self) -> u64 { let mut size = HEADER_SIZE; - if let Some(tkhd) = &self.tkhd { + if let Some(ref tkhd) = self.tkhd { size += tkhd.box_size(); } - if let Some(edts) = &self.edts { + if let Some(ref edts) = self.edts { size += edts.box_size(); } - if let Some(mdia) = &self.mdia { + if let Some(ref mdia) = self.mdia { size += mdia.box_size(); } size } } -impl ReadBox<&mut BufReader> for TrakBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for TrakBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; + let mut trak = TrakBox::new(); - let start = 0u64; - while start < size { + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { // Get box header. - let header = read_box_header(reader, start)?; + let header = BoxHeader::read(reader)?; let BoxHeader{ name, size: s } = header; match name { @@ -61,27 +295,33 @@ impl ReadBox<&mut BufReader> for TrakBox { let mdia = MdiaBox::read_box(reader, s)?; trak.mdia = Some(mdia); } - _ => break + _ => { + // XXX warn!() + skip_box(reader, s)?; + } } + + current = reader.seek(SeekFrom::Current(0))?; } - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(trak) } } -impl WriteBox<&mut BufWriter> for TrakBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for TrakBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; - if let Some(tkhd) = &self.tkhd { + if let Some(ref tkhd) = self.tkhd { tkhd.write_box(writer)?; } - if let Some(edts) = &self.edts { + if let Some(ref edts) = self.edts { edts.write_box(writer)?; } - if let Some(mdia) = &self.mdia { + if let Some(ref mdia) = self.mdia { mdia.write_box(writer)?; } diff --git a/src/atoms/vmhd.rs b/src/atoms/vmhd.rs index c70a60b..eb1c3f0 100644 --- a/src/atoms/vmhd.rs +++ b/src/atoms/vmhd.rs @@ -1,7 +1,8 @@ -use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write}; +use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::*; +use crate::atoms::*; #[derive(Debug, Default, PartialEq)] @@ -20,7 +21,7 @@ pub struct RgbColor { } impl Mp4Box for VmhdBox { - fn box_type(&self) -> BoxType { + fn box_type() -> BoxType { BoxType::VmhdBox } @@ -29,9 +30,9 @@ impl Mp4Box for VmhdBox { } } -impl ReadBox<&mut BufReader> for VmhdBox { - fn read_box(reader: &mut BufReader, size: u64) -> Result { - let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. +impl ReadBox<&mut R> for VmhdBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = get_box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -41,7 +42,8 @@ impl ReadBox<&mut BufReader> for VmhdBox { green: reader.read_u16::()?, blue: reader.read_u16::()?, }; - skip_read(reader, current, size)?; + + skip_read_to(reader, start + size)?; Ok(VmhdBox { version, @@ -52,10 +54,10 @@ impl ReadBox<&mut BufReader> for VmhdBox { } } -impl WriteBox<&mut BufWriter> for VmhdBox { - fn write_box(&self, writer: &mut BufWriter) -> Result { +impl WriteBox<&mut W> for VmhdBox { + fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - BoxHeader::new(self.box_type(), size).write_box(writer)?; + BoxHeader::new(Self::box_type(), size).write(writer)?; write_box_header_ext(writer, self.version, self.flags)?; @@ -72,7 +74,7 @@ impl WriteBox<&mut BufWriter> for VmhdBox { #[cfg(test)] mod tests { use super::*; - use crate::read_box_header; + use crate::atoms::BoxHeader; use std::io::Cursor; #[test] @@ -84,21 +86,15 @@ mod tests { op_color: RgbColor { red: 0, green: 0, blue: 0}, }; let mut buf = Vec::new(); - { - let mut writer = BufWriter::new(&mut buf); - src_box.write_box(&mut writer).unwrap(); - } + src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); - { - let mut reader = BufReader::new(Cursor::new(&buf)); - let header = read_box_header(&mut reader, 0).unwrap(); - assert_eq!(header.name, BoxType::VmhdBox); - assert_eq!(src_box.box_size(), header.size); + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::VmhdBox); + assert_eq!(src_box.box_size(), header.size); - let dst_box = VmhdBox::read_box(&mut reader, header.size).unwrap(); - - assert_eq!(src_box, dst_box); - } + let dst_box = VmhdBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); } } diff --git a/src/error.rs b/src/error.rs index 8f38849..180d90e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,9 +1,21 @@ use thiserror::Error; +use crate::atoms::BoxType; + #[derive(Error, Debug)] pub enum Error { #[error("{0}")] IoError(#[from] std::io::Error), #[error("{0}")] InvalidData(&'static str), + #[error("{0} not found")] + BoxNotFound(BoxType), + #[error("trak[{0}] not found")] + TrakNotFound(u32), + #[error("trak[{0}].{1} not found")] + BoxInTrakNotFound(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), } diff --git a/src/lib.rs b/src/lib.rs index 309c1ab..ed75c0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,9 @@ -use std::io::{BufReader, Read, SeekFrom, Seek}; -use std::fs::File; +use std::fmt; +use std::io::{Seek, SeekFrom, Read}; use std::convert::TryInto; +pub use bytes::Bytes; + mod atoms; use crate::atoms::*; @@ -10,9 +12,6 @@ pub use error::Error; pub type Result = std::result::Result; -// XXX if box has largesize -const HEADER_SIZE: u64 = 8; - #[derive(Debug, PartialEq)] pub enum TrackType { Audio, @@ -21,118 +20,143 @@ pub enum TrackType { Unknown, } -#[derive(Debug, Default)] -pub struct BMFF { - pub ftyp: FtypBox, - pub moov: Option, - pub size: u64, +#[derive(Debug)] +pub struct Mp4Sample { + pub start_time: u64, + pub duration: u32, + pub rendering_offset: i32, + pub is_sync: bool, + pub bytes: Bytes, } -impl BMFF { - fn new() -> BMFF { - Default::default() +impl PartialEq for Mp4Sample { + fn eq(&self, other: &Self) -> bool { + self.start_time == other.start_time + && self.duration == other.duration + && self.rendering_offset == other.rendering_offset + && self.is_sync == other.is_sync + && self.bytes.len() == other.bytes.len() // XXX for easy check } } -#[derive(Debug, Clone, Copy)] -struct BoxHeader { - name: BoxType, +impl fmt::Display for Mp4Sample { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, + "start_time {}, duration {}, rendering_offset {}, is_sync {}, length {}", + self.start_time, self.duration, self.rendering_offset, self.is_sync, + self.bytes.len()) + } +} + +#[derive(Debug)] +pub struct Mp4Reader { + reader: R, + pub ftyp: FtypBox, + pub moov: Option, size: u64, } -impl BoxHeader { - fn new(name: BoxType, size: u64) -> Self { - Self { name, size } +impl Mp4Reader { + pub fn new(reader: R) -> Self { + Mp4Reader { + reader, + ftyp: FtypBox::default(), + moov: None, + size: 0, + } } -} -pub fn read_mp4(f: File) -> Result { + pub fn size(&self) -> u64 { + self.size + } - // Open file and read boxes. - let bmff = read_boxes(f)?; + pub fn read(&mut self, size: u64) -> Result<()> { + let start = self.reader.seek(SeekFrom::Current(0))?; + let mut current = start; + while current < size { + // Get box header. + let header = BoxHeader::read(&mut self.reader)?; + let BoxHeader{ name, size: s } = header; - Ok(bmff) -} - -fn read_boxes(f: File) -> Result { - let filesize = f.metadata()?.len(); - let mut reader = BufReader::new(f); - let mut bmff = BMFF::new(); - bmff.size = filesize; - - let mut start = 0u64; - while start < filesize { - - // Get box header. - let header = read_box_header(&mut reader, start)?; - let BoxHeader{ name, size } = header; - - // Match and parse the atom boxes. - match name { - BoxType::FtypBox => { - let ftyp = FtypBox::read_box(&mut reader, size)?; - bmff.ftyp = ftyp; - } - BoxType::FreeBox => { - start = 0; - } - BoxType::MdatBox => { - start = size - HEADER_SIZE; - } - BoxType::MoovBox => { - let moov = MoovBox::read_box(&mut reader, size)?; - bmff.moov = Some(moov); - } - BoxType::MoofBox => { - start = size - HEADER_SIZE; - } - _ => { - // Skip over unsupported boxes, but stop if the size is zero, - // meaning the last box has been reached. - if size == 0 { - break; - } else { - start = size - HEADER_SIZE; + // Match and parse the atom boxes. + match name { + BoxType::FtypBox => { + let ftyp = FtypBox::read_box(&mut self.reader, s)?; + self.ftyp = ftyp; + } + BoxType::FreeBox => { + skip_box(&mut self.reader, s)?; + } + BoxType::MdatBox => { + skip_box(&mut self.reader, s)?; + } + BoxType::MoovBox => { + let moov = MoovBox::read_box(&mut self.reader, s)?; + self.moov = Some(moov); + } + BoxType::MoofBox => { + skip_box(&mut self.reader, s)?; + } + _ => { + // XXX warn!() + skip_box(&mut self.reader, s)?; } } + current = self.reader.seek(SeekFrom::Current(0))?; + } + self.size = current - start; + Ok(()) + } + + pub fn track_count(&self) -> Result { + if let Some(ref moov) = self.moov { + Ok(moov.traks.len() as u32) + } else { + Err(Error::BoxNotFound(MoovBox::box_type())) + } + } + + pub fn sample_count(&self, track_id: u32) -> Result { + if track_id == 0 { + return Err(Error::TrakNotFound(track_id)); + } + + let moov = if let Some(ref moov) = self.moov { + moov + } else { + return Err(Error::BoxNotFound(MoovBox::box_type())); }; + + let trak = if let Some(trak) = moov.traks.get(track_id as usize - 1) { + trak + } else { + return Err(Error::TrakNotFound(track_id)); + }; + + trak.sample_count() } - Ok(bmff) -} -// TODO: if size is 0, then this box is the last one in the file -fn read_box_header(reader: &mut BufReader, start: u64) -> Result { - // Seek to offset. - let _r = reader.seek(SeekFrom::Current(start as i64)); + pub fn read_sample( + &mut self, + track_id: u32, + sample_id: u32, + ) -> Result> { + if track_id == 0 { + return Err(Error::TrakNotFound(track_id)); + } - // Create and read to buf. - let mut buf = [0u8;8]; // 8 bytes for box header. - reader.read(&mut buf)?; + let moov = if let Some(ref moov) = self.moov { + moov + } else { + return Err(Error::BoxNotFound(MoovBox::box_type())); + }; - // Get size. - let s = buf[0..4].try_into().unwrap(); - let size = u32::from_be_bytes(s); - - // Get box type string. - let t = buf[4..8].try_into().unwrap(); - let typ = u32::from_be_bytes(t); + let trak = if let Some(trak) = moov.traks.get(track_id as usize - 1) { + trak + } else { + return Err(Error::TrakNotFound(track_id)); + }; - // Get largesize if size is 1 - if size == 1 { - reader.read(&mut buf)?; - let s = buf.try_into().unwrap(); - let largesize = u64::from_be_bytes(s); - - Ok(BoxHeader { - name: BoxType::from(typ), - size: largesize, - }) - } else { - Ok(BoxHeader { - name: BoxType::from(typ), - size: size as u64, - }) + trak.read_sample(&mut self.reader, sample_id) } } - - diff --git a/tests/lib.rs b/tests/lib.rs index c229338..11757ad 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,18 +1,23 @@ use mp4; use std::fs::File; +use std::io::BufReader; #[test] fn test_read_mp4() { let filename = "tests/samples/minimal.mp4"; let f = File::open(filename).unwrap(); - let bmff = mp4::read_mp4(f).unwrap(); + let size = f.metadata().unwrap().len(); + let reader = BufReader::new(f); - assert_eq!(2591, bmff.size); + let mut mp4 = mp4::Mp4Reader::new(reader); + mp4.read(size).unwrap(); + + assert_eq!(2591, mp4.size()); // ftyp. - println!("{:?}", bmff.ftyp.compatible_brands); - assert_eq!(4, bmff.ftyp.compatible_brands.len()); + println!("{:?}", mp4.ftyp.compatible_brands); + assert_eq!(4, mp4.ftyp.compatible_brands.len()); // Check compatible_brands. let brands = vec![ @@ -23,16 +28,53 @@ fn test_read_mp4() { ]; for b in brands { - let t = bmff.ftyp.compatible_brands.iter().any(|x| x.to_string() == b); + let t = mp4.ftyp.compatible_brands.iter().any(|x| x.to_string() == b); assert_eq!(t, true); } // moov. - let moov = bmff.moov.unwrap(); - assert_eq!(moov.mvhd.version, 0); - assert_eq!(moov.mvhd.creation_time, 0); - assert_eq!(moov.mvhd.duration, 62); - assert_eq!(moov.mvhd.timescale, 1000); - assert_eq!(moov.traks.len(), 2); + assert!(mp4.moov.is_some()); + if let Some(ref moov) = mp4.moov { + assert_eq!(moov.mvhd.version, 0); + assert_eq!(moov.mvhd.creation_time, 0); + assert_eq!(moov.mvhd.duration, 62); + assert_eq!(moov.mvhd.timescale, 1000); + assert_eq!(moov.traks.len(), 2); + } -} \ No newline at end of file + let sample_count = mp4.sample_count(1).unwrap(); + assert_eq!(sample_count, 0); + + let sample_count = mp4.sample_count(2).unwrap(); + assert_eq!(sample_count, 3); + let sample1 = mp4.read_sample(2, 1).unwrap().unwrap(); + assert_eq!(sample1.bytes.len(), 179); + assert_eq!(sample1, mp4::Mp4Sample { + start_time: 0, + duration: 1024, + rendering_offset: 0, + is_sync: true, + bytes: mp4::Bytes::from(vec![0x0u8; 179]), + }); + + let sample2 = mp4.read_sample(2, 2).unwrap().unwrap(); + assert_eq!(sample2, mp4::Mp4Sample { + start_time: 1024, + duration: 1024, + rendering_offset: 0, + is_sync: true, + bytes: mp4::Bytes::from(vec![0x0u8; 180]), + }); + + let sample3 = mp4.read_sample(2, 3).unwrap().unwrap(); + assert_eq!(sample3, mp4::Mp4Sample { + start_time: 2048, + duration: 896, + rendering_offset: 0, + is_sync: true, + bytes: mp4::Bytes::from(vec![0x0u8; 160]), + }); + + let eos = mp4.read_sample(2, 4).unwrap(); + assert!(eos.is_none()); +}