mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-06-11 09:29:21 +00:00
Add mandatory check when reading boxes
Add some methods to Mp4Reader, TrackReader Format codes
This commit is contained in:
parent
c0fdbcf688
commit
4b82165efc
|
@ -27,7 +27,7 @@ fn copy<P: AsRef<Path>>(src_filename: &P, _dst_filename: &P) -> Result<()> {
|
|||
let mut mp4 = mp4::Mp4Reader::new(reader);
|
||||
mp4.read(size)?;
|
||||
|
||||
for tix in 0..mp4.track_count()? {
|
||||
for tix in 0..mp4.track_count() {
|
||||
let track_id = tix + 1;
|
||||
let sample_count = mp4.sample_count(track_id)?;
|
||||
for six in 0..sample_count {
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::io::prelude::*;
|
|||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Result, Mp4Reader, TrackType};
|
||||
use mp4::{Mp4Reader, Result, TrackType};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
@ -27,22 +27,26 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|||
let mut mp4 = Mp4Reader::new(reader);
|
||||
mp4.read(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!(
|
||||
" brands: {:?} {:?}\n",
|
||||
mp4.major_brand(),
|
||||
mp4.compatible_brands()
|
||||
);
|
||||
println!(
|
||||
"Duration: {}, timescale: {}",
|
||||
mp4.duration()?,
|
||||
mp4.timescale()?
|
||||
);
|
||||
for track in mp4.tracks().iter() {
|
||||
println!(" Track: {}", track.track_id());
|
||||
}
|
||||
|
||||
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();
|
||||
let tkhd = &trak.tkhd;
|
||||
println!("Track: {:?}", tkhd.track_id);
|
||||
println!(" flags: {:?}", tkhd.flags);
|
||||
println!(" id: {:?}", tkhd.track_id);
|
||||
|
@ -51,35 +55,34 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|||
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);
|
||||
let mdia = &trak.mdia;
|
||||
let hdlr = &mdia.hdlr;
|
||||
let mdhd = &mdia.mdhd;
|
||||
let stts = &mdia.minf.stbl.stts;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
println!(
|
||||
" type: {:?}",
|
||||
get_handler_type(hdlr.handler_type.value.as_ref())
|
||||
);
|
||||
println!(" language: {:?}", mdhd.language);
|
||||
|
||||
println!(" media:");
|
||||
println!(" sample count: {:?}", stts.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 {
|
||||
println!(
|
||||
" frame rate: (computed): {:?}",
|
||||
get_framerate(stts.entries[0].sample_count, mdhd.duration, mdhd.timescale)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,11 +112,11 @@ fn get_framerate(sample_count: u32, duration: u64, timescale: u32) -> String {
|
|||
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 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
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
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::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Avc1Box {
|
||||
pub data_reference_index: u16,
|
||||
pub width: u16,
|
||||
|
@ -67,7 +65,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
reader.read_i16::<BigEndian>()?; // 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)?;
|
||||
|
||||
|
@ -120,8 +118,7 @@ impl<W: Write> 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,
|
||||
|
@ -207,8 +204,7 @@ impl<W: Write> WriteBox<&mut W> for AvcCBox {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct NalUnit {
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
@ -222,9 +218,7 @@ impl NalUnit {
|
|||
let length = reader.read_u16::<BigEndian>()? as usize;
|
||||
let mut bytes = vec![0u8; length];
|
||||
reader.read(&mut bytes)?;
|
||||
Ok(NalUnit {
|
||||
bytes,
|
||||
})
|
||||
Ok(NalUnit { bytes })
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Co64Box {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct CttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<CttsEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct CttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_offset: i32,
|
||||
|
@ -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();
|
||||
|
|
|
@ -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::atoms::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct EdtsBox {
|
||||
pub elst: Option<ElstBox>,
|
||||
}
|
||||
|
@ -37,7 +35,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for EdtsBox {
|
|||
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 => {
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct ElstBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<ElstEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct ElstEntry {
|
||||
pub segment_duration: u64,
|
||||
pub media_time: u64,
|
||||
|
@ -46,20 +44,19 @@ impl<R: Read + Seek> ReadBox<&mut R> for ElstBox {
|
|||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
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::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let (segment_duration, media_time) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
|
||||
let entry = ElstEntry{
|
||||
let entry = ElstEntry {
|
||||
segment_duration,
|
||||
media_time,
|
||||
media_rate: reader.read_u16::<BigEndian>()?,
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct FtypBox {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
|
@ -72,14 +70,24 @@ mod tests {
|
|||
#[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();
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct HdlrBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -42,7 +40,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
|
|||
Ok(t) => {
|
||||
assert_eq!(t.len(), buf_size as usize);
|
||||
t
|
||||
},
|
||||
}
|
||||
_ => String::from("null"),
|
||||
};
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MdhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -56,23 +54,22 @@ impl<R: Read + Seek> ReadBox<&mut R> for MdhdBox {
|
|||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let (creation_time, modification_time, timescale, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let language_code = reader.read_u16::<BigEndian>()?;
|
||||
let language = get_language_string(language_code);
|
||||
|
||||
|
|
|
@ -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::{mdhd::MdhdBox, hdlr::HdlrBox, minf::MinfBox};
|
||||
use crate::atoms::{hdlr::HdlrBox, mdhd::MdhdBox, minf::MinfBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct MdiaBox {
|
||||
pub mdhd: Option<MdhdBox>,
|
||||
pub hdlr: Option<HdlrBox>,
|
||||
pub minf: Option<MinfBox>,
|
||||
}
|
||||
|
||||
impl MdiaBox {
|
||||
pub(crate) fn new() -> MdiaBox {
|
||||
Default::default()
|
||||
}
|
||||
pub mdhd: MdhdBox,
|
||||
pub hdlr: HdlrBox,
|
||||
pub minf: MinfBox,
|
||||
}
|
||||
|
||||
impl Mp4Box for MdiaBox {
|
||||
|
@ -24,17 +16,7 @@ impl Mp4Box for 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
|
||||
HEADER_SIZE + self.mdhd.box_size() + self.hdlr.box_size() + self.minf.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,27 +24,26 @@ impl<R: Read + Seek> ReadBox<&mut R> for MdiaBox {
|
|||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
|
||||
let mut mdia = MdiaBox::new();
|
||||
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;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MdhdBox => {
|
||||
let mdhd = MdhdBox::read_box(reader, s)?;
|
||||
mdia.mdhd = Some(mdhd);
|
||||
mdhd = Some(MdhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::HdlrBox => {
|
||||
let hdlr = HdlrBox::read_box(reader, s)?;
|
||||
mdia.hdlr = Some(hdlr);
|
||||
hdlr = Some(HdlrBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MinfBox => {
|
||||
let minf = MinfBox::read_box(reader, s)?;
|
||||
mdia.minf = Some(minf);
|
||||
minf = Some(MinfBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
|
@ -73,9 +54,23 @@ impl<R: Read + Seek> ReadBox<&mut R> for MdiaBox {
|
|||
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(mdia)
|
||||
Ok(MdiaBox {
|
||||
mdhd: mdhd.unwrap(),
|
||||
hdlr: hdlr.unwrap(),
|
||||
minf: minf.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,15 +79,9 @@ impl<W: Write> WriteBox<&mut W> for MdiaBox {
|
|||
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)?;
|
||||
}
|
||||
self.mdhd.write_box(writer)?;
|
||||
self.hdlr.write_box(writer)?;
|
||||
self.minf.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
|
|
|
@ -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::atoms::{smhd::SmhdBox, stbl::StblBox, vmhd::VmhdBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct MinfBox {
|
||||
pub vmhd: Option<VmhdBox>,
|
||||
pub smhd: Option<SmhdBox>,
|
||||
pub stbl: Option<StblBox>,
|
||||
}
|
||||
|
||||
impl MinfBox {
|
||||
pub(crate) fn new() -> MinfBox {
|
||||
Default::default()
|
||||
}
|
||||
pub stbl: StblBox,
|
||||
}
|
||||
|
||||
impl Mp4Box for MinfBox {
|
||||
|
@ -31,9 +23,7 @@ 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
|
||||
}
|
||||
}
|
||||
|
@ -42,30 +32,30 @@ impl<R: Read + Seek> ReadBox<&mut R> for MinfBox {
|
|||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_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<R: Read + Seek> 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<W: Write> 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)
|
||||
}
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
use std::fmt;
|
||||
use std::io::{Seek, SeekFrom, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::fmt;
|
||||
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 avc;
|
||||
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;
|
||||
|
@ -64,7 +64,7 @@ macro_rules! boxtype {
|
|||
}
|
||||
}
|
||||
|
||||
boxtype!{
|
||||
boxtype! {
|
||||
FtypBox => 0x66747970,
|
||||
MvhdBox => 0x6d766864,
|
||||
FreeBox => 0x66726565,
|
||||
|
@ -114,7 +114,7 @@ impl fmt::Display for BoxType {
|
|||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct FourCC {
|
||||
pub value: String
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
|
@ -131,9 +131,7 @@ impl From<u32> for FourCC {
|
|||
_ => String::from("null"), // error to retrieve fourcc
|
||||
};
|
||||
|
||||
FourCC {
|
||||
value: box_string
|
||||
}
|
||||
FourCC { value: box_string }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +156,7 @@ impl From<String> for FourCC {
|
|||
} else {
|
||||
fourcc
|
||||
};
|
||||
FourCC {value}
|
||||
FourCC { value }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +167,7 @@ impl From<&str> for FourCC {
|
|||
} else {
|
||||
fourcc.to_string()
|
||||
};
|
||||
FourCC {value}
|
||||
FourCC { value }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,7 +217,7 @@ impl BoxHeader {
|
|||
// TODO: if size is 0, then this box is the last one in the file
|
||||
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
|
||||
// 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.
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct MoovBox {
|
||||
pub mvhd: MvhdBox,
|
||||
pub traks: Vec<TrakBox>,
|
||||
}
|
||||
|
||||
impl MoovBox {
|
||||
pub(crate) fn new() -> MoovBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MoovBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::MoovBox
|
||||
|
@ -35,23 +27,23 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_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<R: Read + Seek> 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Mp4aBox {
|
||||
pub data_reference_index: u16,
|
||||
pub channel_count: u16,
|
||||
|
@ -51,7 +49,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
|
|||
let samplerate = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if name == BoxType::EsdsBox {
|
||||
let esds = EsdsBox::read_box(reader, s)?;
|
||||
|
||||
|
@ -91,8 +89,7 @@ impl<W: Write> WriteBox<&mut W> for Mp4aBox {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct EsdsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -138,7 +135,6 @@ impl<W: Write> WriteBox<&mut W> for EsdsBox {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
trait Descriptor: Sized {
|
||||
fn desc_tag() -> u8;
|
||||
fn desc_size() -> u32;
|
||||
|
@ -192,8 +188,7 @@ fn write_desc<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
|
|||
Ok(1 + nbytes)
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct ESDescriptor {
|
||||
pub tag: u8,
|
||||
pub size: u32,
|
||||
|
@ -211,9 +206,7 @@ impl Descriptor for ESDescriptor {
|
|||
|
||||
// XXX size > 0x7F
|
||||
fn desc_size() -> u32 {
|
||||
2 + 3
|
||||
+ DecoderConfigDescriptor::desc_size()
|
||||
+ SLConfigDescriptor::desc_size()
|
||||
2 + 3 + DecoderConfigDescriptor::desc_size() + SLConfigDescriptor::desc_size()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,7 +241,7 @@ impl<W: Write> WriteDesc<&mut W> for ESDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct DecoderConfigDescriptor {
|
||||
pub tag: u8,
|
||||
pub size: u32,
|
||||
|
@ -292,7 +285,7 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
|
|||
let dec_specific = DecoderSpecificDescriptor::read_desc(reader)?;
|
||||
|
||||
// XXX skip_read
|
||||
for _ in DecoderConfigDescriptor::desc_size()..size-1 {
|
||||
for _ in DecoderConfigDescriptor::desc_size()..size - 1 {
|
||||
reader.read_u8()?;
|
||||
}
|
||||
|
||||
|
@ -318,7 +311,7 @@ impl<W: Write> WriteDesc<&mut W> for DecoderConfigDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct DecoderSpecificDescriptor {
|
||||
pub tag: u8,
|
||||
pub size: u32,
|
||||
|
@ -369,7 +362,7 @@ impl<W: Write> WriteDesc<&mut W> for DecoderSpecificDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct SLConfigDescriptor {
|
||||
pub tag: u8,
|
||||
pub size: u32,
|
||||
|
@ -395,10 +388,7 @@ impl<R: Read + Seek> ReadDesc<&mut R> for SLConfigDescriptor {
|
|||
|
||||
reader.read_u8()?; // pre-defined
|
||||
|
||||
Ok(SLConfigDescriptor {
|
||||
tag,
|
||||
size,
|
||||
})
|
||||
Ok(SLConfigDescriptor { tag, size })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
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::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MvhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -55,29 +53,28 @@ impl<R: Read + Seek> ReadBox<&mut R> for MvhdBox {
|
|||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let (creation_time, modification_time, timescale, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let numer = reader.read_u32::<BigEndian>()?;
|
||||
let rate = Ratio::new_raw(numer, 0x10000);
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(MvhdBox{
|
||||
Ok(MvhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
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::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -66,7 +64,6 @@ impl<W: Write> WriteBox<&mut W> for SmhdBox {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -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,
|
||||
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<StsdBox>,
|
||||
pub stts: Option<SttsBox>,
|
||||
pub stsd: StsdBox,
|
||||
pub stts: SttsBox,
|
||||
pub ctts: Option<CttsBox>,
|
||||
pub stss: Option<StssBox>,
|
||||
pub stsc: Option<StscBox>,
|
||||
pub stsz: Option<StszBox>,
|
||||
pub stsc: StscBox,
|
||||
pub stsz: StszBox,
|
||||
pub stco: Option<StcoBox>,
|
||||
pub co64: Option<Co64Box>,
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -71,47 +49,46 @@ impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
|||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_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<R: Read + Seek> 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<W: Write> 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)?;
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StcoBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StscBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<StscEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StscEntry {
|
||||
pub first_chunk: u32,
|
||||
pub samples_per_chunk: u32,
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StsdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -43,7 +41,7 @@ impl<R: Read + Seek> 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 => {
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StssBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StszBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct SttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<SttsEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct SttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_delta: u32,
|
||||
|
@ -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();
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
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::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TkhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -14,7 +12,7 @@ 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<u16>,
|
||||
pub matrix: Matrix,
|
||||
|
@ -41,7 +39,7 @@ impl Default for TkhdBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Matrix {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
|
@ -78,24 +76,23 @@ impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
|
|||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, track_id, _, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
let (creation_time, modification_time, track_id, _, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
reader.read_u64::<BigEndian>()?; // reserved
|
||||
let layer = reader.read_u16::<BigEndian>()?;
|
||||
|
@ -104,7 +101,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
|
|||
let volume = Ratio::new_raw(volume_numer, 0x100);
|
||||
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let matrix = Matrix{
|
||||
let matrix = Matrix {
|
||||
a: reader.read_i32::<byteorder::LittleEndian>()?,
|
||||
b: reader.read_i32::<BigEndian>()?,
|
||||
u: reader.read_i32::<BigEndian>()?,
|
||||
|
|
|
@ -1,252 +1,13 @@
|
|||
use std::io::{Seek, SeekFrom, Read, Write};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{
|
||||
tkhd::TkhdBox,
|
||||
edts::EdtsBox,
|
||||
mdia::MdiaBox,
|
||||
stbl::StblBox,
|
||||
stts::SttsBox,
|
||||
stsc::StscBox,
|
||||
stsz::StszBox,
|
||||
};
|
||||
use crate::atoms::{edts::EdtsBox, mdia::MdiaBox, tkhd::TkhdBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct TrakBox {
|
||||
pub id: u32,
|
||||
|
||||
pub tkhd: Option<TkhdBox>,
|
||||
pub tkhd: TkhdBox,
|
||||
pub edts: Option<EdtsBox>,
|
||||
pub mdia: Option<MdiaBox>,
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
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<u64> {
|
||||
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<u32> {
|
||||
let stsz = self.stsz()?;
|
||||
Ok(stsz.sample_sizes.len() as u32)
|
||||
}
|
||||
|
||||
pub fn sample_size(&self, sample_id: u32) -> Result<u32> {
|
||||
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<u64> {
|
||||
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<i32> {
|
||||
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<bool> {
|
||||
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<R: Read + Seek>(
|
||||
&self,
|
||||
reader: &mut R,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Mp4Sample>> {
|
||||
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),
|
||||
}))
|
||||
}
|
||||
pub mdia: MdiaBox,
|
||||
}
|
||||
|
||||
impl Mp4Box for TrakBox {
|
||||
|
@ -256,15 +17,11 @@ impl Mp4Box for TrakBox {
|
|||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(ref tkhd) = self.tkhd {
|
||||
size += tkhd.box_size();
|
||||
}
|
||||
size += self.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 += self.mdia.box_size();
|
||||
size
|
||||
}
|
||||
}
|
||||
|
@ -273,27 +30,26 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
|
||||
let mut trak = TrakBox::new();
|
||||
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;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::TkhdBox => {
|
||||
let tkhd = TkhdBox::read_box(reader, s)?;
|
||||
trak.tkhd = Some(tkhd);
|
||||
tkhd = Some(TkhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::EdtsBox => {
|
||||
let edts = EdtsBox::read_box(reader, s)?;
|
||||
trak.edts = Some(edts);
|
||||
edts = Some(EdtsBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MdiaBox => {
|
||||
let mdia = MdiaBox::read_box(reader, s)?;
|
||||
trak.mdia = Some(mdia);
|
||||
mdia = Some(MdiaBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
|
@ -304,9 +60,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
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(trak)
|
||||
Ok(TrakBox {
|
||||
tkhd: tkhd.unwrap(),
|
||||
edts,
|
||||
mdia: mdia.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,15 +82,11 @@ impl<W: Write> WriteBox<&mut W> for TrakBox {
|
|||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(ref tkhd) = self.tkhd {
|
||||
tkhd.write_box(writer)?;
|
||||
}
|
||||
self.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)?;
|
||||
}
|
||||
self.mdia.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
||||
#[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,
|
||||
|
@ -70,7 +68,6 @@ impl<W: Write> WriteBox<&mut W> for VmhdBox {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -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();
|
||||
|
|
|
@ -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")]
|
||||
|
|
15
src/lib.rs
15
src/lib.rs
|
@ -1,5 +1,5 @@
|
|||
use std::fmt;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
|
||||
pub use bytes::Bytes;
|
||||
|
||||
|
@ -42,9 +42,14 @@ impl PartialEq for Mp4Sample {
|
|||
|
||||
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())
|
||||
write!(
|
||||
f,
|
||||
"start_time {}, duration {}, rendering_offset {}, is_sync {}, length {}",
|
||||
self.start_time,
|
||||
self.duration,
|
||||
self.rendering_offset,
|
||||
self.is_sync,
|
||||
self.bytes.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
303
src/reader.rs
303
src/reader.rs
|
@ -1,7 +1,8 @@
|
|||
use std::io::{Seek, SeekFrom, Read};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use crate::{Result, Error, Mp4Sample};
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{mvhd::MvhdBox, stbl::StblBox, trak::TrakBox};
|
||||
use crate::{Bytes, Error, Mp4Sample, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Reader<R> {
|
||||
|
@ -9,6 +10,8 @@ pub struct Mp4Reader<R> {
|
|||
pub ftyp: FtypBox,
|
||||
pub moov: Option<MoovBox>,
|
||||
size: u64,
|
||||
|
||||
tracks: Vec<TrackReader>,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> Mp4Reader<R> {
|
||||
|
@ -18,6 +21,7 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
ftyp: FtypBox::default(),
|
||||
moov: None,
|
||||
size: 0,
|
||||
tracks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +35,7 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
while current < size {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(&mut self.reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
// Match and parse the atom boxes.
|
||||
match name {
|
||||
|
@ -59,59 +63,288 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
}
|
||||
current = self.reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
if let Some(ref moov) = self.moov {
|
||||
for (i, trak) in moov.traks.iter().enumerate() {
|
||||
self.tracks.push(TrackReader::new(i as u32 + 1, trak));
|
||||
}
|
||||
}
|
||||
self.size = current - start;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn track_count(&self) -> Result<u32> {
|
||||
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
|
||||
}
|
||||
|
||||
fn mvhd(&self) -> Result<&MvhdBox> {
|
||||
if let Some(ref moov) = self.moov {
|
||||
Ok(moov.traks.len() as u32)
|
||||
Ok(&moov.mvhd)
|
||||
} else {
|
||||
Err(Error::BoxNotFound(MoovBox::box_type()))
|
||||
Err(Error::BoxNotFound(BoxType::VmhdBox))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration(&self) -> Result<u64> {
|
||||
let mvhd = self.mvhd()?;
|
||||
Ok(mvhd.duration)
|
||||
}
|
||||
|
||||
pub fn timescale(&self) -> Result<u32> {
|
||||
let mvhd = self.mvhd()?;
|
||||
Ok(mvhd.timescale)
|
||||
}
|
||||
|
||||
pub fn track_count(&self) -> u32 {
|
||||
self.tracks.len() as u32
|
||||
}
|
||||
|
||||
pub fn tracks(&self) -> &[TrackReader] {
|
||||
&self.tracks
|
||||
}
|
||||
|
||||
pub fn sample_count(&self, track_id: u32) -> Result<u32> {
|
||||
if track_id == 0 {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
|
||||
let moov = if let Some(ref moov) = self.moov {
|
||||
moov
|
||||
if let Some(track) = self.tracks.get(track_id as usize - 1) {
|
||||
Ok(track.sample_count())
|
||||
} 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()
|
||||
Err(Error::TrakNotFound(track_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_sample(
|
||||
&mut self,
|
||||
track_id: u32,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Mp4Sample>> {
|
||||
pub fn read_sample(&mut self, track_id: u32, sample_id: u32) -> Result<Option<Mp4Sample>> {
|
||||
if track_id == 0 {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
|
||||
let moov = if let Some(ref moov) = self.moov {
|
||||
moov
|
||||
if let Some(track) = self.tracks.get(track_id as usize - 1) {
|
||||
track.read_sample(&mut self.reader, sample_id)
|
||||
} 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)
|
||||
Err(Error::TrakNotFound(track_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackReader {
|
||||
track_id: u32,
|
||||
trak: TrakBox,
|
||||
}
|
||||
|
||||
impl TrackReader {
|
||||
pub(crate) fn new(track_id: u32, trak: &TrakBox) -> Self {
|
||||
let trak = trak.clone();
|
||||
Self { track_id, trak }
|
||||
}
|
||||
|
||||
pub fn track_id(&self) -> u32 {
|
||||
self.track_id
|
||||
}
|
||||
|
||||
fn stbl(&self) -> &StblBox {
|
||||
&self.trak.mdia.minf.stbl
|
||||
}
|
||||
|
||||
fn stsc_index(&self, sample_id: u32) -> usize {
|
||||
let stsc = &self.stbl().stsc;
|
||||
|
||||
for (i, entry) in stsc.entries.iter().enumerate() {
|
||||
if sample_id < entry.first_sample {
|
||||
assert_ne!(i, 0);
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
assert_ne!(stsc.entries.len(), 0);
|
||||
stsc.entries.len() - 1
|
||||
}
|
||||
|
||||
fn chunk_offset(&self, chunk_id: u32) -> Result<u64> {
|
||||
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.track_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.track_id,
|
||||
BoxType::Co64Box,
|
||||
chunk_id,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(stbl.stco.is_some() || stbl.co64.is_some());
|
||||
return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box));
|
||||
}
|
||||
|
||||
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
|
||||
let stbl = self.stbl();
|
||||
|
||||
assert!(stbl.ctts.is_some());
|
||||
let ctts = if let Some(ref ctts) = stbl.ctts {
|
||||
ctts
|
||||
} else {
|
||||
return Err(Error::BoxInStblNotFound(self.track_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.track_id,
|
||||
BoxType::CttsBox,
|
||||
sample_id,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn sample_count(&self) -> u32 {
|
||||
let stsz = &self.stbl().stsz;
|
||||
stsz.sample_sizes.len() as u32
|
||||
}
|
||||
|
||||
pub fn sample_size(&self, sample_id: u32) -> Result<u32> {
|
||||
let stsz = &self.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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_offset(&self, sample_id: u32) -> Result<u64> {
|
||||
let stsc_index = self.stsc_index(sample_id);
|
||||
|
||||
let stsc = &self.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)
|
||||
}
|
||||
|
||||
pub fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
|
||||
let stts = &self.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,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
|
||||
let stbl = self.stbl();
|
||||
|
||||
if let Some(ref ctts) = 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
|
||||
}
|
||||
|
||||
pub fn is_sync_sample(&self, sample_id: u32) -> bool {
|
||||
let stbl = self.stbl();
|
||||
|
||||
if let Some(ref stss) = stbl.stss {
|
||||
match stss.entries.binary_search(&sample_id) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_sample<R: Read + Seek>(
|
||||
&self,
|
||||
reader: &mut R,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Mp4Sample>> {
|
||||
let sample_size = match self.sample_size(sample_id) {
|
||||
Ok(size) => size,
|
||||
Err(Error::EntryInStblNotFound(_, _, _)) => return Ok(None),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let sample_offset = self.sample_offset(sample_id).unwrap(); // XXX
|
||||
|
||||
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),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
71
tests/lib.rs
71
tests/lib.rs
|
@ -2,7 +2,6 @@ use mp4;
|
|||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_read_mp4() {
|
||||
let filename = "tests/samples/minimal.mp4";
|
||||
|
@ -16,31 +15,24 @@ fn test_read_mp4() {
|
|||
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().unwrap(), 62);
|
||||
assert_eq!(mp4.timescale().unwrap(), 1000);
|
||||
assert_eq!(mp4.track_count(), 2);
|
||||
|
||||
let sample_count = mp4.sample_count(1).unwrap();
|
||||
assert_eq!(sample_count, 0);
|
||||
|
@ -49,31 +41,40 @@ fn test_read_mp4() {
|
|||
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]),
|
||||
});
|
||||
assert_eq!(
|
||||
sample1,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 0,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 179]),
|
||||
}
|
||||
);
|
||||
|
||||
let sample2 = mp4.read_sample(2, 2).unwrap().unwrap();
|
||||
assert_eq!(sample2, mp4::Mp4Sample {
|
||||
start_time: 1024,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 180]),
|
||||
});
|
||||
assert_eq!(
|
||||
sample2,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 1024,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 180]),
|
||||
}
|
||||
);
|
||||
|
||||
let sample3 = mp4.read_sample(2, 3).unwrap().unwrap();
|
||||
assert_eq!(sample3, mp4::Mp4Sample {
|
||||
start_time: 2048,
|
||||
duration: 896,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 160]),
|
||||
});
|
||||
assert_eq!(
|
||||
sample3,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 2048,
|
||||
duration: 896,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 160]),
|
||||
}
|
||||
);
|
||||
|
||||
let eos = mp4.read_sample(2, 4).unwrap();
|
||||
assert!(eos.is_none());
|
||||
|
|
Loading…
Reference in a new issue