mirror of
https://github.com/alfg/mp4-rust.git
synced 2025-01-03 00:48:40 +00:00
Fragmented tracks (#31)
* Add trun box. * Adding Movie Extends Box and subboxes (mvex, mehd, trex). * Adding more support for parsing fragmented tracks. Add mp4sample example. * cleanup * Set default_sample_duration from moov.mvex.trex for getting fragmented samples. * fix trex box parsing/writing.
This commit is contained in:
parent
f8f767dc07
commit
6ec013b7b9
8 changed files with 187 additions and 48 deletions
10
README.md
10
README.md
|
@ -6,6 +6,8 @@
|
|||
* [ISO/IEC 14496-14](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - MP4 file format
|
||||
* ISO/IEC 14496-17 - Streaming text format
|
||||
|
||||
https://crates.io/crates/mp4
|
||||
|
||||
[![Crates.io](https://img.shields.io/crates/v/mp4)](https://crates.io/crates/mp4)
|
||||
[![Crates.io](https://img.shields.io/crates/d/mp4)](https://crates.io/crates/mp4)
|
||||
[![Build Status](https://travis-ci.org/alfg/mp4rs.svg?branch=master)](https://travis-ci.org/alfg/mp4rs)
|
||||
|
@ -55,6 +57,12 @@ fn main() -> Result<()> {
|
|||
|
||||
See [examples/](examples/) for more examples.
|
||||
|
||||
#### Install
|
||||
Add to your `Cargo.toml`:
|
||||
```
|
||||
mp4 = "0.6.0"
|
||||
```
|
||||
|
||||
#### Documentation
|
||||
* https://docs.rs/mp4/
|
||||
|
||||
|
@ -99,7 +107,7 @@ View HTML report at `target/criterion/report/index.html`
|
|||
## Web Assembly
|
||||
See the [mp4-inspector](https://github.com/alfg/mp4-inspector) project as a reference for using this library in Javascript via Web Assembly.
|
||||
|
||||
## Resources
|
||||
## Related Projects
|
||||
* https://github.com/mozilla/mp4parse-rust
|
||||
* https://github.com/pcwalton/rust-media
|
||||
* https://github.com/alfg/mp4
|
||||
|
|
|
@ -52,6 +52,12 @@ fn get_boxes(file: File) -> Result<Vec<Box>> {
|
|||
boxes.push(build_box(&mp4.moov));
|
||||
boxes.push(build_box(&mp4.moov.mvhd));
|
||||
|
||||
if let Some(ref mvex) = &mp4.moov.mvex {
|
||||
boxes.push(build_box(mvex));
|
||||
boxes.push(build_box(&mvex.mehd));
|
||||
boxes.push(build_box(&mvex.trex));
|
||||
}
|
||||
|
||||
// trak.
|
||||
for track in mp4.tracks().iter() {
|
||||
boxes.push(build_box(&track.trak));
|
||||
|
@ -116,6 +122,9 @@ fn get_boxes(file: File) -> Result<Vec<Box>> {
|
|||
for traf in moof.trafs.iter() {
|
||||
boxes.push(build_box(traf));
|
||||
boxes.push(build_box(&traf.tfhd));
|
||||
if let Some(ref trun) = &traf.trun {
|
||||
boxes.push(build_box(trun));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
50
examples/mp4sample.rs
Normal file
50
examples/mp4sample.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Result};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
println!("Usage: mp4sample <track_id> <filename>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(err) = samples(&args[1]) {
|
||||
let _ = writeln!(io::stderr(), "{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn samples<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
||||
let f = File::open(filename)?;
|
||||
let size = f.metadata()?.len();
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
let mut mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
|
||||
for track_idx in 0..mp4.tracks().len() {
|
||||
let track_id = track_idx as u32 + 1;
|
||||
let sample_count = mp4.sample_count(track_id).unwrap();
|
||||
|
||||
for sample_idx in 0..sample_count {
|
||||
let sample_id = sample_idx + 1;
|
||||
let sample = mp4.read_sample(track_id, sample_id);
|
||||
|
||||
if let Some(ref samp) = sample.unwrap() {
|
||||
println!("[{}] start_time={} duration={} rendering_offset={} size={} is_sync={}",
|
||||
sample_id,
|
||||
samp.start_time,
|
||||
samp.duration,
|
||||
samp.rendering_offset,
|
||||
samp.bytes.len(),
|
||||
samp.is_sync,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -16,8 +16,12 @@ pub enum Error {
|
|||
TrakNotFound(u32),
|
||||
#[error("trak[{0}].{1} not found")]
|
||||
BoxInTrakNotFound(u32, BoxType),
|
||||
#[error("traf[{0}].{1} not found")]
|
||||
BoxInTrafNotFound(u32, BoxType),
|
||||
#[error("trak[{0}].stbl.{1} not found")]
|
||||
BoxInStblNotFound(u32, BoxType),
|
||||
#[error("trak[{0}].stbl.{1}.entry[{2}] not found")]
|
||||
EntryInStblNotFound(u32, BoxType, u32),
|
||||
#[error("traf[{0}].trun.{1}.entry[{2}] not found")]
|
||||
EntryInTrunNotFound(u32, BoxType, u32),
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ impl TrexBox {
|
|||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + 20
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 20
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,6 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrexBox {
|
|||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined
|
||||
let track_id = reader.read_u32::<BigEndian>()?;
|
||||
let default_sample_description_index = reader.read_u32::<BigEndian>()?;
|
||||
let default_sample_duration = reader.read_u32::<BigEndian>()?;
|
||||
|
@ -79,7 +78,6 @@ impl<W: Write> WriteBox<&mut W> for TrexBox {
|
|||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u32::<BigEndian>(self.track_id)?;
|
||||
writer.write_u32::<BigEndian>(self.default_sample_description_index)?;
|
||||
writer.write_u32::<BigEndian>(self.default_sample_duration)?;
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct TrunBox {
|
|||
pub sample_count: u32,
|
||||
pub data_offset: i32,
|
||||
|
||||
// #[serde(skip_serializing)]
|
||||
#[serde(skip_serializing)]
|
||||
pub sample_sizes: Vec<u32>,
|
||||
}
|
||||
|
||||
|
@ -56,8 +56,8 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrunBox {
|
|||
|
||||
let mut sample_sizes = Vec::with_capacity(sample_count as usize);
|
||||
for _ in 0..sample_count {
|
||||
let sample_duration = reader.read_u32::<BigEndian>()?;
|
||||
sample_sizes.push(sample_duration);
|
||||
let sample_size = reader.read_u32::<BigEndian>()?;
|
||||
sample_sizes.push(sample_size);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
|
|
@ -63,7 +63,7 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
}
|
||||
|
||||
let size = current - start;
|
||||
let tracks = if let Some(ref moov) = moov {
|
||||
let mut 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);
|
||||
|
@ -74,6 +74,24 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
Vec::new()
|
||||
};
|
||||
|
||||
// Update tracks if any fragmented (moof) boxes are found.
|
||||
if moofs.len() > 0 {
|
||||
let mut default_sample_duration = 0;
|
||||
if let Some(ref moov) = moov {
|
||||
if let Some(ref mvex) = &moov.mvex {
|
||||
default_sample_duration = mvex.trex.default_sample_duration
|
||||
}
|
||||
}
|
||||
|
||||
for moof in moofs.iter() {
|
||||
for traf in moof.trafs.iter() {
|
||||
let track_id = traf.tfhd.track_id as usize - 1;
|
||||
tracks[track_id].default_sample_duration = default_sample_duration;
|
||||
tracks[track_id].trafs.push(traf.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Mp4Reader {
|
||||
reader,
|
||||
ftyp: ftyp.unwrap(),
|
||||
|
|
132
src/track.rs
132
src/track.rs
|
@ -5,6 +5,7 @@ use std::io::{Read, Seek, SeekFrom, Write};
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::mp4box::trak::TrakBox;
|
||||
use crate::mp4box::traf::TrafBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{
|
||||
avc1::Avc1Box,
|
||||
|
@ -88,12 +89,16 @@ impl From<TtxtConfig> for TrackConfig {
|
|||
#[derive(Debug)]
|
||||
pub struct Mp4Track {
|
||||
pub trak: TrakBox,
|
||||
pub trafs: Vec<TrafBox>,
|
||||
|
||||
// Fragmented Tracks Defaults.
|
||||
pub default_sample_duration: u32,
|
||||
}
|
||||
|
||||
impl Mp4Track {
|
||||
pub(crate) fn from(trak: &TrakBox) -> Self {
|
||||
let trak = trak.clone();
|
||||
Self { trak }
|
||||
Self { trak, trafs: Vec::new(), default_sample_duration: 0, }
|
||||
}
|
||||
|
||||
pub fn track_id(&self) -> u32 {
|
||||
|
@ -215,7 +220,17 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
pub fn sample_count(&self) -> u32 {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_count
|
||||
if self.trafs.len() > 0 {
|
||||
let mut sample_count = 0u32;
|
||||
for traf in self.trafs.iter() {
|
||||
if let Some(ref trun) = traf.trun {
|
||||
sample_count += trun.sample_count;
|
||||
}
|
||||
}
|
||||
sample_count
|
||||
} else {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_count
|
||||
}
|
||||
}
|
||||
|
||||
pub fn video_profile(&self) -> Result<AvcProfile> {
|
||||
|
@ -330,18 +345,39 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
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)
|
||||
if self.trafs.len() > 0 {
|
||||
let sample_sizes_count = self.sample_count() / self.trafs.len() as u32;
|
||||
let traf_idx = (sample_id - 1) / sample_sizes_count;
|
||||
if let Some(trun) = &self.trafs[traf_idx as usize].trun {
|
||||
if let Some(size) = trun.sample_sizes.get((sample_id - (sample_sizes_count * traf_idx)) as usize - 1) {
|
||||
Ok(*size)
|
||||
} else {
|
||||
return Err(Error::EntryInTrunNotFound(
|
||||
self.track_id(),
|
||||
BoxType::TrunBox,
|
||||
sample_id,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::BoxInTrafNotFound(
|
||||
self.track_id(),
|
||||
BoxType::TrafBox,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::StszBox,
|
||||
sample_id,
|
||||
));
|
||||
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,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,27 +395,33 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
fn sample_offset(&self, sample_id: u32) -> Result<u64> {
|
||||
let stsc_index = self.stsc_index(sample_id);
|
||||
if self.trafs.len() > 0 {
|
||||
let sample_sizes_count = self.sample_count() / self.trafs.len() as u32;
|
||||
let traf_idx = (sample_id - 1) / sample_sizes_count;
|
||||
Ok(self.trafs[(sample_id - (sample_sizes_count * traf_idx)) as usize].tfhd.base_data_offset as u64)
|
||||
} else {
|
||||
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 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 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_id = first_chunk + (sample_id - first_sample) / samples_per_chunk;
|
||||
|
||||
let chunk_offset = self.chunk_offset(chunk_id)?;
|
||||
let chunk_offset = self.chunk_offset(chunk_id)?;
|
||||
|
||||
let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk;
|
||||
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)?;
|
||||
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)
|
||||
}
|
||||
|
||||
Ok(chunk_offset + sample_offset as u64)
|
||||
}
|
||||
|
||||
fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
|
||||
|
@ -388,22 +430,27 @@ impl Mp4Track {
|
|||
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));
|
||||
if self.trafs.len() > 0 {
|
||||
let start_time = ((sample_id - 1) * self.default_sample_duration) as u64;
|
||||
return Ok((start_time, self.default_sample_duration))
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
));
|
||||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::SttsBox,
|
||||
sample_id,
|
||||
));
|
||||
}
|
||||
|
||||
fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
|
||||
|
@ -417,6 +464,11 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
fn is_sync_sample(&self, sample_id: u32) -> bool {
|
||||
if self.trafs.len() > 0 {
|
||||
let sample_sizes_count = self.sample_count() / self.trafs.len() as u32;
|
||||
return sample_id == 1 || sample_id % sample_sizes_count == 0
|
||||
}
|
||||
|
||||
if let Some(ref stss) = self.trak.mdia.minf.stbl.stss {
|
||||
match stss.entries.binary_search(&sample_id) {
|
||||
Ok(_) => true,
|
||||
|
|
Loading…
Reference in a new issue