1
0
Fork 0
mirror of https://github.com/alfg/mp4-rust.git synced 2024-05-19 16:58:04 +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:
Alfred Gutierrez 2020-09-14 18:05:34 -07:00 committed by GitHub
parent f8f767dc07
commit 6ec013b7b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 187 additions and 48 deletions

View file

@ -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

View file

@ -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
View 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(())
}

View file

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

View file

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

View file

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

View file

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

View file

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