1
0
Fork 0
mirror of https://github.com/alfg/mp4-rust.git synced 2024-06-11 09:29:21 +00:00

Merge remote-tracking branch 'alfg/master'

This commit is contained in:
Ian Jun 2020-08-05 12:21:36 +09:00
commit 02bd7acc76
47 changed files with 4581 additions and 1590 deletions

3
.gitignore vendored
View file

@ -1,3 +1,4 @@
/Cargo.lock
/target
**/*.rs.bk
*.exe
@ -5,4 +6,4 @@
*.mp4
.idea/
.vscode/
!tests/samples/*.mp4
!tests/samples/*.mp4

72
Cargo.lock generated
View file

@ -1,72 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "byteorder"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "mp4"
version = "0.4.3"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thiserror"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thiserror-impl"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
"checksum syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250"
"checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
"checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"

View file

@ -20,4 +20,5 @@ license = "MIT"
[dependencies]
thiserror = "^1.0"
byteorder = "1"
bytes = "0.5"
num-rational = "0.3"

View file

@ -14,13 +14,14 @@ use mp4;
fn main() {
let f = File::open("example.mp4").unwrap();
let size = f.metadata()?.len();
let reader = BufReader::new(f);
let bmff = mp4::read_mp4(f).unwrap();
let mut mp4 = Mp4Reader::new(reader);
mp4.read(size)?;
println!("file size: {}", bmff.size);
println!("brands: {:?} {:?}\n",
bmff.ftyp.major_brand, bmff.ftyp.compatible_brands
);
println!("size: {}", mp4.size());
println!("brands: {:?} {:?}\n", mp4.ftyp.major_brand, mp4.ftyp.compatible_brands);
}
```

84
examples/mp4copy.rs Normal file
View file

@ -0,0 +1,84 @@
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufReader, BufWriter};
use std::path::Path;
use mp4::{AacConfig, AvcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig};
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 3 {
println!("Usage: mp4copy <source file> <target file>");
std::process::exit(1);
}
if let Err(err) = copy(&args[1], &args[2]) {
let _ = writeln!(io::stderr(), "{}", err);
}
}
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 dst_file = File::create(dst_filename)?;
let writer = BufWriter::new(dst_file);
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_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

@ -1,118 +1,84 @@
use mp4;
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufReader};
use std::path::Path;
use mp4::{Mp4Track, Result, TrackType};
fn main() {
let args: Vec<String> = env::args().collect();
match args.len() {
2 => {
let filename = &args[1];
let f = File::open(filename).unwrap();
if args.len() < 2 {
println!("Usage: mp4info <filename>");
std::process::exit(1);
}
let bmff = mp4::read_mp4(f).unwrap();
let moov = bmff.moov.unwrap();
// Print results.
println!("File:");
println!(" file size: {}", bmff.size);
println!(
" brands: {:?} {:?}\n",
bmff.ftyp.major_brand, bmff.ftyp.compatible_brands
);
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()) == mp4::TrackType::Video {
if let Some(ref s) = stts {
println!(
" frame rate: (computed): {:?}",
get_framerate(s.entries[0].sample_count, mdhd.duration, mdhd.timescale)
);
}
}
}
}
}
_ => {
println!("Usage: mp4info <filename>");
}
if let Err(err) = info(&args[1]) {
let _ = writeln!(io::stderr(), "{}", err);
}
}
fn get_handler_type(handler: &str) -> mp4::TrackType {
let mut typ: mp4::TrackType = mp4::TrackType::Unknown;
match handler {
"vide" => typ = mp4::TrackType::Video,
"soun" => typ = mp4::TrackType::Audio,
"meta" => typ = mp4::TrackType::Unknown,
_ => (),
fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
let f = File::open(filename)?;
let size = f.metadata()?.len();
let reader = BufReader::new(f);
let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
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(",");
}
return typ;
}
println!(" compatible_brands: {}", compatible_brands);
println!(
"Duration: {}, timescale: {}",
mp4.duration(),
mp4.timescale()
);
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
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 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 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,68 +0,0 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use crate::*;
use crate::atoms::elst::ElstBox;
#[derive(Debug, Default)]
pub struct EdtsBox {
pub elst: Option<ElstBox>,
}
impl EdtsBox {
pub(crate) fn new() -> EdtsBox {
Default::default()
}
}
impl Mp4Box for EdtsBox {
fn box_type(&self) -> BoxType {
BoxType::EdtsBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE;
if let Some(elst) = &self.elst {
size += elst.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for EdtsBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
let mut edts = EdtsBox::new();
let start = 0u64;
while start < size {
// Get box header.
let header = read_box_header(reader, start)?;
let BoxHeader{ name, size: s } = header;
match name {
BoxType::ElstBox => {
let elst = ElstBox::read_box(reader, s)?;
edts.elst = Some(elst);
}
_ => break
}
}
skip_read(reader, current, size)?;
Ok(edts)
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for EdtsBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
if let Some(elst) = &self.elst {
elst.write_box(writer)?;
}
Ok(size)
}
}

View file

@ -1,90 +0,0 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use crate::*;
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(&self) -> BoxType {
BoxType::MdiaBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE;
if let Some(mdhd) = &self.mdhd {
size += mdhd.box_size();
}
if let Some(hdlr) = &self.hdlr {
size += hdlr.box_size();
}
if let Some(minf) = &self.minf {
size += minf.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdiaBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
let mut mdia = MdiaBox::new();
let start = 0u64;
while start < size {
// Get box header.
let header = read_box_header(reader, start)?;
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);
}
_ => break
}
}
skip_read(reader, current, size)?;
Ok(mdia)
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for MdiaBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
if let Some(mdhd) = &self.mdhd {
mdhd.write_box(writer)?;
}
if let Some(hdlr) = &self.hdlr {
hdlr.write_box(writer)?;
}
if let Some(minf) = &self.minf {
minf.write_box(writer)?;
}
Ok(size)
}
}

View file

@ -1,85 +0,0 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use crate::*;
use crate::atoms::{vmhd::VmhdBox, stbl::StblBox};
#[derive(Debug, Default)]
pub struct MinfBox {
pub vmhd: Option<VmhdBox>,
pub stbl: Option<StblBox>,
}
impl MinfBox {
pub(crate) fn new() -> MinfBox {
Default::default()
}
}
impl Mp4Box for MinfBox {
fn box_type(&self) -> BoxType {
BoxType::MinfBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE;
if let Some(vmhd) = &self.vmhd {
size += vmhd.box_size();
}
if let Some(stbl) = &self.stbl {
size += stbl.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MinfBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
let mut minf = MinfBox::new();
let mut start = 0u64;
while start < size {
// Get box header.
let header = read_box_header(reader, start)?;
let BoxHeader{ name, size: s } = header;
match name {
BoxType::VmhdBox => {
let vmhd = VmhdBox::read_box(reader, s)?;
minf.vmhd = Some(vmhd);
}
BoxType::SmhdBox => {
start = s - HEADER_SIZE;
}
BoxType::DinfBox => {
start = s - HEADER_SIZE;
}
BoxType::StblBox => {
let stbl = StblBox::read_box(reader, s)?;
minf.stbl = Some(stbl);
}
_ => break
}
}
skip_read(reader, current, size)?;
Ok(minf)
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for MinfBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
if let Some(vmhd) = &self.vmhd {
vmhd.write_box(writer)?;
}
if let Some(stbl) = &self.stbl {
stbl.write_box(writer)?;
}
Ok(size)
}
}

View file

@ -1,239 +0,0 @@
use std::fmt;
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
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 stbl;
mod stts;
mod stsd;
pub use ftyp::FtypBox;
pub use moov::MoovBox;
pub const HEADER_EXT_SIZE: u64 = 4;
macro_rules! boxtype {
($( $name:ident => $value:expr ),*) => {
#[derive(Clone, Copy, PartialEq)]
pub enum BoxType {
$( $name, )*
UnknownBox(u32),
}
impl From<u32> for BoxType {
fn from(t: u32) -> BoxType {
match t {
$( $value => BoxType::$name, )*
_ => BoxType::UnknownBox(t),
}
}
}
impl Into<u32> for BoxType {
fn into(self) -> u32 {
match self {
$( BoxType::$name => $value, )*
BoxType::UnknownBox(t) => t,
}
}
}
}
}
boxtype!{
FtypBox => 0x66747970,
MvhdBox => 0x6d766864,
FreeBox => 0x66726565,
MdatBox => 0x6d646174,
MoovBox => 0x6d6f6f76,
MoofBox => 0x6d6f6f66,
TkhdBox => 0x746b6864,
EdtsBox => 0x65647473,
MdiaBox => 0x6d646961,
ElstBox => 0x656c7374,
MdhdBox => 0x6d646864,
HdlrBox => 0x68646c72,
MinfBox => 0x6d696e66,
VmhdBox => 0x766d6864,
StblBox => 0x7374626c,
StsdBox => 0x73747364,
SttsBox => 0x73747473,
TrakBox => 0x7472616b,
UdtaBox => 0x75647461,
DinfBox => 0x64696e66,
SmhdBox => 0x736d6864,
Avc1Box => 0x61766331,
Mp4aBox => 0x6d703461
}
impl fmt::Debug 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(&self) -> BoxType;
fn box_size(&self) -> u64;
}
pub trait ReadBox<T>: Sized {
fn read_box(_: T, size: u64) -> Result<Self>;
}
pub trait WriteBox<T>: Sized {
fn write_box(&self, _: T) -> Result<u64>;
}
pub fn read_box_header_ext<R: Read>(reader: &mut BufReader<R>) -> Result<(u8, u32)> {
let version = reader.read_u8()?;
let flags_a = reader.read_u8()?;
let flags_b = reader.read_u8()?;
let flags_c = reader.read_u8()?;
let flags = u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c);
Ok((version, flags))
}
pub fn write_box_header_ext<W: Write>(w: &mut BufWriter<W>, v: u8, f: u32) -> Result<u64> {
let d = u32::from(v) << 24 | f;
w.write_u32::<BigEndian>(d)?;
Ok(4)
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for BoxHeader {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
if self.size > u32::MAX as u64 {
writer.write_u32::<BigEndian>(1)?;
writer.write_u32::<BigEndian>(self.name.into())?;
writer.write_u64::<BigEndian>(self.size)?;
Ok(16)
} else {
writer.write_u32::<BigEndian>(self.size as u32)?;
writer.write_u32::<BigEndian>(self.name.into())?;
Ok(8)
}
}
}
pub fn skip_read<R: Read + Seek>(reader: &mut BufReader<R>, current: u64, size: u64) -> Result<i64> {
let after = reader.seek(SeekFrom::Current(0))?;
let remaining_bytes = (size - (after - current)) as i64;
let size = remaining_bytes - HEADER_SIZE as i64;
reader.seek(SeekFrom::Current(size))?;
Ok(size)
}
pub fn skip_write<W: Write>(writer: &mut BufWriter<W>, size: u64) -> Result<u64> {
for _ in 0..size {
writer.write_u8(0)?;
}
Ok(size)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fourcc() {
let ftyp_fcc = 0x66747970;
let ftyp_value = FourCC::from(ftyp_fcc);
assert_eq!(ftyp_value.value, "ftyp");
let ftyp_fcc2 = ftyp_value.into();
assert_eq!(ftyp_fcc, ftyp_fcc2);
}
}

View file

@ -1,73 +0,0 @@
use std::io::{BufReader, Seek, Read, BufWriter, Write};
use crate::*;
use crate::atoms::{mvhd::MvhdBox, trak::TrakBox};
#[derive(Debug, 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(&self) -> BoxType {
BoxType::MoovBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE + self.mvhd.box_size();
for trak in self.traks.iter() {
size += trak.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MoovBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let mut moov = MoovBox::new();
let mut start = 0u64;
while start < size {
// Get box header.
let header = read_box_header(reader, start)?;
let BoxHeader{ name, size: s } = header;
match name {
BoxType::MvhdBox => {
moov.mvhd = MvhdBox::read_box(reader, s)?;
}
BoxType::TrakBox => {
let trak = TrakBox::read_box(reader, s)?;
moov.traks.push(trak);
}
BoxType::UdtaBox => {
start = s - HEADER_SIZE;
}
_ => break
}
}
Ok(moov)
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for MoovBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
self.mvhd.write_box(writer)?;
for trak in self.traks.iter() {
trak.write_box(writer)?;
}
Ok(0)
}
}

View file

@ -1,76 +0,0 @@
use std::io::{BufReader, Seek, Read, BufWriter, Write};
use crate::*;
use crate::atoms::{stts::SttsBox, stsd::StsdBox};
#[derive(Debug, Default)]
pub struct StblBox {
pub stts: Option<SttsBox>,
pub stsd: Option<StsdBox>,
}
impl StblBox {
pub(crate) fn new() -> StblBox {
Default::default()
}
}
impl Mp4Box for StblBox {
fn box_type(&self) -> BoxType {
BoxType::StblBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE;
if let Some(stts) = &self.stts {
size += stts.box_size();
}
if let Some(stsd) = &self.stsd {
size += stsd.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for StblBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let mut stbl = StblBox::new();
let start = 0u64;
while start < size {
// Get box header.
let header = read_box_header(reader, start)?;
let BoxHeader{ name, size: s } = header;
match name {
BoxType::SttsBox => {
let stts = SttsBox::read_box(reader, s)?;
stbl.stts = Some(stts);
}
BoxType::StsdBox => {
let stsd = StsdBox::read_box(reader, s)?;
stbl.stsd = Some(stsd);
}
_ => break
}
}
Ok(stbl)
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for StblBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
if let Some(stts) = &self.stts {
stts.write_box(writer)?;
}
if let Some(stsd) = &self.stsd {
stsd.write_box(writer)?;
}
Ok(size)
}
}

View file

@ -1,59 +0,0 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt};
use crate::*;
#[derive(Debug)]
pub struct StsdBox {
pub version: u8,
pub flags: u32,
}
impl Mp4Box for StsdBox {
fn box_type(&self) -> BoxType {
BoxType::StsdBox
}
fn box_size(&self) -> u64 {
// TODO
0
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for StsdBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
let (version, flags) = read_box_header_ext(reader)?;
let _entry_count = reader.read_u32::<BigEndian>()?;
let mut start = 0u64;
while start < size {
// Get box header.
let header = read_box_header(reader, start)?;
let BoxHeader{ name, size: s } = header;
match name {
BoxType::Avc1Box => {}
BoxType::Mp4aBox => {}
_ => break
}
start += s - HEADER_SIZE;
}
skip_read(reader, current, size)?;
Ok(StsdBox {
version,
flags,
})
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for StsdBox {
fn write_box(&self, _writer: &mut BufWriter<W>) -> Result<u64> {
// TODO
Ok(0)
}
}

View file

@ -1,135 +0,0 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crate::*;
#[derive(Debug, Default, PartialEq)]
pub struct SttsBox {
pub version: u8,
pub flags: u32,
pub entries: Vec<SttsEntry>,
}
#[derive(Debug, Default, PartialEq)]
pub struct SttsEntry {
pub sample_count: u32,
pub sample_delta: u32,
}
impl Mp4Box for SttsBox {
fn box_type(&self) -> BoxType {
BoxType::SttsBox
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64)
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for SttsBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
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 _i in 0..entry_count {
let entry = SttsEntry {
sample_count: reader.read_u32::<BigEndian>()?,
sample_delta: reader.read_u32::<BigEndian>()?,
};
entries.push(entry);
}
skip_read(reader, current, size)?;
Ok(SttsBox {
version,
flags,
entries,
})
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for SttsBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
write_box_header_ext(writer, self.version, self.flags)?;
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
for entry in self.entries.iter() {
writer.write_u32::<BigEndian>(entry.sample_count)?;
writer.write_u32::<BigEndian>(entry.sample_delta)?;
}
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::read_box_header;
use std::io::Cursor;
#[test]
fn test_stts32() {
let src_box = SttsBox {
version: 0,
flags: 0,
entries: vec![
SttsEntry {sample_count: 29726, sample_delta: 1024},
SttsEntry {sample_count: 1, sample_delta: 512},
],
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::SttsBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}
#[test]
fn test_stts64() {
let src_box = SttsBox {
version: 1,
flags: 0,
entries: vec![
SttsEntry {sample_count: 29726, sample_delta: 1024},
SttsEntry {sample_count: 1, sample_delta: 512},
],
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::SttsBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}
}

View file

@ -1,90 +0,0 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use crate::*;
use crate::atoms::{tkhd::TkhdBox, edts::EdtsBox, mdia::MdiaBox};
#[derive(Debug, Default)]
pub struct TrakBox {
pub tkhd: Option<TkhdBox>,
pub edts: Option<EdtsBox>,
pub mdia: Option<MdiaBox>,
}
impl TrakBox {
pub(crate) fn new() -> TrakBox {
Default::default()
}
}
impl Mp4Box for TrakBox {
fn box_type(&self) -> BoxType {
BoxType::TrakBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE;
if let Some(tkhd) = &self.tkhd {
size += tkhd.box_size();
}
if let Some(edts) = &self.edts {
size += edts.box_size();
}
if let Some(mdia) = &self.mdia {
size += mdia.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TrakBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
let mut trak = TrakBox::new();
let start = 0u64;
while start < size {
// Get box header.
let header = read_box_header(reader, start)?;
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);
}
_ => break
}
}
skip_read(reader, current, size)?;
Ok(trak)
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for TrakBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
if let Some(tkhd) = &self.tkhd {
tkhd.write_box(writer)?;
}
if let Some(edts) = &self.edts {
edts.write_box(writer)?;
}
if let Some(mdia) = &self.mdia {
mdia.write_box(writer)?;
}
Ok(size)
}
}

View file

@ -1,9 +1,23 @@
use thiserror::Error;
use crate::mp4box::BoxType;
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
IoError(#[from] std::io::Error),
#[error("{0}")]
InvalidData(&'static str),
#[error("{0} not found")]
BoxNotFound(BoxType),
#[error("{0} and {1} not found")]
Box2NotFound(BoxType, BoxType),
#[error("trak[{0}] not found")]
TrakNotFound(u32),
#[error("trak[{0}].{1} not found")]
BoxInTrakNotFound(u32, BoxType),
#[error("trak[{0}].stbl.{1} not found")]
BoxInStblNotFound(u32, BoxType),
#[error("trak[{0}].stbl.{1}.entry[{2}] not found")]
EntryInStblNotFound(u32, BoxType, u32),
}

View file

@ -1,138 +1,18 @@
use std::io::{BufReader, Read, SeekFrom, Seek};
use std::fs::File;
use std::convert::TryInto;
mod atoms;
use crate::atoms::*;
mod error;
pub use error::Error;
pub type Result<T> = std::result::Result<T, Error>;
// XXX if box has largesize
const HEADER_SIZE: u64 = 8;
mod types;
pub use types::*;
#[derive(Debug, PartialEq)]
pub enum TrackType {
Audio,
Video,
Metadata,
Unknown,
}
mod mp4box;
#[derive(Debug, Default)]
pub struct BMFF {
pub ftyp: FtypBox,
pub moov: Option<MoovBox>,
pub size: u64,
}
impl BMFF {
fn new() -> BMFF {
Default::default()
}
}
#[derive(Debug, Clone, Copy)]
struct BoxHeader {
name: BoxType,
size: u64,
}
impl BoxHeader {
fn new(name: BoxType, size: u64) -> Self {
Self { name, size }
}
}
pub fn read_mp4(f: File) -> Result<BMFF> {
// Open file and read boxes.
let bmff = read_boxes(f)?;
Ok(bmff)
}
fn read_boxes(f: File) -> Result<BMFF> {
let filesize = f.metadata()?.len();
let mut reader = BufReader::new(f);
let mut bmff = BMFF::new();
bmff.size = filesize;
let mut start = 0u64;
while start < filesize {
// Get box header.
let header = read_box_header(&mut reader, start)?;
let BoxHeader{ name, size } = header;
// Match and parse the atom boxes.
match name {
BoxType::FtypBox => {
let ftyp = FtypBox::read_box(&mut reader, size)?;
bmff.ftyp = ftyp;
}
BoxType::FreeBox => {
start = 0;
}
BoxType::MdatBox => {
start = size - HEADER_SIZE;
}
BoxType::MoovBox => {
let moov = MoovBox::read_box(&mut reader, size)?;
bmff.moov = Some(moov);
}
BoxType::MoofBox => {
start = size - HEADER_SIZE;
}
_ => {
// Skip over unsupported boxes, but stop if the size is zero,
// meaning the last box has been reached.
if size == 0 {
break;
} else {
start = size - HEADER_SIZE;
}
}
};
}
Ok(bmff)
}
// TODO: if size is 0, then this box is the last one in the file
fn read_box_header<R: Read + Seek>(reader: &mut BufReader<R>, start: u64) -> Result<BoxHeader> {
// Seek to offset.
let _r = reader.seek(SeekFrom::Current(start as i64));
// Create and read to buf.
let mut buf = [0u8;8]; // 8 bytes for box header.
reader.read(&mut buf)?;
// Get size.
let s = buf[0..4].try_into().unwrap();
let size = u32::from_be_bytes(s);
// Get box type string.
let t = buf[4..8].try_into().unwrap();
let typ = u32::from_be_bytes(t);
// Get largesize if size is 1
if size == 1 {
reader.read(&mut buf)?;
let s = buf.try_into().unwrap();
let largesize = u64::from_be_bytes(s);
Ok(BoxHeader {
name: BoxType::from(typ),
size: largesize,
})
} else {
Ok(BoxHeader {
name: BoxType::from(typ),
size: size as u64,
})
}
}
mod track;
pub use track::{Mp4Track, TrackConfig};
mod reader;
pub use reader::Mp4Reader;
mod writer;
pub use writer::{Mp4Config, Mp4Writer};

308
src/mp4box/avc1.rs Normal file
View file

@ -0,0 +1,308 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq)]
pub struct Avc1Box {
pub data_reference_index: u16,
pub width: u16,
pub height: u16,
pub horizresolution: FixedPointU16,
pub vertresolution: FixedPointU16,
pub frame_count: u16,
pub depth: u16,
pub avcc: AvcCBox,
}
impl Default for Avc1Box {
fn default() -> Self {
Avc1Box {
data_reference_index: 0,
width: 0,
height: 0,
horizresolution: FixedPointU16::new(0x48),
vertresolution: FixedPointU16::new(0x48),
frame_count: 1,
depth: 0x0018,
avcc: AvcCBox::default(),
}
}
}
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 + 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 = box_start(reader)?;
reader.read_u32::<BigEndian>()?; // reserved
reader.read_u16::<BigEndian>()?; // reserved
let data_reference_index = reader.read_u16::<BigEndian>()?;
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
reader.read_u64::<BigEndian>()?; // pre-defined
reader.read_u32::<BigEndian>()?; // pre-defined
let width = reader.read_u16::<BigEndian>()?;
let height = reader.read_u16::<BigEndian>()?;
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
let depth = reader.read_u16::<BigEndian>()?;
reader.read_i16::<BigEndian>()?; // pre-defined
let header = BoxHeader::read(reader)?;
let BoxHeader { name, size: s } = header;
if name == BoxType::AvcCBox {
let avcc = AvcCBox::read_box(reader, s)?;
skip_read_to(reader, start + size)?;
Ok(Avc1Box {
data_reference_index,
width,
height,
horizresolution,
vertresolution,
frame_count,
depth,
avcc,
})
} else {
Err(Error::InvalidData("avcc not found"))
}
}
}
impl<W: Write> WriteBox<&mut W> for Avc1Box {
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_u32::<BigEndian>(0)?; // pre-defined, reserved
writer.write_u64::<BigEndian>(0)?; // pre-defined
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.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
skip_write(writer, 32)?;
writer.write_u16::<BigEndian>(self.depth)?;
writer.write_i16::<BigEndian>(-1)?; // pre-defined
self.avcc.write_box(writer)?;
Ok(size)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct AvcCBox {
pub configuration_version: u8,
pub avc_profile_indication: u8,
pub profile_compatibility: u8,
pub avc_level_indication: u8,
pub length_size_minus_one: u8,
pub sequence_parameter_sets: Vec<NalUnit>,
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
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE + 7;
for sps in self.sequence_parameter_sets.iter() {
size += sps.size() as u64;
}
for pps in self.picture_parameter_sets.iter() {
size += pps.size() as u64;
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut R> for AvcCBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let configuration_version = reader.read_u8()?;
let avc_profile_indication = reader.read_u8()?;
let profile_compatibility = reader.read_u8()?;
let avc_level_indication = reader.read_u8()?;
let length_size_minus_one = reader.read_u8()? & 0x3;
let num_of_spss = reader.read_u8()? & 0x1F;
let mut sequence_parameter_sets = Vec::with_capacity(num_of_spss as usize);
for _ in 0..num_of_spss {
let nal_unit = NalUnit::read(reader)?;
sequence_parameter_sets.push(nal_unit);
}
let num_of_ppss = reader.read_u8()?;
let mut picture_parameter_sets = Vec::with_capacity(num_of_ppss as usize);
for _ in 0..num_of_ppss {
let nal_unit = NalUnit::read(reader)?;
picture_parameter_sets.push(nal_unit);
}
skip_read_to(reader, start + size)?;
Ok(AvcCBox {
configuration_version,
avc_profile_indication,
profile_compatibility,
avc_level_indication,
length_size_minus_one,
sequence_parameter_sets,
picture_parameter_sets,
})
}
}
impl<W: Write> WriteBox<&mut W> for AvcCBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(Self::box_type(), size).write(writer)?;
writer.write_u8(self.configuration_version)?;
writer.write_u8(self.avc_profile_indication)?;
writer.write_u8(self.profile_compatibility)?;
writer.write_u8(self.avc_level_indication)?;
writer.write_u8(self.length_size_minus_one | 0xFC)?;
writer.write_u8(self.sequence_parameter_sets.len() as u8 | 0xE0)?;
for sps in self.sequence_parameter_sets.iter() {
sps.write(writer)?;
}
writer.write_u8(self.picture_parameter_sets.len() as u8)?;
for pps in self.picture_parameter_sets.iter() {
pps.write(writer)?;
}
Ok(size)
}
}
#[derive(Debug, 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 {
fn size(&self) -> usize {
2 + self.bytes.len()
}
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 })
}
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);
}
}

87
src/mp4box/co64.rs Normal file
View file

@ -0,0 +1,87 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Co64Box {
pub version: u8,
pub flags: u32,
pub entries: Vec<u64>,
}
impl Mp4Box for Co64Box {
fn box_type() -> BoxType {
BoxType::Co64Box
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64)
}
}
impl<R: Read + Seek> ReadBox<&mut R> for Co64Box {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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 _i in 0..entry_count {
let chunk_offset = reader.read_u64::<BigEndian>()?;
entries.push(chunk_offset);
}
skip_read_to(reader, start + size)?;
Ok(Co64Box {
version,
flags,
entries,
})
}
}
impl<W: Write> WriteBox<&mut W> for Co64Box {
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)?;
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
for chunk_offset in self.entries.iter() {
writer.write_u64::<BigEndian>(*chunk_offset)?;
}
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
fn test_co64() {
let src_box = Co64Box {
version: 0,
flags: 0,
entries: vec![267, 1970, 2535, 2803, 11843, 22223, 33584],
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::Co64Box);
assert_eq!(src_box.box_size(), header.size);
let dst_box = Co64Box::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

106
src/mp4box/ctts.rs Normal file
View file

@ -0,0 +1,106 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct CttsBox {
pub version: u8,
pub flags: u32,
pub entries: Vec<CttsEntry>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct CttsEntry {
pub sample_count: u32,
pub sample_offset: i32,
}
impl Mp4Box for CttsBox {
fn box_type() -> BoxType {
BoxType::CttsBox
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64)
}
}
impl<R: Read + Seek> ReadBox<&mut R> for CttsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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 entry = CttsEntry {
sample_count: reader.read_u32::<BigEndian>()?,
sample_offset: reader.read_i32::<BigEndian>()?,
};
entries.push(entry);
}
skip_read_to(reader, start + size)?;
Ok(CttsBox {
version,
flags,
entries,
})
}
}
impl<W: Write> WriteBox<&mut W> for CttsBox {
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)?;
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
for entry in self.entries.iter() {
writer.write_u32::<BigEndian>(entry.sample_count)?;
writer.write_i32::<BigEndian>(entry.sample_offset)?;
}
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
fn test_ctts() {
let src_box = CttsBox {
version: 0,
flags: 0,
entries: vec![
CttsEntry {
sample_count: 1,
sample_offset: 200,
},
CttsEntry {
sample_count: 2,
sample_offset: -100,
},
],
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::CttsBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = CttsBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

65
src/mp4box/edts.rs Normal file
View file

@ -0,0 +1,65 @@
use std::io::{Read, Seek, Write};
use crate::mp4box::elst::ElstBox;
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct EdtsBox {
pub elst: Option<ElstBox>,
}
impl EdtsBox {
pub(crate) fn new() -> EdtsBox {
Default::default()
}
}
impl Mp4Box for EdtsBox {
fn box_type() -> BoxType {
BoxType::EdtsBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE;
if let Some(ref elst) = self.elst {
size += elst.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut R> for EdtsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let mut edts = EdtsBox::new();
let header = BoxHeader::read(reader)?;
let BoxHeader { name, size: s } = header;
match name {
BoxType::ElstBox => {
let elst = ElstBox::read_box(reader, s)?;
edts.elst = Some(elst);
}
_ => {}
}
skip_read_to(reader, start + size)?;
Ok(edts)
}
}
impl<W: Write> WriteBox<&mut W> for EdtsBox {
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 elst) = self.elst {
elst.write_box(writer)?;
}
Ok(size)
}
}

View file

@ -1,17 +1,16 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::*;
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,
@ -20,7 +19,7 @@ pub struct ElstEntry {
}
impl Mp4Box for ElstBox {
fn box_type(&self) -> BoxType {
fn box_type() -> BoxType {
BoxType::ElstBox
}
@ -36,29 +35,28 @@ impl Mp4Box for ElstBox {
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for ElstBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
impl<R: Read + Seek> ReadBox<&mut R> for ElstBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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>()?,
@ -66,7 +64,8 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for ElstBox {
};
entries.push(entry);
}
skip_read(reader, current, size)?;
skip_read_to(reader, start + size)?;
Ok(ElstBox {
version,
@ -76,10 +75,10 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for ElstBox {
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for ElstBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
impl<W: Write> WriteBox<&mut W> for ElstBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
BoxHeader::new(Self::box_type(), size).write(writer)?;
write_box_header_ext(writer, self.version, self.flags)?;
@ -103,7 +102,7 @@ impl<W: Write> WriteBox<&mut BufWriter<W>> for ElstBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::read_box_header;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
@ -119,22 +118,16 @@ mod tests {
}],
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::ElstBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::ElstBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
#[test]
@ -150,21 +143,15 @@ mod tests {
}],
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::ElstBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::ElstBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

View file

@ -1,10 +1,9 @@
use std::io::{BufReader, Seek, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::*;
use crate::mp4box::*;
#[derive(Debug, Default, PartialEq)]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct FtypBox {
pub major_brand: FourCC,
pub minor_version: u32,
@ -12,7 +11,7 @@ pub struct FtypBox {
}
impl Mp4Box for FtypBox {
fn box_type(&self) -> BoxType {
fn box_type() -> BoxType {
BoxType::FtypBox
}
@ -21,8 +20,10 @@ impl Mp4Box for FtypBox {
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for FtypBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
impl<R: Read + Seek> ReadBox<&mut R> for FtypBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let major = reader.read_u32::<BigEndian>()?;
let minor = reader.read_u32::<BigEndian>()?;
if size % 4 != 0 {
@ -36,6 +37,8 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for FtypBox {
brands.push(From::from(b));
}
skip_read_to(reader, start + size)?;
Ok(FtypBox {
major_brand: From::from(major),
minor_version: minor,
@ -44,10 +47,10 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for FtypBox {
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for FtypBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
impl<W: Write> WriteBox<&mut W> for FtypBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
BoxHeader::new(Self::box_type(), size).write(writer)?;
writer.write_u32::<BigEndian>((&self.major_brand).into())?;
writer.write_u32::<BigEndian>(self.minor_version)?;
@ -61,37 +64,41 @@ impl<W: Write> WriteBox<&mut BufWriter<W>> for FtypBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::read_box_header;
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();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::FtypBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::FtypBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = FtypBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = FtypBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

View file

@ -1,10 +1,9 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::*;
use crate::mp4box::*;
#[derive(Debug, Default, PartialEq)]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct HdlrBox {
pub version: u8,
pub flags: u32,
@ -13,7 +12,7 @@ pub struct HdlrBox {
}
impl Mp4Box for HdlrBox {
fn box_type(&self) -> BoxType {
fn box_type() -> BoxType {
BoxType::HdlrBox
}
@ -22,18 +21,18 @@ impl Mp4Box for HdlrBox {
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for HdlrBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
impl<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
reader.read_u32::<BigEndian>()?; // pre-defined
let handler = reader.read_u32::<BigEndian>()?;
let n = reader.seek(SeekFrom::Current(12))?; // 12 bytes reserved.
skip_read(reader, 12)?; // reserved
let buf_size = (size - (n - current)) - HEADER_SIZE - 1;
let buf_size = size - HEADER_SIZE - HEADER_EXT_SIZE - 20 - 1;
let mut buf = vec![0u8; buf_size as usize];
reader.read_exact(&mut buf)?;
@ -41,11 +40,11 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for HdlrBox {
Ok(t) => {
assert_eq!(t.len(), buf_size as usize);
t
},
}
_ => String::from("null"),
};
skip_read(reader, current, size)?;
skip_read_to(reader, start + size)?;
Ok(HdlrBox {
version,
@ -56,10 +55,10 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for HdlrBox {
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for HdlrBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
impl<W: Write> WriteBox<&mut W> for HdlrBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
BoxHeader::new(Self::box_type(), size).write(writer)?;
write_box_header_ext(writer, self.version, self.flags)?;
@ -81,7 +80,7 @@ impl<W: Write> WriteBox<&mut BufWriter<W>> for HdlrBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::read_box_header;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
@ -93,21 +92,15 @@ mod tests {
name: String::from("VideoHandler"),
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::HdlrBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::HdlrBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

View file

@ -1,11 +1,10 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, 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::mp4box::*;
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct MdhdBox {
pub version: u8,
pub flags: u32,
@ -31,7 +30,7 @@ impl Default for MdhdBox {
}
impl Mp4Box for MdhdBox {
fn box_type(&self) -> BoxType {
fn box_type() -> BoxType {
BoxType::MdhdBox
}
@ -49,32 +48,32 @@ impl Mp4Box for MdhdBox {
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdhdBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
impl<R: Read + Seek> ReadBox<&mut R> for MdhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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);
skip_read(reader, current, size)?;
let language = language_string(language_code);
skip_read_to(reader, start + size)?;
Ok(MdhdBox {
version,
@ -88,10 +87,10 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdhdBox {
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for MdhdBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
impl<W: Write> WriteBox<&mut W> for MdhdBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
BoxHeader::new(Self::box_type(), size).write(writer)?;
write_box_header_ext(writer, self.version, self.flags)?;
@ -108,7 +107,7 @@ impl<W: Write> WriteBox<&mut BufWriter<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
@ -116,7 +115,7 @@ impl<W: Write> WriteBox<&mut BufWriter<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;
@ -131,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;
@ -142,12 +141,12 @@ fn get_language_code(language: &str) -> u16 {
#[cfg(test)]
mod tests {
use super::*;
use crate::read_box_header;
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);
}
@ -170,22 +169,16 @@ mod tests {
language: String::from("und"),
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::MdhdBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::MdhdBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
#[test]
@ -200,21 +193,15 @@ mod tests {
language: String::from("eng"),
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::MdhdBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::MdhdBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

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

98
src/mp4box/minf.rs Normal file
View file

@ -0,0 +1,98 @@
use std::io::{Read, Seek, SeekFrom, Write};
use crate::mp4box::*;
use crate::mp4box::{smhd::SmhdBox, stbl::StblBox, vmhd::VmhdBox};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct MinfBox {
pub vmhd: Option<VmhdBox>,
pub smhd: Option<SmhdBox>,
pub stbl: StblBox,
}
impl Mp4Box for MinfBox {
fn box_type() -> BoxType {
BoxType::MinfBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE;
if let Some(ref vmhd) = self.vmhd {
size += vmhd.box_size();
}
if let Some(ref smhd) = self.smhd {
size += smhd.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 = box_start(reader)?;
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;
match name {
BoxType::VmhdBox => {
vmhd = Some(VmhdBox::read_box(reader, s)?);
}
BoxType::SmhdBox => {
smhd = Some(SmhdBox::read_box(reader, s)?);
}
BoxType::DinfBox => {
// XXX warn!()
skip_box(reader, s)?;
}
BoxType::StblBox => {
stbl = Some(StblBox::read_box(reader, s)?);
}
_ => {
// XXX warn!()
skip_box(reader, s)?;
}
}
current = reader.seek(SeekFrom::Current(0))?;
}
if stbl.is_none() {
return Err(Error::BoxNotFound(BoxType::StblBox));
}
skip_read_to(reader, start + size)?;
Ok(MinfBox {
vmhd,
smhd,
stbl: stbl.unwrap(),
})
}
}
impl<W: Write> WriteBox<&mut W> for MinfBox {
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 vmhd) = self.vmhd {
vmhd.write_box(writer)?;
}
if let Some(ref smhd) = self.smhd {
smhd.write_box(writer)?;
}
self.stbl.write_box(writer)?;
Ok(size)
}
}

223
src/mp4box/mod.rs Normal file
View file

@ -0,0 +1,223 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::convert::TryInto;
use std::io::{Read, Seek, SeekFrom, Write};
use crate::*;
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;
pub const HEADER_SIZE: u64 = 8;
// const HEADER_LARGE_SIZE: u64 = 16;
pub const HEADER_EXT_SIZE: u64 = 4;
macro_rules! boxtype {
($( $name:ident => $value:expr ),*) => {
#[derive(Clone, Copy, PartialEq)]
pub enum BoxType {
$( $name, )*
UnknownBox(u32),
}
impl From<u32> for BoxType {
fn from(t: u32) -> BoxType {
match t {
$( $value => BoxType::$name, )*
_ => BoxType::UnknownBox(t),
}
}
}
impl Into<u32> for BoxType {
fn into(self) -> u32 {
match self {
$( BoxType::$name => $value, )*
BoxType::UnknownBox(t) => t,
}
}
}
}
}
boxtype! {
FtypBox => 0x66747970,
MvhdBox => 0x6d766864,
FreeBox => 0x66726565,
MdatBox => 0x6d646174,
MoovBox => 0x6d6f6f76,
MoofBox => 0x6d6f6f66,
TkhdBox => 0x746b6864,
EdtsBox => 0x65647473,
MdiaBox => 0x6d646961,
ElstBox => 0x656c7374,
MdhdBox => 0x6d646864,
HdlrBox => 0x68646c72,
MinfBox => 0x6d696e66,
VmhdBox => 0x766d6864,
StblBox => 0x7374626c,
StsdBox => 0x73747364,
SttsBox => 0x73747473,
CttsBox => 0x63747473,
StssBox => 0x73747373,
StscBox => 0x73747363,
StszBox => 0x7374737A,
StcoBox => 0x7374636F,
Co64Box => 0x636F3634,
TrakBox => 0x7472616b,
UdtaBox => 0x75647461,
DinfBox => 0x64696e66,
SmhdBox => 0x736d6864,
Avc1Box => 0x61766331,
AvcCBox => 0x61766343,
Mp4aBox => 0x6d703461,
EsdsBox => 0x65736473
}
pub trait Mp4Box: Sized {
fn box_type() -> BoxType;
fn box_size(&self) -> u64;
}
pub trait ReadBox<T>: Sized {
fn read_box(_: T, size: u64) -> Result<Self>;
}
pub trait WriteBox<T>: Sized {
fn write_box(&self, _: T) -> Result<u64>;
}
#[derive(Debug, Clone, Copy)]
pub struct BoxHeader {
pub name: BoxType,
pub size: u64,
}
impl BoxHeader {
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.
reader.read(&mut buf)?;
// Get size.
let s = buf[0..4].try_into().unwrap();
let size = u32::from_be_bytes(s);
// Get box type string.
let t = buf[4..8].try_into().unwrap();
let typ = u32::from_be_bytes(t);
// Get largesize if size is 1
if size == 1 {
reader.read(&mut buf)?;
let s = buf.try_into().unwrap();
let largesize = u64::from_be_bytes(s);
Ok(BoxHeader {
name: BoxType::from(typ),
size: largesize,
})
} else {
Ok(BoxHeader {
name: BoxType::from(typ),
size: size as u64,
})
}
}
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())?;
writer.write_u64::<BigEndian>(self.size)?;
Ok(16)
} else {
writer.write_u32::<BigEndian>(self.size as u32)?;
writer.write_u32::<BigEndian>(self.name.into())?;
Ok(8)
}
}
}
pub fn read_box_header_ext<R: Read>(reader: &mut R) -> Result<(u8, u32)> {
let version = reader.read_u8()?;
let flags = reader.read_u24::<BigEndian>()?;
Ok((version, flags))
}
pub fn write_box_header_ext<W: Write>(w: &mut W, v: u8, f: u32) -> Result<u64> {
w.write_u8(v)?;
w.write_u24::<BigEndian>(f)?;
Ok(4)
}
pub fn box_start<R: Seek>(reader: &mut R) -> Result<u64> {
Ok(reader.seek(SeekFrom::Current(0))? - HEADER_SIZE)
}
pub fn skip_read<R: Read + Seek>(reader: &mut R, size: i64) -> Result<()> {
assert!(size >= 0);
reader.seek(SeekFrom::Current(size))?;
Ok(())
}
pub fn skip_read_to<R: Read + Seek>(reader: &mut R, pos: u64) -> Result<()> {
reader.seek(SeekFrom::Start(pos))?;
Ok(())
}
pub fn skip_box<R: Read + Seek>(reader: &mut R, size: u64) -> Result<()> {
let start = box_start(reader)?;
skip_read_to(reader, start + size)?;
Ok(())
}
pub fn skip_write<W: Write>(writer: &mut W, size: u64) -> Result<()> {
for _ in 0..size {
writer.write_u8(0)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fourcc() {
let ftyp_fcc = 0x66747970;
let ftyp_value = FourCC::from(ftyp_fcc);
assert_eq!(ftyp_value.value, "ftyp");
let ftyp_fcc2 = ftyp_value.into();
assert_eq!(ftyp_fcc, ftyp_fcc2);
}
}

85
src/mp4box/moov.rs Normal file
View file

@ -0,0 +1,85 @@
use std::io::{Read, Seek, SeekFrom, Write};
use crate::mp4box::*;
use crate::mp4box::{mvhd::MvhdBox, trak::TrakBox};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct MoovBox {
pub mvhd: MvhdBox,
pub traks: Vec<TrakBox>,
}
impl Mp4Box for MoovBox {
fn box_type() -> BoxType {
BoxType::MoovBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_SIZE + self.mvhd.box_size();
for trak in self.traks.iter() {
size += trak.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
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;
match name {
BoxType::MvhdBox => {
mvhd = Some(MvhdBox::read_box(reader, s)?);
}
BoxType::TrakBox => {
let trak = TrakBox::read_box(reader, s)?;
traks.push(trak);
}
BoxType::UdtaBox => {
// XXX warn!()
skip_box(reader, s)?;
}
_ => {
// XXX warn!()
skip_box(reader, s)?;
}
}
current = reader.seek(SeekFrom::Current(0))?;
}
if mvhd.is_none() {
return Err(Error::BoxNotFound(BoxType::MvhdBox));
}
skip_read_to(reader, start + size)?;
Ok(MoovBox {
mvhd: mvhd.unwrap(),
traks,
})
}
}
impl<W: Write> WriteBox<&mut W> for MoovBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(Self::box_type(), size).write(writer)?;
self.mvhd.write_box(writer)?;
for trak in self.traks.iter() {
trak.write_box(writer)?;
}
Ok(0)
}
}

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,11 +1,9 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use num_rational::Ratio;
use std::io::{Read, Seek, Write};
use crate::*;
use crate::mp4box::*;
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct MvhdBox {
pub version: u8,
pub flags: u32,
@ -13,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 {
@ -25,13 +23,13 @@ impl Default for MvhdBox {
modification_time: 0,
timescale: 1000,
duration: 0,
rate: Ratio::new_raw(0x00010000, 0x10000),
rate: FixedPointU16::new(1),
}
}
}
impl Mp4Box for MvhdBox {
fn box_type(&self) -> BoxType {
fn box_type() -> BoxType {
BoxType::MvhdBox
}
@ -48,34 +46,33 @@ impl Mp4Box for MvhdBox {
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MvhdBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
impl<R: Read + Seek> ReadBox<&mut R> for MvhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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);
skip_read(reader, current, size)?;
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>()?);
Ok(MvhdBox{
skip_read_to(reader, start + size)?;
Ok(MvhdBox {
version,
flags,
creation_time,
@ -87,10 +84,10 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MvhdBox {
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for MvhdBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
impl<W: Write> WriteBox<&mut W> for MvhdBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
BoxHeader::new(Self::box_type(), size).write(writer)?;
write_box_header_ext(writer, self.version, self.flags)?;
@ -106,7 +103,7 @@ impl<W: Write> WriteBox<&mut BufWriter<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)?;
@ -118,7 +115,7 @@ impl<W: Write> WriteBox<&mut BufWriter<W>> for MvhdBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::read_box_header;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
@ -130,25 +127,19 @@ mod tests {
modification_time: 200,
timescale: 1000,
duration: 634634,
rate: Ratio::new_raw(0x00010000, 0x10000),
rate: FixedPointU16::new(1),
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::MvhdBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::MvhdBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
#[test]
@ -160,24 +151,18 @@ mod tests {
modification_time: 200,
timescale: 1000,
duration: 634634,
rate: Ratio::new_raw(0x00010000, 0x10000),
rate: FixedPointU16::new(1),
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::MvhdBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::MvhdBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

90
src/mp4box/smhd.rs Normal file
View file

@ -0,0 +1,90 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq)]
pub struct SmhdBox {
pub version: u8,
pub flags: u32,
pub balance: FixedPointI8,
}
impl Default for SmhdBox {
fn default() -> Self {
SmhdBox {
version: 0,
flags: 0,
balance: FixedPointI8::new_raw(0),
}
}
}
impl Mp4Box for SmhdBox {
fn box_type() -> BoxType {
BoxType::SmhdBox
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 4
}
}
impl<R: Read + Seek> ReadBox<&mut R> for SmhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
let balance = FixedPointI8::new_raw(reader.read_i16::<BigEndian>()?);
skip_read_to(reader, start + size)?;
Ok(SmhdBox {
version,
flags,
balance,
})
}
}
impl<W: Write> WriteBox<&mut W> for SmhdBox {
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)?;
writer.write_i16::<BigEndian>(self.balance.raw_value())?;
writer.write_u16::<BigEndian>(0)?; // reserved
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
fn test_smhd() {
let src_box = SmhdBox {
version: 0,
flags: 0,
balance: FixedPointI8::new_raw(-1),
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::SmhdBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = SmhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

156
src/mp4box/stbl.rs Normal file
View file

@ -0,0 +1,156 @@
use std::io::{Read, Seek, SeekFrom, Write};
use crate::mp4box::*;
use crate::mp4box::{
co64::Co64Box, ctts::CttsBox, stco::StcoBox, stsc::StscBox, stsd::StsdBox, stss::StssBox,
stsz::StszBox, stts::SttsBox,
};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct StblBox {
pub stsd: StsdBox,
pub stts: SttsBox,
pub ctts: Option<CttsBox>,
pub stss: Option<StssBox>,
pub stsc: StscBox,
pub stsz: StszBox,
pub stco: Option<StcoBox>,
pub co64: Option<Co64Box>,
}
impl Mp4Box for StblBox {
fn box_type() -> BoxType {
BoxType::StblBox
}
fn box_size(&self) -> u64 {
let mut size = HEADER_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();
}
size += self.stsc.box_size();
size += self.stsz.box_size();
if let Some(ref stco) = self.stco {
size += stco.box_size();
}
if let Some(ref co64) = self.co64 {
size += co64.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
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;
match name {
BoxType::StsdBox => {
stsd = Some(StsdBox::read_box(reader, s)?);
}
BoxType::SttsBox => {
stts = Some(SttsBox::read_box(reader, s)?);
}
BoxType::CttsBox => {
ctts = Some(CttsBox::read_box(reader, s)?);
}
BoxType::StssBox => {
stss = Some(StssBox::read_box(reader, s)?);
}
BoxType::StscBox => {
stsc = Some(StscBox::read_box(reader, s)?);
}
BoxType::StszBox => {
stsz = Some(StszBox::read_box(reader, s)?);
}
BoxType::StcoBox => {
stco = Some(StcoBox::read_box(reader, s)?);
}
BoxType::Co64Box => {
co64 = Some(Co64Box::read_box(reader, s)?);
}
_ => {
// XXX warn!()
skip_box(reader, s)?;
}
}
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(StblBox {
stsd: stsd.unwrap(),
stts: stts.unwrap(),
ctts: ctts,
stss: stss,
stsc: stsc.unwrap(),
stsz: stsz.unwrap(),
stco: stco,
co64: co64,
})
}
}
impl<W: Write> WriteBox<&mut W> for StblBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(Self::box_type(), size).write(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)?;
}
self.stsc.write_box(writer)?;
self.stsz.write_box(writer)?;
if let Some(ref stco) = self.stco {
stco.write_box(writer)?;
}
if let Some(ref co64) = self.co64 {
co64.write_box(writer)?;
}
Ok(size)
}
}

87
src/mp4box/stco.rs Normal file
View file

@ -0,0 +1,87 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct StcoBox {
pub version: u8,
pub flags: u32,
pub entries: Vec<u32>,
}
impl Mp4Box for StcoBox {
fn box_type() -> BoxType {
BoxType::StcoBox
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (4 * self.entries.len() as u64)
}
}
impl<R: Read + Seek> ReadBox<&mut R> for StcoBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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 _i in 0..entry_count {
let chunk_offset = reader.read_u32::<BigEndian>()?;
entries.push(chunk_offset);
}
skip_read_to(reader, start + size)?;
Ok(StcoBox {
version,
flags,
entries,
})
}
}
impl<W: Write> WriteBox<&mut W> for StcoBox {
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)?;
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
for chunk_offset in self.entries.iter() {
writer.write_u32::<BigEndian>(*chunk_offset)?;
}
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
fn test_stco() {
let src_box = StcoBox {
version: 0,
flags: 0,
entries: vec![267, 1970, 2535, 2803, 11843, 22223, 33584],
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::StcoBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = StcoBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

128
src/mp4box/stsc.rs Normal file
View file

@ -0,0 +1,128 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct StscBox {
pub version: u8,
pub flags: u32,
pub entries: Vec<StscEntry>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct StscEntry {
pub first_chunk: u32,
pub samples_per_chunk: u32,
pub sample_description_index: u32,
pub first_sample: u32,
}
impl Mp4Box for StscBox {
fn box_type() -> BoxType {
BoxType::StscBox
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (12 * self.entries.len() as u64)
}
}
impl<R: Read + Seek> ReadBox<&mut R> for StscBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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 entry = StscEntry {
first_chunk: reader.read_u32::<BigEndian>()?,
samples_per_chunk: reader.read_u32::<BigEndian>()?,
sample_description_index: reader.read_u32::<BigEndian>()?,
first_sample: 0,
};
entries.push(entry);
}
let mut sample_id = 1;
for i in 0..entry_count {
let (first_chunk, samples_per_chunk) = {
let mut entry = entries.get_mut(i as usize).unwrap();
entry.first_sample = sample_id;
(entry.first_chunk, entry.samples_per_chunk)
};
if i < entry_count - 1 {
let next_entry = entries.get(i as usize + 1).unwrap();
sample_id += (next_entry.first_chunk - first_chunk) * samples_per_chunk;
}
}
skip_read_to(reader, start + size)?;
Ok(StscBox {
version,
flags,
entries,
})
}
}
impl<W: Write> WriteBox<&mut W> for StscBox {
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)?;
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
for entry in self.entries.iter() {
writer.write_u32::<BigEndian>(entry.first_chunk)?;
writer.write_u32::<BigEndian>(entry.samples_per_chunk)?;
writer.write_u32::<BigEndian>(entry.sample_description_index)?;
}
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
fn test_stsc() {
let src_box = StscBox {
version: 0,
flags: 0,
entries: vec![
StscEntry {
first_chunk: 1,
samples_per_chunk: 1,
sample_description_index: 1,
first_sample: 1,
},
StscEntry {
first_chunk: 19026,
samples_per_chunk: 14,
sample_description_index: 1,
first_sample: 19026,
},
],
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::StscBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = StscBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

84
src/mp4box/stsd.rs Normal file
View file

@ -0,0 +1,84 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
use crate::mp4box::{avc1::Avc1Box, mp4a::Mp4aBox};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct StsdBox {
pub version: u8,
pub flags: u32,
pub avc1: Option<Avc1Box>,
pub mp4a: Option<Mp4aBox>,
}
impl Mp4Box for StsdBox {
fn box_type() -> BoxType {
BoxType::StsdBox
}
fn box_size(&self) -> u64 {
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 {
size += mp4a.box_size();
}
size
}
}
impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
reader.read_u32::<BigEndian>()?; // XXX entry_count
let mut avc1 = None;
let mut mp4a = None;
// Get box header.
let header = BoxHeader::read(reader)?;
let BoxHeader { name, size: s } = header;
match name {
BoxType::Avc1Box => {
avc1 = Some(Avc1Box::read_box(reader, s)?);
}
BoxType::Mp4aBox => {
mp4a = Some(Mp4aBox::read_box(reader, s)?);
}
_ => {}
}
skip_read_to(reader, start + size)?;
Ok(StsdBox {
version,
flags,
avc1,
mp4a,
})
}
}
impl<W: Write> WriteBox<&mut W> for StsdBox {
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)?;
writer.write_u32::<BigEndian>(1)?; // entry_count
if let Some(ref avc1) = self.avc1 {
avc1.write_box(writer)?;
} else if let Some(ref mp4a) = self.mp4a {
mp4a.write_box(writer)?;
}
Ok(size)
}
}

87
src/mp4box/stss.rs Normal file
View file

@ -0,0 +1,87 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct StssBox {
pub version: u8,
pub flags: u32,
pub entries: Vec<u32>,
}
impl Mp4Box for StssBox {
fn box_type() -> BoxType {
BoxType::StssBox
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (4 * self.entries.len() as u64)
}
}
impl<R: Read + Seek> ReadBox<&mut R> for StssBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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 _i in 0..entry_count {
let sample_number = reader.read_u32::<BigEndian>()?;
entries.push(sample_number);
}
skip_read_to(reader, start + size)?;
Ok(StssBox {
version,
flags,
entries,
})
}
}
impl<W: Write> WriteBox<&mut W> for StssBox {
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)?;
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
for sample_number in self.entries.iter() {
writer.write_u32::<BigEndian>(*sample_number)?;
}
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
fn test_stss() {
let src_box = StssBox {
version: 0,
flags: 0,
entries: vec![1, 61, 121, 181, 241, 301, 361, 421, 481],
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::StssBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = StssBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

122
src/mp4box/stsz.rs Normal file
View file

@ -0,0 +1,122 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[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>,
}
impl Mp4Box for StszBox {
fn box_type() -> BoxType {
BoxType::StszBox
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 8 + (4 * self.sample_sizes.len() as u64)
}
}
impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
let sample_size = reader.read_u32::<BigEndian>()?;
let sample_count = reader.read_u32::<BigEndian>()?;
let mut sample_sizes = Vec::with_capacity(sample_count as usize);
if sample_size == 0 {
for _ in 0..sample_count {
let sample_number = reader.read_u32::<BigEndian>()?;
sample_sizes.push(sample_number);
}
}
skip_read_to(reader, start + size)?;
Ok(StszBox {
version,
flags,
sample_size,
sample_count,
sample_sizes,
})
}
}
impl<W: Write> WriteBox<&mut W> for StszBox {
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)?;
writer.write_u32::<BigEndian>(self.sample_size)?;
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)?;
}
}
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
fn test_stsz_same_size() {
let src_box = StszBox {
version: 0,
flags: 0,
sample_size: 1165,
sample_count: 12,
sample_sizes: vec![],
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::StszBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = StszBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
#[test]
fn test_stsz_many_sizes() {
let src_box = StszBox {
version: 0,
flags: 0,
sample_size: 0,
sample_count: 9,
sample_sizes: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730],
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::StszBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = StszBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

106
src/mp4box/stts.rs Normal file
View file

@ -0,0 +1,106 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::mp4box::*;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct SttsBox {
pub version: u8,
pub flags: u32,
pub entries: Vec<SttsEntry>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct SttsEntry {
pub sample_count: u32,
pub sample_delta: u32,
}
impl Mp4Box for SttsBox {
fn box_type() -> BoxType {
BoxType::SttsBox
}
fn box_size(&self) -> u64 {
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64)
}
}
impl<R: Read + Seek> ReadBox<&mut R> for SttsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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 _i in 0..entry_count {
let entry = SttsEntry {
sample_count: reader.read_u32::<BigEndian>()?,
sample_delta: reader.read_u32::<BigEndian>()?,
};
entries.push(entry);
}
skip_read_to(reader, start + size)?;
Ok(SttsBox {
version,
flags,
entries,
})
}
}
impl<W: Write> WriteBox<&mut W> for SttsBox {
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)?;
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
for entry in self.entries.iter() {
writer.write_u32::<BigEndian>(entry.sample_count)?;
writer.write_u32::<BigEndian>(entry.sample_delta)?;
}
Ok(size)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
fn test_stts() {
let src_box = SttsBox {
version: 0,
flags: 0,
entries: vec![
SttsEntry {
sample_count: 29726,
sample_delta: 1024,
},
SttsEntry {
sample_count: 1,
sample_delta: 512,
},
],
};
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::SttsBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

View file

@ -1,11 +1,9 @@
use std::io::{BufReader, Seek, SeekFrom, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use num_rational::Ratio;
use std::io::{Read, Seek, Write};
use crate::*;
use crate::mp4box::*;
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct TkhdBox {
pub version: u8,
pub flags: u32,
@ -13,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 {
@ -32,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,
@ -53,8 +51,18 @@ 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(&self) -> BoxType {
fn box_type() -> BoxType {
BoxType::TkhdBox
}
@ -71,39 +79,37 @@ impl Mp4Box for TkhdBox {
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TkhdBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
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>()?,
@ -115,10 +121,10 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<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(reader, current, size)?;
skip_read_to(reader, start + size)?;
Ok(TkhdBox {
version,
@ -137,10 +143,10 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TkhdBox {
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for TkhdBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
impl<W: Write> WriteBox<&mut W> for TkhdBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
BoxHeader::new(Self::box_type(), size).write(writer)?;
write_box_header_ext(writer, self.version, self.flags)?;
@ -162,7 +168,7 @@ impl<W: Write> WriteBox<&mut BufWriter<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
@ -176,8 +182,8 @@ impl<W: Write> WriteBox<&mut BufWriter<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)
}
@ -186,7 +192,7 @@ impl<W: Write> WriteBox<&mut BufWriter<W>> for TkhdBox {
#[cfg(test)]
mod tests {
use super::*;
use crate::read_box_header;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
@ -200,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,
@ -212,26 +218,20 @@ mod tests {
y: 0,
w: 0x40000000,
},
width: 512,
height: 288,
width: FixedPointU16::new(512),
height: FixedPointU16::new(288),
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::TkhdBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::TkhdBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
#[test]
@ -245,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,
@ -257,25 +257,19 @@ mod tests {
y: 0,
w: 0x40000000,
},
width: 512,
height: 288,
width: FixedPointU16::new(512),
height: FixedPointU16::new(288),
};
let mut buf = Vec::new();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::TkhdBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::TkhdBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

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,10 +1,9 @@
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Seek, Write};
use crate::*;
use crate::mp4box::*;
#[derive(Debug, Default, PartialEq)]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct VmhdBox {
pub version: u8,
pub flags: u32,
@ -12,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,
@ -20,7 +19,7 @@ pub struct RgbColor {
}
impl Mp4Box for VmhdBox {
fn box_type(&self) -> BoxType {
fn box_type() -> BoxType {
BoxType::VmhdBox
}
@ -29,9 +28,9 @@ impl Mp4Box for VmhdBox {
}
}
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for VmhdBox {
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
impl<R: Read + Seek> ReadBox<&mut R> for VmhdBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let (version, flags) = read_box_header_ext(reader)?;
@ -41,7 +40,8 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for VmhdBox {
green: reader.read_u16::<BigEndian>()?,
blue: reader.read_u16::<BigEndian>()?,
};
skip_read(reader, current, size)?;
skip_read_to(reader, start + size)?;
Ok(VmhdBox {
version,
@ -52,10 +52,10 @@ impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for VmhdBox {
}
}
impl<W: Write> WriteBox<&mut BufWriter<W>> for VmhdBox {
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
impl<W: Write> WriteBox<&mut W> for VmhdBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write_box(writer)?;
BoxHeader::new(Self::box_type(), size).write(writer)?;
write_box_header_ext(writer, self.version, self.flags)?;
@ -68,11 +68,10 @@ impl<W: Write> WriteBox<&mut BufWriter<W>> for VmhdBox {
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::read_box_header;
use crate::mp4box::BoxHeader;
use std::io::Cursor;
#[test]
@ -81,24 +80,22 @@ 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();
{
let mut writer = BufWriter::new(&mut buf);
src_box.write_box(&mut writer).unwrap();
}
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);
{
let mut reader = BufReader::new(Cursor::new(&buf));
let header = read_box_header(&mut reader, 0).unwrap();
assert_eq!(header.name, BoxType::VmhdBox);
assert_eq!(src_box.box_size(), header.size);
let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::VmhdBox);
assert_eq!(src_box.box_size(), header.size);
let dst_box = VmhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
let dst_box = VmhdBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}

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,38 +1,120 @@
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";
let f = File::open(filename).unwrap();
let bmff = mp4::read_mp4(f).unwrap();
let size = f.metadata().unwrap().len();
let reader = BufReader::new(f);
assert_eq!(2591, bmff.size);
let mut mp4 = mp4::Mp4Reader::read_header(reader, size).unwrap();
assert_eq!(2591, mp4.size());
// ftyp.
println!("{:?}", bmff.ftyp.compatible_brands);
assert_eq!(4, bmff.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 = bmff.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.
let moov = bmff.moov.unwrap();
assert_eq!(moov.mvhd.version, 0);
assert_eq!(moov.mvhd.creation_time, 0);
assert_eq!(moov.mvhd.duration, 62);
assert_eq!(moov.mvhd.timescale, 1000);
assert_eq!(moov.traks.len(), 2);
assert_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, 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 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 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 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);
}