diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index 2ec9eba..aa55fb3 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -1,10 +1,10 @@ use std::env; use std::fs::File; use std::io::prelude::*; -use std::io::{self, BufReader}; +use std::io::{self, BufReader, BufWriter}; use std::path::Path; -use mp4::Result; +use mp4::{AacConfig, AvcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig}; fn main() { let args: Vec = env::args().collect(); @@ -19,23 +19,66 @@ fn main() { } } -fn copy>(src_filename: &P, _dst_filename: &P) -> Result<()> { +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)?; + let dst_file = File::create(dst_filename)?; + let writer = BufWriter::new(dst_file); - for tix in 0..mp4.track_count()? { - let track_id = tix + 1; - let sample_count = mp4.sample_count(track_id)?; + let mut mp4_reader = mp4::Mp4Reader::read_header(reader, size)?; + let mut mp4_writer = mp4::Mp4Writer::write_start( + writer, + &Mp4Config { + major_brand: mp4_reader.major_brand().clone(), + minor_version: mp4_reader.minor_version(), + compatible_brands: mp4_reader.compatible_brands().to_vec(), + timescale: mp4_reader.timescale(), + }, + )?; + + // TODO interleaving + for track_idx in 0..mp4_reader.tracks().len() { + if let Some(ref track) = mp4_reader.tracks().get(track_idx) { + let media_conf = match track.media_type()? { + MediaType::H264 => MediaConfig::AvcConfig(AvcConfig { + width: track.width(), + height: track.height(), + seq_param_set: track.sequence_parameter_set()?.to_vec(), + pic_param_set: track.picture_parameter_set()?.to_vec(), + }), + MediaType::AAC => MediaConfig::AacConfig(AacConfig { + bitrate: track.bitrate(), + profile: track.audio_profile()?, + freq_index: track.sample_freq_index()?, + chan_conf: track.channel_config()?, + }), + }; + + let track_conf = TrackConfig { + track_type: track.track_type()?, + timescale: track.timescale(), + language: track.language().to_string(), + media_conf, + }; + + mp4_writer.add_track(&track_conf)?; + } else { + unreachable!() + } + + let track_id = track_idx as u32 + 1; + let sample_count = mp4_reader.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); + let sample = mp4_reader.read_sample(track_id, sample_id)?.unwrap(); + mp4_writer.write_sample(track_id, &sample)?; + // println!("copy {}:({})", sample_id, sample); } } + mp4_writer.write_end()?; + Ok(()) } diff --git a/examples/mp4info.rs b/examples/mp4info.rs index daf0e46..62ca2d0 100644 --- a/examples/mp4info.rs +++ b/examples/mp4info.rs @@ -4,7 +4,7 @@ use std::io::prelude::*; use std::io::{self, BufReader}; use std::path::Path; -use mp4::{Result, Mp4Reader, TrackType}; +use mp4::{Mp4Track, Result, TrackType}; fn main() { let args: Vec = env::args().collect(); @@ -24,96 +24,61 @@ fn info>(filename: &P) -> Result<()> { let size = f.metadata()?.len(); let reader = BufReader::new(f); - let mut mp4 = Mp4Reader::new(reader); - mp4.read(size)?; + let mp4 = mp4::Mp4Reader::read_header(reader, size)?; - println!("File:"); - println!(" size: {}", mp4.size()); - println!(" brands: {:?} {:?}\n", - mp4.ftyp.major_brand, mp4.ftyp.compatible_brands); + println!("Metadata:"); + println!(" size : {}", mp4.size()); + println!(" major_brand : {}", mp4.major_brand()); + let mut compatible_brands = String::new(); + for brand in mp4.compatible_brands().iter() { + compatible_brands.push_str(&brand.to_string()); + compatible_brands.push_str(","); + } + println!(" compatible_brands: {}", compatible_brands); + println!( + "Duration: {}, timescale: {}", + mp4.duration(), + mp4.timescale() + ); - 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); - } - 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!(" frame rate: (computed): {:?}", - get_framerate(s.entries[0].sample_count, - mdhd.duration, mdhd.timescale)); - } - } - } - } + for track in mp4.tracks().iter() { + let media_info = match track.track_type()? { + TrackType::Video => video_info(track)?, + TrackType::Audio => audio_info(track)?, + }; + println!( + " Track: #{}({}) {}: {}", + track.track_id(), + track.language(), + track.track_type()?, + media_info + ); } Ok(()) } -fn get_handler_type(handler: &str) -> TrackType { - let mut typ: TrackType = TrackType::Unknown; - match handler { - "vide" => typ = TrackType::Video, - "soun" => typ = TrackType::Audio, - "meta" => typ = TrackType::Unknown, - _ => (), - } - return typ; +fn video_info(track: &Mp4Track) -> Result { + Ok(format!( + "{} ({}) ({:?}), {}x{}, {} kb/s, {:.2} fps", + track.media_type()?, + track.video_profile()?, + track.box_type()?, + track.width(), + track.height(), + track.bitrate() / 1000, + track.frame_rate_f64() + )) } -fn get_duration_ms(duration: u64, timescale: u32) -> String { - let ms = (duration as f64 / timescale as f64) * 1000.0; - return format!("{:.2}", ms.floor()); -} - -fn get_framerate(sample_count: u32, duration: u64, timescale: u32) -> String { - let sc = (sample_count as f64) * 1000.0; - let ms = (duration as f64 / timescale as f64) * 1000.0; - return format!("{:.2}", sc / ms.floor()); -} - -fn creation_time(creation_time: u64) -> u64 { - // convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01) - if creation_time >= 2082844800 { - creation_time - 2082844800 - } else { - creation_time - } +fn audio_info(track: &Mp4Track) -> Result { + Ok(format!( + "{} ({}) ({:?}), {} Hz, {}, {} kb/s", + track.media_type()?, + track.audio_profile()?, + track.box_type()?, + track.sample_freq_index()?.freq(), + track.channel_config()?, + track.bitrate() / 1000 + )) } diff --git a/src/atoms/mdia.rs b/src/atoms/mdia.rs deleted file mode 100644 index 7367d5b..0000000 --- a/src/atoms/mdia.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::io::{Seek, SeekFrom, Read, Write}; - -use crate::*; -use crate::atoms::*; -use crate::atoms::{mdhd::MdhdBox, hdlr::HdlrBox, minf::MinfBox}; - - -#[derive(Debug, Default)] -pub struct MdiaBox { - pub mdhd: Option, - pub hdlr: Option, - pub minf: Option, -} - -impl MdiaBox { - pub(crate) fn new() -> MdiaBox { - Default::default() - } -} - -impl Mp4Box for MdiaBox { - fn box_type() -> BoxType { - BoxType::MdiaBox - } - - fn box_size(&self) -> u64 { - let mut size = HEADER_SIZE; - if let Some(ref mdhd) = self.mdhd { - size += mdhd.box_size(); - } - if let Some(ref hdlr) = self.hdlr { - size += hdlr.box_size(); - } - if let Some(ref minf) = self.minf { - size += minf.box_size(); - } - size - } -} - -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 mut current = reader.seek(SeekFrom::Current(0))?; - let end = start + size; - while current < end { - // Get box header. - let header = BoxHeader::read(reader)?; - let BoxHeader{ name, size: s } = header; - - match name { - BoxType::MdhdBox => { - let mdhd = MdhdBox::read_box(reader, s)?; - mdia.mdhd = Some(mdhd); - } - BoxType::HdlrBox => { - let hdlr = HdlrBox::read_box(reader, s)?; - mdia.hdlr = Some(hdlr); - } - BoxType::MinfBox => { - let minf = MinfBox::read_box(reader, s)?; - mdia.minf = Some(minf); - } - _ => { - // XXX warn!() - skip_box(reader, s)?; - } - } - - current = reader.seek(SeekFrom::Current(0))?; - } - - skip_read_to(reader, start + size)?; - - Ok(mdia) - } -} - -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(writer)?; - - if let Some(ref mdhd) = self.mdhd { - mdhd.write_box(writer)?; - } - if let Some(ref hdlr) = self.hdlr { - hdlr.write_box(writer)?; - } - if let Some(ref minf) = self.minf { - minf.write_box(writer)?; - } - - Ok(size) - } -} diff --git a/src/atoms/mp4a.rs b/src/atoms/mp4a.rs deleted file mode 100644 index c1fa804..0000000 --- a/src/atoms/mp4a.rs +++ /dev/null @@ -1,411 +0,0 @@ -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/trak.rs b/src/atoms/trak.rs deleted file mode 100644 index 7e5004f..0000000 --- a/src/atoms/trak.rs +++ /dev/null @@ -1,330 +0,0 @@ -use std::io::{Seek, SeekFrom, Read, Write}; - -use crate::*; -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, -} - -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() -> BoxType { - BoxType::TrakBox - } - - fn box_size(&self) -> u64 { - let mut size = HEADER_SIZE; - if let Some(ref tkhd) = self.tkhd { - size += tkhd.box_size(); - } - if let Some(ref edts) = self.edts { - size += edts.box_size(); - } - if let Some(ref mdia) = self.mdia { - size += mdia.box_size(); - } - size - } -} - -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 mut current = reader.seek(SeekFrom::Current(0))?; - let end = start + size; - while current < end { - // Get box header. - let header = BoxHeader::read(reader)?; - let BoxHeader{ name, size: s } = header; - - match name { - BoxType::TkhdBox => { - let tkhd = TkhdBox::read_box(reader, s)?; - trak.tkhd = Some(tkhd); - } - BoxType::EdtsBox => { - let edts = EdtsBox::read_box(reader, s)?; - trak.edts = Some(edts); - } - BoxType::MdiaBox => { - let mdia = MdiaBox::read_box(reader, s)?; - trak.mdia = Some(mdia); - } - _ => { - // XXX warn!() - skip_box(reader, s)?; - } - } - - current = reader.seek(SeekFrom::Current(0))?; - } - - skip_read_to(reader, start + size)?; - - Ok(trak) - } -} - -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(writer)?; - - if let Some(ref tkhd) = self.tkhd { - tkhd.write_box(writer)?; - } - if let Some(ref edts) = self.edts { - edts.write_box(writer)?; - } - if let Some(ref mdia) = self.mdia { - mdia.write_box(writer)?; - } - - Ok(size) - } -} diff --git a/src/error.rs b/src/error.rs index 180d90e..d549d9b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ use thiserror::Error; -use crate::atoms::BoxType; +use crate::mp4box::BoxType; #[derive(Error, Debug)] pub enum Error { @@ -10,6 +10,8 @@ pub enum Error { InvalidData(&'static str), #[error("{0} not found")] BoxNotFound(BoxType), + #[error("{0} and {1} not found")] + Box2NotFound(BoxType, BoxType), #[error("trak[{0}] not found")] TrakNotFound(u32), #[error("trak[{0}].{1} not found")] diff --git a/src/lib.rs b/src/lib.rs index ed75c0c..fde9222 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,162 +1,18 @@ -use std::fmt; -use std::io::{Seek, SeekFrom, Read}; -use std::convert::TryInto; - -pub use bytes::Bytes; - -mod atoms; -use crate::atoms::*; - mod error; pub use error::Error; pub type Result = std::result::Result; -#[derive(Debug, PartialEq)] -pub enum TrackType { - Audio, - Video, - Metadata, - Unknown, -} +mod types; +pub use types::*; -#[derive(Debug)] -pub struct Mp4Sample { - pub start_time: u64, - pub duration: u32, - pub rendering_offset: i32, - pub is_sync: bool, - pub bytes: Bytes, -} +mod mp4box; -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 - } -} +mod track; +pub use track::{Mp4Track, TrackConfig}; -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()) - } -} +mod reader; +pub use reader::Mp4Reader; -#[derive(Debug)] -pub struct Mp4Reader { - reader: R, - pub ftyp: FtypBox, - pub moov: Option, - size: u64, -} - -impl Mp4Reader { - pub fn new(reader: R) -> Self { - Mp4Reader { - reader, - ftyp: FtypBox::default(), - moov: None, - size: 0, - } - } - - pub fn size(&self) -> u64 { - self.size - } - - 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; - - // 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() - } - - pub fn read_sample( - &mut self, - track_id: u32, - sample_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.read_sample(&mut self.reader, sample_id) - } -} +mod writer; +pub use writer::{Mp4Config, Mp4Writer}; diff --git a/src/atoms/avc.rs b/src/mp4box/avc1.rs similarity index 62% rename from src/atoms/avc.rs rename to src/mp4box/avc1.rs index c6cbc81..bc11ef5 100644 --- a/src/atoms/avc.rs +++ b/src/mp4box/avc1.rs @@ -1,18 +1,15 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use num_rational::Ratio; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Avc1Box { pub data_reference_index: u16, pub width: u16, pub height: u16, - pub horizresolution: Ratio, - pub vertresolution: Ratio, + pub horizresolution: FixedPointU16, + pub vertresolution: FixedPointU16, pub frame_count: u16, pub depth: u16, pub avcc: AvcCBox, @@ -24,8 +21,8 @@ impl Default for Avc1Box { data_reference_index: 0, width: 0, height: 0, - horizresolution: Ratio::new_raw(0x00480000, 0x10000), - vertresolution: Ratio::new_raw(0x00480000, 0x10000), + horizresolution: FixedPointU16::new(0x48), + vertresolution: FixedPointU16::new(0x48), frame_count: 1, depth: 0x0018, avcc: AvcCBox::default(), @@ -33,19 +30,34 @@ impl Default for Avc1Box { } } +impl Avc1Box { + pub fn new(config: &AvcConfig) -> Self { + Avc1Box { + data_reference_index: 1, + width: config.width, + height: config.height, + horizresolution: FixedPointU16::new(0x48), + vertresolution: FixedPointU16::new(0x48), + frame_count: 1, + depth: 0x0018, + avcc: AvcCBox::new(&config.seq_param_set, &config.pic_param_set), + } + } +} + impl Mp4Box for Avc1Box { fn box_type() -> BoxType { BoxType::Avc1Box } fn box_size(&self) -> u64 { - HEADER_SIZE + 8 + 74 + self.avcc.box_size() + HEADER_SIZE + 8 + 70 + 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)?; + let start = box_start(reader)?; reader.read_u32::()?; // reserved reader.read_u16::()?; // reserved @@ -56,10 +68,8 @@ impl ReadBox<&mut R> for Avc1Box { 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); + let horizresolution = FixedPointU16::new_raw(reader.read_u32::()?); + let vertresolution = FixedPointU16::new_raw(reader.read_u32::()?); reader.read_u32::()?; // reserved let frame_count = reader.read_u16::()?; skip_read(reader, 32)?; // compressorname @@ -67,7 +77,7 @@ impl ReadBox<&mut R> for Avc1Box { reader.read_i16::()?; // pre-defined let header = BoxHeader::read(reader)?; - let BoxHeader{ name, size: s } = header; + let BoxHeader { name, size: s } = header; if name == BoxType::AvcCBox { let avcc = AvcCBox::read_box(reader, s)?; @@ -103,14 +113,12 @@ impl WriteBox<&mut W> for Avc1Box { 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::(self.horizresolution.raw_value())?; + writer.write_u32::(self.vertresolution.raw_value())?; writer.write_u32::(0)?; // reserved writer.write_u16::(self.frame_count)?; // skip compressorname - for _ in 0..4 { - writer.write_u64::(0)?; - } + skip_write(writer, 32)?; writer.write_u16::(self.depth)?; writer.write_i16::(-1)?; // pre-defined @@ -120,8 +128,7 @@ impl WriteBox<&mut W> for Avc1Box { } } - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct AvcCBox { pub configuration_version: u8, pub avc_profile_indication: u8, @@ -132,6 +139,20 @@ pub struct AvcCBox { pub picture_parameter_sets: Vec, } +impl AvcCBox { + pub fn new(sps: &[u8], pps: &[u8]) -> Self { + Self { + configuration_version: 1, + avc_profile_indication: sps[1], + profile_compatibility: sps[2], + avc_level_indication: sps[3], + length_size_minus_one: 0xff, // length_size = 4 + sequence_parameter_sets: vec![NalUnit::from(sps)], + picture_parameter_sets: vec![NalUnit::from(pps)], + } + } +} + impl Mp4Box for AvcCBox { fn box_type() -> BoxType { BoxType::AvcCBox @@ -151,7 +172,7 @@ impl Mp4Box for AvcCBox { impl ReadBox<&mut R> for AvcCBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let configuration_version = reader.read_u8()?; let avc_profile_indication = reader.read_u8()?; @@ -207,29 +228,81 @@ impl WriteBox<&mut W> for AvcCBox { } } - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct NalUnit { pub bytes: Vec, } +impl From<&[u8]> for NalUnit { + fn from(bytes: &[u8]) -> Self { + Self { + bytes: bytes.to_vec(), + } + } +} + impl NalUnit { - pub fn size(&self) -> usize { + fn size(&self) -> usize { 2 + self.bytes.len() } - pub fn read(reader: &mut R) -> Result { + 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, - }) + Ok(NalUnit { bytes }) } - pub fn write(&self, writer: &mut W) -> Result { + fn write(&self, writer: &mut W) -> Result { writer.write_u16::(self.bytes.len() as u16)?; writer.write(&self.bytes)?; Ok(self.size() as u64) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_avc1() { + let src_box = Avc1Box { + data_reference_index: 1, + width: 320, + height: 240, + horizresolution: FixedPointU16::new(0x48), + vertresolution: FixedPointU16::new(0x48), + frame_count: 1, + depth: 24, + avcc: AvcCBox { + configuration_version: 1, + avc_profile_indication: 100, + profile_compatibility: 0, + avc_level_indication: 13, + length_size_minus_one: 3, + sequence_parameter_sets: vec![NalUnit { + bytes: vec![ + 0x67, 0x64, 0x00, 0x0D, 0xAC, 0xD9, 0x41, 0x41, 0xFA, 0x10, 0x00, 0x00, + 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x42, 0x99, 0x60, + ], + }], + picture_parameter_sets: vec![NalUnit { + bytes: vec![0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0], + }], + }, + }; + 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::Avc1Box); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = Avc1Box::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/co64.rs b/src/mp4box/co64.rs similarity index 92% rename from src/atoms/co64.rs rename to src/mp4box/co64.rs index a578bc4..c425b4c 100644 --- a/src/atoms/co64.rs +++ b/src/mp4box/co64.rs @@ -1,11 +1,9 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct Co64Box { pub version: u8, pub flags: u32, @@ -24,7 +22,7 @@ impl Mp4Box for Co64Box { impl ReadBox<&mut R> for Co64Box { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -64,7 +62,7 @@ impl WriteBox<&mut W> for Co64Box { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] diff --git a/src/atoms/ctts.rs b/src/mp4box/ctts.rs similarity index 83% rename from src/atoms/ctts.rs rename to src/mp4box/ctts.rs index 4db657a..c3ee0c3 100644 --- a/src/atoms/ctts.rs +++ b/src/mp4box/ctts.rs @@ -1,18 +1,16 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct CttsBox { pub version: u8, pub flags: u32, pub entries: Vec, } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct CttsEntry { pub sample_count: u32, pub sample_offset: i32, @@ -30,7 +28,7 @@ impl Mp4Box for CttsBox { impl ReadBox<&mut R> for CttsBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -74,7 +72,7 @@ impl WriteBox<&mut W> for CttsBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] @@ -83,8 +81,14 @@ mod tests { version: 0, flags: 0, entries: vec![ - CttsEntry {sample_count: 1, sample_offset: 200}, - CttsEntry {sample_count: 2, sample_offset: -100}, + CttsEntry { + sample_count: 1, + sample_offset: 200, + }, + CttsEntry { + sample_count: 2, + sample_offset: -100, + }, ], }; let mut buf = Vec::new(); diff --git a/src/atoms/edts.rs b/src/mp4box/edts.rs similarity index 84% rename from src/atoms/edts.rs rename to src/mp4box/edts.rs index f8bf46b..4eb1842 100644 --- a/src/atoms/edts.rs +++ b/src/mp4box/edts.rs @@ -1,11 +1,9 @@ -use std::io::{Seek, Read, Write}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; -use crate::atoms::elst::ElstBox; +use crate::mp4box::elst::ElstBox; +use crate::mp4box::*; - -#[derive(Debug, Default)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct EdtsBox { pub elst: Option, } @@ -32,12 +30,12 @@ impl Mp4Box for EdtsBox { impl ReadBox<&mut R> for EdtsBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let mut edts = EdtsBox::new(); let header = BoxHeader::read(reader)?; - let BoxHeader{ name, size: s } = header; + let BoxHeader { name, size: s } = header; match name { BoxType::ElstBox => { diff --git a/src/atoms/elst.rs b/src/mp4box/elst.rs similarity index 84% rename from src/atoms/elst.rs rename to src/mp4box/elst.rs index 018194a..75e29a4 100644 --- a/src/atoms/elst.rs +++ b/src/mp4box/elst.rs @@ -1,18 +1,16 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct ElstBox { pub version: u8, pub flags: u32, pub entries: Vec, } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct ElstEntry { pub segment_duration: u64, pub media_time: u64, @@ -39,27 +37,26 @@ impl Mp4Box for ElstBox { impl ReadBox<&mut R> for ElstBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = 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 (segment_duration, media_time) - = if version == 1 { - ( - reader.read_u64::()?, - reader.read_u64::()?, - ) - } else { - ( - reader.read_u32::()? as u64, - reader.read_u32::()? as u64, - ) - }; + let (segment_duration, media_time) = if version == 1 { + ( + reader.read_u64::()?, + reader.read_u64::()?, + ) + } else { + ( + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + ) + }; - let entry = ElstEntry{ + let entry = ElstEntry { segment_duration, media_time, media_rate: reader.read_u16::()?, @@ -105,7 +102,7 @@ impl WriteBox<&mut W> for ElstBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] diff --git a/src/atoms/ftyp.rs b/src/mp4box/ftyp.rs similarity index 77% rename from src/atoms/ftyp.rs rename to src/mp4box/ftyp.rs index 5e1af83..fc8d132 100644 --- a/src/atoms/ftyp.rs +++ b/src/mp4box/ftyp.rs @@ -1,11 +1,9 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct FtypBox { pub major_brand: FourCC, pub minor_version: u32, @@ -24,7 +22,7 @@ impl Mp4Box for FtypBox { impl ReadBox<&mut R> for FtypBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let major = reader.read_u32::()?; let minor = reader.read_u32::()?; @@ -66,20 +64,30 @@ impl WriteBox<&mut W> for FtypBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] fn test_ftyp() { let src_box = FtypBox { - major_brand: FourCC { value: String::from("isom") }, + major_brand: FourCC { + value: String::from("isom"), + }, minor_version: 0, compatible_brands: vec![ - FourCC { value: String::from("isom") }, - FourCC { value: String::from("iso2") }, - FourCC { value: String::from("avc1") }, - FourCC { value: String::from("mp41") }, - ] + FourCC { + value: String::from("isom"), + }, + FourCC { + value: String::from("iso2"), + }, + FourCC { + value: String::from("avc1"), + }, + FourCC { + value: String::from("mp41"), + }, + ], }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); diff --git a/src/atoms/hdlr.rs b/src/mp4box/hdlr.rs similarity index 93% rename from src/atoms/hdlr.rs rename to src/mp4box/hdlr.rs index 3d7736e..178f223 100644 --- a/src/atoms/hdlr.rs +++ b/src/mp4box/hdlr.rs @@ -1,11 +1,9 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct HdlrBox { pub version: u8, pub flags: u32, @@ -25,7 +23,7 @@ impl Mp4Box for HdlrBox { impl ReadBox<&mut R> for HdlrBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -42,7 +40,7 @@ impl ReadBox<&mut R> for HdlrBox { Ok(t) => { assert_eq!(t.len(), buf_size as usize); t - }, + } _ => String::from("null"), }; @@ -82,7 +80,7 @@ impl WriteBox<&mut W> for HdlrBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] diff --git a/src/atoms/mdhd.rs b/src/mp4box/mdhd.rs similarity index 81% rename from src/atoms/mdhd.rs rename to src/mp4box/mdhd.rs index 03b3445..67ef558 100644 --- a/src/atoms/mdhd.rs +++ b/src/mp4box/mdhd.rs @@ -1,12 +1,10 @@ -use std::io::{Seek, Read, Write}; -use std::char::{decode_utf16, REPLACEMENT_CHARACTER}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::char::{decode_utf16, REPLACEMENT_CHARACTER}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct MdhdBox { pub version: u8, pub flags: u32, @@ -52,29 +50,28 @@ impl Mp4Box for MdhdBox { impl ReadBox<&mut R> for MdhdBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; - let (creation_time, modification_time, timescale, duration) - = if version == 1 { - ( - reader.read_u64::()?, - reader.read_u64::()?, - reader.read_u32::()?, - reader.read_u64::()?, - ) - } else { - assert_eq!(version, 0); - ( - reader.read_u32::()? as u64, - reader.read_u32::()? as u64, - reader.read_u32::()?, - reader.read_u32::()? as u64, - ) - }; + let (creation_time, modification_time, timescale, duration) = if version == 1 { + ( + reader.read_u64::()?, + reader.read_u64::()?, + reader.read_u32::()?, + reader.read_u64::()?, + ) + } else { + assert_eq!(version, 0); + ( + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + reader.read_u32::()?, + reader.read_u32::()? as u64, + ) + }; let language_code = reader.read_u16::()?; - let language = get_language_string(language_code); + let language = language_string(language_code); skip_read_to(reader, start + size)?; @@ -110,7 +107,7 @@ impl WriteBox<&mut W> for MdhdBox { writer.write_u32::(self.duration as u32)?; } - let language_code = get_language_code(&self.language); + let language_code = language_code(&self.language); writer.write_u16::(language_code)?; writer.write_u16::(0)?; // pre-defined @@ -118,7 +115,7 @@ impl WriteBox<&mut W> for MdhdBox { } } -fn get_language_string(language: u16) -> String { +fn language_string(language: u16) -> String { let mut lang: [u16; 3] = [0; 3]; lang[0] = ((language >> 10) & 0x1F) + 0x60; @@ -133,7 +130,7 @@ fn get_language_string(language: u16) -> String { return lang_str; } -fn get_language_code(language: &str) -> u16 { +fn language_code(language: &str) -> u16 { let mut lang = language.encode_utf16(); let mut code = (lang.next().unwrap_or(0) & 0x1F) << 10; code += (lang.next().unwrap_or(0) & 0x1F) << 5; @@ -144,12 +141,12 @@ fn get_language_code(language: &str) -> u16 { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; fn test_language_code(lang: &str) { - let code = get_language_code(lang); - let lang2 = get_language_string(code); + let code = language_code(lang); + let lang2 = language_string(code); assert_eq!(lang, lang2); } diff --git a/src/mp4box/mdia.rs b/src/mp4box/mdia.rs new file mode 100644 index 0000000..92914b0 --- /dev/null +++ b/src/mp4box/mdia.rs @@ -0,0 +1,88 @@ +use std::io::{Read, Seek, SeekFrom, Write}; + +use crate::mp4box::*; +use crate::mp4box::{hdlr::HdlrBox, mdhd::MdhdBox, minf::MinfBox}; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct MdiaBox { + pub mdhd: MdhdBox, + pub hdlr: HdlrBox, + pub minf: MinfBox, +} + +impl Mp4Box for MdiaBox { + fn box_type() -> BoxType { + BoxType::MdiaBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + self.mdhd.box_size() + self.hdlr.box_size() + self.minf.box_size() + } +} + +impl ReadBox<&mut R> for MdiaBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut mdhd = None; + let mut hdlr = None; + let mut minf = None; + + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + + match name { + BoxType::MdhdBox => { + mdhd = Some(MdhdBox::read_box(reader, s)?); + } + BoxType::HdlrBox => { + hdlr = Some(HdlrBox::read_box(reader, s)?); + } + BoxType::MinfBox => { + minf = Some(MinfBox::read_box(reader, s)?); + } + _ => { + // XXX warn!() + skip_box(reader, s)?; + } + } + + current = reader.seek(SeekFrom::Current(0))?; + } + + if mdhd.is_none() { + return Err(Error::BoxNotFound(BoxType::MdhdBox)); + } + if hdlr.is_none() { + return Err(Error::BoxNotFound(BoxType::HdlrBox)); + } + if minf.is_none() { + return Err(Error::BoxNotFound(BoxType::MinfBox)); + } + + skip_read_to(reader, start + size)?; + + Ok(MdiaBox { + mdhd: mdhd.unwrap(), + hdlr: hdlr.unwrap(), + minf: minf.unwrap(), + }) + } +} + +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(writer)?; + + self.mdhd.write_box(writer)?; + self.hdlr.write_box(writer)?; + self.minf.write_box(writer)?; + + Ok(size) + } +} diff --git a/src/atoms/minf.rs b/src/mp4box/minf.rs similarity index 63% rename from src/atoms/minf.rs rename to src/mp4box/minf.rs index 0187741..f20f9ab 100644 --- a/src/atoms/minf.rs +++ b/src/mp4box/minf.rs @@ -1,21 +1,13 @@ -use std::io::{Seek, SeekFrom, Read, Write}; +use std::io::{Read, Seek, SeekFrom, Write}; -use crate::*; -use crate::atoms::*; -use crate::atoms::{vmhd::VmhdBox, smhd::SmhdBox, stbl::StblBox}; +use crate::mp4box::*; +use crate::mp4box::{smhd::SmhdBox, stbl::StblBox, vmhd::VmhdBox}; - -#[derive(Debug, Default)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct MinfBox { pub vmhd: Option, pub smhd: Option, - pub stbl: Option, -} - -impl MinfBox { - pub(crate) fn new() -> MinfBox { - Default::default() - } + pub stbl: StblBox, } impl Mp4Box for MinfBox { @@ -31,41 +23,39 @@ impl Mp4Box for MinfBox { if let Some(ref smhd) = self.smhd { size += smhd.box_size(); } - if let Some(ref stbl) = self.stbl { - size += stbl.box_size(); - } + size += self.stbl.box_size(); size } } impl ReadBox<&mut R> for MinfBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; - let mut minf = MinfBox::new(); + let mut vmhd = None; + let mut smhd = None; + let mut stbl = None; let mut current = reader.seek(SeekFrom::Current(0))?; let end = start + size; while current < end { // Get box header. let header = BoxHeader::read(reader)?; - let BoxHeader{ name, size: s } = header; + let BoxHeader { name, size: s } = header; match name { BoxType::VmhdBox => { - let vmhd = VmhdBox::read_box(reader, s)?; - minf.vmhd = Some(vmhd); + vmhd = Some(VmhdBox::read_box(reader, s)?); } BoxType::SmhdBox => { - let smhd = SmhdBox::read_box(reader, s)?; - minf.smhd = Some(smhd); + smhd = Some(SmhdBox::read_box(reader, s)?); } - BoxType::DinfBox => {// XXX warn!() + BoxType::DinfBox => { + // XXX warn!() skip_box(reader, s)?; } BoxType::StblBox => { - let stbl = StblBox::read_box(reader, s)?; - minf.stbl = Some(stbl); + stbl = Some(StblBox::read_box(reader, s)?); } _ => { // XXX warn!() @@ -76,9 +66,17 @@ impl ReadBox<&mut R> for MinfBox { current = reader.seek(SeekFrom::Current(0))?; } + if stbl.is_none() { + return Err(Error::BoxNotFound(BoxType::StblBox)); + } + skip_read_to(reader, start + size)?; - Ok(minf) + Ok(MinfBox { + vmhd, + smhd, + stbl: stbl.unwrap(), + }) } } @@ -93,9 +91,7 @@ impl WriteBox<&mut W> for MinfBox { if let Some(ref smhd) = self.smhd { smhd.write_box(writer)?; } - if let Some(ref stbl) = self.stbl { - stbl.write_box(writer)?; - } + self.stbl.write_box(writer)?; Ok(size) } diff --git a/src/atoms/mod.rs b/src/mp4box/mod.rs similarity index 63% rename from src/atoms/mod.rs rename to src/mp4box/mod.rs index b61e229..8367e4b 100644 --- a/src/atoms/mod.rs +++ b/src/mp4box/mod.rs @@ -1,38 +1,38 @@ -use std::fmt; -use std::io::{Seek, SeekFrom, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::convert::TryInto; +use std::io::{Read, Seek, SeekFrom, Write}; use crate::*; -mod ftyp; -mod moov; -mod mvhd; -mod trak; -mod tkhd; -mod edts; -mod elst; -mod mdia; -mod mdhd; -mod hdlr; -mod minf; -mod vmhd; -mod smhd; -mod stbl; -mod stsd; -mod stts; -mod ctts; -mod stss; -mod stsc; -mod stsz; -mod stco; -mod co64; -mod avc; -mod mp4a; +pub(crate) mod avc1; +pub(crate) mod co64; +pub(crate) mod ctts; +pub(crate) mod edts; +pub(crate) mod elst; +pub(crate) mod ftyp; +pub(crate) mod hdlr; +pub(crate) mod mdhd; +pub(crate) mod mdia; +pub(crate) mod minf; +pub(crate) mod moov; +pub(crate) mod mp4a; +pub(crate) mod mvhd; +pub(crate) mod smhd; +pub(crate) mod stbl; +pub(crate) mod stco; +pub(crate) mod stsc; +pub(crate) mod stsd; +pub(crate) mod stss; +pub(crate) mod stsz; +pub(crate) mod stts; +pub(crate) mod tkhd; +pub(crate) mod trak; +pub(crate) mod vmhd; pub use ftyp::FtypBox; pub use moov::MoovBox; -const HEADER_SIZE: u64 = 8; +pub const HEADER_SIZE: u64 = 8; // const HEADER_LARGE_SIZE: u64 = 16; pub const HEADER_EXT_SIZE: u64 = 4; @@ -64,7 +64,7 @@ macro_rules! boxtype { } } -boxtype!{ +boxtype! { FtypBox => 0x66747970, MvhdBox => 0x6d766864, FreeBox => 0x66726565, @@ -98,100 +98,6 @@ boxtype!{ EsdsBox => 0x65736473 } -impl fmt::Debug for BoxType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let fourcc: FourCC = From::from(self.clone()); - write!(f, "{}", fourcc) - } -} - -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 -} - -impl From for FourCC { - fn from(number: u32) -> Self { - let mut box_chars = Vec::new(); - for x in 0..4 { - let c = (number >> (x * 8) & 0x0000_00FF) as u8; - box_chars.push(c); - } - box_chars.reverse(); - - let box_string = match String::from_utf8(box_chars) { - Ok(t) => t, - _ => String::from("null"), // error to retrieve fourcc - }; - - FourCC { - value: box_string - } - } -} - -impl From for u32 { - fn from(fourcc: FourCC) -> u32 { - (&fourcc).into() - } -} - -impl From<&FourCC> for u32 { - fn from(fourcc: &FourCC) -> u32 { - let mut b: [u8; 4] = Default::default(); - b.copy_from_slice(fourcc.value.as_bytes()); - u32::from_be_bytes(b) - } -} - -impl From for FourCC { - fn from(fourcc: String) -> FourCC { - let value = if fourcc.len() > 4 { - fourcc[0..4].to_string() - } else { - fourcc - }; - FourCC {value} - } -} - -impl From<&str> for FourCC { - fn from(fourcc: &str) -> FourCC { - let value = if fourcc.len() > 4 { - fourcc[0..4].to_string() - } else { - fourcc.to_string() - }; - FourCC {value} - } -} - -impl From for FourCC { - fn from(t: BoxType) -> FourCC { - let box_num: u32 = Into::into(t); - From::from(box_num) - } -} - -impl fmt::Debug for FourCC { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.value) - } -} - -impl fmt::Display for FourCC { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.value) - } -} - pub trait Mp4Box: Sized { fn box_type() -> BoxType; fn box_size(&self) -> u64; @@ -212,14 +118,14 @@ pub struct BoxHeader { } impl BoxHeader { - fn new(name: BoxType, size: u64) -> Self { + pub fn new(name: BoxType, size: u64) -> Self { Self { name, size } } // 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. + let mut buf = [0u8; 8]; // 8 bytes for box header. reader.read(&mut buf)?; // Get size. @@ -248,7 +154,7 @@ impl BoxHeader { } } - fn write(&self, writer: &mut W) -> Result { + pub fn write(&self, writer: &mut W) -> Result { if self.size > u32::MAX as u64 { writer.write_u32::(1)?; writer.write_u32::(self.name.into())?; @@ -274,7 +180,7 @@ pub fn write_box_header_ext(w: &mut W, v: u8, f: u32) -> Result { Ok(4) } -pub fn get_box_start(reader: &mut R) -> Result { +pub fn box_start(reader: &mut R) -> Result { Ok(reader.seek(SeekFrom::Current(0))? - HEADER_SIZE) } @@ -290,7 +196,7 @@ pub fn skip_read_to(reader: &mut R, pos: u64) -> Result<()> { } pub fn skip_box(reader: &mut R, size: u64) -> Result<()> { - let start = get_box_start(reader)?; + let start = box_start(reader)?; skip_read_to(reader, start + size)?; Ok(()) } diff --git a/src/atoms/moov.rs b/src/mp4box/moov.rs similarity index 70% rename from src/atoms/moov.rs rename to src/mp4box/moov.rs index 5e7bd74..218dc5a 100644 --- a/src/atoms/moov.rs +++ b/src/mp4box/moov.rs @@ -1,22 +1,14 @@ -use std::io::{Seek, SeekFrom, Read, Write}; +use std::io::{Read, Seek, SeekFrom, Write}; -use crate::*; -use crate::atoms::*; -use crate::atoms::{mvhd::MvhdBox, trak::TrakBox}; +use crate::mp4box::*; +use crate::mp4box::{mvhd::MvhdBox, trak::TrakBox}; - -#[derive(Debug, Default)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct MoovBox { pub mvhd: MvhdBox, pub traks: Vec, } -impl MoovBox { - pub(crate) fn new() -> MoovBox { - Default::default() - } -} - impl Mp4Box for MoovBox { fn box_type() -> BoxType { BoxType::MoovBox @@ -33,25 +25,25 @@ impl Mp4Box for MoovBox { impl ReadBox<&mut R> for MoovBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; - let mut moov = MoovBox::new(); + let mut mvhd = None; + let mut traks = Vec::new(); let mut current = reader.seek(SeekFrom::Current(0))?; let end = start + size; while current < end { // Get box header. let header = BoxHeader::read(reader)?; - let BoxHeader{ name, size: s } = header; + let BoxHeader { name, size: s } = header; match name { BoxType::MvhdBox => { - moov.mvhd = MvhdBox::read_box(reader, s)?; + mvhd = Some(MvhdBox::read_box(reader, s)?); } BoxType::TrakBox => { - let mut trak = TrakBox::read_box(reader, s)?; - trak.id = moov.traks.len() as u32 + 1; - moov.traks.push(trak); + let trak = TrakBox::read_box(reader, s)?; + traks.push(trak); } BoxType::UdtaBox => { // XXX warn!() @@ -66,9 +58,16 @@ impl ReadBox<&mut R> for MoovBox { current = reader.seek(SeekFrom::Current(0))?; } + if mvhd.is_none() { + return Err(Error::BoxNotFound(BoxType::MvhdBox)); + } + skip_read_to(reader, start + size)?; - Ok(moov) + Ok(MoovBox { + mvhd: mvhd.unwrap(), + traks, + }) } } diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs new file mode 100644 index 0000000..f6ac5f3 --- /dev/null +++ b/src/mp4box/mp4a.rs @@ -0,0 +1,561 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct Mp4aBox { + pub data_reference_index: u16, + pub channelcount: u16, + pub samplesize: u16, + pub samplerate: FixedPointU16, + pub esds: EsdsBox, +} + +impl Default for Mp4aBox { + fn default() -> Self { + Self { + data_reference_index: 0, + channelcount: 2, + samplesize: 16, + samplerate: FixedPointU16::new(48000), + esds: EsdsBox::default(), + } + } +} + +impl Mp4aBox { + pub fn new(config: &AacConfig) -> Self { + Self { + data_reference_index: 1, + channelcount: config.chan_conf as u16, + samplesize: 16, + samplerate: FixedPointU16::new(config.freq_index.freq() as u16), + esds: EsdsBox::new(config), + } + } +} + +impl Mp4Box for Mp4aBox { + fn box_type() -> BoxType { + BoxType::Mp4aBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + 8 + 20 + self.esds.box_size() + } +} + +impl ReadBox<&mut R> for Mp4aBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + reader.read_u32::()?; // reserved + reader.read_u16::()?; // reserved + let data_reference_index = reader.read_u16::()?; + + reader.read_u64::()?; // reserved + let channelcount = reader.read_u16::()?; + let samplesize = reader.read_u16::()?; + reader.read_u32::()?; // pre-defined, reserved + let samplerate = FixedPointU16::new_raw(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, + channelcount, + 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.channelcount)?; + writer.write_u16::(self.samplesize)?; + writer.write_u32::(0)?; // reserved + writer.write_u32::(self.samplerate.raw_value())?; + + self.esds.write_box(writer)?; + + Ok(size) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct EsdsBox { + pub version: u8, + pub flags: u32, + pub es_desc: ESDescriptor, +} + +impl EsdsBox { + pub fn new(config: &AacConfig) -> Self { + Self { + version: 0, + flags: 0, + es_desc: ESDescriptor::new(config), + } + } +} + +impl Mp4Box for EsdsBox { + fn box_type() -> BoxType { + BoxType::EsdsBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + ESDescriptor::desc_size() as u64 + } +} + +impl ReadBox<&mut R> for EsdsBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let mut es_desc = None; + + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { + let (desc_tag, desc_size) = read_desc(reader)?; + match desc_tag { + 0x03 => { + es_desc = Some(ESDescriptor::read_desc(reader, desc_size)?); + } + _ => break, + } + current = reader.seek(SeekFrom::Current(0))?; + } + + if es_desc.is_none() { + return Err(Error::InvalidData("ESDescriptor not found")); + } + + skip_read_to(reader, start + size)?; + + Ok(EsdsBox { + version, + flags, + es_desc: es_desc.unwrap(), + }) + } +} + +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)?; + + self.es_desc.write_desc(writer)?; + + Ok(size) + } +} + +trait Descriptor: Sized { + fn desc_tag() -> u8; + fn desc_size() -> u32; +} + +trait ReadDesc: Sized { + fn read_desc(_: T, size: u32) -> Result; +} + +trait WriteDesc: Sized { + fn write_desc(&self, _: T) -> Result; +} + +// XXX assert_eq!(size, 1) +fn desc_start(reader: &mut R) -> Result { + Ok(reader.seek(SeekFrom::Current(0))? - 2) +} + +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 as u64 > std::u32::MAX as u64 { + 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 >> ((nbytes - i - 1) * 7)) as u8 & 0x7F; + if i < nbytes - 1 { + b |= 0x80; + } + writer.write_u8(b)?; + } + + Ok(1 + nbytes) +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct ESDescriptor { + pub es_id: u16, + + pub dec_config: DecoderConfigDescriptor, + pub sl_config: SLConfigDescriptor, +} + +impl ESDescriptor { + pub fn new(config: &AacConfig) -> Self { + Self { + es_id: 1, + dec_config: DecoderConfigDescriptor::new(config), + sl_config: SLConfigDescriptor::new(), + } + } +} + +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, size: u32) -> Result { + let start = desc_start(reader)?; + + let es_id = reader.read_u16::()?; + reader.read_u8()?; // XXX flags must be 0 + + let mut dec_config = None; + let mut sl_config = None; + + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size as u64 + 1; + while current < end { + let (desc_tag, desc_size) = read_desc(reader)?; + match desc_tag { + 0x04 => { + dec_config = Some(DecoderConfigDescriptor::read_desc(reader, desc_size)?); + } + 0x06 => { + sl_config = Some(SLConfigDescriptor::read_desc(reader, desc_size)?); + } + _ => { + skip_read(reader, desc_size as i64 - 1)?; + } + } + current = reader.seek(SeekFrom::Current(0))?; + } + + if dec_config.is_none() { + return Err(Error::InvalidData("DecoderConfigDescriptor not found")); + } + + Ok(ESDescriptor { + es_id, + dec_config: dec_config.unwrap(), + sl_config: sl_config.unwrap_or(SLConfigDescriptor::default()), + }) + } +} + +impl WriteDesc<&mut W> for ESDescriptor { + fn write_desc(&self, writer: &mut W) -> Result { + let size = Self::desc_size(); + write_desc(writer, Self::desc_tag(), size - 1)?; + + writer.write_u16::(self.es_id)?; + writer.write_u8(0)?; + + self.dec_config.write_desc(writer)?; + self.sl_config.write_desc(writer)?; + + Ok(size) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct DecoderConfigDescriptor { + 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 DecoderConfigDescriptor { + pub fn new(config: &AacConfig) -> Self { + Self { + object_type_indication: 0x40, // XXX AAC + stream_type: 0x05, // XXX Audio + up_stream: 0, + buffer_size_db: 0, + max_bitrate: config.bitrate, // XXX + avg_bitrate: config.bitrate, + dec_specific: DecoderSpecificDescriptor::new(config), + } + } +} + +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, size: u32) -> Result { + let start = desc_start(reader)?; + + let object_type_indication = reader.read_u8()?; + let byte_a = reader.read_u8()?; + let stream_type = (byte_a & 0xFC) >> 2; + 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 mut dec_specific = None; + + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size as u64 + 1; + while current < end { + let (desc_tag, desc_size) = read_desc(reader)?; + match desc_tag { + 0x05 => { + dec_specific = Some(DecoderSpecificDescriptor::read_desc(reader, desc_size)?); + } + _ => { + skip_read(reader, desc_size as i64 - 1)?; + } + } + current = reader.seek(SeekFrom::Current(0))?; + } + + if dec_specific.is_none() { + return Err(Error::InvalidData("DecoderSpecificDescriptor not found")); + } + + Ok(DecoderConfigDescriptor { + object_type_indication, + stream_type, + up_stream, + buffer_size_db, + max_bitrate, + avg_bitrate, + dec_specific: dec_specific.unwrap(), + }) + } +} + +impl WriteDesc<&mut W> for DecoderConfigDescriptor { + fn write_desc(&self, writer: &mut W) -> Result { + let size = Self::desc_size(); + write_desc(writer, Self::desc_tag(), size - 1)?; + + writer.write_u8(self.object_type_indication)?; + writer.write_u8((self.stream_type << 2) + (self.up_stream & 0x02))?; + writer.write_u24::(self.buffer_size_db)?; + writer.write_u32::(self.max_bitrate)?; + writer.write_u32::(self.avg_bitrate)?; + + self.dec_specific.write_desc(writer)?; + + Ok(size) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct DecoderSpecificDescriptor { + pub profile: u8, + pub freq_index: u8, + pub chan_conf: u8, +} + +impl DecoderSpecificDescriptor { + pub fn new(config: &AacConfig) -> Self { + Self { + profile: config.profile as u8, + freq_index: config.freq_index as u8, + chan_conf: config.chan_conf as 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, _size: u32) -> Result { + 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 { + profile, + freq_index, + chan_conf, + }) + } +} + +impl WriteDesc<&mut W> for DecoderSpecificDescriptor { + fn write_desc(&self, writer: &mut W) -> Result { + let size = Self::desc_size(); + write_desc(writer, Self::desc_tag(), size - 1)?; + + writer.write_u8((self.profile << 3) + (self.freq_index >> 1))?; + writer.write_u8((self.freq_index << 7) + (self.chan_conf << 3))?; + + Ok(size) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct SLConfigDescriptor {} + +impl SLConfigDescriptor { + pub fn new() -> Self { + SLConfigDescriptor {} + } +} + +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, _size: u32) -> Result { + reader.read_u8()?; // pre-defined + + Ok(SLConfigDescriptor {}) + } +} + +impl WriteDesc<&mut W> for SLConfigDescriptor { + fn write_desc(&self, writer: &mut W) -> Result { + let size = Self::desc_size(); + write_desc(writer, Self::desc_tag(), size - 1)?; + + writer.write_u8(0)?; // pre-defined + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_mp4a() { + let src_box = Mp4aBox { + data_reference_index: 1, + channelcount: 2, + samplesize: 16, + samplerate: FixedPointU16::new(48000), + esds: EsdsBox { + version: 0, + flags: 0, + es_desc: ESDescriptor { + es_id: 2, + dec_config: DecoderConfigDescriptor { + object_type_indication: 0x40, + stream_type: 0x05, + up_stream: 0, + buffer_size_db: 0, + max_bitrate: 67695, + avg_bitrate: 67695, + dec_specific: DecoderSpecificDescriptor { + profile: 2, + freq_index: 3, + chan_conf: 1, + }, + }, + sl_config: SLConfigDescriptor::default(), + }, + }, + }; + 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::Mp4aBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = Mp4aBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/atoms/mvhd.rs b/src/mp4box/mvhd.rs similarity index 75% rename from src/atoms/mvhd.rs rename to src/mp4box/mvhd.rs index ed88fae..909ab7d 100644 --- a/src/atoms/mvhd.rs +++ b/src/mp4box/mvhd.rs @@ -1,12 +1,9 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use num_rational::Ratio; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct MvhdBox { pub version: u8, pub flags: u32, @@ -14,7 +11,7 @@ pub struct MvhdBox { pub modification_time: u64, pub timescale: u32, pub duration: u64, - pub rate: Ratio, + pub rate: FixedPointU16, } impl Default for MvhdBox { @@ -26,7 +23,7 @@ impl Default for MvhdBox { modification_time: 0, timescale: 1000, duration: 0, - rate: Ratio::new_raw(0x00010000, 0x10000), + rate: FixedPointU16::new(1), } } } @@ -51,33 +48,31 @@ impl Mp4Box for MvhdBox { impl ReadBox<&mut R> for MvhdBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; - let (creation_time, modification_time, timescale, duration) - = if version == 1 { - ( - reader.read_u64::()?, - reader.read_u64::()?, - reader.read_u32::()?, - reader.read_u64::()?, - ) - } else { - assert_eq!(version, 0); - ( - reader.read_u32::()? as u64, - reader.read_u32::()? as u64, - reader.read_u32::()?, - reader.read_u32::()? as u64, - ) - }; - let numer = reader.read_u32::()?; - let rate = Ratio::new_raw(numer, 0x10000); + let (creation_time, modification_time, timescale, duration) = if version == 1 { + ( + reader.read_u64::()?, + reader.read_u64::()?, + reader.read_u32::()?, + reader.read_u64::()?, + ) + } else { + assert_eq!(version, 0); + ( + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + reader.read_u32::()?, + reader.read_u32::()? as u64, + ) + }; + let rate = FixedPointU16::new_raw(reader.read_u32::()?); skip_read_to(reader, start + size)?; - Ok(MvhdBox{ + Ok(MvhdBox { version, flags, creation_time, @@ -108,7 +103,7 @@ impl WriteBox<&mut W> for MvhdBox { writer.write_u32::(self.timescale)?; writer.write_u32::(self.duration as u32)?; } - writer.write_u32::(*self.rate.numer())?; + writer.write_u32::(self.rate.raw_value())?; // XXX volume, ... skip_write(writer, 76)?; @@ -120,7 +115,7 @@ impl WriteBox<&mut W> for MvhdBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] @@ -132,7 +127,7 @@ mod tests { modification_time: 200, timescale: 1000, duration: 634634, - rate: Ratio::new_raw(0x00010000, 0x10000), + rate: FixedPointU16::new(1), }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); @@ -156,7 +151,7 @@ mod tests { modification_time: 200, timescale: 1000, duration: 634634, - rate: Ratio::new_raw(0x00010000, 0x10000), + rate: FixedPointU16::new(1), }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); diff --git a/src/atoms/smhd.rs b/src/mp4box/smhd.rs similarity index 77% rename from src/atoms/smhd.rs rename to src/mp4box/smhd.rs index 07416bb..5f80ddb 100644 --- a/src/atoms/smhd.rs +++ b/src/mp4box/smhd.rs @@ -1,16 +1,13 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use num_rational::Ratio; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct SmhdBox { pub version: u8, pub flags: u32, - pub balance: Ratio, + pub balance: FixedPointI8, } impl Default for SmhdBox { @@ -18,7 +15,7 @@ impl Default for SmhdBox { SmhdBox { version: 0, flags: 0, - balance: Ratio::new_raw(0, 0x100), + balance: FixedPointI8::new_raw(0), } } } @@ -35,12 +32,11 @@ impl Mp4Box for SmhdBox { impl ReadBox<&mut R> for SmhdBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = 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); + let balance = FixedPointI8::new_raw(reader.read_i16::()?); skip_read_to(reader, start + size)?; @@ -59,18 +55,17 @@ impl WriteBox<&mut W> for SmhdBox { write_box_header_ext(writer, self.version, self.flags)?; - writer.write_i16::(*self.balance.numer())?; + writer.write_i16::(self.balance.raw_value())?; writer.write_u16::(0)?; // reserved Ok(size) } } - #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] @@ -78,7 +73,7 @@ mod tests { let src_box = SmhdBox { version: 0, flags: 0, - balance: Ratio::new_raw(-0x100, 0x100), + balance: FixedPointI8::new_raw(-1), }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); diff --git a/src/atoms/stbl.rs b/src/mp4box/stbl.rs similarity index 51% rename from src/atoms/stbl.rs rename to src/mp4box/stbl.rs index bb5aaa5..4236f64 100644 --- a/src/atoms/stbl.rs +++ b/src/mp4box/stbl.rs @@ -1,37 +1,23 @@ -use std::io::{Seek, SeekFrom, Read, Write}; +use std::io::{Read, Seek, SeekFrom, Write}; -use crate::*; -use crate::atoms::*; -use crate::atoms::{ - stsd::StsdBox, - stts::SttsBox, - ctts::CttsBox, - stss::StssBox, - stsc::StscBox, - stsz::StszBox, - stco::StcoBox, - co64::Co64Box, +use crate::mp4box::*; +use crate::mp4box::{ + co64::Co64Box, ctts::CttsBox, stco::StcoBox, stsc::StscBox, stsd::StsdBox, stss::StssBox, + stsz::StszBox, stts::SttsBox, }; - -#[derive(Debug, Default)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct StblBox { - pub stsd: Option, - pub stts: Option, + pub stsd: StsdBox, + pub stts: SttsBox, pub ctts: Option, pub stss: Option, - pub stsc: Option, - pub stsz: Option, + pub stsc: StscBox, + pub stsz: StszBox, pub stco: Option, pub co64: Option, } -impl StblBox { - pub(crate) fn new() -> StblBox { - Default::default() - } -} - impl Mp4Box for StblBox { fn box_type() -> BoxType { BoxType::StblBox @@ -39,24 +25,16 @@ impl Mp4Box for StblBox { fn box_size(&self) -> u64 { let mut size = HEADER_SIZE; - if let Some(ref stsd) = self.stsd { - size += stsd.box_size(); - } - if let Some(ref stts) = self.stts { - size += stts.box_size(); - } + size += self.stsd.box_size(); + size += self.stts.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(); - } + size += self.stsc.box_size(); + size += self.stsz.box_size(); if let Some(ref stco) = self.stco { size += stco.box_size(); } @@ -69,49 +47,48 @@ impl Mp4Box for StblBox { impl ReadBox<&mut R> for StblBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; - let mut stbl = StblBox::new(); + let mut stsd = None; + let mut stts = None; + let mut ctts = None; + let mut stss = None; + let mut stsc = None; + let mut stsz = None; + let mut stco = None; + let mut co64 = None; let mut current = reader.seek(SeekFrom::Current(0))?; let end = start + size; while current < end { // Get box header. let header = BoxHeader::read(reader)?; - let BoxHeader{ name, size: s } = header; + let BoxHeader { name, size: s } = header; match name { BoxType::StsdBox => { - let stsd = StsdBox::read_box(reader, s)?; - stbl.stsd = Some(stsd); + stsd = Some(StsdBox::read_box(reader, s)?); } BoxType::SttsBox => { - let stts = SttsBox::read_box(reader, s)?; - stbl.stts = Some(stts); + stts = Some(SttsBox::read_box(reader, s)?); } BoxType::CttsBox => { - let ctts = CttsBox::read_box(reader, s)?; - stbl.ctts = Some(ctts); + ctts = Some(CttsBox::read_box(reader, s)?); } BoxType::StssBox => { - let stss = StssBox::read_box(reader, s)?; - stbl.stss = Some(stss); + stss = Some(StssBox::read_box(reader, s)?); } BoxType::StscBox => { - let stsc = StscBox::read_box(reader, s)?; - stbl.stsc = Some(stsc); + stsc = Some(StscBox::read_box(reader, s)?); } BoxType::StszBox => { - let stsz = StszBox::read_box(reader, s)?; - stbl.stsz = Some(stsz); + stsz = Some(StszBox::read_box(reader, s)?); } BoxType::StcoBox => { - let stco = StcoBox::read_box(reader, s)?; - stbl.stco = Some(stco); + stco = Some(StcoBox::read_box(reader, s)?); } BoxType::Co64Box => { - let co64 = Co64Box::read_box(reader, s)?; - stbl.co64 = Some(co64); + co64 = Some(Co64Box::read_box(reader, s)?); } _ => { // XXX warn!() @@ -121,9 +98,34 @@ impl ReadBox<&mut R> for StblBox { current = reader.seek(SeekFrom::Current(0))?; } + if stsd.is_none() { + return Err(Error::BoxNotFound(BoxType::StsdBox)); + } + if stts.is_none() { + return Err(Error::BoxNotFound(BoxType::SttsBox)); + } + if stsc.is_none() { + return Err(Error::BoxNotFound(BoxType::StscBox)); + } + if stsz.is_none() { + return Err(Error::BoxNotFound(BoxType::StszBox)); + } + if stco.is_none() && co64.is_none() { + return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box)); + } + skip_read_to(reader, start + size)?; - Ok(stbl) + Ok(StblBox { + stsd: stsd.unwrap(), + stts: stts.unwrap(), + ctts: ctts, + stss: stss, + stsc: stsc.unwrap(), + stsz: stsz.unwrap(), + stco: stco, + co64: co64, + }) } } @@ -132,24 +134,16 @@ impl WriteBox<&mut W> for StblBox { let size = self.box_size(); BoxHeader::new(Self::box_type(), size).write(writer)?; - if let Some(ref stsd) = self.stsd { - stsd.write_box(writer)?; - } - if let Some(ref stts) = self.stts { - stts.write_box(writer)?; - } + self.stsd.write_box(writer)?; + self.stts.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)?; - } + self.stsc.write_box(writer)?; + self.stsz.write_box(writer)?; if let Some(ref stco) = self.stco { stco.write_box(writer)?; } diff --git a/src/atoms/stco.rs b/src/mp4box/stco.rs similarity index 92% rename from src/atoms/stco.rs rename to src/mp4box/stco.rs index c6a85cc..e6822c1 100644 --- a/src/atoms/stco.rs +++ b/src/mp4box/stco.rs @@ -1,11 +1,9 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct StcoBox { pub version: u8, pub flags: u32, @@ -24,7 +22,7 @@ impl Mp4Box for StcoBox { impl ReadBox<&mut R> for StcoBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -64,7 +62,7 @@ impl WriteBox<&mut W> for StcoBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] diff --git a/src/atoms/stsc.rs b/src/mp4box/stsc.rs similarity index 94% rename from src/atoms/stsc.rs rename to src/mp4box/stsc.rs index d6ea620..e712646 100644 --- a/src/atoms/stsc.rs +++ b/src/mp4box/stsc.rs @@ -1,18 +1,16 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct StscBox { pub version: u8, pub flags: u32, pub entries: Vec, } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct StscEntry { pub first_chunk: u32, pub samples_per_chunk: u32, @@ -32,7 +30,7 @@ impl Mp4Box for StscBox { impl ReadBox<&mut R> for StscBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -92,7 +90,7 @@ impl WriteBox<&mut W> for StscBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] diff --git a/src/atoms/stsd.rs b/src/mp4box/stsd.rs similarity index 86% rename from src/atoms/stsd.rs rename to src/mp4box/stsd.rs index 0817428..f83667e 100644 --- a/src/atoms/stsd.rs +++ b/src/mp4box/stsd.rs @@ -1,12 +1,10 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; -use crate::atoms::{avc::Avc1Box, mp4a::Mp4aBox}; +use crate::mp4box::*; +use crate::mp4box::{avc1::Avc1Box, mp4a::Mp4aBox}; - -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct StsdBox { pub version: u8, pub flags: u32, @@ -20,7 +18,7 @@ impl Mp4Box for StsdBox { } fn box_size(&self) -> u64 { - let mut size = HEADER_SIZE + HEADER_EXT_SIZE; + let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4; if let Some(ref avc1) = self.avc1 { size += avc1.box_size(); } else if let Some(ref mp4a) = self.mp4a { @@ -32,7 +30,7 @@ impl Mp4Box for StsdBox { impl ReadBox<&mut R> for StsdBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -43,7 +41,7 @@ impl ReadBox<&mut R> for StsdBox { // Get box header. let header = BoxHeader::read(reader)?; - let BoxHeader{ name, size: s } = header; + let BoxHeader { name, size: s } = header; match name { BoxType::Avc1Box => { diff --git a/src/atoms/stss.rs b/src/mp4box/stss.rs similarity index 92% rename from src/atoms/stss.rs rename to src/mp4box/stss.rs index 8e001aa..422c975 100644 --- a/src/atoms/stss.rs +++ b/src/mp4box/stss.rs @@ -1,11 +1,9 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct StssBox { pub version: u8, pub flags: u32, @@ -24,7 +22,7 @@ impl Mp4Box for StssBox { impl ReadBox<&mut R> for StssBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -64,7 +62,7 @@ impl WriteBox<&mut W> for StssBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] diff --git a/src/atoms/stsz.rs b/src/mp4box/stsz.rs similarity index 87% rename from src/atoms/stsz.rs rename to src/mp4box/stsz.rs index 51c0631..41c1663 100644 --- a/src/atoms/stsz.rs +++ b/src/mp4box/stsz.rs @@ -1,15 +1,14 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct StszBox { pub version: u8, pub flags: u32, pub sample_size: u32, + pub sample_count: u32, pub sample_sizes: Vec, } @@ -25,7 +24,7 @@ impl Mp4Box for StszBox { impl ReadBox<&mut R> for StszBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -33,7 +32,7 @@ impl ReadBox<&mut R> for StszBox { 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 { + for _ in 0..sample_count { let sample_number = reader.read_u32::()?; sample_sizes.push(sample_number); } @@ -45,6 +44,7 @@ impl ReadBox<&mut R> for StszBox { version, flags, sample_size, + sample_count, sample_sizes, }) } @@ -58,8 +58,9 @@ impl WriteBox<&mut W> for StszBox { write_box_header_ext(writer, self.version, self.flags)?; writer.write_u32::(self.sample_size)?; - writer.write_u32::(self.sample_sizes.len() as u32)?; + writer.write_u32::(self.sample_count)?; if self.sample_size == 0 { + assert_eq!(self.sample_count, self.sample_sizes.len() as u32); for sample_number in self.sample_sizes.iter() { writer.write_u32::(*sample_number)?; } @@ -72,7 +73,7 @@ impl WriteBox<&mut W> for StszBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] @@ -81,6 +82,7 @@ mod tests { version: 0, flags: 0, sample_size: 1165, + sample_count: 12, sample_sizes: vec![], }; let mut buf = Vec::new(); @@ -102,6 +104,7 @@ mod tests { version: 0, flags: 0, sample_size: 0, + sample_count: 9, sample_sizes: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730], }; let mut buf = Vec::new(); diff --git a/src/atoms/stts.rs b/src/mp4box/stts.rs similarity index 83% rename from src/atoms/stts.rs rename to src/mp4box/stts.rs index a9bfeb4..5a01f82 100644 --- a/src/atoms/stts.rs +++ b/src/mp4box/stts.rs @@ -1,18 +1,16 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct SttsBox { pub version: u8, pub flags: u32, pub entries: Vec, } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct SttsEntry { pub sample_count: u32, pub sample_delta: u32, @@ -30,7 +28,7 @@ impl Mp4Box for SttsBox { impl ReadBox<&mut R> for SttsBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -74,7 +72,7 @@ impl WriteBox<&mut W> for SttsBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] @@ -83,8 +81,14 @@ mod tests { version: 0, flags: 0, entries: vec![ - SttsEntry {sample_count: 29726, sample_delta: 1024}, - SttsEntry {sample_count: 1, sample_delta: 512}, + SttsEntry { + sample_count: 29726, + sample_delta: 1024, + }, + SttsEntry { + sample_count: 1, + sample_delta: 512, + }, ], }; let mut buf = Vec::new(); diff --git a/src/atoms/tkhd.rs b/src/mp4box/tkhd.rs similarity index 76% rename from src/atoms/tkhd.rs rename to src/mp4box/tkhd.rs index c6b0337..ff3119b 100644 --- a/src/atoms/tkhd.rs +++ b/src/mp4box/tkhd.rs @@ -1,12 +1,9 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use num_rational::Ratio; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct TkhdBox { pub version: u8, pub flags: u32, @@ -14,12 +11,12 @@ pub struct TkhdBox { pub modification_time: u64, pub track_id: u32, pub duration: u64, - pub layer: u16, + pub layer: u16, pub alternate_group: u16, - pub volume: Ratio, + pub volume: FixedPointU8, pub matrix: Matrix, - pub width: u32, - pub height: u32, + pub width: FixedPointU16, + pub height: FixedPointU16, } impl Default for TkhdBox { @@ -33,15 +30,15 @@ impl Default for TkhdBox { duration: 0, layer: 0, alternate_group: 0, - volume: Ratio::new_raw(0x0100, 0x100), + volume: FixedPointU8::new(1), matrix: Matrix::default(), - width: 0, - height: 0, + width: FixedPointU16::new(0), + height: FixedPointU16::new(0), } } } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct Matrix { pub a: i32, pub b: i32, @@ -54,6 +51,16 @@ pub struct Matrix { pub w: i32, } +impl TkhdBox { + pub fn set_width(&mut self, width: u16) { + self.width = FixedPointU16::new(width); + } + + pub fn set_height(&mut self, height: u16) { + self.height = FixedPointU16::new(height); + } +} + impl Mp4Box for TkhdBox { fn box_type() -> BoxType { BoxType::TkhdBox @@ -74,37 +81,35 @@ impl Mp4Box for TkhdBox { impl ReadBox<&mut R> for TkhdBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; - let (creation_time, modification_time, track_id, _, duration) - = if version == 1 { - ( - reader.read_u64::()?, - reader.read_u64::()?, - reader.read_u32::()?, - reader.read_u32::()?, - reader.read_u64::()?, - ) + let (creation_time, modification_time, track_id, _, duration) = if version == 1 { + ( + reader.read_u64::()?, + reader.read_u64::()?, + reader.read_u32::()?, + reader.read_u32::()?, + reader.read_u64::()?, + ) } else { - assert_eq!(version, 0); - ( - reader.read_u32::()? as u64, - reader.read_u32::()? as u64, - reader.read_u32::()?, - reader.read_u32::()?, - reader.read_u32::()? as u64, - ) + assert_eq!(version, 0); + ( + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + reader.read_u32::()?, + reader.read_u32::()?, + reader.read_u32::()? as u64, + ) }; reader.read_u64::()?; // reserved let layer = reader.read_u16::()?; let alternate_group = reader.read_u16::()?; - let volume_numer = reader.read_u16::()?; - let volume = Ratio::new_raw(volume_numer, 0x100); + let volume = FixedPointU8::new_raw(reader.read_u16::()?); reader.read_u16::()?; // reserved - let matrix = Matrix{ + let matrix = Matrix { a: reader.read_i32::()?, b: reader.read_i32::()?, u: reader.read_i32::()?, @@ -116,8 +121,8 @@ impl ReadBox<&mut R> for TkhdBox { w: reader.read_i32::()?, }; - let width = reader.read_u32::()? >> 16; - let height = reader.read_u32::()? >> 16; + let width = FixedPointU16::new_raw(reader.read_u32::()?); + let height = FixedPointU16::new_raw(reader.read_u32::()?); skip_read_to(reader, start + size)?; @@ -163,7 +168,7 @@ impl WriteBox<&mut W> for TkhdBox { writer.write_u64::(0)?; // reserved writer.write_u16::(self.layer)?; writer.write_u16::(self.alternate_group)?; - writer.write_u16::(*self.volume.numer())?; + writer.write_u16::(self.volume.raw_value())?; writer.write_u16::(0)?; // reserved @@ -177,8 +182,8 @@ impl WriteBox<&mut W> for TkhdBox { writer.write_i32::(self.matrix.y)?; writer.write_i32::(self.matrix.w)?; - writer.write_u32::(self.width << 16)?; - writer.write_u32::(self.height << 16)?; + writer.write_u32::(self.width.raw_value())?; + writer.write_u32::(self.height.raw_value())?; Ok(size) } @@ -187,7 +192,7 @@ impl WriteBox<&mut W> for TkhdBox { #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] @@ -201,7 +206,7 @@ mod tests { duration: 634634, layer: 0, alternate_group: 0, - volume: Ratio::new_raw(0x0100, 0x100), + volume: FixedPointU8::new(1), matrix: Matrix { a: 0x00010000, b: 0, @@ -213,8 +218,8 @@ mod tests { y: 0, w: 0x40000000, }, - width: 512, - height: 288, + width: FixedPointU16::new(512), + height: FixedPointU16::new(288), }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); @@ -240,7 +245,7 @@ mod tests { duration: 634634, layer: 0, alternate_group: 0, - volume: Ratio::new_raw(0x0100, 0x100), + volume: FixedPointU8::new(1), matrix: Matrix { a: 0x00010000, b: 0, @@ -252,8 +257,8 @@ mod tests { y: 0, w: 0x40000000, }, - width: 512, - height: 288, + width: FixedPointU16::new(512), + height: FixedPointU16::new(288), }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); diff --git a/src/mp4box/trak.rs b/src/mp4box/trak.rs new file mode 100644 index 0000000..e92683a --- /dev/null +++ b/src/mp4box/trak.rs @@ -0,0 +1,93 @@ +use std::io::{Read, Seek, SeekFrom, Write}; + +use crate::mp4box::*; +use crate::mp4box::{edts::EdtsBox, mdia::MdiaBox, tkhd::TkhdBox}; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct TrakBox { + pub tkhd: TkhdBox, + pub edts: Option, + pub mdia: MdiaBox, +} + +impl Mp4Box for TrakBox { + fn box_type() -> BoxType { + BoxType::TrakBox + } + + fn box_size(&self) -> u64 { + let mut size = HEADER_SIZE; + size += self.tkhd.box_size(); + if let Some(ref edts) = self.edts { + size += edts.box_size(); + } + size += self.mdia.box_size(); + size + } +} + +impl ReadBox<&mut R> for TrakBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut tkhd = None; + let mut edts = None; + let mut mdia = None; + + let mut current = reader.seek(SeekFrom::Current(0))?; + let end = start + size; + while current < end { + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + + match name { + BoxType::TkhdBox => { + tkhd = Some(TkhdBox::read_box(reader, s)?); + } + BoxType::EdtsBox => { + edts = Some(EdtsBox::read_box(reader, s)?); + } + BoxType::MdiaBox => { + mdia = Some(MdiaBox::read_box(reader, s)?); + } + _ => { + // XXX warn!() + skip_box(reader, s)?; + } + } + + current = reader.seek(SeekFrom::Current(0))?; + } + + if tkhd.is_none() { + return Err(Error::BoxNotFound(BoxType::TkhdBox)); + } + if mdia.is_none() { + return Err(Error::BoxNotFound(BoxType::MdiaBox)); + } + + skip_read_to(reader, start + size)?; + + Ok(TrakBox { + tkhd: tkhd.unwrap(), + edts, + mdia: mdia.unwrap(), + }) + } +} + +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(writer)?; + + self.tkhd.write_box(writer)?; + if let Some(ref edts) = self.edts { + edts.write_box(writer)?; + } + self.mdia.write_box(writer)?; + + Ok(size) + } +} diff --git a/src/atoms/vmhd.rs b/src/mp4box/vmhd.rs similarity index 86% rename from src/atoms/vmhd.rs rename to src/mp4box/vmhd.rs index eb1c3f0..45e420d 100644 --- a/src/atoms/vmhd.rs +++ b/src/mp4box/vmhd.rs @@ -1,11 +1,9 @@ -use std::io::{Seek, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; -use crate::*; -use crate::atoms::*; +use crate::mp4box::*; - -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct VmhdBox { pub version: u8, pub flags: u32, @@ -13,7 +11,7 @@ pub struct VmhdBox { pub op_color: RgbColor, } -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct RgbColor { pub red: u16, pub green: u16, @@ -32,7 +30,7 @@ impl Mp4Box for VmhdBox { impl ReadBox<&mut R> for VmhdBox { fn read_box(reader: &mut R, size: u64) -> Result { - let start = get_box_start(reader)?; + let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; @@ -70,11 +68,10 @@ impl WriteBox<&mut W> for VmhdBox { } } - #[cfg(test)] mod tests { use super::*; - use crate::atoms::BoxHeader; + use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] @@ -83,7 +80,11 @@ mod tests { version: 0, flags: 1, graphics_mode: 0, - op_color: RgbColor { red: 0, green: 0, blue: 0}, + op_color: RgbColor { + red: 0, + green: 0, + blue: 0, + }, }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 0000000..fbb9191 --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,133 @@ +use std::io::{Read, Seek, SeekFrom}; + +use crate::mp4box::*; +use crate::*; + +#[derive(Debug)] +pub struct Mp4Reader { + reader: R, + ftyp: FtypBox, + moov: MoovBox, + + tracks: Vec, + size: u64, +} + +impl Mp4Reader { + pub fn read_header(mut reader: R, size: u64) -> Result { + let start = reader.seek(SeekFrom::Current(0))?; + + let mut ftyp = None; + let mut moov = None; + + let mut current = start; + while current < size { + // Get box header. + let header = BoxHeader::read(&mut reader)?; + let BoxHeader { name, size: s } = header; + + // Match and parse the atom boxes. + match name { + BoxType::FtypBox => { + ftyp = Some(FtypBox::read_box(&mut reader, s)?); + } + BoxType::FreeBox => { + skip_box(&mut reader, s)?; + } + BoxType::MdatBox => { + skip_box(&mut reader, s)?; + } + BoxType::MoovBox => { + moov = Some(MoovBox::read_box(&mut reader, s)?); + } + BoxType::MoofBox => { + skip_box(&mut reader, s)?; + } + _ => { + // XXX warn!() + skip_box(&mut reader, s)?; + } + } + current = reader.seek(SeekFrom::Current(0))?; + } + + if ftyp.is_none() { + return Err(Error::BoxNotFound(BoxType::FtypBox)); + } + if moov.is_none() { + return Err(Error::BoxNotFound(BoxType::MoovBox)); + } + + let size = current - start; + let tracks = if let Some(ref moov) = moov { + let mut tracks = Vec::with_capacity(moov.traks.len()); + for (i, trak) in moov.traks.iter().enumerate() { + assert_eq!(trak.tkhd.track_id, i as u32 + 1); + tracks.push(Mp4Track::from(trak)); + } + tracks + } else { + Vec::new() + }; + + Ok(Mp4Reader { + reader, + ftyp: ftyp.unwrap(), + moov: moov.unwrap(), + size, + tracks, + }) + } + + pub fn size(&self) -> u64 { + self.size + } + + pub fn major_brand(&self) -> &FourCC { + &self.ftyp.major_brand + } + + pub fn minor_version(&self) -> u32 { + self.ftyp.minor_version + } + + pub fn compatible_brands(&self) -> &[FourCC] { + &self.ftyp.compatible_brands + } + + pub fn duration(&self) -> u64 { + self.moov.mvhd.duration + } + + pub fn timescale(&self) -> u32 { + self.moov.mvhd.timescale + } + + pub fn tracks(&self) -> &[Mp4Track] { + &self.tracks + } + + pub fn sample_count(&self, track_id: u32) -> Result { + if track_id == 0 { + return Err(Error::TrakNotFound(track_id)); + } + + if let Some(track) = self.tracks.get(track_id as usize - 1) { + Ok(track.sample_count()) + } else { + Err(Error::TrakNotFound(track_id)) + } + } + + pub fn read_sample(&mut self, track_id: u32, sample_id: u32) -> Result> { + if track_id == 0 { + return Err(Error::TrakNotFound(track_id)); + } + + if let Some(ref track) = self.tracks.get(track_id as usize - 1) { + track.read_sample(&mut self.reader, sample_id) + } else { + Err(Error::TrakNotFound(track_id)) + } + } +} diff --git a/src/track.rs b/src/track.rs new file mode 100644 index 0000000..be7c181 --- /dev/null +++ b/src/track.rs @@ -0,0 +1,671 @@ +use bytes::BytesMut; +use std::cmp; +use std::convert::TryFrom; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::time::Duration; + +use crate::mp4box::trak::TrakBox; +use crate::mp4box::*; +use crate::mp4box::{ + avc1::Avc1Box, ctts::CttsBox, ctts::CttsEntry, mp4a::Mp4aBox, smhd::SmhdBox, stco::StcoBox, + stsc::StscEntry, stss::StssBox, stts::SttsEntry, vmhd::VmhdBox, +}; +use crate::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct TrackConfig { + pub track_type: TrackType, + pub timescale: u32, + pub language: String, + pub media_conf: MediaConfig, +} + +impl From for TrackConfig { + fn from(media_conf: MediaConfig) -> Self { + match media_conf { + MediaConfig::AvcConfig(avc_conf) => Self::from(avc_conf), + MediaConfig::AacConfig(aac_conf) => Self::from(aac_conf), + } + } +} + +impl From for TrackConfig { + fn from(avc_conf: AvcConfig) -> Self { + Self { + track_type: TrackType::Video, + timescale: 1000, // XXX + language: String::from("und"), // XXX + media_conf: MediaConfig::AvcConfig(avc_conf), + } + } +} + +impl From for TrackConfig { + fn from(aac_conf: AacConfig) -> Self { + Self { + track_type: TrackType::Audio, + timescale: 1000, // XXX + language: String::from("und"), // XXX + media_conf: MediaConfig::AacConfig(aac_conf), + } + } +} + +#[derive(Debug)] +pub struct Mp4Track { + trak: TrakBox, +} + +impl Mp4Track { + pub(crate) fn from(trak: &TrakBox) -> Self { + let trak = trak.clone(); + Self { trak } + } + + pub fn track_id(&self) -> u32 { + self.trak.tkhd.track_id + } + + pub fn track_type(&self) -> Result { + TrackType::try_from(&self.trak.mdia.hdlr.handler_type) + } + + pub fn media_type(&self) -> Result { + if self.trak.mdia.minf.stbl.stsd.avc1.is_some() { + Ok(MediaType::H264) + } else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() { + Ok(MediaType::AAC) + } else { + Err(Error::InvalidData("unsupported media type")) + } + } + + pub fn box_type(&self) -> Result { + if self.trak.mdia.minf.stbl.stsd.avc1.is_some() { + Ok(FourCC::from(BoxType::Avc1Box)) + } else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() { + Ok(FourCC::from(BoxType::Mp4aBox)) + } else { + Err(Error::InvalidData("unsupported sample entry box")) + } + } + + pub fn width(&self) -> u16 { + if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { + avc1.width + } else { + self.trak.tkhd.width.value() + } + } + + pub fn height(&self) -> u16 { + if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { + avc1.height + } else { + self.trak.tkhd.height.value() + } + } + + pub fn frame_rate(&self) -> Ratio { + let dur_msec = self.duration().as_millis() as u64; + if dur_msec > 0 { + Ratio::new(self.sample_count() as u64 * 1_000, dur_msec) + } else { + Ratio::new(0, 0) + } + } + + pub fn frame_rate_f64(&self) -> f64 { + let fr = self.frame_rate(); + if fr.to_integer() > 0 { + *fr.numer() as f64 / *fr.denom() as f64 + } else { + 0.0 + } + } + + pub fn sample_freq_index(&self) -> Result { + if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + SampleFreqIndex::try_from(mp4a.esds.es_desc.dec_config.dec_specific.freq_index) + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) + } + } + + pub fn channel_config(&self) -> Result { + if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + ChannelConfig::try_from(mp4a.esds.es_desc.dec_config.dec_specific.chan_conf) + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) + } + } + + pub fn language(&self) -> &str { + &self.trak.mdia.mdhd.language + } + + pub fn timescale(&self) -> u32 { + self.trak.mdia.mdhd.timescale + } + + pub fn duration(&self) -> Duration { + Duration::from_micros( + self.trak.mdia.mdhd.duration * 1_000_000 / self.trak.mdia.mdhd.timescale as u64, + ) + } + + pub fn bitrate(&self) -> u32 { + if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + mp4a.esds.es_desc.dec_config.avg_bitrate + } else { + let dur_sec = self.duration().as_secs(); + if dur_sec > 0 { + let bitrate = self.total_sample_size() * 8 / dur_sec; + bitrate as u32 + } else { + 0 + } + } + } + + pub fn sample_count(&self) -> u32 { + self.trak.mdia.minf.stbl.stsz.sample_count + } + + pub fn video_profile(&self) -> Result { + if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { + AvcProfile::try_from(( + avc1.avcc.avc_profile_indication, + avc1.avcc.profile_compatibility, + )) + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box)) + } + } + + pub fn sequence_parameter_set(&self) -> Result<&[u8]> { + if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { + match avc1.avcc.sequence_parameter_sets.get(0) { + Some(ref nal) => Ok(nal.bytes.as_ref()), + None => Err(Error::EntryInStblNotFound( + self.track_id(), + BoxType::AvcCBox, + 0, + )), + } + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box)) + } + } + + pub fn picture_parameter_set(&self) -> Result<&[u8]> { + if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { + match avc1.avcc.picture_parameter_sets.get(0) { + Some(ref nal) => Ok(nal.bytes.as_ref()), + None => Err(Error::EntryInStblNotFound( + self.track_id(), + BoxType::AvcCBox, + 0, + )), + } + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box)) + } + } + + pub fn audio_profile(&self) -> Result { + if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + AudioObjectType::try_from(mp4a.esds.es_desc.dec_config.dec_specific.profile) + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) + } + } + + fn stsc_index(&self, sample_id: u32) -> usize { + for (i, entry) in self.trak.mdia.minf.stbl.stsc.entries.iter().enumerate() { + if sample_id < entry.first_sample { + assert_ne!(i, 0); + return i - 1; + } + } + + assert_ne!(self.trak.mdia.minf.stbl.stsc.entries.len(), 0); + self.trak.mdia.minf.stbl.stsc.entries.len() - 1 + } + + fn chunk_offset(&self, chunk_id: u32) -> Result { + if let Some(ref stco) = self.trak.mdia.minf.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.track_id(), + BoxType::StcoBox, + chunk_id, + )); + } + } else { + if let Some(ref co64) = self.trak.mdia.minf.stbl.co64 { + if let Some(offset) = co64.entries.get(chunk_id as usize - 1) { + return Ok(*offset); + } else { + return Err(Error::EntryInStblNotFound( + self.track_id(), + BoxType::Co64Box, + chunk_id, + )); + } + } + } + + assert!(self.trak.mdia.minf.stbl.stco.is_some() || self.trak.mdia.minf.stbl.co64.is_some()); + return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box)); + } + + fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> { + let ctts = self.trak.mdia.minf.stbl.ctts.as_ref().unwrap(); + 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.track_id(), + BoxType::CttsBox, + sample_id, + )); + } + + fn sample_size(&self, sample_id: u32) -> Result { + let stsz = &self.trak.mdia.minf.stbl.stsz; + if stsz.sample_size > 0 { + return Ok(stsz.sample_size); + } + if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) { + Ok(*size) + } else { + return Err(Error::EntryInStblNotFound( + self.track_id(), + BoxType::StszBox, + sample_id, + )); + } + } + + fn total_sample_size(&self) -> u64 { + let stsz = &self.trak.mdia.minf.stbl.stsz; + if stsz.sample_size > 0 { + stsz.sample_size as u64 * self.sample_count() as u64 + } else { + let mut total_size = 0; + for size in stsz.sample_sizes.iter() { + total_size += *size as u64; + } + total_size + } + } + + fn sample_offset(&self, sample_id: u32) -> Result { + let stsc_index = self.stsc_index(sample_id); + + let stsc = &self.trak.mdia.minf.stbl.stsc; + let stsc_entry = stsc.entries.get(stsc_index).unwrap(); + + let 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) + } + + fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> { + let stts = &self.trak.mdia.minf.stbl.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.track_id(), + BoxType::SttsBox, + sample_id, + )); + } + + fn sample_rendering_offset(&self, sample_id: u32) -> i32 { + if let Some(ref ctts) = self.trak.mdia.minf.stbl.ctts { + if let Ok((ctts_index, _)) = self.ctts_index(sample_id) { + let ctts_entry = ctts.entries.get(ctts_index).unwrap(); + return ctts_entry.sample_offset; + } + } + 0 + } + + fn is_sync_sample(&self, sample_id: u32) -> bool { + if let Some(ref stss) = self.trak.mdia.minf.stbl.stss { + match stss.entries.binary_search(&sample_id) { + Ok(_) => true, + Err(_) => false, + } + } else { + true + } + } + + pub(crate) 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).unwrap(); + + 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).unwrap(); // XXX + 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), + })) + } +} + +// TODO creation_time, modification_time +#[derive(Debug, Default)] +pub(crate) struct Mp4TrackWriter { + trak: TrakBox, + + sample_id: u32, + fixed_sample_size: u32, + is_fixed_sample_size: bool, + chunk_samples: u32, + chunk_duration: u32, + chunk_buffer: BytesMut, + + samples_per_chunk: u32, + duration_per_chunk: u32, +} + +impl Mp4TrackWriter { + pub(crate) fn new(track_id: u32, config: &TrackConfig) -> Result { + let mut trak = TrakBox::default(); + trak.tkhd.track_id = track_id; + trak.mdia.mdhd.timescale = config.timescale; + trak.mdia.mdhd.language = config.language.to_owned(); + trak.mdia.hdlr.handler_type = config.track_type.into(); + // XXX largesize + trak.mdia.minf.stbl.stco = Some(StcoBox::default()); + match config.media_conf { + MediaConfig::AvcConfig(ref avc_config) => { + trak.tkhd.set_width(avc_config.width); + trak.tkhd.set_height(avc_config.height); + + let vmhd = VmhdBox::default(); + trak.mdia.minf.vmhd = Some(vmhd); + + let avc1 = Avc1Box::new(avc_config); + trak.mdia.minf.stbl.stsd.avc1 = Some(avc1); + } + MediaConfig::AacConfig(ref aac_config) => { + let smhd = SmhdBox::default(); + trak.mdia.minf.smhd = Some(smhd); + + let mp4a = Mp4aBox::new(aac_config); + trak.mdia.minf.stbl.stsd.mp4a = Some(mp4a); + } + } + Ok(Mp4TrackWriter { + trak, + chunk_buffer: BytesMut::new(), + sample_id: 1, + duration_per_chunk: config.timescale, // 1 second + ..Self::default() + }) + } + + fn update_sample_sizes(&mut self, size: u32) { + if self.trak.mdia.minf.stbl.stsz.sample_count == 0 { + if size == 0 { + self.trak.mdia.minf.stbl.stsz.sample_size = 0; + self.is_fixed_sample_size = false; + self.trak.mdia.minf.stbl.stsz.sample_sizes.push(0); + } else { + self.trak.mdia.minf.stbl.stsz.sample_size = size; + self.fixed_sample_size = size; + self.is_fixed_sample_size = true; + } + } else { + assert!(self.trak.mdia.minf.stbl.stsz.sample_count > 0); + if self.is_fixed_sample_size { + if self.fixed_sample_size != size { + self.is_fixed_sample_size = false; + if self.trak.mdia.minf.stbl.stsz.sample_size > 0 { + self.trak.mdia.minf.stbl.stsz.sample_size = 0; + for _ in 0..self.trak.mdia.minf.stbl.stsz.sample_count { + self.trak + .mdia + .minf + .stbl + .stsz + .sample_sizes + .push(self.fixed_sample_size); + } + } + self.trak.mdia.minf.stbl.stsz.sample_sizes.push(size); + } + } else { + self.trak.mdia.minf.stbl.stsz.sample_sizes.push(size); + } + } + self.trak.mdia.minf.stbl.stsz.sample_count += 1; + } + + fn update_sample_times(&mut self, dur: u32) { + if let Some(ref mut entry) = self.trak.mdia.minf.stbl.stts.entries.last_mut() { + if entry.sample_delta == dur { + entry.sample_count += 1; + return; + } + } + + let entry = SttsEntry { + sample_count: 1, + sample_delta: dur, + }; + self.trak.mdia.minf.stbl.stts.entries.push(entry); + } + + fn update_rendering_offsets(&mut self, offset: i32) { + let ctts = if let Some(ref mut ctts) = self.trak.mdia.minf.stbl.ctts { + ctts + } else { + if offset == 0 { + return; + } + let mut ctts = CttsBox::default(); + if self.sample_id > 1 { + let entry = CttsEntry { + sample_count: self.sample_id - 1, + sample_offset: 0, + }; + ctts.entries.push(entry); + } + self.trak.mdia.minf.stbl.ctts = Some(ctts); + self.trak.mdia.minf.stbl.ctts.as_mut().unwrap() + }; + + if let Some(ref mut entry) = ctts.entries.last_mut() { + if entry.sample_offset == offset { + entry.sample_count += 1; + return; + } + } + + let entry = CttsEntry { + sample_count: 1, + sample_offset: offset, + }; + ctts.entries.push(entry); + } + + fn update_sync_samples(&mut self, is_sync: bool) { + if let Some(ref mut stss) = self.trak.mdia.minf.stbl.stss { + stss.entries.push(self.sample_id); + } else { + if is_sync { + return; + } + + let mut stss = StssBox::default(); + for i in 1..=self.trak.mdia.minf.stbl.stsz.sample_count { + stss.entries.push(i); + } + self.trak.mdia.minf.stbl.stss = Some(stss); + }; + } + + fn is_chunk_full(&self) -> bool { + if self.samples_per_chunk > 0 { + self.chunk_samples >= self.samples_per_chunk + } else { + self.chunk_duration >= self.duration_per_chunk + } + } + + fn update_durations(&mut self, dur: u32, movie_timescale: u32) { + self.trak.mdia.mdhd.duration += dur as u64; + self.trak.tkhd.duration += + dur as u64 * movie_timescale as u64 / self.trak.mdia.mdhd.timescale as u64; + } + + pub(crate) fn write_sample( + &mut self, + writer: &mut W, + sample: &Mp4Sample, + movie_timescale: u32, + ) -> Result { + self.chunk_buffer.extend_from_slice(&sample.bytes); + self.chunk_samples += 1; + self.chunk_duration += sample.duration; + self.update_sample_sizes(sample.bytes.len() as u32); + self.update_sample_times(sample.duration); + self.update_rendering_offsets(sample.rendering_offset); + self.update_sync_samples(sample.is_sync); + if self.is_chunk_full() { + self.write_chunk(writer)?; + } + self.update_durations(sample.duration, movie_timescale); + + self.sample_id += 1; + + Ok(self.trak.tkhd.duration) + } + + // XXX largesize + fn chunk_count(&self) -> u32 { + let stco = self.trak.mdia.minf.stbl.stco.as_ref().unwrap(); + stco.entries.len() as u32 + } + + fn update_sample_to_chunk(&mut self, chunk_id: u32) { + if let Some(ref entry) = self.trak.mdia.minf.stbl.stsc.entries.last() { + if entry.samples_per_chunk == self.chunk_samples { + return; + } + } + + let entry = StscEntry { + first_chunk: chunk_id, + samples_per_chunk: self.chunk_samples, + sample_description_index: 1, + first_sample: self.sample_id - self.chunk_samples + 1, + }; + self.trak.mdia.minf.stbl.stsc.entries.push(entry); + } + + fn update_chunk_offsets(&mut self, offset: u64) { + let stco = self.trak.mdia.minf.stbl.stco.as_mut().unwrap(); + stco.entries.push(offset as u32); + } + + fn write_chunk(&mut self, writer: &mut W) -> Result<()> { + if self.chunk_buffer.is_empty() { + return Ok(()); + } + let chunk_offset = writer.seek(SeekFrom::Current(0))?; + + writer.write_all(&self.chunk_buffer)?; + + self.update_sample_to_chunk(self.chunk_count() + 1); + self.update_chunk_offsets(chunk_offset); + + self.chunk_buffer.clear(); + self.chunk_samples = 0; + self.chunk_duration = 0; + + Ok(()) + } + + fn max_sample_size(&self) -> u32 { + if self.trak.mdia.minf.stbl.stsz.sample_size > 0 { + self.trak.mdia.minf.stbl.stsz.sample_size + } else { + let mut max_size = 0; + for sample_size in self.trak.mdia.minf.stbl.stsz.sample_sizes.iter() { + max_size = cmp::max(max_size, *sample_size); + } + max_size + } + } + + pub(crate) fn write_end(&mut self, writer: &mut W) -> Result { + self.write_chunk(writer)?; + + let max_sample_size = self.max_sample_size(); + if let Some(ref mut mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + mp4a.esds.es_desc.dec_config.buffer_size_db = max_sample_size; + // TODO + // mp4a.esds.es_desc.dec_config.max_bitrate + // mp4a.esds.es_desc.dec_config.avg_bitrate + } + + Ok(self.trak.clone()) + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..320ada4 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,519 @@ +use std::convert::TryFrom; +use std::fmt; + +use crate::mp4box::*; +use crate::*; + +pub use bytes::Bytes; +pub use num_rational::Ratio; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FixedPointU8(Ratio); + +impl FixedPointU8 { + pub fn new(val: u8) -> Self { + Self(Ratio::new_raw(val as u16 * 0x100, 0x100)) + } + + pub fn new_raw(val: u16) -> Self { + Self(Ratio::new_raw(val, 0x100)) + } + + pub fn value(&self) -> u8 { + self.0.to_integer() as u8 + } + + pub fn raw_value(&self) -> u16 { + *self.0.numer() + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FixedPointI8(Ratio); + +impl FixedPointI8 { + pub fn new(val: i8) -> Self { + Self(Ratio::new_raw(val as i16 * 0x100, 0x100)) + } + + pub fn new_raw(val: i16) -> Self { + Self(Ratio::new_raw(val, 0x100)) + } + + pub fn value(&self) -> i8 { + self.0.to_integer() as i8 + } + + pub fn raw_value(&self) -> i16 { + *self.0.numer() + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FixedPointU16(Ratio); + +impl FixedPointU16 { + pub fn new(val: u16) -> Self { + Self(Ratio::new_raw(val as u32 * 0x10000, 0x10000)) + } + + pub fn new_raw(val: u32) -> Self { + Self(Ratio::new_raw(val, 0x10000)) + } + + pub fn value(&self) -> u16 { + self.0.to_integer() as u16 + } + + pub fn raw_value(&self) -> u32 { + *self.0.numer() + } +} + +impl fmt::Debug for BoxType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let fourcc: FourCC = From::from(self.clone()); + write!(f, "{}", fourcc) + } +} + +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, +} + +impl From for FourCC { + fn from(number: u32) -> Self { + let mut box_chars = Vec::new(); + for x in 0..4 { + let c = (number >> (x * 8) & 0x0000_00FF) as u8; + box_chars.push(c); + } + box_chars.reverse(); + + let box_string = match String::from_utf8(box_chars) { + Ok(t) => t, + _ => String::from("null"), // error to retrieve fourcc + }; + + FourCC { value: box_string } + } +} + +impl From for u32 { + fn from(fourcc: FourCC) -> u32 { + (&fourcc).into() + } +} + +impl From<&FourCC> for u32 { + fn from(fourcc: &FourCC) -> u32 { + let mut b: [u8; 4] = Default::default(); + b.copy_from_slice(fourcc.value.as_bytes()); + u32::from_be_bytes(b) + } +} + +impl From for FourCC { + fn from(fourcc: String) -> FourCC { + let value = if fourcc.len() > 4 { + fourcc[0..4].to_string() + } else { + fourcc + }; + FourCC { value } + } +} + +impl From<&str> for FourCC { + fn from(fourcc: &str) -> FourCC { + let value = if fourcc.len() > 4 { + fourcc[0..4].to_string() + } else { + fourcc.to_string() + }; + FourCC { value } + } +} + +impl From for FourCC { + fn from(t: BoxType) -> FourCC { + let box_num: u32 = Into::into(t); + From::from(box_num) + } +} + +impl fmt::Debug for FourCC { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let code: u32 = self.into(); + write!(f, "{} / {:#010X}", self.value, code) + } +} + +impl fmt::Display for FourCC { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.value) + } +} + +const DISPLAY_TYPE_VIDEO: &str = "Video"; +const DISPLAY_TYPE_AUDIO: &str = "Audio"; + +const HANDLER_TYPE_VIDEO: &str = "vide"; +const HANDLER_TYPE_AUDIO: &str = "soun"; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum TrackType { + Video, + Audio, +} + +impl fmt::Display for TrackType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + TrackType::Video => DISPLAY_TYPE_VIDEO, + TrackType::Audio => DISPLAY_TYPE_AUDIO, + }; + write!(f, "{}", s) + } +} + +impl TryFrom<&str> for TrackType { + type Error = Error; + fn try_from(handler: &str) -> Result { + match handler { + HANDLER_TYPE_VIDEO => Ok(TrackType::Video), + HANDLER_TYPE_AUDIO => Ok(TrackType::Audio), + _ => Err(Error::InvalidData("unsupported handler type")), + } + } +} + +impl Into<&str> for TrackType { + fn into(self) -> &'static str { + match self { + TrackType::Video => HANDLER_TYPE_VIDEO, + TrackType::Audio => HANDLER_TYPE_AUDIO, + } + } +} + +impl Into<&str> for &TrackType { + fn into(self) -> &'static str { + match self { + TrackType::Video => HANDLER_TYPE_VIDEO, + TrackType::Audio => HANDLER_TYPE_AUDIO, + } + } +} + +impl TryFrom<&FourCC> for TrackType { + type Error = Error; + fn try_from(fourcc: &FourCC) -> Result { + TrackType::try_from(fourcc.value.as_str()) + } +} + +impl Into for TrackType { + fn into(self) -> FourCC { + let s: &str = self.into(); + FourCC::from(s) + } +} + +const MEDIA_TYPE_H264: &str = "h264"; +const MEDIA_TYPE_AAC: &str = "aac"; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum MediaType { + H264, + AAC, +} + +impl fmt::Display for MediaType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s: &str = self.into(); + write!(f, "{}", s) + } +} + +impl TryFrom<&str> for MediaType { + type Error = Error; + fn try_from(media: &str) -> Result { + match media { + MEDIA_TYPE_H264 => Ok(MediaType::H264), + MEDIA_TYPE_AAC => Ok(MediaType::AAC), + _ => Err(Error::InvalidData("unsupported media type")), + } + } +} + +impl Into<&str> for MediaType { + fn into(self) -> &'static str { + match self { + MediaType::H264 => MEDIA_TYPE_H264, + MediaType::AAC => MEDIA_TYPE_AAC, + } + } +} + +impl Into<&str> for &MediaType { + fn into(self) -> &'static str { + match self { + MediaType::H264 => MEDIA_TYPE_H264, + MediaType::AAC => MEDIA_TYPE_AAC, + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum AvcProfile { + AvcConstrainedBaseline, // 66 with constraint set 1 + AvcBaseline, // 66, + AvcMain, // 77, + AvcExtended, // 88, + AvcHigh, // 100 + // TODO Progressive High Profile, Constrained High Profile, ... +} + +impl TryFrom<(u8, u8)> for AvcProfile { + type Error = Error; + fn try_from(value: (u8, u8)) -> Result { + let profile = value.0; + let constraint_set1_flag = value.1 & 0x40 >> 7; + match (profile, constraint_set1_flag) { + (66, 1) => Ok(AvcProfile::AvcConstrainedBaseline), + (66, 0) => Ok(AvcProfile::AvcBaseline), + (77, _) => Ok(AvcProfile::AvcMain), + (88, _) => Ok(AvcProfile::AvcExtended), + (100, _) => Ok(AvcProfile::AvcHigh), + _ => Err(Error::InvalidData("unsupported avc profile")), + } + } +} + +impl fmt::Display for AvcProfile { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let profile = match self { + AvcProfile::AvcConstrainedBaseline => "Constrained Baseline", + AvcProfile::AvcBaseline => "Baseline", + AvcProfile::AvcMain => "Main", + AvcProfile::AvcExtended => "Extended", + AvcProfile::AvcHigh => "High", + }; + write!(f, "{}", profile) + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum AudioObjectType { + AacMain = 1, + AacLowComplexity = 2, + AacScalableSampleRate = 3, + AacLongTermPrediction = 4, +} + +impl TryFrom for AudioObjectType { + type Error = Error; + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(AudioObjectType::AacMain), + 2 => Ok(AudioObjectType::AacLowComplexity), + 3 => Ok(AudioObjectType::AacScalableSampleRate), + 4 => Ok(AudioObjectType::AacLongTermPrediction), + _ => Err(Error::InvalidData("invalid audio object type")), + } + } +} + +impl fmt::Display for AudioObjectType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let type_str = match self { + AudioObjectType::AacMain => "main", + AudioObjectType::AacLowComplexity => "LC", + AudioObjectType::AacScalableSampleRate => "SSR", + AudioObjectType::AacLongTermPrediction => "LTP", + }; + write!(f, "{}", type_str) + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum SampleFreqIndex { + Freq96000 = 0x0, + Freq88200 = 0x1, + Freq64000 = 0x2, + Freq48000 = 0x3, + Freq44100 = 0x4, + Freq32000 = 0x5, + Freq24000 = 0x6, + Freq22050 = 0x7, + Freq16000 = 0x8, + Freq12000 = 0x9, + Freq11025 = 0xa, + Freq8000 = 0xb, +} + +impl TryFrom for SampleFreqIndex { + type Error = Error; + fn try_from(value: u8) -> Result { + match value { + 0x0 => Ok(SampleFreqIndex::Freq96000), + 0x1 => Ok(SampleFreqIndex::Freq88200), + 0x2 => Ok(SampleFreqIndex::Freq64000), + 0x3 => Ok(SampleFreqIndex::Freq48000), + 0x4 => Ok(SampleFreqIndex::Freq44100), + 0x5 => Ok(SampleFreqIndex::Freq32000), + 0x6 => Ok(SampleFreqIndex::Freq24000), + 0x7 => Ok(SampleFreqIndex::Freq22050), + 0x8 => Ok(SampleFreqIndex::Freq16000), + 0x9 => Ok(SampleFreqIndex::Freq12000), + 0xa => Ok(SampleFreqIndex::Freq11025), + 0xb => Ok(SampleFreqIndex::Freq8000), + _ => Err(Error::InvalidData("invalid sampling frequency index")), + } + } +} + +impl SampleFreqIndex { + pub fn freq(&self) -> u32 { + match self { + &SampleFreqIndex::Freq96000 => 96000, + &SampleFreqIndex::Freq88200 => 88200, + &SampleFreqIndex::Freq64000 => 64000, + &SampleFreqIndex::Freq48000 => 48000, + &SampleFreqIndex::Freq44100 => 44100, + &SampleFreqIndex::Freq32000 => 32000, + &SampleFreqIndex::Freq24000 => 24000, + &SampleFreqIndex::Freq22050 => 22050, + &SampleFreqIndex::Freq16000 => 16000, + &SampleFreqIndex::Freq12000 => 12000, + &SampleFreqIndex::Freq11025 => 11025, + &SampleFreqIndex::Freq8000 => 8000, + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum ChannelConfig { + Mono = 0x1, + Stereo = 0x2, + Three = 0x3, + Four = 0x4, + Five = 0x5, + FiveOne = 0x6, + SevenOne = 0x7, +} + +impl TryFrom for ChannelConfig { + type Error = Error; + fn try_from(value: u8) -> Result { + match value { + 0x1 => Ok(ChannelConfig::Mono), + 0x2 => Ok(ChannelConfig::Stereo), + 0x3 => Ok(ChannelConfig::Three), + 0x4 => Ok(ChannelConfig::Four), + 0x5 => Ok(ChannelConfig::Five), + 0x6 => Ok(ChannelConfig::FiveOne), + 0x7 => Ok(ChannelConfig::SevenOne), + _ => Err(Error::InvalidData("invalid channel configuration")), + } + } +} + +impl fmt::Display for ChannelConfig { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + ChannelConfig::Mono => "mono", + ChannelConfig::Stereo => "stereo", + ChannelConfig::Three => "three", + ChannelConfig::Four => "four", + ChannelConfig::Five => "five", + ChannelConfig::FiveOne => "five.one", + ChannelConfig::SevenOne => "seven.one", + }; + write!(f, "{}", s) + } +} + +#[derive(Debug, PartialEq, Clone, Default)] +pub struct AvcConfig { + pub width: u16, + pub height: u16, + pub seq_param_set: Vec, + pub pic_param_set: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct AacConfig { + pub bitrate: u32, + pub profile: AudioObjectType, + pub freq_index: SampleFreqIndex, + pub chan_conf: ChannelConfig, +} + +impl Default for AacConfig { + fn default() -> Self { + Self { + bitrate: 0, + profile: AudioObjectType::AacLowComplexity, + freq_index: SampleFreqIndex::Freq48000, + chan_conf: ChannelConfig::Stereo, + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum MediaConfig { + AvcConfig(AvcConfig), + AacConfig(AacConfig), +} + +#[derive(Debug)] +pub struct Mp4Sample { + pub start_time: u64, + pub duration: u32, + pub rendering_offset: i32, + pub is_sync: bool, + pub bytes: Bytes, +} + +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 + } +} + +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() + ) + } +} + +pub fn creation_time(creation_time: u64) -> u64 { + // convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01) + if creation_time >= 2082844800 { + creation_time - 2082844800 + } else { + creation_time + } +} diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..bc15872 --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,102 @@ +use byteorder::{BigEndian, WriteBytesExt}; +use std::io::{Seek, SeekFrom, Write}; + +use crate::mp4box::*; +use crate::track::Mp4TrackWriter; +use crate::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct Mp4Config { + pub major_brand: FourCC, + pub minor_version: u32, + pub compatible_brands: Vec, + pub timescale: u32, +} + +#[derive(Debug)] +pub struct Mp4Writer { + writer: W, + tracks: Vec, + mdat_pos: u64, + timescale: u32, + duration: u64, +} + +impl Mp4Writer { + pub fn write_start(mut writer: W, config: &Mp4Config) -> Result { + let ftyp = FtypBox { + major_brand: config.major_brand.clone(), + minor_version: config.minor_version.clone(), + compatible_brands: config.compatible_brands.clone(), + }; + ftyp.write_box(&mut writer)?; + + // TODO largesize + let mdat_pos = writer.seek(SeekFrom::Current(0))?; + BoxHeader::new(BoxType::MdatBox, HEADER_SIZE).write(&mut writer)?; + + let tracks = Vec::new(); + let timescale = config.timescale; + let duration = 0; + Ok(Self { + writer, + tracks, + mdat_pos, + timescale, + duration, + }) + } + + pub fn add_track(&mut self, config: &TrackConfig) -> Result<()> { + let track_id = self.tracks.len() as u32 + 1; + let track = Mp4TrackWriter::new(track_id, config)?; + self.tracks.push(track); + Ok(()) + } + + fn update_durations(&mut self, track_dur: u64) { + if track_dur > self.duration { + self.duration = track_dur; + } + } + + pub fn write_sample(&mut self, track_id: u32, sample: &Mp4Sample) -> Result<()> { + if track_id == 0 { + return Err(Error::TrakNotFound(track_id)); + } + + let track_dur = if let Some(ref mut track) = self.tracks.get_mut(track_id as usize - 1) { + track.write_sample(&mut self.writer, sample, self.timescale)? + } else { + return Err(Error::TrakNotFound(track_id)); + }; + + self.update_durations(track_dur); + + Ok(()) + } + + fn update_mdat_size(&mut self) -> Result<()> { + let mdat_end = self.writer.seek(SeekFrom::Current(0))?; + let mdat_size = mdat_end - self.mdat_pos; + assert!(mdat_size < std::u32::MAX as u64); + self.writer.seek(SeekFrom::Start(self.mdat_pos))?; + self.writer.write_u32::(mdat_size as u32)?; + self.writer.seek(SeekFrom::Start(mdat_end))?; + Ok(()) + } + + pub fn write_end(&mut self) -> Result<()> { + let mut moov = MoovBox::default(); + + for track in self.tracks.iter_mut() { + moov.traks.push(track.write_end(&mut self.writer)?); + } + self.update_mdat_size()?; + + moov.mvhd.timescale = self.timescale; + moov.mvhd.duration = self.duration; + moov.write_box(&mut self.writer)?; + Ok(()) + } +} diff --git a/tests/lib.rs b/tests/lib.rs index 11757ad..9c34180 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,8 +1,7 @@ -use mp4; +use mp4::{AudioObjectType, AvcProfile, ChannelConfig, MediaType, SampleFreqIndex, TrackType}; use std::fs::File; use std::io::BufReader; - #[test] fn test_read_mp4() { let filename = "tests/samples/minimal.mp4"; @@ -10,71 +9,112 @@ fn test_read_mp4() { let size = f.metadata().unwrap().len(); let reader = BufReader::new(f); - let mut mp4 = mp4::Mp4Reader::new(reader); - mp4.read(size).unwrap(); + let mut mp4 = mp4::Mp4Reader::read_header(reader, size).unwrap(); assert_eq!(2591, mp4.size()); // ftyp. - println!("{:?}", mp4.ftyp.compatible_brands); - assert_eq!(4, mp4.ftyp.compatible_brands.len()); + assert_eq!(4, mp4.compatible_brands().len()); // Check compatible_brands. let brands = vec![ String::from("isom"), String::from("iso2"), String::from("avc1"), - String::from("mp41") + String::from("mp41"), ]; for b in brands { - let t = mp4.ftyp.compatible_brands.iter().any(|x| x.to_string() == b); + let t = mp4.compatible_brands().iter().any(|x| x.to_string() == b); assert_eq!(t, true); } - // moov. - 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); - } + assert_eq!(mp4.duration(), 62); + assert_eq!(mp4.timescale(), 1000); + assert_eq!(mp4.tracks().len(), 2); let sample_count = mp4.sample_count(1).unwrap(); - assert_eq!(sample_count, 0); + assert_eq!(sample_count, 1); + let sample_1_1 = mp4.read_sample(1, 1).unwrap().unwrap(); + assert_eq!(sample_1_1.bytes.len(), 751); + assert_eq!( + sample_1_1, + mp4::Mp4Sample { + start_time: 0, + duration: 512, + rendering_offset: 0, + is_sync: true, + bytes: mp4::Bytes::from(vec![0x0u8; 751]), + } + ); + let eos = mp4.read_sample(1, 2).unwrap(); + assert!(eos.is_none()); 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 sample_2_1 = mp4.read_sample(2, 1).unwrap().unwrap(); + assert_eq!(sample_2_1.bytes.len(), 179); + assert_eq!( + sample_2_1, + 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 sample_2_2 = mp4.read_sample(2, 2).unwrap().unwrap(); + assert_eq!( + sample_2_2, + 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 sample_2_3 = mp4.read_sample(2, 3).unwrap().unwrap(); + assert_eq!( + sample_2_3, + 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()); + + // track #1 + let track1 = mp4.tracks().get(0).unwrap(); + assert_eq!(track1.track_id(), 1); + assert_eq!(track1.track_type().unwrap(), TrackType::Video); + assert_eq!(track1.media_type().unwrap(), MediaType::H264); + assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh); + assert_eq!(track1.width(), 320); + assert_eq!(track1.height(), 240); + assert_eq!(track1.bitrate(), 0); // XXX + assert_eq!(track1.frame_rate().to_integer(), 25); // XXX + + // track #2 + let track2 = mp4.tracks().get(1).unwrap(); + assert_eq!(track2.track_type().unwrap(), TrackType::Audio); + assert_eq!(track2.media_type().unwrap(), MediaType::AAC); + assert_eq!( + track2.audio_profile().unwrap(), + AudioObjectType::AacLowComplexity + ); + assert_eq!( + track2.sample_freq_index().unwrap(), + SampleFreqIndex::Freq48000 + ); + assert_eq!(track2.channel_config().unwrap(), ChannelConfig::Mono); + assert_eq!(track2.bitrate(), 67695); }