mirror of
https://github.com/alfg/mp4-rust.git
synced 2025-01-03 08:58:40 +00:00
Feature/mp4copy (#14)
* Add ReadBox trait * Add boxtype macro * Remove offset in BoxHeader * Fix parsing error when box has largesize * Remove duplicated codes reading version and flags * Add avc1 box * Add mp4a box * Add mp4a box * Add DecoderSpecificDescriptor in esds box * Add necessary sub-boxes to stbl box * Improve ReadBox::read_box() * Add smhd box * Refactor BoxHeader * Refactor BMFF * Refactor * Add some functions to get offset and size of sample * Add Mp4Reader::read_sample() that read media samples * Move Mp4Reader to reader.rs * Add mandatory check when reading boxes Add some methods to Mp4Reader, TrackReader Format codes * Update mp4info * Refactor common types * Add FixedPointX types * Add media configuration, profile, ... * Add initial Mp4Writer * Run cargo fmt * Add Mp4Writer and examples/mp4copy * Add test codes for Avc1Box and Mp4aBox * Remove prefix "get_" from method names * Rename atoms to mp4box * Fix some bugs Co-authored-by: Byungwan Jun <unipro.kr@gmail.com>
This commit is contained in:
parent
ba69f3812b
commit
3104a2d95b
37 changed files with 2858 additions and 1662 deletions
|
@ -1,10 +1,10 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::io::{self, BufReader, BufWriter};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::Result;
|
||||
use mp4::{AacConfig, AvcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
@ -19,23 +19,66 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
fn copy<P: AsRef<Path>>(src_filename: &P, _dst_filename: &P) -> Result<()> {
|
||||
fn copy<P: AsRef<Path>>(src_filename: &P, dst_filename: &P) -> Result<()> {
|
||||
let src_file = File::open(src_filename)?;
|
||||
let size = src_file.metadata()?.len();
|
||||
let reader = BufReader::new(src_file);
|
||||
|
||||
let mut mp4 = mp4::Mp4Reader::new(reader);
|
||||
mp4.read(size)?;
|
||||
let dst_file = File::create(dst_filename)?;
|
||||
let writer = BufWriter::new(dst_file);
|
||||
|
||||
for tix in 0..mp4.track_count()? {
|
||||
let track_id = tix + 1;
|
||||
let sample_count = mp4.sample_count(track_id)?;
|
||||
let mut mp4_reader = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
let mut mp4_writer = mp4::Mp4Writer::write_start(
|
||||
writer,
|
||||
&Mp4Config {
|
||||
major_brand: mp4_reader.major_brand().clone(),
|
||||
minor_version: mp4_reader.minor_version(),
|
||||
compatible_brands: mp4_reader.compatible_brands().to_vec(),
|
||||
timescale: mp4_reader.timescale(),
|
||||
},
|
||||
)?;
|
||||
|
||||
// TODO interleaving
|
||||
for track_idx in 0..mp4_reader.tracks().len() {
|
||||
if let Some(ref track) = mp4_reader.tracks().get(track_idx) {
|
||||
let media_conf = match track.media_type()? {
|
||||
MediaType::H264 => MediaConfig::AvcConfig(AvcConfig {
|
||||
width: track.width(),
|
||||
height: track.height(),
|
||||
seq_param_set: track.sequence_parameter_set()?.to_vec(),
|
||||
pic_param_set: track.picture_parameter_set()?.to_vec(),
|
||||
}),
|
||||
MediaType::AAC => MediaConfig::AacConfig(AacConfig {
|
||||
bitrate: track.bitrate(),
|
||||
profile: track.audio_profile()?,
|
||||
freq_index: track.sample_freq_index()?,
|
||||
chan_conf: track.channel_config()?,
|
||||
}),
|
||||
};
|
||||
|
||||
let track_conf = TrackConfig {
|
||||
track_type: track.track_type()?,
|
||||
timescale: track.timescale(),
|
||||
language: track.language().to_string(),
|
||||
media_conf,
|
||||
};
|
||||
|
||||
mp4_writer.add_track(&track_conf)?;
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
let track_id = track_idx as u32 + 1;
|
||||
let sample_count = mp4_reader.sample_count(track_id)?;
|
||||
for six in 0..sample_count {
|
||||
let sample_id = six + 1;
|
||||
let sample = mp4.read_sample(track_id, sample_id)?.unwrap();
|
||||
println!("sample_id: {}, {}", sample_id, sample);
|
||||
let sample = mp4_reader.read_sample(track_id, sample_id)?.unwrap();
|
||||
mp4_writer.write_sample(track_id, &sample)?;
|
||||
// println!("copy {}:({})", sample_id, sample);
|
||||
}
|
||||
}
|
||||
|
||||
mp4_writer.write_end()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::io::prelude::*;
|
|||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Result, Mp4Reader, TrackType};
|
||||
use mp4::{Mp4Track, Result, TrackType};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
@ -24,96 +24,61 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|||
let size = f.metadata()?.len();
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
let mut mp4 = Mp4Reader::new(reader);
|
||||
mp4.read(size)?;
|
||||
let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
|
||||
println!("File:");
|
||||
println!(" size: {}", mp4.size());
|
||||
println!(" brands: {:?} {:?}\n",
|
||||
mp4.ftyp.major_brand, mp4.ftyp.compatible_brands);
|
||||
println!("Metadata:");
|
||||
println!(" size : {}", mp4.size());
|
||||
println!(" major_brand : {}", mp4.major_brand());
|
||||
let mut compatible_brands = String::new();
|
||||
for brand in mp4.compatible_brands().iter() {
|
||||
compatible_brands.push_str(&brand.to_string());
|
||||
compatible_brands.push_str(",");
|
||||
}
|
||||
println!(" compatible_brands: {}", compatible_brands);
|
||||
println!(
|
||||
"Duration: {}, timescale: {}",
|
||||
mp4.duration(),
|
||||
mp4.timescale()
|
||||
);
|
||||
|
||||
if let Some(ref moov) = mp4.moov {
|
||||
println!("Movie:");
|
||||
println!(" version: {:?}", moov.mvhd.version);
|
||||
println!(" creation time: {}",
|
||||
creation_time(moov.mvhd.creation_time));
|
||||
println!(" duration: {:?}", moov.mvhd.duration);
|
||||
println!(" timescale: {:?}\n", moov.mvhd.timescale);
|
||||
|
||||
println!("Found {} Tracks", moov.traks.len());
|
||||
for trak in moov.traks.iter() {
|
||||
let tkhd = trak.tkhd.as_ref().unwrap();
|
||||
println!("Track: {:?}", tkhd.track_id);
|
||||
println!(" flags: {:?}", tkhd.flags);
|
||||
println!(" id: {:?}", tkhd.track_id);
|
||||
println!(" duration: {:?}", tkhd.duration);
|
||||
if tkhd.width != 0 && tkhd.height != 0 {
|
||||
println!(" width: {:?}", tkhd.width);
|
||||
println!(" height: {:?}", tkhd.height);
|
||||
}
|
||||
if let Some(ref mdia) = trak.mdia {
|
||||
let hdlr = mdia.hdlr.as_ref().unwrap();
|
||||
let mdhd = mdia.mdhd.as_ref().unwrap();
|
||||
let stts = mdia
|
||||
.minf
|
||||
.as_ref()
|
||||
.map(|m| m.stbl.as_ref().map(|s| s.stts.as_ref()).flatten())
|
||||
.flatten();
|
||||
|
||||
println!(" type: {:?}",
|
||||
get_handler_type(hdlr.handler_type.value.as_ref()));
|
||||
println!(" language: {:?}", mdhd.language);
|
||||
|
||||
println!(" media:");
|
||||
if let Some(ref s) = stts {
|
||||
println!(" sample count: {:?}", s.entries[0].sample_count);
|
||||
}
|
||||
println!(" timescale: {:?}", mdhd.timescale);
|
||||
println!(" duration: {:?} (media timescale units)",
|
||||
mdhd.duration);
|
||||
println!(" duration: {:?} (ms)",
|
||||
get_duration_ms(mdhd.duration, mdhd.timescale));
|
||||
if get_handler_type(hdlr.handler_type.value.as_ref()) == TrackType::Video {
|
||||
if let Some(ref s) = stts {
|
||||
println!(" frame rate: (computed): {:?}",
|
||||
get_framerate(s.entries[0].sample_count,
|
||||
mdhd.duration, mdhd.timescale));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for track in mp4.tracks().iter() {
|
||||
let media_info = match track.track_type()? {
|
||||
TrackType::Video => video_info(track)?,
|
||||
TrackType::Audio => audio_info(track)?,
|
||||
};
|
||||
println!(
|
||||
" Track: #{}({}) {}: {}",
|
||||
track.track_id(),
|
||||
track.language(),
|
||||
track.track_type()?,
|
||||
media_info
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_handler_type(handler: &str) -> TrackType {
|
||||
let mut typ: TrackType = TrackType::Unknown;
|
||||
match handler {
|
||||
"vide" => typ = TrackType::Video,
|
||||
"soun" => typ = TrackType::Audio,
|
||||
"meta" => typ = TrackType::Unknown,
|
||||
_ => (),
|
||||
}
|
||||
return typ;
|
||||
fn video_info(track: &Mp4Track) -> Result<String> {
|
||||
Ok(format!(
|
||||
"{} ({}) ({:?}), {}x{}, {} kb/s, {:.2} fps",
|
||||
track.media_type()?,
|
||||
track.video_profile()?,
|
||||
track.box_type()?,
|
||||
track.width(),
|
||||
track.height(),
|
||||
track.bitrate() / 1000,
|
||||
track.frame_rate_f64()
|
||||
))
|
||||
}
|
||||
|
||||
fn get_duration_ms(duration: u64, timescale: u32) -> String {
|
||||
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
||||
return format!("{:.2}", ms.floor());
|
||||
}
|
||||
|
||||
fn get_framerate(sample_count: u32, duration: u64, timescale: u32) -> String {
|
||||
let sc = (sample_count as f64) * 1000.0;
|
||||
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
||||
return format!("{:.2}", sc / ms.floor());
|
||||
}
|
||||
|
||||
fn creation_time(creation_time: u64) -> u64 {
|
||||
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
|
||||
if creation_time >= 2082844800 {
|
||||
creation_time - 2082844800
|
||||
} else {
|
||||
creation_time
|
||||
}
|
||||
fn audio_info(track: &Mp4Track) -> Result<String> {
|
||||
Ok(format!(
|
||||
"{} ({}) ({:?}), {} Hz, {}, {} kb/s",
|
||||
track.media_type()?,
|
||||
track.audio_profile()?,
|
||||
track.box_type()?,
|
||||
track.sample_freq_index()?.freq(),
|
||||
track.channel_config()?,
|
||||
track.bitrate() / 1000
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
use std::io::{Seek, SeekFrom, Read, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{mdhd::MdhdBox, hdlr::HdlrBox, minf::MinfBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MdiaBox {
|
||||
pub mdhd: Option<MdhdBox>,
|
||||
pub hdlr: Option<HdlrBox>,
|
||||
pub minf: Option<MinfBox>,
|
||||
}
|
||||
|
||||
impl MdiaBox {
|
||||
pub(crate) fn new() -> MdiaBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MdiaBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::MdiaBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(ref mdhd) = self.mdhd {
|
||||
size += mdhd.box_size();
|
||||
}
|
||||
if let Some(ref hdlr) = self.hdlr {
|
||||
size += hdlr.box_size();
|
||||
}
|
||||
if let Some(ref minf) = self.minf {
|
||||
size += minf.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MdiaBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
|
||||
let mut mdia = MdiaBox::new();
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MdhdBox => {
|
||||
let mdhd = MdhdBox::read_box(reader, s)?;
|
||||
mdia.mdhd = Some(mdhd);
|
||||
}
|
||||
BoxType::HdlrBox => {
|
||||
let hdlr = HdlrBox::read_box(reader, s)?;
|
||||
mdia.hdlr = Some(hdlr);
|
||||
}
|
||||
BoxType::MinfBox => {
|
||||
let minf = MinfBox::read_box(reader, s)?;
|
||||
mdia.minf = Some(minf);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(mdia)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MdiaBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(ref mdhd) = self.mdhd {
|
||||
mdhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref hdlr) = self.hdlr {
|
||||
hdlr.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref minf) = self.minf {
|
||||
minf.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
|
@ -1,411 +0,0 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Mp4aBox {
|
||||
pub data_reference_index: u16,
|
||||
pub channel_count: u16,
|
||||
pub samplesize: u16,
|
||||
pub samplerate: u32,
|
||||
pub esds: EsdsBox,
|
||||
}
|
||||
|
||||
impl Default for Mp4aBox {
|
||||
fn default() -> Self {
|
||||
Mp4aBox {
|
||||
data_reference_index: 0,
|
||||
channel_count: 2,
|
||||
samplesize: 16,
|
||||
samplerate: 0, // XXX
|
||||
esds: EsdsBox::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Mp4aBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::Mp4aBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + 8 + 74 + self.esds.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
reader.read_u64::<BigEndian>()?; // reserved
|
||||
let channel_count = reader.read_u16::<BigEndian>()?;
|
||||
let samplesize = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
|
||||
let samplerate = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
if name == BoxType::EsdsBox {
|
||||
let esds = EsdsBox::read_box(reader, s)?;
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(Mp4aBox {
|
||||
data_reference_index,
|
||||
channel_count,
|
||||
samplesize,
|
||||
samplerate,
|
||||
esds,
|
||||
})
|
||||
} else {
|
||||
Err(Error::InvalidData("esds not found"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Mp4aBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
|
||||
writer.write_u64::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.channel_count)?;
|
||||
writer.write_u16::<BigEndian>(self.samplesize)?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u32::<BigEndian>(self.samplerate)?;
|
||||
|
||||
self.esds.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct EsdsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub es_desc: ESDescriptor,
|
||||
}
|
||||
|
||||
impl Mp4Box for EsdsBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::EsdsBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for EsdsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let es_desc = ESDescriptor::read_desc(reader)?;
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(EsdsBox {
|
||||
version,
|
||||
flags,
|
||||
es_desc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for EsdsBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
trait Descriptor: Sized {
|
||||
fn desc_tag() -> u8;
|
||||
fn desc_size() -> u32;
|
||||
}
|
||||
|
||||
trait ReadDesc<T>: Sized {
|
||||
fn read_desc(_: T) -> Result<Self>;
|
||||
}
|
||||
|
||||
trait WriteDesc<T>: Sized {
|
||||
fn write_desc(&self, _: T) -> Result<u32>;
|
||||
}
|
||||
|
||||
fn read_desc<R: Read>(reader: &mut R) -> Result<(u8, u32)> {
|
||||
let tag = reader.read_u8()?;
|
||||
|
||||
let mut size: u32 = 0;
|
||||
for _ in 0..4 {
|
||||
let b = reader.read_u8()?;
|
||||
size = (size << 7) | (b & 0x7F) as u32;
|
||||
if b & 0x80 == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((tag, size))
|
||||
}
|
||||
|
||||
fn write_desc<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
|
||||
writer.write_u8(tag)?;
|
||||
|
||||
if size > 0x0FFFFFFF {
|
||||
return Err(Error::InvalidData("invalid descriptor length range"));
|
||||
}
|
||||
|
||||
let nbytes = match size {
|
||||
0x0..=0x7F => 1,
|
||||
0x80..=0x3FFF => 2,
|
||||
0x4000..=0x1FFFFF => 3,
|
||||
_ => 4,
|
||||
};
|
||||
|
||||
for i in 0..nbytes {
|
||||
let mut b = (size >> ((3 - i) * 7)) as u8 & 0x7F;
|
||||
if i < nbytes - 1 {
|
||||
b |= 0x80;
|
||||
}
|
||||
writer.write_u8(b)?;
|
||||
}
|
||||
|
||||
Ok(1 + nbytes)
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct ESDescriptor {
|
||||
pub tag: u8,
|
||||
pub size: u32,
|
||||
|
||||
pub es_id: u16,
|
||||
|
||||
pub dec_config: DecoderConfigDescriptor,
|
||||
pub sl_config: SLConfigDescriptor,
|
||||
}
|
||||
|
||||
impl Descriptor for ESDescriptor {
|
||||
fn desc_tag() -> u8 {
|
||||
0x03
|
||||
}
|
||||
|
||||
// XXX size > 0x7F
|
||||
fn desc_size() -> u32 {
|
||||
2 + 3
|
||||
+ DecoderConfigDescriptor::desc_size()
|
||||
+ SLConfigDescriptor::desc_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
|
||||
fn read_desc(reader: &mut R) -> Result<Self> {
|
||||
let (tag, size) = read_desc(reader)?;
|
||||
if tag != Self::desc_tag() {
|
||||
return Err(Error::InvalidData("ESDescriptor not found"));
|
||||
}
|
||||
|
||||
let es_id = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_u8()?; // XXX flags must be 0
|
||||
|
||||
let dec_config = DecoderConfigDescriptor::read_desc(reader)?;
|
||||
let sl_config = SLConfigDescriptor::read_desc(reader)?;
|
||||
|
||||
Ok(ESDescriptor {
|
||||
tag,
|
||||
size,
|
||||
es_id,
|
||||
dec_config,
|
||||
sl_config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteDesc<&mut W> for ESDescriptor {
|
||||
fn write_desc(&self, writer: &mut W) -> Result<u32> {
|
||||
write_desc(writer, self.tag, self.size)?;
|
||||
|
||||
Ok(self.size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct DecoderConfigDescriptor {
|
||||
pub tag: u8,
|
||||
pub size: u32,
|
||||
|
||||
pub object_type_indication: u8,
|
||||
pub stream_type: u8,
|
||||
pub up_stream: u8,
|
||||
pub buffer_size_db: u32,
|
||||
pub max_bitrate: u32,
|
||||
pub avg_bitrate: u32,
|
||||
|
||||
pub dec_specific: DecoderSpecificDescriptor,
|
||||
}
|
||||
|
||||
impl Descriptor for DecoderConfigDescriptor {
|
||||
fn desc_tag() -> u8 {
|
||||
0x04
|
||||
}
|
||||
|
||||
// XXX size > 0x7F
|
||||
fn desc_size() -> u32 {
|
||||
2 + 13 + DecoderSpecificDescriptor::desc_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
|
||||
fn read_desc(reader: &mut R) -> Result<Self> {
|
||||
let (tag, size) = read_desc(reader)?;
|
||||
if tag != Self::desc_tag() {
|
||||
return Err(Error::InvalidData("DecoderConfigDescriptor not found"));
|
||||
}
|
||||
|
||||
let object_type_indication = reader.read_u8()?;
|
||||
let byte_a = reader.read_u8()?;
|
||||
let stream_type = byte_a & 0xFC;
|
||||
let up_stream = byte_a & 0x02;
|
||||
let buffer_size_db = reader.read_u24::<BigEndian>()?;
|
||||
let max_bitrate = reader.read_u32::<BigEndian>()?;
|
||||
let avg_bitrate = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let dec_specific = DecoderSpecificDescriptor::read_desc(reader)?;
|
||||
|
||||
// XXX skip_read
|
||||
for _ in DecoderConfigDescriptor::desc_size()..size-1 {
|
||||
reader.read_u8()?;
|
||||
}
|
||||
|
||||
Ok(DecoderConfigDescriptor {
|
||||
tag,
|
||||
size,
|
||||
object_type_indication,
|
||||
stream_type,
|
||||
up_stream,
|
||||
buffer_size_db,
|
||||
max_bitrate,
|
||||
avg_bitrate,
|
||||
dec_specific,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteDesc<&mut W> for DecoderConfigDescriptor {
|
||||
fn write_desc(&self, writer: &mut W) -> Result<u32> {
|
||||
write_desc(writer, self.tag, self.size)?;
|
||||
|
||||
Ok(self.size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct DecoderSpecificDescriptor {
|
||||
pub tag: u8,
|
||||
pub size: u32,
|
||||
pub profile: u8,
|
||||
pub freq_index: u8,
|
||||
pub chan_conf: u8,
|
||||
}
|
||||
|
||||
impl Descriptor for DecoderSpecificDescriptor {
|
||||
fn desc_tag() -> u8 {
|
||||
0x05
|
||||
}
|
||||
|
||||
// XXX size > 0x7F
|
||||
fn desc_size() -> u32 {
|
||||
2 + 2
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadDesc<&mut R> for DecoderSpecificDescriptor {
|
||||
fn read_desc(reader: &mut R) -> Result<Self> {
|
||||
let (tag, size) = read_desc(reader)?;
|
||||
if tag != Self::desc_tag() {
|
||||
return Err(Error::InvalidData("DecoderSpecificDescriptor not found"));
|
||||
}
|
||||
|
||||
let byte_a = reader.read_u8()?;
|
||||
let byte_b = reader.read_u8()?;
|
||||
let profile = byte_a >> 3;
|
||||
let freq_index = ((byte_a & 0x07) << 1) + (byte_b >> 7);
|
||||
let chan_conf = (byte_b >> 3) & 0x0F;
|
||||
|
||||
Ok(DecoderSpecificDescriptor {
|
||||
tag,
|
||||
size,
|
||||
profile,
|
||||
freq_index,
|
||||
chan_conf,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteDesc<&mut W> for DecoderSpecificDescriptor {
|
||||
fn write_desc(&self, writer: &mut W) -> Result<u32> {
|
||||
write_desc(writer, self.tag, self.size)?;
|
||||
|
||||
Ok(self.size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct SLConfigDescriptor {
|
||||
pub tag: u8,
|
||||
pub size: u32,
|
||||
}
|
||||
|
||||
impl Descriptor for SLConfigDescriptor {
|
||||
fn desc_tag() -> u8 {
|
||||
0x06
|
||||
}
|
||||
|
||||
// XXX size > 0x7F
|
||||
fn desc_size() -> u32 {
|
||||
2 + 1
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadDesc<&mut R> for SLConfigDescriptor {
|
||||
fn read_desc(reader: &mut R) -> Result<Self> {
|
||||
let (tag, size) = read_desc(reader)?;
|
||||
if tag != Self::desc_tag() {
|
||||
return Err(Error::InvalidData("SLConfigDescriptor not found"));
|
||||
}
|
||||
|
||||
reader.read_u8()?; // pre-defined
|
||||
|
||||
Ok(SLConfigDescriptor {
|
||||
tag,
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteDesc<&mut W> for SLConfigDescriptor {
|
||||
fn write_desc(&self, writer: &mut W) -> Result<u32> {
|
||||
write_desc(writer, self.tag, self.size)?;
|
||||
|
||||
Ok(self.size)
|
||||
}
|
||||
}
|
|
@ -1,330 +0,0 @@
|
|||
use std::io::{Seek, SeekFrom, Read, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{
|
||||
tkhd::TkhdBox,
|
||||
edts::EdtsBox,
|
||||
mdia::MdiaBox,
|
||||
stbl::StblBox,
|
||||
stts::SttsBox,
|
||||
stsc::StscBox,
|
||||
stsz::StszBox,
|
||||
};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TrakBox {
|
||||
pub id: u32,
|
||||
|
||||
pub tkhd: Option<TkhdBox>,
|
||||
pub edts: Option<EdtsBox>,
|
||||
pub mdia: Option<MdiaBox>,
|
||||
}
|
||||
|
||||
impl TrakBox {
|
||||
pub(crate) fn new() -> TrakBox {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn stbl(&self) -> Result<&StblBox> {
|
||||
if let Some(ref mdia) = self.mdia {
|
||||
if let Some(ref minf) = mdia.minf {
|
||||
if let Some(ref stbl) = minf.stbl {
|
||||
Ok(stbl)
|
||||
} else {
|
||||
Err(Error::BoxInTrakNotFound(self.id, BoxType::StblBox))
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInTrakNotFound(self.id, BoxType::MinfBox))
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInTrakNotFound(self.id, BoxType::MdiaBox))
|
||||
}
|
||||
}
|
||||
|
||||
fn stts(&self) -> Result<&SttsBox> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref stts) = stbl.stts {
|
||||
Ok(stts)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.id, BoxType::SttsBox))
|
||||
}
|
||||
}
|
||||
|
||||
fn stsc(&self) -> Result<&StscBox> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref stsc) = stbl.stsc {
|
||||
Ok(stsc)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.id, BoxType::StscBox))
|
||||
}
|
||||
}
|
||||
|
||||
fn stsz(&self) -> Result<&StszBox> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref stsz) = stbl.stsz {
|
||||
Ok(stsz)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.id, BoxType::StszBox))
|
||||
}
|
||||
}
|
||||
|
||||
fn stsc_index(&self, sample_id: u32) -> Result<usize> {
|
||||
let stsc = self.stsc()?;
|
||||
|
||||
for (i, entry) in stsc.entries.iter().enumerate() {
|
||||
if sample_id < entry.first_sample {
|
||||
assert_ne!(i, 0);
|
||||
return Ok(i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
assert_ne!(stsc.entries.len(), 0);
|
||||
Ok(stsc.entries.len() - 1)
|
||||
}
|
||||
|
||||
fn chunk_offset(&self, chunk_id: u32) -> Result<u64> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref stco) = stbl.stco {
|
||||
if let Some(offset) = stco.entries.get(chunk_id as usize - 1) {
|
||||
return Ok(*offset as u64);
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(self.id, BoxType::StcoBox,
|
||||
chunk_id));
|
||||
}
|
||||
} else {
|
||||
if let Some(ref co64) = stbl.co64 {
|
||||
if let Some(offset) = co64.entries.get(chunk_id as usize - 1) {
|
||||
return Ok(*offset);
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(self.id, BoxType::Co64Box,
|
||||
chunk_id));
|
||||
}
|
||||
} else {
|
||||
// XXX BoxType::StcoBox & BoxType::Co64Box
|
||||
Err(Error::BoxInStblNotFound(self.id, BoxType::Co64Box))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
let ctts = if let Some(ref ctts) = stbl.ctts {
|
||||
ctts
|
||||
} else {
|
||||
return Err(Error::BoxInStblNotFound(self.id, BoxType::CttsBox));
|
||||
};
|
||||
|
||||
let mut sample_count = 1;
|
||||
for (i, entry) in ctts.entries.iter().enumerate() {
|
||||
if sample_id <= sample_count + entry.sample_count -1 {
|
||||
return Ok((i, sample_count))
|
||||
}
|
||||
sample_count += entry.sample_count;
|
||||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(self.id, BoxType::CttsBox, sample_id));
|
||||
}
|
||||
|
||||
pub fn sample_count(&self) -> Result<u32> {
|
||||
let stsz = self.stsz()?;
|
||||
Ok(stsz.sample_sizes.len() as u32)
|
||||
}
|
||||
|
||||
pub fn sample_size(&self, sample_id: u32) -> Result<u32> {
|
||||
let stsz = self.stsz()?;
|
||||
if stsz.sample_size > 0 {
|
||||
return Ok(stsz.sample_size);
|
||||
}
|
||||
if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) {
|
||||
Ok(*size)
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(self.id, BoxType::StszBox, sample_id));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_offset(&self, sample_id: u32) -> Result<u64> {
|
||||
let stsc_index = self.stsc_index(sample_id)?;
|
||||
|
||||
let stsc = self.stsc()?;
|
||||
let stsc_entry = stsc.entries.get(stsc_index).unwrap();
|
||||
|
||||
let first_chunk = stsc_entry.first_chunk;
|
||||
let first_sample = stsc_entry.first_sample;
|
||||
let samples_per_chunk = stsc_entry.samples_per_chunk;
|
||||
|
||||
let chunk_id = first_chunk + (sample_id - first_sample) / samples_per_chunk;
|
||||
|
||||
let chunk_offset = self.chunk_offset(chunk_id)?;
|
||||
|
||||
let first_sample_in_chunk = sample_id - (sample_id - first_sample)
|
||||
% samples_per_chunk;
|
||||
|
||||
let mut sample_offset = 0;
|
||||
for i in first_sample_in_chunk..sample_id {
|
||||
sample_offset += self.sample_size(i)?;
|
||||
}
|
||||
|
||||
Ok(chunk_offset + sample_offset as u64)
|
||||
}
|
||||
|
||||
pub fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
|
||||
let stts = self.stts()?;
|
||||
|
||||
let mut sample_count = 1;
|
||||
let mut elapsed = 0;
|
||||
|
||||
for entry in stts.entries.iter() {
|
||||
if sample_id <= sample_count + entry.sample_count - 1 {
|
||||
let start_time = (sample_id - sample_count) as u64
|
||||
* entry.sample_delta as u64 + elapsed;
|
||||
return Ok((start_time, entry.sample_delta));
|
||||
}
|
||||
|
||||
sample_count += entry.sample_count;
|
||||
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
|
||||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(self.id, BoxType::SttsBox, sample_id));
|
||||
}
|
||||
|
||||
pub fn sample_rendering_offset(&self, sample_id: u32) -> Result<i32> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref ctts) = stbl.ctts {
|
||||
let (ctts_index, _) = self.ctts_index(sample_id)?;
|
||||
let ctts_entry = ctts.entries.get(ctts_index).unwrap();
|
||||
Ok(ctts_entry.sample_offset)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_sync_sample(&self, sample_id: u32) -> Result<bool> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref stss) = stbl.stss {
|
||||
match stss.entries.binary_search(&sample_id) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false)
|
||||
}
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_sample<R: Read + Seek>(
|
||||
&self,
|
||||
reader: &mut R,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Mp4Sample>> {
|
||||
let sample_offset = match self.sample_offset(sample_id) {
|
||||
Ok(offset) => offset,
|
||||
Err(Error::EntryInStblNotFound(_,_,_)) => return Ok(None),
|
||||
Err(err) => return Err(err)
|
||||
};
|
||||
let sample_size = self.sample_size(sample_id)?;
|
||||
|
||||
let mut buffer = vec![0x0u8; sample_size as usize];
|
||||
reader.seek(SeekFrom::Start(sample_offset))?;
|
||||
reader.read_exact(&mut buffer)?;
|
||||
|
||||
let (start_time, duration) = self.sample_time(sample_id)?;
|
||||
let rendering_offset = self.sample_rendering_offset(sample_id)?;
|
||||
let is_sync = self.is_sync_sample(sample_id)?;
|
||||
|
||||
Ok(Some(Mp4Sample {
|
||||
start_time,
|
||||
duration,
|
||||
rendering_offset,
|
||||
is_sync,
|
||||
bytes: Bytes::from(buffer),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TrakBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::TrakBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(ref tkhd) = self.tkhd {
|
||||
size += tkhd.box_size();
|
||||
}
|
||||
if let Some(ref edts) = self.edts {
|
||||
size += edts.box_size();
|
||||
}
|
||||
if let Some(ref mdia) = self.mdia {
|
||||
size += mdia.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
|
||||
let mut trak = TrakBox::new();
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::TkhdBox => {
|
||||
let tkhd = TkhdBox::read_box(reader, s)?;
|
||||
trak.tkhd = Some(tkhd);
|
||||
}
|
||||
BoxType::EdtsBox => {
|
||||
let edts = EdtsBox::read_box(reader, s)?;
|
||||
trak.edts = Some(edts);
|
||||
}
|
||||
BoxType::MdiaBox => {
|
||||
let mdia = MdiaBox::read_box(reader, s)?;
|
||||
trak.mdia = Some(mdia);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(trak)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for TrakBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(ref tkhd) = self.tkhd {
|
||||
tkhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref edts) = self.edts {
|
||||
edts.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref mdia) = self.mdia {
|
||||
mdia.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::atoms::BoxType;
|
||||
use crate::mp4box::BoxType;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
|
@ -10,6 +10,8 @@ pub enum Error {
|
|||
InvalidData(&'static str),
|
||||
#[error("{0} not found")]
|
||||
BoxNotFound(BoxType),
|
||||
#[error("{0} and {1} not found")]
|
||||
Box2NotFound(BoxType, BoxType),
|
||||
#[error("trak[{0}] not found")]
|
||||
TrakNotFound(u32),
|
||||
#[error("trak[{0}].{1} not found")]
|
||||
|
|
162
src/lib.rs
162
src/lib.rs
|
@ -1,162 +1,18 @@
|
|||
use std::fmt;
|
||||
use std::io::{Seek, SeekFrom, Read};
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub use bytes::Bytes;
|
||||
|
||||
mod atoms;
|
||||
use crate::atoms::*;
|
||||
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TrackType {
|
||||
Audio,
|
||||
Video,
|
||||
Metadata,
|
||||
Unknown,
|
||||
}
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Sample {
|
||||
pub start_time: u64,
|
||||
pub duration: u32,
|
||||
pub rendering_offset: i32,
|
||||
pub is_sync: bool,
|
||||
pub bytes: Bytes,
|
||||
}
|
||||
mod mp4box;
|
||||
|
||||
impl PartialEq for Mp4Sample {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.start_time == other.start_time
|
||||
&& self.duration == other.duration
|
||||
&& self.rendering_offset == other.rendering_offset
|
||||
&& self.is_sync == other.is_sync
|
||||
&& self.bytes.len() == other.bytes.len() // XXX for easy check
|
||||
}
|
||||
}
|
||||
mod track;
|
||||
pub use track::{Mp4Track, TrackConfig};
|
||||
|
||||
impl fmt::Display for Mp4Sample {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f,
|
||||
"start_time {}, duration {}, rendering_offset {}, is_sync {}, length {}",
|
||||
self.start_time, self.duration, self.rendering_offset, self.is_sync,
|
||||
self.bytes.len())
|
||||
}
|
||||
}
|
||||
mod reader;
|
||||
pub use reader::Mp4Reader;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Reader<R> {
|
||||
reader: R,
|
||||
pub ftyp: FtypBox,
|
||||
pub moov: Option<MoovBox>,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> Mp4Reader<R> {
|
||||
pub fn new(reader: R) -> Self {
|
||||
Mp4Reader {
|
||||
reader,
|
||||
ftyp: FtypBox::default(),
|
||||
moov: None,
|
||||
size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn read(&mut self, size: u64) -> Result<()> {
|
||||
let start = self.reader.seek(SeekFrom::Current(0))?;
|
||||
let mut current = start;
|
||||
while current < size {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(&mut self.reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
// Match and parse the atom boxes.
|
||||
match name {
|
||||
BoxType::FtypBox => {
|
||||
let ftyp = FtypBox::read_box(&mut self.reader, s)?;
|
||||
self.ftyp = ftyp;
|
||||
}
|
||||
BoxType::FreeBox => {
|
||||
skip_box(&mut self.reader, s)?;
|
||||
}
|
||||
BoxType::MdatBox => {
|
||||
skip_box(&mut self.reader, s)?;
|
||||
}
|
||||
BoxType::MoovBox => {
|
||||
let moov = MoovBox::read_box(&mut self.reader, s)?;
|
||||
self.moov = Some(moov);
|
||||
}
|
||||
BoxType::MoofBox => {
|
||||
skip_box(&mut self.reader, s)?;
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(&mut self.reader, s)?;
|
||||
}
|
||||
}
|
||||
current = self.reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
self.size = current - start;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn track_count(&self) -> Result<u32> {
|
||||
if let Some(ref moov) = self.moov {
|
||||
Ok(moov.traks.len() as u32)
|
||||
} else {
|
||||
Err(Error::BoxNotFound(MoovBox::box_type()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_count(&self, track_id: u32) -> Result<u32> {
|
||||
if track_id == 0 {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
|
||||
let moov = if let Some(ref moov) = self.moov {
|
||||
moov
|
||||
} else {
|
||||
return Err(Error::BoxNotFound(MoovBox::box_type()));
|
||||
};
|
||||
|
||||
let trak = if let Some(trak) = moov.traks.get(track_id as usize - 1) {
|
||||
trak
|
||||
} else {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
};
|
||||
|
||||
trak.sample_count()
|
||||
}
|
||||
|
||||
pub fn read_sample(
|
||||
&mut self,
|
||||
track_id: u32,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Mp4Sample>> {
|
||||
if track_id == 0 {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
|
||||
let moov = if let Some(ref moov) = self.moov {
|
||||
moov
|
||||
} else {
|
||||
return Err(Error::BoxNotFound(MoovBox::box_type()));
|
||||
};
|
||||
|
||||
let trak = if let Some(trak) = moov.traks.get(track_id as usize - 1) {
|
||||
trak
|
||||
} else {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
};
|
||||
|
||||
trak.read_sample(&mut self.reader, sample_id)
|
||||
}
|
||||
}
|
||||
mod writer;
|
||||
pub use writer::{Mp4Config, Mp4Writer};
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use num_rational::Ratio;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Avc1Box {
|
||||
pub data_reference_index: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub horizresolution: Ratio<u32>,
|
||||
pub vertresolution: Ratio<u32>,
|
||||
pub horizresolution: FixedPointU16,
|
||||
pub vertresolution: FixedPointU16,
|
||||
pub frame_count: u16,
|
||||
pub depth: u16,
|
||||
pub avcc: AvcCBox,
|
||||
|
@ -24,8 +21,8 @@ impl Default for Avc1Box {
|
|||
data_reference_index: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
horizresolution: Ratio::new_raw(0x00480000, 0x10000),
|
||||
vertresolution: Ratio::new_raw(0x00480000, 0x10000),
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 0x0018,
|
||||
avcc: AvcCBox::default(),
|
||||
|
@ -33,19 +30,34 @@ impl Default for Avc1Box {
|
|||
}
|
||||
}
|
||||
|
||||
impl Avc1Box {
|
||||
pub fn new(config: &AvcConfig) -> Self {
|
||||
Avc1Box {
|
||||
data_reference_index: 1,
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 0x0018,
|
||||
avcc: AvcCBox::new(&config.seq_param_set, &config.pic_param_set),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Avc1Box {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::Avc1Box
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + 8 + 74 + self.avcc.box_size()
|
||||
HEADER_SIZE + 8 + 70 + self.avcc.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
|
@ -56,10 +68,8 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
reader.read_u32::<BigEndian>()?; // pre-defined
|
||||
let width = reader.read_u16::<BigEndian>()?;
|
||||
let height = reader.read_u16::<BigEndian>()?;
|
||||
let horiznumer = reader.read_u32::<BigEndian>()?;
|
||||
let horizresolution = Ratio::new_raw(horiznumer, 0x10000);
|
||||
let vertnumer = reader.read_u32::<BigEndian>()?;
|
||||
let vertresolution = Ratio::new_raw(vertnumer, 0x10000);
|
||||
let horizresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
let vertresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
let frame_count = reader.read_u16::<BigEndian>()?;
|
||||
skip_read(reader, 32)?; // compressorname
|
||||
|
@ -67,7 +77,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
reader.read_i16::<BigEndian>()?; // pre-defined
|
||||
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if name == BoxType::AvcCBox {
|
||||
let avcc = AvcCBox::read_box(reader, s)?;
|
||||
|
||||
|
@ -103,14 +113,12 @@ impl<W: Write> WriteBox<&mut W> for Avc1Box {
|
|||
writer.write_u32::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u16::<BigEndian>(self.width)?;
|
||||
writer.write_u16::<BigEndian>(self.height)?;
|
||||
writer.write_u32::<BigEndian>(*self.horizresolution.numer())?;
|
||||
writer.write_u32::<BigEndian>(*self.vertresolution.numer())?;
|
||||
writer.write_u32::<BigEndian>(self.horizresolution.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(self.vertresolution.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.frame_count)?;
|
||||
// skip compressorname
|
||||
for _ in 0..4 {
|
||||
writer.write_u64::<BigEndian>(0)?;
|
||||
}
|
||||
skip_write(writer, 32)?;
|
||||
writer.write_u16::<BigEndian>(self.depth)?;
|
||||
writer.write_i16::<BigEndian>(-1)?; // pre-defined
|
||||
|
||||
|
@ -120,8 +128,7 @@ impl<W: Write> WriteBox<&mut W> for Avc1Box {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct AvcCBox {
|
||||
pub configuration_version: u8,
|
||||
pub avc_profile_indication: u8,
|
||||
|
@ -132,6 +139,20 @@ pub struct AvcCBox {
|
|||
pub picture_parameter_sets: Vec<NalUnit>,
|
||||
}
|
||||
|
||||
impl AvcCBox {
|
||||
pub fn new(sps: &[u8], pps: &[u8]) -> Self {
|
||||
Self {
|
||||
configuration_version: 1,
|
||||
avc_profile_indication: sps[1],
|
||||
profile_compatibility: sps[2],
|
||||
avc_level_indication: sps[3],
|
||||
length_size_minus_one: 0xff, // length_size = 4
|
||||
sequence_parameter_sets: vec![NalUnit::from(sps)],
|
||||
picture_parameter_sets: vec![NalUnit::from(pps)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for AvcCBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::AvcCBox
|
||||
|
@ -151,7 +172,7 @@ impl Mp4Box for AvcCBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for AvcCBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let configuration_version = reader.read_u8()?;
|
||||
let avc_profile_indication = reader.read_u8()?;
|
||||
|
@ -207,29 +228,81 @@ impl<W: Write> WriteBox<&mut W> for AvcCBox {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct NalUnit {
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<&[u8]> for NalUnit {
|
||||
fn from(bytes: &[u8]) -> Self {
|
||||
Self {
|
||||
bytes: bytes.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NalUnit {
|
||||
pub fn size(&self) -> usize {
|
||||
fn size(&self) -> usize {
|
||||
2 + self.bytes.len()
|
||||
}
|
||||
|
||||
pub fn read<R: Read + Seek>(reader: &mut R) -> Result<Self> {
|
||||
fn read<R: Read + Seek>(reader: &mut R) -> Result<Self> {
|
||||
let length = reader.read_u16::<BigEndian>()? as usize;
|
||||
let mut bytes = vec![0u8; length];
|
||||
reader.read(&mut bytes)?;
|
||||
Ok(NalUnit {
|
||||
bytes,
|
||||
})
|
||||
Ok(NalUnit { bytes })
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
||||
writer.write_u16::<BigEndian>(self.bytes.len() as u16)?;
|
||||
writer.write(&self.bytes)?;
|
||||
Ok(self.size() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_avc1() {
|
||||
let src_box = Avc1Box {
|
||||
data_reference_index: 1,
|
||||
width: 320,
|
||||
height: 240,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 24,
|
||||
avcc: AvcCBox {
|
||||
configuration_version: 1,
|
||||
avc_profile_indication: 100,
|
||||
profile_compatibility: 0,
|
||||
avc_level_indication: 13,
|
||||
length_size_minus_one: 3,
|
||||
sequence_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![
|
||||
0x67, 0x64, 0x00, 0x0D, 0xAC, 0xD9, 0x41, 0x41, 0xFA, 0x10, 0x00, 0x00,
|
||||
0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x42, 0x99, 0x60,
|
||||
],
|
||||
}],
|
||||
picture_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0],
|
||||
}],
|
||||
},
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Avc1Box);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Avc1Box::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Co64Box {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -24,7 +22,7 @@ impl Mp4Box for Co64Box {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Co64Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -64,7 +62,7 @@ impl<W: Write> WriteBox<&mut W> for Co64Box {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
|
@ -1,18 +1,16 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct CttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<CttsEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct CttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_offset: i32,
|
||||
|
@ -30,7 +28,7 @@ impl Mp4Box for CttsBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CttsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -74,7 +72,7 @@ impl<W: Write> WriteBox<&mut W> for CttsBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
|
@ -83,8 +81,14 @@ mod tests {
|
|||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![
|
||||
CttsEntry {sample_count: 1, sample_offset: 200},
|
||||
CttsEntry {sample_count: 2, sample_offset: -100},
|
||||
CttsEntry {
|
||||
sample_count: 1,
|
||||
sample_offset: 200,
|
||||
},
|
||||
CttsEntry {
|
||||
sample_count: 2,
|
||||
sample_offset: -100,
|
||||
},
|
||||
],
|
||||
};
|
||||
let mut buf = Vec::new();
|
|
@ -1,11 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::elst::ElstBox;
|
||||
use crate::mp4box::elst::ElstBox;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct EdtsBox {
|
||||
pub elst: Option<ElstBox>,
|
||||
}
|
||||
|
@ -32,12 +30,12 @@ impl Mp4Box for EdtsBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for EdtsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut edts = EdtsBox::new();
|
||||
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::ElstBox => {
|
|
@ -1,18 +1,16 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct ElstBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<ElstEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct ElstEntry {
|
||||
pub segment_duration: u64,
|
||||
pub media_time: u64,
|
||||
|
@ -39,27 +37,26 @@ impl Mp4Box for ElstBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for ElstBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _ in 0..entry_count {
|
||||
let (segment_duration, media_time)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let (segment_duration, media_time) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
|
||||
let entry = ElstEntry{
|
||||
let entry = ElstEntry {
|
||||
segment_duration,
|
||||
media_time,
|
||||
media_rate: reader.read_u16::<BigEndian>()?,
|
||||
|
@ -105,7 +102,7 @@ impl<W: Write> WriteBox<&mut W> for ElstBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
|
@ -1,11 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct FtypBox {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
|
@ -24,7 +22,7 @@ impl Mp4Box for FtypBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for FtypBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let major = reader.read_u32::<BigEndian>()?;
|
||||
let minor = reader.read_u32::<BigEndian>()?;
|
||||
|
@ -66,20 +64,30 @@ impl<W: Write> WriteBox<&mut W> for FtypBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_ftyp() {
|
||||
let src_box = FtypBox {
|
||||
major_brand: FourCC { value: String::from("isom") },
|
||||
major_brand: FourCC {
|
||||
value: String::from("isom"),
|
||||
},
|
||||
minor_version: 0,
|
||||
compatible_brands: vec![
|
||||
FourCC { value: String::from("isom") },
|
||||
FourCC { value: String::from("iso2") },
|
||||
FourCC { value: String::from("avc1") },
|
||||
FourCC { value: String::from("mp41") },
|
||||
]
|
||||
FourCC {
|
||||
value: String::from("isom"),
|
||||
},
|
||||
FourCC {
|
||||
value: String::from("iso2"),
|
||||
},
|
||||
FourCC {
|
||||
value: String::from("avc1"),
|
||||
},
|
||||
FourCC {
|
||||
value: String::from("mp41"),
|
||||
},
|
||||
],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
|
@ -1,11 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct HdlrBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -25,7 +23,7 @@ impl Mp4Box for HdlrBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -42,7 +40,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
|
|||
Ok(t) => {
|
||||
assert_eq!(t.len(), buf_size as usize);
|
||||
t
|
||||
},
|
||||
}
|
||||
_ => String::from("null"),
|
||||
};
|
||||
|
||||
|
@ -82,7 +80,7 @@ impl<W: Write> WriteBox<&mut W> for HdlrBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
|
@ -1,12 +1,10 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MdhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -52,29 +50,28 @@ impl Mp4Box for MdhdBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MdhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let (creation_time, modification_time, timescale, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let language_code = reader.read_u16::<BigEndian>()?;
|
||||
let language = get_language_string(language_code);
|
||||
let language = language_string(language_code);
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
|
@ -110,7 +107,7 @@ impl<W: Write> WriteBox<&mut W> for MdhdBox {
|
|||
writer.write_u32::<BigEndian>(self.duration as u32)?;
|
||||
}
|
||||
|
||||
let language_code = get_language_code(&self.language);
|
||||
let language_code = language_code(&self.language);
|
||||
writer.write_u16::<BigEndian>(language_code)?;
|
||||
writer.write_u16::<BigEndian>(0)?; // pre-defined
|
||||
|
||||
|
@ -118,7 +115,7 @@ impl<W: Write> WriteBox<&mut W> for MdhdBox {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_language_string(language: u16) -> String {
|
||||
fn language_string(language: u16) -> String {
|
||||
let mut lang: [u16; 3] = [0; 3];
|
||||
|
||||
lang[0] = ((language >> 10) & 0x1F) + 0x60;
|
||||
|
@ -133,7 +130,7 @@ fn get_language_string(language: u16) -> String {
|
|||
return lang_str;
|
||||
}
|
||||
|
||||
fn get_language_code(language: &str) -> u16 {
|
||||
fn language_code(language: &str) -> u16 {
|
||||
let mut lang = language.encode_utf16();
|
||||
let mut code = (lang.next().unwrap_or(0) & 0x1F) << 10;
|
||||
code += (lang.next().unwrap_or(0) & 0x1F) << 5;
|
||||
|
@ -144,12 +141,12 @@ fn get_language_code(language: &str) -> u16 {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
fn test_language_code(lang: &str) {
|
||||
let code = get_language_code(lang);
|
||||
let lang2 = get_language_string(code);
|
||||
let code = language_code(lang);
|
||||
let lang2 = language_string(code);
|
||||
assert_eq!(lang, lang2);
|
||||
}
|
||||
|
88
src/mp4box/mdia.rs
Normal file
88
src/mp4box/mdia.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -1,21 +1,13 @@
|
|||
use std::io::{Seek, SeekFrom, Read, Write};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{vmhd::VmhdBox, smhd::SmhdBox, stbl::StblBox};
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{smhd::SmhdBox, stbl::StblBox, vmhd::VmhdBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct MinfBox {
|
||||
pub vmhd: Option<VmhdBox>,
|
||||
pub smhd: Option<SmhdBox>,
|
||||
pub stbl: Option<StblBox>,
|
||||
}
|
||||
|
||||
impl MinfBox {
|
||||
pub(crate) fn new() -> MinfBox {
|
||||
Default::default()
|
||||
}
|
||||
pub stbl: StblBox,
|
||||
}
|
||||
|
||||
impl Mp4Box for MinfBox {
|
||||
|
@ -31,41 +23,39 @@ impl Mp4Box for MinfBox {
|
|||
if let Some(ref smhd) = self.smhd {
|
||||
size += smhd.box_size();
|
||||
}
|
||||
if let Some(ref stbl) = self.stbl {
|
||||
size += stbl.box_size();
|
||||
}
|
||||
size += self.stbl.box_size();
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MinfBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut minf = MinfBox::new();
|
||||
let mut vmhd = None;
|
||||
let mut smhd = None;
|
||||
let mut stbl = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::VmhdBox => {
|
||||
let vmhd = VmhdBox::read_box(reader, s)?;
|
||||
minf.vmhd = Some(vmhd);
|
||||
vmhd = Some(VmhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::SmhdBox => {
|
||||
let smhd = SmhdBox::read_box(reader, s)?;
|
||||
minf.smhd = Some(smhd);
|
||||
smhd = Some(SmhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::DinfBox => {// XXX warn!()
|
||||
BoxType::DinfBox => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
BoxType::StblBox => {
|
||||
let stbl = StblBox::read_box(reader, s)?;
|
||||
minf.stbl = Some(stbl);
|
||||
stbl = Some(StblBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
|
@ -76,9 +66,17 @@ impl<R: Read + Seek> ReadBox<&mut R> for MinfBox {
|
|||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
|
||||
if stbl.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::StblBox));
|
||||
}
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(minf)
|
||||
Ok(MinfBox {
|
||||
vmhd,
|
||||
smhd,
|
||||
stbl: stbl.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,9 +91,7 @@ impl<W: Write> WriteBox<&mut W> for MinfBox {
|
|||
if let Some(ref smhd) = self.smhd {
|
||||
smhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref stbl) = self.stbl {
|
||||
stbl.write_box(writer)?;
|
||||
}
|
||||
self.stbl.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
|
@ -1,38 +1,38 @@
|
|||
use std::fmt;
|
||||
use std::io::{Seek, SeekFrom, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::convert::TryInto;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::*;
|
||||
|
||||
mod ftyp;
|
||||
mod moov;
|
||||
mod mvhd;
|
||||
mod trak;
|
||||
mod tkhd;
|
||||
mod edts;
|
||||
mod elst;
|
||||
mod mdia;
|
||||
mod mdhd;
|
||||
mod hdlr;
|
||||
mod minf;
|
||||
mod vmhd;
|
||||
mod smhd;
|
||||
mod stbl;
|
||||
mod stsd;
|
||||
mod stts;
|
||||
mod ctts;
|
||||
mod stss;
|
||||
mod stsc;
|
||||
mod stsz;
|
||||
mod stco;
|
||||
mod co64;
|
||||
mod avc;
|
||||
mod mp4a;
|
||||
pub(crate) mod avc1;
|
||||
pub(crate) mod co64;
|
||||
pub(crate) mod ctts;
|
||||
pub(crate) mod edts;
|
||||
pub(crate) mod elst;
|
||||
pub(crate) mod ftyp;
|
||||
pub(crate) mod hdlr;
|
||||
pub(crate) mod mdhd;
|
||||
pub(crate) mod mdia;
|
||||
pub(crate) mod minf;
|
||||
pub(crate) mod moov;
|
||||
pub(crate) mod mp4a;
|
||||
pub(crate) mod mvhd;
|
||||
pub(crate) mod smhd;
|
||||
pub(crate) mod stbl;
|
||||
pub(crate) mod stco;
|
||||
pub(crate) mod stsc;
|
||||
pub(crate) mod stsd;
|
||||
pub(crate) mod stss;
|
||||
pub(crate) mod stsz;
|
||||
pub(crate) mod stts;
|
||||
pub(crate) mod tkhd;
|
||||
pub(crate) mod trak;
|
||||
pub(crate) mod vmhd;
|
||||
|
||||
pub use ftyp::FtypBox;
|
||||
pub use moov::MoovBox;
|
||||
|
||||
const HEADER_SIZE: u64 = 8;
|
||||
pub const HEADER_SIZE: u64 = 8;
|
||||
// const HEADER_LARGE_SIZE: u64 = 16;
|
||||
pub const HEADER_EXT_SIZE: u64 = 4;
|
||||
|
||||
|
@ -64,7 +64,7 @@ macro_rules! boxtype {
|
|||
}
|
||||
}
|
||||
|
||||
boxtype!{
|
||||
boxtype! {
|
||||
FtypBox => 0x66747970,
|
||||
MvhdBox => 0x6d766864,
|
||||
FreeBox => 0x66726565,
|
||||
|
@ -98,100 +98,6 @@ boxtype!{
|
|||
EsdsBox => 0x65736473
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct FourCC {
|
||||
pub value: String
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> Self {
|
||||
let mut box_chars = Vec::new();
|
||||
for x in 0..4 {
|
||||
let c = (number >> (x * 8) & 0x0000_00FF) as u8;
|
||||
box_chars.push(c);
|
||||
}
|
||||
box_chars.reverse();
|
||||
|
||||
let box_string = match String::from_utf8(box_chars) {
|
||||
Ok(t) => t,
|
||||
_ => String::from("null"), // error to retrieve fourcc
|
||||
};
|
||||
|
||||
FourCC {
|
||||
value: box_string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FourCC> for u32 {
|
||||
fn from(fourcc: FourCC) -> u32 {
|
||||
(&fourcc).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FourCC> for u32 {
|
||||
fn from(fourcc: &FourCC) -> u32 {
|
||||
let mut b: [u8; 4] = Default::default();
|
||||
b.copy_from_slice(fourcc.value.as_bytes());
|
||||
u32::from_be_bytes(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for FourCC {
|
||||
fn from(fourcc: String) -> FourCC {
|
||||
let value = if fourcc.len() > 4 {
|
||||
fourcc[0..4].to_string()
|
||||
} else {
|
||||
fourcc
|
||||
};
|
||||
FourCC {value}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for FourCC {
|
||||
fn from(fourcc: &str) -> FourCC {
|
||||
let value = if fourcc.len() > 4 {
|
||||
fourcc[0..4].to_string()
|
||||
} else {
|
||||
fourcc.to_string()
|
||||
};
|
||||
FourCC {value}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoxType> for FourCC {
|
||||
fn from(t: BoxType) -> FourCC {
|
||||
let box_num: u32 = Into::into(t);
|
||||
From::from(box_num)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Mp4Box: Sized {
|
||||
fn box_type() -> BoxType;
|
||||
fn box_size(&self) -> u64;
|
||||
|
@ -212,14 +118,14 @@ pub struct BoxHeader {
|
|||
}
|
||||
|
||||
impl BoxHeader {
|
||||
fn new(name: BoxType, size: u64) -> Self {
|
||||
pub fn new(name: BoxType, size: u64) -> Self {
|
||||
Self { name, size }
|
||||
}
|
||||
|
||||
// TODO: if size is 0, then this box is the last one in the file
|
||||
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
|
||||
// Create and read to buf.
|
||||
let mut buf = [0u8;8]; // 8 bytes for box header.
|
||||
let mut buf = [0u8; 8]; // 8 bytes for box header.
|
||||
reader.read(&mut buf)?;
|
||||
|
||||
// Get size.
|
||||
|
@ -248,7 +154,7 @@ impl BoxHeader {
|
|||
}
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
||||
pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
||||
if self.size > u32::MAX as u64 {
|
||||
writer.write_u32::<BigEndian>(1)?;
|
||||
writer.write_u32::<BigEndian>(self.name.into())?;
|
||||
|
@ -274,7 +180,7 @@ pub fn write_box_header_ext<W: Write>(w: &mut W, v: u8, f: u32) -> Result<u64> {
|
|||
Ok(4)
|
||||
}
|
||||
|
||||
pub fn get_box_start<R: Seek>(reader: &mut R) -> Result<u64> {
|
||||
pub fn box_start<R: Seek>(reader: &mut R) -> Result<u64> {
|
||||
Ok(reader.seek(SeekFrom::Current(0))? - HEADER_SIZE)
|
||||
}
|
||||
|
||||
|
@ -290,7 +196,7 @@ pub fn skip_read_to<R: Read + Seek>(reader: &mut R, pos: u64) -> Result<()> {
|
|||
}
|
||||
|
||||
pub fn skip_box<R: Read + Seek>(reader: &mut R, size: u64) -> Result<()> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
skip_read_to(reader, start + size)?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,22 +1,14 @@
|
|||
use std::io::{Seek, SeekFrom, Read, Write};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{mvhd::MvhdBox, trak::TrakBox};
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{mvhd::MvhdBox, trak::TrakBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct MoovBox {
|
||||
pub mvhd: MvhdBox,
|
||||
pub traks: Vec<TrakBox>,
|
||||
}
|
||||
|
||||
impl MoovBox {
|
||||
pub(crate) fn new() -> MoovBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MoovBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::MoovBox
|
||||
|
@ -33,25 +25,25 @@ impl Mp4Box for MoovBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut moov = MoovBox::new();
|
||||
let mut mvhd = None;
|
||||
let mut traks = Vec::new();
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MvhdBox => {
|
||||
moov.mvhd = MvhdBox::read_box(reader, s)?;
|
||||
mvhd = Some(MvhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::TrakBox => {
|
||||
let mut trak = TrakBox::read_box(reader, s)?;
|
||||
trak.id = moov.traks.len() as u32 + 1;
|
||||
moov.traks.push(trak);
|
||||
let trak = TrakBox::read_box(reader, s)?;
|
||||
traks.push(trak);
|
||||
}
|
||||
BoxType::UdtaBox => {
|
||||
// XXX warn!()
|
||||
|
@ -66,9 +58,16 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
|
||||
if mvhd.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::MvhdBox));
|
||||
}
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(moov)
|
||||
Ok(MoovBox {
|
||||
mvhd: mvhd.unwrap(),
|
||||
traks,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
561
src/mp4box/mp4a.rs
Normal file
561
src/mp4box/mp4a.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use num_rational::Ratio;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MvhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -14,7 +11,7 @@ pub struct MvhdBox {
|
|||
pub modification_time: u64,
|
||||
pub timescale: u32,
|
||||
pub duration: u64,
|
||||
pub rate: Ratio<u32>,
|
||||
pub rate: FixedPointU16,
|
||||
}
|
||||
|
||||
impl Default for MvhdBox {
|
||||
|
@ -26,7 +23,7 @@ impl Default for MvhdBox {
|
|||
modification_time: 0,
|
||||
timescale: 1000,
|
||||
duration: 0,
|
||||
rate: Ratio::new_raw(0x00010000, 0x10000),
|
||||
rate: FixedPointU16::new(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,33 +48,31 @@ impl Mp4Box for MvhdBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MvhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let numer = reader.read_u32::<BigEndian>()?;
|
||||
let rate = Ratio::new_raw(numer, 0x10000);
|
||||
let (creation_time, modification_time, timescale, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let rate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(MvhdBox{
|
||||
Ok(MvhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
|
@ -108,7 +103,7 @@ impl<W: Write> WriteBox<&mut W> for MvhdBox {
|
|||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u32::<BigEndian>(self.duration as u32)?;
|
||||
}
|
||||
writer.write_u32::<BigEndian>(*self.rate.numer())?;
|
||||
writer.write_u32::<BigEndian>(self.rate.raw_value())?;
|
||||
|
||||
// XXX volume, ...
|
||||
skip_write(writer, 76)?;
|
||||
|
@ -120,7 +115,7 @@ impl<W: Write> WriteBox<&mut W> for MvhdBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
|
@ -132,7 +127,7 @@ mod tests {
|
|||
modification_time: 200,
|
||||
timescale: 1000,
|
||||
duration: 634634,
|
||||
rate: Ratio::new_raw(0x00010000, 0x10000),
|
||||
rate: FixedPointU16::new(1),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
|
@ -156,7 +151,7 @@ mod tests {
|
|||
modification_time: 200,
|
||||
timescale: 1000,
|
||||
duration: 634634,
|
||||
rate: Ratio::new_raw(0x00010000, 0x10000),
|
||||
rate: FixedPointU16::new(1),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
|
@ -1,16 +1,13 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use num_rational::Ratio;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub balance: Ratio<i16>,
|
||||
pub balance: FixedPointI8,
|
||||
}
|
||||
|
||||
impl Default for SmhdBox {
|
||||
|
@ -18,7 +15,7 @@ impl Default for SmhdBox {
|
|||
SmhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
balance: Ratio::new_raw(0, 0x100),
|
||||
balance: FixedPointI8::new_raw(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,12 +32,11 @@ impl Mp4Box for SmhdBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for SmhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let balance_numer = reader.read_i16::<BigEndian>()?;
|
||||
let balance = Ratio::new_raw(balance_numer, 0x100);
|
||||
let balance = FixedPointI8::new_raw(reader.read_i16::<BigEndian>()?);
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
|
@ -59,18 +55,17 @@ impl<W: Write> WriteBox<&mut W> for SmhdBox {
|
|||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_i16::<BigEndian>(*self.balance.numer())?;
|
||||
writer.write_i16::<BigEndian>(self.balance.raw_value())?;
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
|
@ -78,7 +73,7 @@ mod tests {
|
|||
let src_box = SmhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
balance: Ratio::new_raw(-0x100, 0x100),
|
||||
balance: FixedPointI8::new_raw(-1),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
|
@ -1,37 +1,23 @@
|
|||
use std::io::{Seek, SeekFrom, Read, Write};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{
|
||||
stsd::StsdBox,
|
||||
stts::SttsBox,
|
||||
ctts::CttsBox,
|
||||
stss::StssBox,
|
||||
stsc::StscBox,
|
||||
stsz::StszBox,
|
||||
stco::StcoBox,
|
||||
co64::Co64Box,
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{
|
||||
co64::Co64Box, ctts::CttsBox, stco::StcoBox, stsc::StscBox, stsd::StsdBox, stss::StssBox,
|
||||
stsz::StszBox, stts::SttsBox,
|
||||
};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StblBox {
|
||||
pub stsd: Option<StsdBox>,
|
||||
pub stts: Option<SttsBox>,
|
||||
pub stsd: StsdBox,
|
||||
pub stts: SttsBox,
|
||||
pub ctts: Option<CttsBox>,
|
||||
pub stss: Option<StssBox>,
|
||||
pub stsc: Option<StscBox>,
|
||||
pub stsz: Option<StszBox>,
|
||||
pub stsc: StscBox,
|
||||
pub stsz: StszBox,
|
||||
pub stco: Option<StcoBox>,
|
||||
pub co64: Option<Co64Box>,
|
||||
}
|
||||
|
||||
impl StblBox {
|
||||
pub(crate) fn new() -> StblBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for StblBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::StblBox
|
||||
|
@ -39,24 +25,16 @@ impl Mp4Box for StblBox {
|
|||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(ref stsd) = self.stsd {
|
||||
size += stsd.box_size();
|
||||
}
|
||||
if let Some(ref stts) = self.stts {
|
||||
size += stts.box_size();
|
||||
}
|
||||
size += self.stsd.box_size();
|
||||
size += self.stts.box_size();
|
||||
if let Some(ref ctts) = self.ctts {
|
||||
size += ctts.box_size();
|
||||
}
|
||||
if let Some(ref stss) = self.stss {
|
||||
size += stss.box_size();
|
||||
}
|
||||
if let Some(ref stsc) = self.stsc {
|
||||
size += stsc.box_size();
|
||||
}
|
||||
if let Some(ref stsz) = self.stsz {
|
||||
size += stsz.box_size();
|
||||
}
|
||||
size += self.stsc.box_size();
|
||||
size += self.stsz.box_size();
|
||||
if let Some(ref stco) = self.stco {
|
||||
size += stco.box_size();
|
||||
}
|
||||
|
@ -69,49 +47,48 @@ impl Mp4Box for StblBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut stbl = StblBox::new();
|
||||
let mut stsd = None;
|
||||
let mut stts = None;
|
||||
let mut ctts = None;
|
||||
let mut stss = None;
|
||||
let mut stsc = None;
|
||||
let mut stsz = None;
|
||||
let mut stco = None;
|
||||
let mut co64 = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::StsdBox => {
|
||||
let stsd = StsdBox::read_box(reader, s)?;
|
||||
stbl.stsd = Some(stsd);
|
||||
stsd = Some(StsdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::SttsBox => {
|
||||
let stts = SttsBox::read_box(reader, s)?;
|
||||
stbl.stts = Some(stts);
|
||||
stts = Some(SttsBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::CttsBox => {
|
||||
let ctts = CttsBox::read_box(reader, s)?;
|
||||
stbl.ctts = Some(ctts);
|
||||
ctts = Some(CttsBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StssBox => {
|
||||
let stss = StssBox::read_box(reader, s)?;
|
||||
stbl.stss = Some(stss);
|
||||
stss = Some(StssBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StscBox => {
|
||||
let stsc = StscBox::read_box(reader, s)?;
|
||||
stbl.stsc = Some(stsc);
|
||||
stsc = Some(StscBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StszBox => {
|
||||
let stsz = StszBox::read_box(reader, s)?;
|
||||
stbl.stsz = Some(stsz);
|
||||
stsz = Some(StszBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StcoBox => {
|
||||
let stco = StcoBox::read_box(reader, s)?;
|
||||
stbl.stco = Some(stco);
|
||||
stco = Some(StcoBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::Co64Box => {
|
||||
let co64 = Co64Box::read_box(reader, s)?;
|
||||
stbl.co64 = Some(co64);
|
||||
co64 = Some(Co64Box::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
|
@ -121,9 +98,34 @@ impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
|||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
|
||||
if stsd.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::StsdBox));
|
||||
}
|
||||
if stts.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::SttsBox));
|
||||
}
|
||||
if stsc.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::StscBox));
|
||||
}
|
||||
if stsz.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::StszBox));
|
||||
}
|
||||
if stco.is_none() && co64.is_none() {
|
||||
return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box));
|
||||
}
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(stbl)
|
||||
Ok(StblBox {
|
||||
stsd: stsd.unwrap(),
|
||||
stts: stts.unwrap(),
|
||||
ctts: ctts,
|
||||
stss: stss,
|
||||
stsc: stsc.unwrap(),
|
||||
stsz: stsz.unwrap(),
|
||||
stco: stco,
|
||||
co64: co64,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,24 +134,16 @@ impl<W: Write> WriteBox<&mut W> for StblBox {
|
|||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(ref stsd) = self.stsd {
|
||||
stsd.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref stts) = self.stts {
|
||||
stts.write_box(writer)?;
|
||||
}
|
||||
self.stsd.write_box(writer)?;
|
||||
self.stts.write_box(writer)?;
|
||||
if let Some(ref ctts) = self.ctts {
|
||||
ctts.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref stss) = self.stss {
|
||||
stss.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref stsc) = self.stsc {
|
||||
stsc.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref stsz) = self.stsz {
|
||||
stsz.write_box(writer)?;
|
||||
}
|
||||
self.stsc.write_box(writer)?;
|
||||
self.stsz.write_box(writer)?;
|
||||
if let Some(ref stco) = self.stco {
|
||||
stco.write_box(writer)?;
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StcoBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -24,7 +22,7 @@ impl Mp4Box for StcoBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StcoBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -64,7 +62,7 @@ impl<W: Write> WriteBox<&mut W> for StcoBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
|
@ -1,18 +1,16 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StscBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<StscEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StscEntry {
|
||||
pub first_chunk: u32,
|
||||
pub samples_per_chunk: u32,
|
||||
|
@ -32,7 +30,7 @@ impl Mp4Box for StscBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StscBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -92,7 +90,7 @@ impl<W: Write> WriteBox<&mut W> for StscBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
|
@ -1,12 +1,10 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::atoms::{avc::Avc1Box, mp4a::Mp4aBox};
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{avc1::Avc1Box, mp4a::Mp4aBox};
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StsdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -20,7 +18,7 @@ impl Mp4Box for StsdBox {
|
|||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4;
|
||||
if let Some(ref avc1) = self.avc1 {
|
||||
size += avc1.box_size();
|
||||
} else if let Some(ref mp4a) = self.mp4a {
|
||||
|
@ -32,7 +30,7 @@ impl Mp4Box for StsdBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -43,7 +41,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
|||
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::Avc1Box => {
|
|
@ -1,11 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StssBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -24,7 +22,7 @@ impl Mp4Box for StssBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StssBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -64,7 +62,7 @@ impl<W: Write> WriteBox<&mut W> for StssBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
|
@ -1,15 +1,14 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct StszBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub sample_size: u32,
|
||||
pub sample_count: u32,
|
||||
pub sample_sizes: Vec<u32>,
|
||||
}
|
||||
|
||||
|
@ -25,7 +24,7 @@ impl Mp4Box for StszBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -33,7 +32,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
|
|||
let sample_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut sample_sizes = Vec::with_capacity(sample_count as usize);
|
||||
if sample_size == 0 {
|
||||
for _i in 0..sample_count {
|
||||
for _ in 0..sample_count {
|
||||
let sample_number = reader.read_u32::<BigEndian>()?;
|
||||
sample_sizes.push(sample_number);
|
||||
}
|
||||
|
@ -45,6 +44,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
|
|||
version,
|
||||
flags,
|
||||
sample_size,
|
||||
sample_count,
|
||||
sample_sizes,
|
||||
})
|
||||
}
|
||||
|
@ -58,8 +58,9 @@ impl<W: Write> WriteBox<&mut W> for StszBox {
|
|||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.sample_size)?;
|
||||
writer.write_u32::<BigEndian>(self.sample_sizes.len() as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.sample_count)?;
|
||||
if self.sample_size == 0 {
|
||||
assert_eq!(self.sample_count, self.sample_sizes.len() as u32);
|
||||
for sample_number in self.sample_sizes.iter() {
|
||||
writer.write_u32::<BigEndian>(*sample_number)?;
|
||||
}
|
||||
|
@ -72,7 +73,7 @@ impl<W: Write> WriteBox<&mut W> for StszBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
|
@ -81,6 +82,7 @@ mod tests {
|
|||
version: 0,
|
||||
flags: 0,
|
||||
sample_size: 1165,
|
||||
sample_count: 12,
|
||||
sample_sizes: vec![],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
|
@ -102,6 +104,7 @@ mod tests {
|
|||
version: 0,
|
||||
flags: 0,
|
||||
sample_size: 0,
|
||||
sample_count: 9,
|
||||
sample_sizes: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730],
|
||||
};
|
||||
let mut buf = Vec::new();
|
|
@ -1,18 +1,16 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct SttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<SttsEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct SttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_delta: u32,
|
||||
|
@ -30,7 +28,7 @@ impl Mp4Box for SttsBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for SttsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -74,7 +72,7 @@ impl<W: Write> WriteBox<&mut W> for SttsBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
|
@ -83,8 +81,14 @@ mod tests {
|
|||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![
|
||||
SttsEntry {sample_count: 29726, sample_delta: 1024},
|
||||
SttsEntry {sample_count: 1, sample_delta: 512},
|
||||
SttsEntry {
|
||||
sample_count: 29726,
|
||||
sample_delta: 1024,
|
||||
},
|
||||
SttsEntry {
|
||||
sample_count: 1,
|
||||
sample_delta: 512,
|
||||
},
|
||||
],
|
||||
};
|
||||
let mut buf = Vec::new();
|
|
@ -1,12 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use num_rational::Ratio;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TkhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -14,12 +11,12 @@ pub struct TkhdBox {
|
|||
pub modification_time: u64,
|
||||
pub track_id: u32,
|
||||
pub duration: u64,
|
||||
pub layer: u16,
|
||||
pub layer: u16,
|
||||
pub alternate_group: u16,
|
||||
pub volume: Ratio<u16>,
|
||||
pub volume: FixedPointU8,
|
||||
pub matrix: Matrix,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub width: FixedPointU16,
|
||||
pub height: FixedPointU16,
|
||||
}
|
||||
|
||||
impl Default for TkhdBox {
|
||||
|
@ -33,15 +30,15 @@ impl Default for TkhdBox {
|
|||
duration: 0,
|
||||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: Ratio::new_raw(0x0100, 0x100),
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: Matrix::default(),
|
||||
width: 0,
|
||||
height: 0,
|
||||
width: FixedPointU16::new(0),
|
||||
height: FixedPointU16::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Matrix {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
|
@ -54,6 +51,16 @@ pub struct Matrix {
|
|||
pub w: i32,
|
||||
}
|
||||
|
||||
impl TkhdBox {
|
||||
pub fn set_width(&mut self, width: u16) {
|
||||
self.width = FixedPointU16::new(width);
|
||||
}
|
||||
|
||||
pub fn set_height(&mut self, height: u16) {
|
||||
self.height = FixedPointU16::new(height);
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TkhdBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::TkhdBox
|
||||
|
@ -74,37 +81,35 @@ impl Mp4Box for TkhdBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, track_id, _, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
let (creation_time, modification_time, track_id, _, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
reader.read_u64::<BigEndian>()?; // reserved
|
||||
let layer = reader.read_u16::<BigEndian>()?;
|
||||
let alternate_group = reader.read_u16::<BigEndian>()?;
|
||||
let volume_numer = reader.read_u16::<BigEndian>()?;
|
||||
let volume = Ratio::new_raw(volume_numer, 0x100);
|
||||
let volume = FixedPointU8::new_raw(reader.read_u16::<BigEndian>()?);
|
||||
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let matrix = Matrix{
|
||||
let matrix = Matrix {
|
||||
a: reader.read_i32::<byteorder::LittleEndian>()?,
|
||||
b: reader.read_i32::<BigEndian>()?,
|
||||
u: reader.read_i32::<BigEndian>()?,
|
||||
|
@ -116,8 +121,8 @@ impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
|
|||
w: reader.read_i32::<BigEndian>()?,
|
||||
};
|
||||
|
||||
let width = reader.read_u32::<BigEndian>()? >> 16;
|
||||
let height = reader.read_u32::<BigEndian>()? >> 16;
|
||||
let width = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
let height = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
|
@ -163,7 +168,7 @@ impl<W: Write> WriteBox<&mut W> for TkhdBox {
|
|||
writer.write_u64::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.layer)?;
|
||||
writer.write_u16::<BigEndian>(self.alternate_group)?;
|
||||
writer.write_u16::<BigEndian>(*self.volume.numer())?;
|
||||
writer.write_u16::<BigEndian>(self.volume.raw_value())?;
|
||||
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
|
||||
|
@ -177,8 +182,8 @@ impl<W: Write> WriteBox<&mut W> for TkhdBox {
|
|||
writer.write_i32::<BigEndian>(self.matrix.y)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.w)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.width << 16)?;
|
||||
writer.write_u32::<BigEndian>(self.height << 16)?;
|
||||
writer.write_u32::<BigEndian>(self.width.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(self.height.raw_value())?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
|
@ -187,7 +192,7 @@ impl<W: Write> WriteBox<&mut W> for TkhdBox {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
|
@ -201,7 +206,7 @@ mod tests {
|
|||
duration: 634634,
|
||||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: Ratio::new_raw(0x0100, 0x100),
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: Matrix {
|
||||
a: 0x00010000,
|
||||
b: 0,
|
||||
|
@ -213,8 +218,8 @@ mod tests {
|
|||
y: 0,
|
||||
w: 0x40000000,
|
||||
},
|
||||
width: 512,
|
||||
height: 288,
|
||||
width: FixedPointU16::new(512),
|
||||
height: FixedPointU16::new(288),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
|
@ -240,7 +245,7 @@ mod tests {
|
|||
duration: 634634,
|
||||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: Ratio::new_raw(0x0100, 0x100),
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: Matrix {
|
||||
a: 0x00010000,
|
||||
b: 0,
|
||||
|
@ -252,8 +257,8 @@ mod tests {
|
|||
y: 0,
|
||||
w: 0x40000000,
|
||||
},
|
||||
width: 512,
|
||||
height: 288,
|
||||
width: FixedPointU16::new(512),
|
||||
height: FixedPointU16::new(288),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
93
src/mp4box/trak.rs
Normal file
93
src/mp4box/trak.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct VmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -13,7 +11,7 @@ pub struct VmhdBox {
|
|||
pub op_color: RgbColor,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct RgbColor {
|
||||
pub red: u16,
|
||||
pub green: u16,
|
||||
|
@ -32,7 +30,7 @@ impl Mp4Box for VmhdBox {
|
|||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for VmhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
|
@ -70,11 +68,10 @@ impl<W: Write> WriteBox<&mut W> for VmhdBox {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
|
@ -83,7 +80,11 @@ mod tests {
|
|||
version: 0,
|
||||
flags: 1,
|
||||
graphics_mode: 0,
|
||||
op_color: RgbColor { red: 0, green: 0, blue: 0},
|
||||
op_color: RgbColor {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
},
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
133
src/reader.rs
Normal file
133
src/reader.rs
Normal 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
671
src/track.rs
Normal 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
519
src/types.rs
Normal 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
102
src/writer.rs
Normal 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(())
|
||||
}
|
||||
}
|
126
tests/lib.rs
126
tests/lib.rs
|
@ -1,8 +1,7 @@
|
|||
use mp4;
|
||||
use mp4::{AudioObjectType, AvcProfile, ChannelConfig, MediaType, SampleFreqIndex, TrackType};
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_read_mp4() {
|
||||
let filename = "tests/samples/minimal.mp4";
|
||||
|
@ -10,71 +9,112 @@ fn test_read_mp4() {
|
|||
let size = f.metadata().unwrap().len();
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
let mut mp4 = mp4::Mp4Reader::new(reader);
|
||||
mp4.read(size).unwrap();
|
||||
let mut mp4 = mp4::Mp4Reader::read_header(reader, size).unwrap();
|
||||
|
||||
assert_eq!(2591, mp4.size());
|
||||
|
||||
// ftyp.
|
||||
println!("{:?}", mp4.ftyp.compatible_brands);
|
||||
assert_eq!(4, mp4.ftyp.compatible_brands.len());
|
||||
assert_eq!(4, mp4.compatible_brands().len());
|
||||
|
||||
// Check compatible_brands.
|
||||
let brands = vec![
|
||||
String::from("isom"),
|
||||
String::from("iso2"),
|
||||
String::from("avc1"),
|
||||
String::from("mp41")
|
||||
String::from("mp41"),
|
||||
];
|
||||
|
||||
for b in brands {
|
||||
let t = mp4.ftyp.compatible_brands.iter().any(|x| x.to_string() == b);
|
||||
let t = mp4.compatible_brands().iter().any(|x| x.to_string() == b);
|
||||
assert_eq!(t, true);
|
||||
}
|
||||
|
||||
// moov.
|
||||
assert!(mp4.moov.is_some());
|
||||
if let Some(ref moov) = mp4.moov {
|
||||
assert_eq!(moov.mvhd.version, 0);
|
||||
assert_eq!(moov.mvhd.creation_time, 0);
|
||||
assert_eq!(moov.mvhd.duration, 62);
|
||||
assert_eq!(moov.mvhd.timescale, 1000);
|
||||
assert_eq!(moov.traks.len(), 2);
|
||||
}
|
||||
assert_eq!(mp4.duration(), 62);
|
||||
assert_eq!(mp4.timescale(), 1000);
|
||||
assert_eq!(mp4.tracks().len(), 2);
|
||||
|
||||
let sample_count = mp4.sample_count(1).unwrap();
|
||||
assert_eq!(sample_count, 0);
|
||||
assert_eq!(sample_count, 1);
|
||||
let sample_1_1 = mp4.read_sample(1, 1).unwrap().unwrap();
|
||||
assert_eq!(sample_1_1.bytes.len(), 751);
|
||||
assert_eq!(
|
||||
sample_1_1,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 0,
|
||||
duration: 512,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 751]),
|
||||
}
|
||||
);
|
||||
let eos = mp4.read_sample(1, 2).unwrap();
|
||||
assert!(eos.is_none());
|
||||
|
||||
let sample_count = mp4.sample_count(2).unwrap();
|
||||
assert_eq!(sample_count, 3);
|
||||
let sample1 = mp4.read_sample(2, 1).unwrap().unwrap();
|
||||
assert_eq!(sample1.bytes.len(), 179);
|
||||
assert_eq!(sample1, mp4::Mp4Sample {
|
||||
start_time: 0,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 179]),
|
||||
});
|
||||
let sample_2_1 = mp4.read_sample(2, 1).unwrap().unwrap();
|
||||
assert_eq!(sample_2_1.bytes.len(), 179);
|
||||
assert_eq!(
|
||||
sample_2_1,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 0,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 179]),
|
||||
}
|
||||
);
|
||||
|
||||
let sample2 = mp4.read_sample(2, 2).unwrap().unwrap();
|
||||
assert_eq!(sample2, mp4::Mp4Sample {
|
||||
start_time: 1024,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 180]),
|
||||
});
|
||||
let sample_2_2 = mp4.read_sample(2, 2).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
sample_2_2,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 1024,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 180]),
|
||||
}
|
||||
);
|
||||
|
||||
let sample3 = mp4.read_sample(2, 3).unwrap().unwrap();
|
||||
assert_eq!(sample3, mp4::Mp4Sample {
|
||||
start_time: 2048,
|
||||
duration: 896,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 160]),
|
||||
});
|
||||
let sample_2_3 = mp4.read_sample(2, 3).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
sample_2_3,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 2048,
|
||||
duration: 896,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 160]),
|
||||
}
|
||||
);
|
||||
|
||||
let eos = mp4.read_sample(2, 4).unwrap();
|
||||
assert!(eos.is_none());
|
||||
|
||||
// track #1
|
||||
let track1 = mp4.tracks().get(0).unwrap();
|
||||
assert_eq!(track1.track_id(), 1);
|
||||
assert_eq!(track1.track_type().unwrap(), TrackType::Video);
|
||||
assert_eq!(track1.media_type().unwrap(), MediaType::H264);
|
||||
assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh);
|
||||
assert_eq!(track1.width(), 320);
|
||||
assert_eq!(track1.height(), 240);
|
||||
assert_eq!(track1.bitrate(), 0); // XXX
|
||||
assert_eq!(track1.frame_rate().to_integer(), 25); // XXX
|
||||
|
||||
// track #2
|
||||
let track2 = mp4.tracks().get(1).unwrap();
|
||||
assert_eq!(track2.track_type().unwrap(), TrackType::Audio);
|
||||
assert_eq!(track2.media_type().unwrap(), MediaType::AAC);
|
||||
assert_eq!(
|
||||
track2.audio_profile().unwrap(),
|
||||
AudioObjectType::AacLowComplexity
|
||||
);
|
||||
assert_eq!(
|
||||
track2.sample_freq_index().unwrap(),
|
||||
SampleFreqIndex::Freq48000
|
||||
);
|
||||
assert_eq!(track2.channel_config().unwrap(), ChannelConfig::Mono);
|
||||
assert_eq!(track2.bitrate(), 67695);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue