1
0
Fork 0
mirror of https://github.com/alfg/mp4-rust.git synced 2024-05-19 16:58:04 +00:00

Feature/mp4copy (#14)

* Add ReadBox trait

* Add boxtype macro

* Remove offset in BoxHeader

* Fix parsing error when box has largesize

* Remove duplicated codes reading version and flags

* Add avc1 box

* Add mp4a box

* Add mp4a box

* Add DecoderSpecificDescriptor in esds box

* Add necessary sub-boxes to stbl box

* Improve ReadBox::read_box()

* Add smhd box

* Refactor BoxHeader

* Refactor BMFF

* Refactor

* Add some functions to get offset and size of sample

* Add Mp4Reader::read_sample() that read media samples

* Move Mp4Reader to reader.rs

* Add mandatory check when reading boxes

Add some methods to Mp4Reader, TrackReader
Format codes

* Update mp4info

* Refactor common types

* Add FixedPointX types

* Add media configuration, profile, ...

* Add initial Mp4Writer

* Run cargo fmt

* Add Mp4Writer and examples/mp4copy

* Add test codes for Avc1Box and Mp4aBox

* Remove prefix "get_" from method names

* Rename atoms to mp4box

* Fix some bugs

Co-authored-by: Byungwan Jun <unipro.kr@gmail.com>
This commit is contained in:
Ian Jun 2020-08-05 08:56:59 +09:00 committed by GitHub
parent ba69f3812b
commit 3104a2d95b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 2858 additions and 1662 deletions

View file

@ -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<String> = env::args().collect();
@ -19,23 +19,66 @@ fn main() {
}
}
fn copy<P: AsRef<Path>>(src_filename: &P, _dst_filename: &P) -> Result<()> {
fn copy<P: AsRef<Path>>(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(())
}

View file

@ -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<String> = env::args().collect();
@ -24,96 +24,61 @@ fn info<P: AsRef<Path>>(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<String> {
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<String> {
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
))
}

View file

@ -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<MdhdBox>,
pub hdlr: Option<HdlrBox>,
pub minf: Option<MinfBox>,
}
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<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 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<W: Write> WriteBox<&mut W> for MdiaBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
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)
}
}

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
reader.read_u32::<BigEndian>()?; // reserved
reader.read_u16::<BigEndian>()?; // reserved
let data_reference_index = reader.read_u16::<BigEndian>()?;
reader.read_u64::<BigEndian>()?; // reserved
let channel_count = reader.read_u16::<BigEndian>()?;
let samplesize = reader.read_u16::<BigEndian>()?;
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
let samplerate = reader.read_u32::<BigEndian>()?;
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<W: Write> WriteBox<&mut W> for Mp4aBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(Self::box_type(), size).write(writer)?;
writer.write_u32::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.data_reference_index)?;
writer.write_u64::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.channel_count)?;
writer.write_u16::<BigEndian>(self.samplesize)?;
writer.write_u32::<BigEndian>(0)?; // reserved
writer.write_u32::<BigEndian>(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<R: Read + Seek> ReadBox<&mut R> for EsdsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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<W: Write> WriteBox<&mut W> for EsdsBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
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<T>: Sized {
fn read_desc(_: T) -> Result<Self>;
}
trait WriteDesc<T>: Sized {
fn write_desc(&self, _: T) -> Result<u32>;
}
fn read_desc<R: Read>(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<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
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<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
fn read_desc(reader: &mut R) -> Result<Self> {
let (tag, size) = read_desc(reader)?;
if tag != Self::desc_tag() {
return Err(Error::InvalidData("ESDescriptor not found"));
}
let es_id = reader.read_u16::<BigEndian>()?;
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<W: Write> WriteDesc<&mut W> for ESDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
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<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
fn read_desc(reader: &mut R) -> Result<Self> {
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::<BigEndian>()?;
let max_bitrate = reader.read_u32::<BigEndian>()?;
let avg_bitrate = reader.read_u32::<BigEndian>()?;
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<W: Write> WriteDesc<&mut W> for DecoderConfigDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
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<R: Read + Seek> ReadDesc<&mut R> for DecoderSpecificDescriptor {
fn read_desc(reader: &mut R) -> Result<Self> {
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<W: Write> WriteDesc<&mut W> for DecoderSpecificDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
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<R: Read + Seek> ReadDesc<&mut R> for SLConfigDescriptor {
fn read_desc(reader: &mut R) -> Result<Self> {
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<W: Write> WriteDesc<&mut W> for SLConfigDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
write_desc(writer, self.tag, self.size)?;
Ok(self.size)
}
}

View file

@ -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<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),
}))
}
}
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<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 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<W: Write> WriteBox<&mut W> for TrakBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
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)
}
}

View file

@ -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")]

View file

@ -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<T> = std::result::Result<T, Error>;
#[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<R> {
reader: R,
pub ftyp: FtypBox,
pub moov: Option<MoovBox>,
size: u64,
}
impl<R: Read + Seek> Mp4Reader<R> {
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<u32> {
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<u32> {
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<Option<Mp4Sample>> {
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};

View file

@ -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<u32>,
pub vertresolution: Ratio<u32>,
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<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
reader.read_u32::<BigEndian>()?; // reserved
reader.read_u16::<BigEndian>()?; // reserved
@ -56,10 +68,8 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
reader.read_u32::<BigEndian>()?; // pre-defined
let width = reader.read_u16::<BigEndian>()?;
let height = reader.read_u16::<BigEndian>()?;
let horiznumer = reader.read_u32::<BigEndian>()?;
let horizresolution = Ratio::new_raw(horiznumer, 0x10000);
let vertnumer = reader.read_u32::<BigEndian>()?;
let vertresolution = Ratio::new_raw(vertnumer, 0x10000);
let horizresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
let vertresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
reader.read_u32::<BigEndian>()?; // reserved
let frame_count = reader.read_u16::<BigEndian>()?;
skip_read(reader, 32)?; // compressorname
@ -67,7 +77,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)?;
@ -103,14 +113,12 @@ impl<W: Write> WriteBox<&mut W> for Avc1Box {
writer.write_u32::<BigEndian>(0)?; // pre-defined
writer.write_u16::<BigEndian>(self.width)?;
writer.write_u16::<BigEndian>(self.height)?;
writer.write_u32::<BigEndian>(*self.horizresolution.numer())?;
writer.write_u32::<BigEndian>(*self.vertresolution.numer())?;
writer.write_u32::<BigEndian>(self.horizresolution.raw_value())?;
writer.write_u32::<BigEndian>(self.vertresolution.raw_value())?;
writer.write_u32::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.frame_count)?;
// skip compressorname
for _ in 0..4 {
writer.write_u64::<BigEndian>(0)?;
}
skip_write(writer, 32)?;
writer.write_u16::<BigEndian>(self.depth)?;
writer.write_i16::<BigEndian>(-1)?; // pre-defined
@ -120,8 +128,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,
@ -132,6 +139,20 @@ pub struct AvcCBox {
pub picture_parameter_sets: Vec<NalUnit>,
}
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<R: Read + Seek> ReadBox<&mut R> for AvcCBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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<W: Write> WriteBox<&mut W> for AvcCBox {
}
}
#[derive(Debug, Default, PartialEq)]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct NalUnit {
pub bytes: Vec<u8>,
}
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<R: Read + Seek>(reader: &mut R) -> Result<Self> {
fn read<R: Read + Seek>(reader: &mut R) -> Result<Self> {
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> {
fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
writer.write_u16::<BigEndian>(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);
}
}

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for Co64Box {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -64,7 +62,7 @@ impl<W: Write> WriteBox<&mut W> for Co64Box {
#[cfg(test)]
mod tests {
use super::*;
use crate::atoms::BoxHeader;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]

View file

@ -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<CttsEntry>,
}
#[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<R: Read + Seek> ReadBox<&mut R> for CttsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -74,7 +72,7 @@ impl<W: Write> 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();

View file

@ -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<ElstBox>,
}
@ -32,12 +30,12 @@ impl Mp4Box for EdtsBox {
impl<R: Read + Seek> ReadBox<&mut R> for EdtsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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 => {

View file

@ -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<ElstEntry>,
}
#[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<R: Read + Seek> ReadBox<&mut R> for ElstBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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::<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>()?,
@ -105,7 +102,7 @@ impl<W: Write> WriteBox<&mut W> for ElstBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::atoms::BoxHeader;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for FtypBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let major = reader.read_u32::<BigEndian>()?;
let minor = reader.read_u32::<BigEndian>()?;
@ -66,20 +64,30 @@ impl<W: Write> 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();

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -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"),
};
@ -82,7 +80,7 @@ impl<W: Write> WriteBox<&mut W> for HdlrBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::atoms::BoxHeader;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for MdhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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::<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);
let language = language_string(language_code);
skip_read_to(reader, start + size)?;
@ -110,7 +107,7 @@ impl<W: Write> WriteBox<&mut W> for MdhdBox {
writer.write_u32::<BigEndian>(self.duration as u32)?;
}
let language_code = get_language_code(&self.language);
let language_code = language_code(&self.language);
writer.write_u16::<BigEndian>(language_code)?;
writer.write_u16::<BigEndian>(0)?; // pre-defined
@ -118,7 +115,7 @@ impl<W: Write> 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);
}

88
src/mp4box/mdia.rs Normal file
View file

@ -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<R: Read + Seek> ReadBox<&mut R> for MdiaBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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<W: Write> WriteBox<&mut W> for MdiaBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
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)
}
}

View file

@ -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<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,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<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 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<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)
}

View file

@ -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<u32> 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<FourCC> 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<String> 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<BoxType> 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<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.
@ -248,7 +154,7 @@ impl BoxHeader {
}
}
fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
if self.size > u32::MAX as u64 {
writer.write_u32::<BigEndian>(1)?;
writer.write_u32::<BigEndian>(self.name.into())?;
@ -274,7 +180,7 @@ pub fn write_box_header_ext<W: Write>(w: &mut W, v: u8, f: u32) -> Result<u64> {
Ok(4)
}
pub fn get_box_start<R: Seek>(reader: &mut R) -> Result<u64> {
pub fn box_start<R: Seek>(reader: &mut R) -> Result<u64> {
Ok(reader.seek(SeekFrom::Current(0))? - HEADER_SIZE)
}
@ -290,7 +196,7 @@ pub fn skip_read_to<R: Read + Seek>(reader: &mut R, pos: u64) -> Result<()> {
}
pub fn skip_box<R: Read + Seek>(reader: &mut R, size: u64) -> Result<()> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
skip_read_to(reader, start + size)?;
Ok(())
}

View file

@ -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<TrakBox>,
}
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<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 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<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,
})
}
}

561
src/mp4box/mp4a.rs Normal file
View file

@ -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<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
reader.read_u32::<BigEndian>()?; // reserved
reader.read_u16::<BigEndian>()?; // reserved
let data_reference_index = reader.read_u16::<BigEndian>()?;
reader.read_u64::<BigEndian>()?; // reserved
let channelcount = reader.read_u16::<BigEndian>()?;
let samplesize = reader.read_u16::<BigEndian>()?;
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
let samplerate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
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<W: Write> WriteBox<&mut W> for Mp4aBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(Self::box_type(), size).write(writer)?;
writer.write_u32::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.data_reference_index)?;
writer.write_u64::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.channelcount)?;
writer.write_u16::<BigEndian>(self.samplesize)?;
writer.write_u32::<BigEndian>(0)?; // reserved
writer.write_u32::<BigEndian>(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<R: Read + Seek> ReadBox<&mut R> for EsdsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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<W: Write> WriteBox<&mut W> for EsdsBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
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<T>: Sized {
fn read_desc(_: T, size: u32) -> Result<Self>;
}
trait WriteDesc<T>: Sized {
fn write_desc(&self, _: T) -> Result<u32>;
}
// XXX assert_eq!(size, 1)
fn desc_start<R: Seek>(reader: &mut R) -> Result<u64> {
Ok(reader.seek(SeekFrom::Current(0))? - 2)
}
fn read_desc<R: Read>(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<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
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<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
fn read_desc(reader: &mut R, size: u32) -> Result<Self> {
let start = desc_start(reader)?;
let es_id = reader.read_u16::<BigEndian>()?;
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<W: Write> WriteDesc<&mut W> for ESDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
let size = Self::desc_size();
write_desc(writer, Self::desc_tag(), size - 1)?;
writer.write_u16::<BigEndian>(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<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
fn read_desc(reader: &mut R, size: u32) -> Result<Self> {
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::<BigEndian>()?;
let max_bitrate = reader.read_u32::<BigEndian>()?;
let avg_bitrate = reader.read_u32::<BigEndian>()?;
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<W: Write> WriteDesc<&mut W> for DecoderConfigDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
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::<BigEndian>(self.buffer_size_db)?;
writer.write_u32::<BigEndian>(self.max_bitrate)?;
writer.write_u32::<BigEndian>(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<R: Read + Seek> ReadDesc<&mut R> for DecoderSpecificDescriptor {
fn read_desc(reader: &mut R, _size: u32) -> Result<Self> {
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<W: Write> WriteDesc<&mut W> for DecoderSpecificDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
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<R: Read + Seek> ReadDesc<&mut R> for SLConfigDescriptor {
fn read_desc(reader: &mut R, _size: u32) -> Result<Self> {
reader.read_u8()?; // pre-defined
Ok(SLConfigDescriptor {})
}
}
impl<W: Write> WriteDesc<&mut W> for SLConfigDescriptor {
fn write_desc(&self, writer: &mut W) -> Result<u32> {
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);
}
}

View file

@ -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<u32>,
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<R: Read + Seek> ReadBox<&mut R> for MvhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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::<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);
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 rate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
skip_read_to(reader, start + size)?;
Ok(MvhdBox{
Ok(MvhdBox {
version,
flags,
creation_time,
@ -108,7 +103,7 @@ impl<W: Write> WriteBox<&mut W> for MvhdBox {
writer.write_u32::<BigEndian>(self.timescale)?;
writer.write_u32::<BigEndian>(self.duration as u32)?;
}
writer.write_u32::<BigEndian>(*self.rate.numer())?;
writer.write_u32::<BigEndian>(self.rate.raw_value())?;
// XXX volume, ...
skip_write(writer, 76)?;
@ -120,7 +115,7 @@ impl<W: Write> 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();

View file

@ -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<i16>,
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<R: Read + Seek> ReadBox<&mut R> for SmhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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::<BigEndian>()?;
let balance = Ratio::new_raw(balance_numer, 0x100);
let balance = FixedPointI8::new_raw(reader.read_i16::<BigEndian>()?);
skip_read_to(reader, start + size)?;
@ -59,18 +55,17 @@ impl<W: Write> WriteBox<&mut W> for SmhdBox {
write_box_header_ext(writer, self.version, self.flags)?;
writer.write_i16::<BigEndian>(*self.balance.numer())?;
writer.write_i16::<BigEndian>(self.balance.raw_value())?;
writer.write_u16::<BigEndian>(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();

View file

@ -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<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();
}
@ -69,49 +47,48 @@ impl Mp4Box for StblBox {
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 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<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)?;
}

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for StcoBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -64,7 +62,7 @@ impl<W: Write> WriteBox<&mut W> for StcoBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::atoms::BoxHeader;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]

View file

@ -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<StscEntry>,
}
#[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<R: Read + Seek> ReadBox<&mut R> for StscBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -92,7 +90,7 @@ impl<W: Write> WriteBox<&mut W> for StscBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::atoms::BoxHeader;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for StsdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -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 => {

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for StssBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -64,7 +62,7 @@ impl<W: Write> WriteBox<&mut W> for StssBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::atoms::BoxHeader;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]

View file

@ -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<u32>,
}
@ -25,7 +24,7 @@ impl Mp4Box for StszBox {
impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -33,7 +32,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
let sample_count = reader.read_u32::<BigEndian>()?;
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::<BigEndian>()?;
sample_sizes.push(sample_number);
}
@ -45,6 +44,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
version,
flags,
sample_size,
sample_count,
sample_sizes,
})
}
@ -58,8 +58,9 @@ impl<W: Write> WriteBox<&mut W> for StszBox {
write_box_header_ext(writer, self.version, self.flags)?;
writer.write_u32::<BigEndian>(self.sample_size)?;
writer.write_u32::<BigEndian>(self.sample_sizes.len() as u32)?;
writer.write_u32::<BigEndian>(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::<BigEndian>(*sample_number)?;
}
@ -72,7 +73,7 @@ impl<W: Write> 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();

View file

@ -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<SttsEntry>,
}
#[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<R: Read + Seek> ReadBox<&mut R> for SttsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -74,7 +72,7 @@ impl<W: Write> 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();

View file

@ -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<u16>,
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<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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::<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>()?;
let alternate_group = reader.read_u16::<BigEndian>()?;
let volume_numer = reader.read_u16::<BigEndian>()?;
let volume = Ratio::new_raw(volume_numer, 0x100);
let volume = FixedPointU8::new_raw(reader.read_u16::<BigEndian>()?);
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>()?,
@ -116,8 +121,8 @@ impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
w: reader.read_i32::<BigEndian>()?,
};
let width = reader.read_u32::<BigEndian>()? >> 16;
let height = reader.read_u32::<BigEndian>()? >> 16;
let width = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
let height = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
skip_read_to(reader, start + size)?;
@ -163,7 +168,7 @@ impl<W: Write> WriteBox<&mut W> for TkhdBox {
writer.write_u64::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.layer)?;
writer.write_u16::<BigEndian>(self.alternate_group)?;
writer.write_u16::<BigEndian>(*self.volume.numer())?;
writer.write_u16::<BigEndian>(self.volume.raw_value())?;
writer.write_u16::<BigEndian>(0)?; // reserved
@ -177,8 +182,8 @@ impl<W: Write> WriteBox<&mut W> for TkhdBox {
writer.write_i32::<BigEndian>(self.matrix.y)?;
writer.write_i32::<BigEndian>(self.matrix.w)?;
writer.write_u32::<BigEndian>(self.width << 16)?;
writer.write_u32::<BigEndian>(self.height << 16)?;
writer.write_u32::<BigEndian>(self.width.raw_value())?;
writer.write_u32::<BigEndian>(self.height.raw_value())?;
Ok(size)
}
@ -187,7 +192,7 @@ impl<W: Write> 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();

93
src/mp4box/trak.rs Normal file
View file

@ -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<EdtsBox>,
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<R: Read + Seek> ReadBox<&mut R> for TrakBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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<W: Write> WriteBox<&mut W> for TrakBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
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)
}
}

View file

@ -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<R: Read + Seek> ReadBox<&mut R> for VmhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = get_box_start(reader)?;
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -70,11 +68,10 @@ impl<W: Write> 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();

133
src/reader.rs Normal file
View file

@ -0,0 +1,133 @@
use std::io::{Read, Seek, SeekFrom};
use crate::mp4box::*;
use crate::*;
#[derive(Debug)]
pub struct Mp4Reader<R> {
reader: R,
ftyp: FtypBox,
moov: MoovBox,
tracks: Vec<Mp4Track>,
size: u64,
}
impl<R: Read + Seek> Mp4Reader<R> {
pub fn read_header(mut reader: R, size: u64) -> Result<Self> {
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<u32> {
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<Option<Mp4Sample>> {
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))
}
}
}

671
src/track.rs Normal file
View file

@ -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<MediaConfig> 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<AvcConfig> 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<AacConfig> 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> {
TrackType::try_from(&self.trak.mdia.hdlr.handler_type)
}
pub fn media_type(&self) -> Result<MediaType> {
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<FourCC> {
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<u64> {
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<SampleFreqIndex> {
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<ChannelConfig> {
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<AvcProfile> {
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<AudioObjectType> {
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<u64> {
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<u32> {
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<u64> {
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<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).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<Self> {
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<W: Write + Seek>(
&mut self,
writer: &mut W,
sample: &Mp4Sample,
movie_timescale: u32,
) -> Result<u64> {
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<W: Write + Seek>(&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<W: Write + Seek>(&mut self, writer: &mut W) -> Result<TrakBox> {
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())
}
}

519
src/types.rs Normal file
View file

@ -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<u16>);
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<i16>);
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<u32>);
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<u32> 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<FourCC> 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<String> 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<BoxType> 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<TrackType> {
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> {
TrackType::try_from(fourcc.value.as_str())
}
}
impl Into<FourCC> 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<MediaType> {
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<AvcProfile> {
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<u8> for AudioObjectType {
type Error = Error;
fn try_from(value: u8) -> Result<AudioObjectType> {
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<u8> for SampleFreqIndex {
type Error = Error;
fn try_from(value: u8) -> Result<SampleFreqIndex> {
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<u8> for ChannelConfig {
type Error = Error;
fn try_from(value: u8) -> Result<ChannelConfig> {
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<u8>,
pub pic_param_set: Vec<u8>,
}
#[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
}
}

102
src/writer.rs Normal file
View file

@ -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<FourCC>,
pub timescale: u32,
}
#[derive(Debug)]
pub struct Mp4Writer<W> {
writer: W,
tracks: Vec<Mp4TrackWriter>,
mdat_pos: u64,
timescale: u32,
duration: u64,
}
impl<W: Write + Seek> Mp4Writer<W> {
pub fn write_start(mut writer: W, config: &Mp4Config) -> Result<Self> {
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::<BigEndian>(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(())
}
}

View file

@ -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);
}