Skip to content

Commit

Permalink
[AVS-15] add support to parse fragmented mp4 file (#96)
Browse files Browse the repository at this point in the history
* add support to parse fragmented mp4 file

* add start_position to moof header for traf data offset calculation

* fix typos, cleanup and remove unused code

* force bindgen to use version 0.59.2, since 0.60.1 seems to have an issue to cause overflow

* disable bindgen layout_tests
  • Loading branch information
lzuosym authored Jun 16, 2022
1 parent 76d31a4 commit ecac4bf
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 0 deletions.
92 changes: 92 additions & 0 deletions qtff/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ use super::{
data::{AtomData, MovieData, ReadData},
};

use crate::moof::MovieFragment;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};

pub struct File {
f: std::fs::File,
movie_data: Option<MovieData>,
movie_fragments: Option<Vec<(u64, MovieFragment)>>,
}

enum Data {
Expand All @@ -34,6 +36,7 @@ impl File {
Ok(File {
f: std::fs::File::open(path)?,
movie_data: None,
movie_fragments: None,
})
}

Expand Down Expand Up @@ -65,6 +68,40 @@ impl File {
}
}

fn get_moof_data(&mut self) -> Result<Vec<(u64, MovieFragment)>> {
let mut fragments = vec![];
loop {
let start_position = self.f.stream_position()?;

let atom = match AtomReader::new(&mut self.f).find(|a| match a {
Ok(a) => a.typ == MovieFragment::TYPE,
Err(_) => true,
}) {
Some(Ok(a)) => a,
Some(Err(err)) => return Err(err.into()),
None => return Ok(fragments),
};
let mut buf = Vec::new();
atom.data(&mut self.f).read_to_end(&mut buf)?;

fragments.push((start_position, MovieFragment::read(Cursor::new(buf.as_slice()))?));
}
}

pub fn get_movie_fragments(&mut self) -> Result<Vec<(u64, MovieFragment)>> {
if self.movie_data.is_none() {
self.get_movie_data()?;
}
match &self.movie_fragments {
Some(v) => Ok(v.clone()),
None => {
let movie_fragments = self.get_moof_data()?;
self.movie_fragments = Some(movie_fragments.clone());
Ok(movie_fragments)
}
}
}

pub fn trim_frames<W: Write>(&mut self, w: W, start_frame: u64, frame_count: u64) -> Result<()> {
let movie_data = self.get_movie_data()?;

Expand Down Expand Up @@ -484,6 +521,7 @@ impl<'a> Seek for &'a File {
#[cfg(test)]
mod tests {
use super::*;
use crate::moof::{FragmentHeader, TrackFragmentHeader, TrackFragmentRunSampleData};

#[test]
fn test_file_prores() {
Expand Down Expand Up @@ -692,4 +730,58 @@ mod tests {
let movie_data = f.get_movie_data().unwrap();
assert_eq!(movie_data.tracks.len(), 3);
}

#[test]
fn test_file_fragmented_mp4() {
let mut f = File::open("src/testdata/fragmented.mp4").unwrap();
let movie_data = f.get_movie_data().unwrap();
assert_eq!(movie_data.tracks.len(), 1);
let fragments = f.get_movie_fragments().unwrap();
assert_eq!(fragments.len(), 2);

let fragment = &fragments[0];
assert_eq!(fragment.0, 834);
assert_eq!(
fragment.1.fragment_header,
FragmentHeader {
version: 0,
flags: 0,
sequence_number: 1,
}
);
assert_eq!(fragment.1.track_fragments.len(), 1);

let track_fragment = &fragment.1.track_fragments[0];
assert_eq!(
track_fragment.track_fragment_header,
TrackFragmentHeader {
version: 0,
flags: 57,
track_id: 1,
base_data_offset: Some(834),
sample_description_index: None,
default_sample_duration: Some(3003),
default_sample_size: Some(1288964),
default_sample_flags: Some(16842752),
duration_is_empty: false,
}
);

assert_eq!(track_fragment.track_fragment_runs.len(), 1);

let track_fragment_run = &track_fragment.track_fragment_runs[0];
assert_eq!(track_fragment_run.sample_count, 64);
assert_eq!(track_fragment_run.data_offset, Some(376));
assert_eq!(track_fragment_run.first_sample_flags, Some(33554432));
assert_eq!(track_fragment_run.sample_data.len(), 64);
assert_eq!(
track_fragment_run.sample_data[0],
TrackFragmentRunSampleData {
sample_duration: None,
sample_size: Some(1288964,),
sample_flags: None,
sample_composition_time_offset: None,
}
);
}
}
1 change: 1 addition & 0 deletions qtff/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod data;
pub mod deserializer;
pub mod error;
pub mod file;
pub mod moof;
pub mod serializer;

pub use atom::*;
Expand Down
164 changes: 164 additions & 0 deletions qtff/src/moof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use super::error::{Error, Result};
use crate::{read_all, read_one, AtomData, FourCC, ReadData};
use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Read, Seek};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FragmentHeader {
pub version: u8,
pub flags: u32,
pub sequence_number: u32,
}

impl AtomData for FragmentHeader {
const TYPE: FourCC = FourCC::MFHD;
}

impl ReadData for FragmentHeader {
fn read<R: Read + Seek>(mut reader: R) -> Result<Self> {
Ok(Self {
version: reader.read_u8()?,
flags: reader.read_u24::<BigEndian>()?,
sequence_number: reader.read_u32::<BigEndian>()?,
})
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrackFragmentHeader {
pub version: u8,
pub flags: u32,
pub track_id: u32,
pub base_data_offset: Option<u64>,
pub sample_description_index: Option<u32>,
pub default_sample_duration: Option<u32>,
pub default_sample_size: Option<u32>,
pub default_sample_flags: Option<u32>,
pub duration_is_empty: bool,
}

impl AtomData for TrackFragmentHeader {
const TYPE: FourCC = FourCC::TFHD;
}

impl ReadData for TrackFragmentHeader {
fn read<R: Read + Seek>(mut reader: R) -> Result<Self> {
let version = reader.read_u8()?;
let flags = reader.read_u24::<BigEndian>()?;
Ok(Self {
version,
flags,
track_id: reader.read_u32::<BigEndian>()?,
base_data_offset: if (flags & 0x1) == 1 { Some(reader.read_u64::<BigEndian>()?) } else { None },
sample_description_index: if (flags & 0x2) != 0 { Some(reader.read_u32::<BigEndian>()?) } else { None },
default_sample_duration: if (flags & 0x8) != 0 { Some(reader.read_u32::<BigEndian>()?) } else { None },
default_sample_size: if (flags & 0x10) != 0 { Some(reader.read_u32::<BigEndian>()?) } else { None },
default_sample_flags: if (flags & 0x20) != 0 { Some(reader.read_u32::<BigEndian>()?) } else { None },
duration_is_empty: flags & 0x10000 != 0,
})
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrackFragmentRunSampleData {
pub sample_duration: Option<u32>,
pub sample_size: Option<u32>,
pub sample_flags: Option<u32>,
pub sample_composition_time_offset: Option<u32>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrackFragmentRun {
pub version: u8,
pub flags: u32,
pub sample_count: u32,
pub data_offset: Option<i32>,
pub first_sample_flags: Option<u32>,
pub sample_data: Vec<TrackFragmentRunSampleData>,
}

impl AtomData for TrackFragmentRun {
const TYPE: FourCC = FourCC::TRUN;
}

impl ReadData for TrackFragmentRun {
fn read<R: Read + Seek>(mut reader: R) -> Result<Self> {
let version = reader.read_u8()?;
let flags = reader.read_u24::<BigEndian>()?;
let sample_count = reader.read_u32::<BigEndian>()?;
let data_offset = if flags & 0x1 != 0 { Some(reader.read_i32::<BigEndian>()?) } else { None };
let first_sample_flags = if flags & 0x4 != 0 { Some(reader.read_u32::<BigEndian>()?) } else { None };
let sample_duration_present = flags & 0x100 != 0;
let sample_size_present = flags & 0x200 != 0;
let sample_flags_present = flags & 0x400 != 0;
let sample_composition_time_offsets_present = flags & 0x800 != 0;
let mut sample_data = vec![];
for _ in 0..sample_count {
let sample_duration = if sample_duration_present {
Some(reader.read_u32::<BigEndian>()?)
} else {
None
};
let sample_size = if sample_size_present { Some(reader.read_u32::<BigEndian>()?) } else { None };
let sample_flags = if sample_flags_present { Some(reader.read_u32::<BigEndian>()?) } else { None };
let sample_composition_time_offset = if sample_composition_time_offsets_present {
Some(reader.read_u32::<BigEndian>()?)
} else {
None
};
sample_data.push(TrackFragmentRunSampleData {
sample_duration,
sample_size,
sample_flags,
sample_composition_time_offset,
});
}

Ok(Self {
version,
flags,
sample_count,
data_offset,
first_sample_flags,
sample_data,
})
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrackFragment {
pub track_fragment_header: TrackFragmentHeader, // tfhd
pub track_fragment_runs: Vec<TrackFragmentRun>, // trun
}

impl AtomData for TrackFragment {
const TYPE: FourCC = FourCC::TRAF;
}

impl ReadData for TrackFragment {
fn read<R: Read + Seek>(mut reader: R) -> Result<Self> {
Ok(Self {
track_fragment_header: read_one(&mut reader)?.ok_or(Error::MalformedFile("missing movie track fragment header"))?,
track_fragment_runs: read_all(&mut reader)?,
})
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MovieFragment {
pub fragment_header: FragmentHeader, // mfhd
pub track_fragments: Vec<TrackFragment>, // traf
}

impl AtomData for MovieFragment {
const TYPE: FourCC = FourCC::MOOF;
}

impl ReadData for MovieFragment {
fn read<R: Read + Seek>(mut reader: R) -> Result<Self> {
Ok(Self {
fragment_header: read_one(&mut reader)?.ok_or(Error::MalformedFile("missing movie fragment header"))?,
track_fragments: read_all(&mut reader)?,
})
}
}
Binary file added qtff/src/testdata/fragmented.mp4
Binary file not shown.
3 changes: 3 additions & 0 deletions xcoder/xcoder-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ fn main() {
.whitelist_function("ni_.+")
.whitelist_type("ni_.+")
.whitelist_var("NI_.+")
// bindgen 0.60.0 broke layout tests: https://github.com/rust-lang/rust-bindgen/issues/2218
// 0.60.1 claims to have fixed the issue, but does not.
.layout_tests(false)
.clang_arg(format!("-I{}", source_path.to_str().unwrap()))
.generate()
.expect("unable to generate bindings");
Expand Down

0 comments on commit ecac4bf

Please sign in to comment.