Skip to content

Commit 6ec013b

Browse files
authored
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.
1 parent f8f767d commit 6ec013b

File tree

8 files changed

+187
-48
lines changed

8 files changed

+187
-48
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
* [ISO/IEC 14496-14](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - MP4 file format
77
* ISO/IEC 14496-17 - Streaming text format
88

9+
https://crates.io/crates/mp4
10+
911
[![Crates.io](https://img.shields.io/crates/v/mp4)](https://crates.io/crates/mp4)
1012
[![Crates.io](https://img.shields.io/crates/d/mp4)](https://crates.io/crates/mp4)
1113
[![Build Status](https://travis-ci.org/alfg/mp4rs.svg?branch=master)](https://travis-ci.org/alfg/mp4rs)
@@ -55,6 +57,12 @@ fn main() -> Result<()> {
5557

5658
See [examples/](examples/) for more examples.
5759

60+
#### Install
61+
Add to your `Cargo.toml`:
62+
```
63+
mp4 = "0.6.0"
64+
```
65+
5866
#### Documentation
5967
* https://docs.rs/mp4/
6068

@@ -99,7 +107,7 @@ View HTML report at `target/criterion/report/index.html`
99107
## Web Assembly
100108
See the [mp4-inspector](https://github.com/alfg/mp4-inspector) project as a reference for using this library in Javascript via Web Assembly.
101109

102-
## Resources
110+
## Related Projects
103111
* https://github.com/mozilla/mp4parse-rust
104112
* https://github.com/pcwalton/rust-media
105113
* https://github.com/alfg/mp4

examples/mp4dump.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ fn get_boxes(file: File) -> Result<Vec<Box>> {
5252
boxes.push(build_box(&mp4.moov));
5353
boxes.push(build_box(&mp4.moov.mvhd));
5454

55+
if let Some(ref mvex) = &mp4.moov.mvex {
56+
boxes.push(build_box(mvex));
57+
boxes.push(build_box(&mvex.mehd));
58+
boxes.push(build_box(&mvex.trex));
59+
}
60+
5561
// trak.
5662
for track in mp4.tracks().iter() {
5763
boxes.push(build_box(&track.trak));
@@ -116,6 +122,9 @@ fn get_boxes(file: File) -> Result<Vec<Box>> {
116122
for traf in moof.trafs.iter() {
117123
boxes.push(build_box(traf));
118124
boxes.push(build_box(&traf.tfhd));
125+
if let Some(ref trun) = &traf.trun {
126+
boxes.push(build_box(trun));
127+
}
119128
}
120129
}
121130

examples/mp4sample.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::env;
2+
use std::fs::File;
3+
use std::io::prelude::*;
4+
use std::io::{self, BufReader};
5+
use std::path::Path;
6+
7+
use mp4::{Result};
8+
9+
fn main() {
10+
let args: Vec<String> = env::args().collect();
11+
12+
if args.len() < 2 {
13+
println!("Usage: mp4sample <track_id> <filename>");
14+
std::process::exit(1);
15+
}
16+
17+
if let Err(err) = samples(&args[1]) {
18+
let _ = writeln!(io::stderr(), "{}", err);
19+
}
20+
}
21+
22+
fn samples<P: AsRef<Path>>(filename: &P) -> Result<()> {
23+
let f = File::open(filename)?;
24+
let size = f.metadata()?.len();
25+
let reader = BufReader::new(f);
26+
27+
let mut mp4 = mp4::Mp4Reader::read_header(reader, size)?;
28+
29+
for track_idx in 0..mp4.tracks().len() {
30+
let track_id = track_idx as u32 + 1;
31+
let sample_count = mp4.sample_count(track_id).unwrap();
32+
33+
for sample_idx in 0..sample_count {
34+
let sample_id = sample_idx + 1;
35+
let sample = mp4.read_sample(track_id, sample_id);
36+
37+
if let Some(ref samp) = sample.unwrap() {
38+
println!("[{}] start_time={} duration={} rendering_offset={} size={} is_sync={}",
39+
sample_id,
40+
samp.start_time,
41+
samp.duration,
42+
samp.rendering_offset,
43+
samp.bytes.len(),
44+
samp.is_sync,
45+
);
46+
}
47+
}
48+
}
49+
Ok(())
50+
}

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ pub enum Error {
1616
TrakNotFound(u32),
1717
#[error("trak[{0}].{1} not found")]
1818
BoxInTrakNotFound(u32, BoxType),
19+
#[error("traf[{0}].{1} not found")]
20+
BoxInTrafNotFound(u32, BoxType),
1921
#[error("trak[{0}].stbl.{1} not found")]
2022
BoxInStblNotFound(u32, BoxType),
2123
#[error("trak[{0}].stbl.{1}.entry[{2}] not found")]
2224
EntryInStblNotFound(u32, BoxType, u32),
25+
#[error("traf[{0}].trun.{1}.entry[{2}] not found")]
26+
EntryInTrunNotFound(u32, BoxType, u32),
2327
}

src/mp4box/trex.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl TrexBox {
2121
}
2222

2323
pub fn get_size(&self) -> u64 {
24-
HEADER_SIZE + HEADER_EXT_SIZE + 4 + 20
24+
HEADER_SIZE + HEADER_EXT_SIZE + 20
2525
}
2626
}
2727

@@ -51,7 +51,6 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrexBox {
5151

5252
let (version, flags) = read_box_header_ext(reader)?;
5353

54-
reader.read_u32::<BigEndian>()?; // pre-defined
5554
let track_id = reader.read_u32::<BigEndian>()?;
5655
let default_sample_description_index = reader.read_u32::<BigEndian>()?;
5756
let default_sample_duration = reader.read_u32::<BigEndian>()?;
@@ -79,7 +78,6 @@ impl<W: Write> WriteBox<&mut W> for TrexBox {
7978

8079
write_box_header_ext(writer, self.version, self.flags)?;
8180

82-
writer.write_u32::<BigEndian>(0)?; // pre-defined
8381
writer.write_u32::<BigEndian>(self.track_id)?;
8482
writer.write_u32::<BigEndian>(self.default_sample_description_index)?;
8583
writer.write_u32::<BigEndian>(self.default_sample_duration)?;

src/mp4box/trun.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct TrunBox {
1111
pub sample_count: u32,
1212
pub data_offset: i32,
1313

14-
// #[serde(skip_serializing)]
14+
#[serde(skip_serializing)]
1515
pub sample_sizes: Vec<u32>,
1616
}
1717

@@ -56,8 +56,8 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrunBox {
5656

5757
let mut sample_sizes = Vec::with_capacity(sample_count as usize);
5858
for _ in 0..sample_count {
59-
let sample_duration = reader.read_u32::<BigEndian>()?;
60-
sample_sizes.push(sample_duration);
59+
let sample_size = reader.read_u32::<BigEndian>()?;
60+
sample_sizes.push(sample_size);
6161
}
6262

6363
skip_bytes_to(reader, start + size)?;

src/reader.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl<R: Read + Seek> Mp4Reader<R> {
6363
}
6464

6565
let size = current - start;
66-
let tracks = if let Some(ref moov) = moov {
66+
let mut tracks = if let Some(ref moov) = moov {
6767
let mut tracks = Vec::with_capacity(moov.traks.len());
6868
for (i, trak) in moov.traks.iter().enumerate() {
6969
assert_eq!(trak.tkhd.track_id, i as u32 + 1);
@@ -74,6 +74,24 @@ impl<R: Read + Seek> Mp4Reader<R> {
7474
Vec::new()
7575
};
7676

77+
// Update tracks if any fragmented (moof) boxes are found.
78+
if moofs.len() > 0 {
79+
let mut default_sample_duration = 0;
80+
if let Some(ref moov) = moov {
81+
if let Some(ref mvex) = &moov.mvex {
82+
default_sample_duration = mvex.trex.default_sample_duration
83+
}
84+
}
85+
86+
for moof in moofs.iter() {
87+
for traf in moof.trafs.iter() {
88+
let track_id = traf.tfhd.track_id as usize - 1;
89+
tracks[track_id].default_sample_duration = default_sample_duration;
90+
tracks[track_id].trafs.push(traf.clone());
91+
}
92+
}
93+
}
94+
7795
Ok(Mp4Reader {
7896
reader,
7997
ftyp: ftyp.unwrap(),

src/track.rs

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::io::{Read, Seek, SeekFrom, Write};
55
use std::time::Duration;
66

77
use crate::mp4box::trak::TrakBox;
8+
use crate::mp4box::traf::TrafBox;
89
use crate::mp4box::*;
910
use crate::mp4box::{
1011
avc1::Avc1Box,
@@ -88,12 +89,16 @@ impl From<TtxtConfig> for TrackConfig {
8889
#[derive(Debug)]
8990
pub struct Mp4Track {
9091
pub trak: TrakBox,
92+
pub trafs: Vec<TrafBox>,
93+
94+
// Fragmented Tracks Defaults.
95+
pub default_sample_duration: u32,
9196
}
9297

9398
impl Mp4Track {
9499
pub(crate) fn from(trak: &TrakBox) -> Self {
95100
let trak = trak.clone();
96-
Self { trak }
101+
Self { trak, trafs: Vec::new(), default_sample_duration: 0, }
97102
}
98103

99104
pub fn track_id(&self) -> u32 {
@@ -215,7 +220,17 @@ impl Mp4Track {
215220
}
216221

217222
pub fn sample_count(&self) -> u32 {
218-
self.trak.mdia.minf.stbl.stsz.sample_count
223+
if self.trafs.len() > 0 {
224+
let mut sample_count = 0u32;
225+
for traf in self.trafs.iter() {
226+
if let Some(ref trun) = traf.trun {
227+
sample_count += trun.sample_count;
228+
}
229+
}
230+
sample_count
231+
} else {
232+
self.trak.mdia.minf.stbl.stsz.sample_count
233+
}
219234
}
220235

221236
pub fn video_profile(&self) -> Result<AvcProfile> {
@@ -330,18 +345,39 @@ impl Mp4Track {
330345
}
331346

332347
fn sample_size(&self, sample_id: u32) -> Result<u32> {
333-
let stsz = &self.trak.mdia.minf.stbl.stsz;
334-
if stsz.sample_size > 0 {
335-
return Ok(stsz.sample_size);
336-
}
337-
if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) {
338-
Ok(*size)
348+
if self.trafs.len() > 0 {
349+
let sample_sizes_count = self.sample_count() / self.trafs.len() as u32;
350+
let traf_idx = (sample_id - 1) / sample_sizes_count;
351+
if let Some(trun) = &self.trafs[traf_idx as usize].trun {
352+
if let Some(size) = trun.sample_sizes.get((sample_id - (sample_sizes_count * traf_idx)) as usize - 1) {
353+
Ok(*size)
354+
} else {
355+
return Err(Error::EntryInTrunNotFound(
356+
self.track_id(),
357+
BoxType::TrunBox,
358+
sample_id,
359+
));
360+
}
361+
} else {
362+
return Err(Error::BoxInTrafNotFound(
363+
self.track_id(),
364+
BoxType::TrafBox,
365+
));
366+
}
339367
} else {
340-
return Err(Error::EntryInStblNotFound(
341-
self.track_id(),
342-
BoxType::StszBox,
343-
sample_id,
344-
));
368+
let stsz = &self.trak.mdia.minf.stbl.stsz;
369+
if stsz.sample_size > 0 {
370+
return Ok(stsz.sample_size);
371+
}
372+
if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) {
373+
Ok(*size)
374+
} else {
375+
return Err(Error::EntryInStblNotFound(
376+
self.track_id(),
377+
BoxType::StszBox,
378+
sample_id,
379+
));
380+
}
345381
}
346382
}
347383

@@ -359,27 +395,33 @@ impl Mp4Track {
359395
}
360396

361397
fn sample_offset(&self, sample_id: u32) -> Result<u64> {
362-
let stsc_index = self.stsc_index(sample_id);
398+
if self.trafs.len() > 0 {
399+
let sample_sizes_count = self.sample_count() / self.trafs.len() as u32;
400+
let traf_idx = (sample_id - 1) / sample_sizes_count;
401+
Ok(self.trafs[(sample_id - (sample_sizes_count * traf_idx)) as usize].tfhd.base_data_offset as u64)
402+
} else {
403+
let stsc_index = self.stsc_index(sample_id);
363404

364-
let stsc = &self.trak.mdia.minf.stbl.stsc;
365-
let stsc_entry = stsc.entries.get(stsc_index).unwrap();
405+
let stsc = &self.trak.mdia.minf.stbl.stsc;
406+
let stsc_entry = stsc.entries.get(stsc_index).unwrap();
366407

367-
let first_chunk = stsc_entry.first_chunk;
368-
let first_sample = stsc_entry.first_sample;
369-
let samples_per_chunk = stsc_entry.samples_per_chunk;
408+
let first_chunk = stsc_entry.first_chunk;
409+
let first_sample = stsc_entry.first_sample;
410+
let samples_per_chunk = stsc_entry.samples_per_chunk;
370411

371-
let chunk_id = first_chunk + (sample_id - first_sample) / samples_per_chunk;
412+
let chunk_id = first_chunk + (sample_id - first_sample) / samples_per_chunk;
372413

373-
let chunk_offset = self.chunk_offset(chunk_id)?;
414+
let chunk_offset = self.chunk_offset(chunk_id)?;
374415

375-
let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk;
416+
let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk;
376417

377-
let mut sample_offset = 0;
378-
for i in first_sample_in_chunk..sample_id {
379-
sample_offset += self.sample_size(i)?;
380-
}
418+
let mut sample_offset = 0;
419+
for i in first_sample_in_chunk..sample_id {
420+
sample_offset += self.sample_size(i)?;
421+
}
381422

382-
Ok(chunk_offset + sample_offset as u64)
423+
Ok(chunk_offset + sample_offset as u64)
424+
}
383425
}
384426

385427
fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
@@ -388,22 +430,27 @@ impl Mp4Track {
388430
let mut sample_count = 1;
389431
let mut elapsed = 0;
390432

391-
for entry in stts.entries.iter() {
392-
if sample_id <= sample_count + entry.sample_count - 1 {
393-
let start_time =
394-
(sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed;
395-
return Ok((start_time, entry.sample_delta));
433+
if self.trafs.len() > 0 {
434+
let start_time = ((sample_id - 1) * self.default_sample_duration) as u64;
435+
return Ok((start_time, self.default_sample_duration))
436+
} else {
437+
for entry in stts.entries.iter() {
438+
if sample_id <= sample_count + entry.sample_count - 1 {
439+
let start_time =
440+
(sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed;
441+
return Ok((start_time, entry.sample_delta));
442+
}
443+
444+
sample_count += entry.sample_count;
445+
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
396446
}
397447

398-
sample_count += entry.sample_count;
399-
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
448+
return Err(Error::EntryInStblNotFound(
449+
self.track_id(),
450+
BoxType::SttsBox,
451+
sample_id,
452+
));
400453
}
401-
402-
return Err(Error::EntryInStblNotFound(
403-
self.track_id(),
404-
BoxType::SttsBox,
405-
sample_id,
406-
));
407454
}
408455

409456
fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
@@ -417,6 +464,11 @@ impl Mp4Track {
417464
}
418465

419466
fn is_sync_sample(&self, sample_id: u32) -> bool {
467+
if self.trafs.len() > 0 {
468+
let sample_sizes_count = self.sample_count() / self.trafs.len() as u32;
469+
return sample_id == 1 || sample_id % sample_sizes_count == 0
470+
}
471+
420472
if let Some(ref stss) = self.trak.mdia.minf.stbl.stss {
421473
match stss.entries.binary_search(&sample_id) {
422474
Ok(_) => true,

0 commit comments

Comments
 (0)