Bug 1217261 - Update mp4parse-rust to v0.1.2. r=kinetik
authorRalph Giles <giles@mozilla.com>
Wed, 21 Oct 2015 18:16:02 -0700
changeset 268875 2ca3997df07f3f46dd97fe15bbfdec8e51913693
parent 268874 56bc1314e660a052012bf87de1494d4b5835e315
child 268876 2d19d26df5128c2694ce34ac1b6a86681a64da15
push id66957
push userrgiles@mozilla.com
push dateThu, 22 Oct 2015 01:54:26 +0000
treeherdermozilla-inbound@ec846e6a0134 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs1217261
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1217261 - Update mp4parse-rust to v0.1.2. r=kinetik This switches to #[derive(Debug)] for trace println!s, and updates the C api wrapper to thread through the new MediaContext as an opaque struct. The idea is to be able to query it later. 'extern crate byteorder' converted to 'mod byteorder'.
media/libstagefright/binding/MP4Metadata.rs
media/libstagefright/binding/capi.rs
media/libstagefright/binding/include/mp4parse.h
--- a/media/libstagefright/binding/MP4Metadata.rs
+++ b/media/libstagefright/binding/MP4Metadata.rs
@@ -1,137 +1,170 @@
 // Module for parsing ISO Base Media Format aka video/mp4 streams.
 
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
 
+use std::fmt;
+
+/// Expose C api wrapper.
+pub mod capi;
+// FIXME: We can 'pub use capi::*' in rustc 1.5 and later.
+pub use capi::{mp4parse_new, mp4parse_free, mp4parse_read};
+
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub struct FourCC(pub u32);
+
+impl fmt::Debug for FourCC {
+  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    write!(f, "'{}'", fourcc_to_string(*self))
+  }
+}
+
 /// Basic ISO box structure.
+#[derive(Debug)]
 pub struct BoxHeader {
     /// Four character box type
-    pub name: u32,
+    pub name: FourCC,
     /// Size of the box in bytes
     pub size: u64,
     /// Offset to the start of the contained data (or header size).
     pub offset: u64,
 }
 
 /// File type box 'ftyp'.
+#[derive(Debug)]
 pub struct FileTypeBox {
-    name: u32,
+    name: FourCC,
     size: u64,
-    major_brand: u32,
+    major_brand: FourCC,
     minor_version: u32,
-    compatible_brands: Vec<u32>,
+    compatible_brands: Vec<FourCC>,
 }
 
 /// Movie header box 'mvhd'.
+#[derive(Debug)]
 pub struct MovieHeaderBox {
-    pub name: u32,
+    pub name: FourCC,
     pub size: u64,
     pub timescale: u32,
     pub duration: u64,
     // Ignore other fields.
 }
 
 /// Track header box 'tkhd'
+#[derive(Debug)]
 pub struct TrackHeaderBox {
-    pub name: u32,
+    pub name: FourCC,
     pub size: u64,
     pub track_id: u32,
     pub enabled: bool,
     pub duration: u64,
     pub width: u32,
     pub height: u32,
 }
 
 /// Edit list box 'elst'
+#[derive(Debug)]
 pub struct EditListBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     edits: Vec<Edit>,
 }
 
+#[derive(Debug)]
 pub struct Edit {
     segment_duration: u64,
     media_time: i64,
     media_rate_integer: i16,
     media_rate_fraction: i16,
 }
 
 /// Media header box 'mdhd'
+#[derive(Debug)]
 pub struct MediaHeaderBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     timescale: u32,
     duration: u64,
 }
 
 // Chunk offset box 'stco' or 'co64'
+#[derive(Debug)]
 pub struct ChunkOffsetBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     offsets: Vec<u64>,
 }
 
 // Sync sample box 'stss'
+#[derive(Debug)]
 pub struct SyncSampleBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     samples: Vec<u32>,
 }
 
 // Sample to chunk box 'stsc'
+#[derive(Debug)]
 pub struct SampleToChunkBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     samples: Vec<SampleToChunk>,
 }
 
+#[derive(Debug)]
 pub struct SampleToChunk {
     first_chunk: u32,
     samples_per_chunk: u32,
     sample_description_index: u32,
 }
 
 // Sample size box 'stsz'
+#[derive(Debug)]
 pub struct SampleSizeBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     sample_size: u32,
     sample_sizes: Vec<u32>,
 }
 
 // Time to sample box 'stts'
+#[derive(Debug)]
 pub struct TimeToSampleBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     samples: Vec<Sample>,
 }
 
+#[derive(Debug)]
 pub struct Sample {
     sample_count: u32,
     sample_delta: u32,
 }
 
 // Handler reference box 'hdlr'
+#[derive(Debug)]
 pub struct HandlerBox {
-    name: u32,
+    name: FourCC,
     size: u64,
-    handler_type: u32,
+    handler_type: FourCC,
 }
 
 // Sample description box 'stsd'
+#[derive(Debug)]
 pub struct SampleDescriptionBox {
-    name: u32,
+    name: FourCC,
     size: u64,
     descriptions: Vec<SampleEntry>,
 }
 
 #[allow(dead_code)]
+#[derive(Debug)]
 enum SampleEntry {
     Audio {
         data_reference_index: u16,
         channelcount: u16,
         samplesize: u16,
         samplerate: u32,
         esds: ES_Descriptor,
     },
@@ -139,50 +172,63 @@ enum SampleEntry {
         data_reference_index: u16,
         width: u16,
         height: u16,
         avcc: AVCDecoderConfigurationRecord,
     },
 }
 
 #[allow(dead_code)]
+#[derive(Debug)]
 pub struct AVCDecoderConfigurationRecord {
     data: Vec<u8>,
 }
 
 #[allow(non_camel_case_types)]
 #[allow(dead_code)]
+#[derive(Debug)]
 pub struct ES_Descriptor {
     data: Vec<u8>,
 }
 
 /// Internal data structures.
+#[derive(Debug)]
 pub struct MediaContext {
-    pub tracks: Vec<Track>,
+    tracks: Vec<Track>,
 }
 
+impl MediaContext {
+    pub fn new() -> MediaContext {
+        MediaContext {
+            tracks: Vec::new(),
+        }
+    }
+}
+
+#[derive(Debug)]
 enum TrackType {
     Video,
     Audio
 }
 
+#[derive(Debug)]
 pub struct Track {
     track_type: TrackType,
 }
 
 mod byteorder; // 'extern crate' upstream.
 use byteorder::{BigEndian, ReadBytesExt};
 use std::io::{Read, BufRead, Take};
 use std::io::Cursor;
 use std::cmp;
 
 /// Parse a box out of a data buffer.
 pub fn read_box_header<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<BoxHeader> {
     let size32 = try!(be_u32(src));
-    let name = try!(be_u32(src));
+    let name = FourCC(try!(be_u32(src)));
     let size = match size32 {
         0 => panic!("unknown box size not implemented"),
         1 => {
             let size64 = try!(be_u64(src));
             assert!(size64 >= 16);
             size64
         },
         2 ... 7 => panic!("invalid box size"),
@@ -227,17 +273,17 @@ pub fn skip_remaining_box_content<T: Buf
 /// Helper to construct a Take over the contents of a box.
 fn limit<'a, T: Read>(f: &'a mut T, h: &BoxHeader) -> Take<&'a mut T> {
     f.take(h.size - h.offset)
 }
 
 /// Helper to construct a Cursor over the contents of a box.
 fn recurse<T: Read>(f: &mut T, h: &BoxHeader, context: &mut MediaContext) -> byteorder::Result<()> {
     use std::error::Error;
-    println!("{} -- recursing", h);
+    println!("{:?} -- recursing", h);
     // FIXME: I couldn't figure out how to do this without copying.
     // We use Seek on the Read we return in skip_box_content, but
     // that trait isn't implemented for a Take like our limit()
     // returns. Slurping the buffer and wrapping it in a Cursor
     // functions as a work around.
     let buf: Vec<u8> = f
         .bytes()
         .map(|u| u.unwrap())
@@ -249,159 +295,123 @@ fn recurse<T: Read>(f: &mut T, h: &BoxHe
             Err(byteorder::Error::UnexpectedEOF) => {
                 // byteorder returns EOF at the end of the buffer.
                 // This isn't an error for us, just an signal to
                 // stop recursion.
                 println!("Caught byteorder::Error::UnexpectedEOF");
                 break;
             },
             Err(byteorder::Error::Io(e)) => {
-                println!("I/O Error '{:?}' reading box: {}",
+                println!("I/O Error '{:?}' reading box: {:?}",
                          e.kind(), e.description());
                 return Err(byteorder::Error::Io(e));
             },
         }
     }
     assert!(content.position() == h.size - h.offset);
-    println!("{} -- end", h);
+    println!("{:?} -- end", h);
     Ok(())
 }
 
 /// Read the contents of a box, including sub boxes.
 /// Metadata is accumulated in the passed-through MediaContext struct.
 pub fn read_box<T: BufRead>(f: &mut T, context: &mut MediaContext) -> byteorder::Result<()> {
     read_box_header(f).and_then(|h| {
         let mut content = limit(f, &h);
         match &fourcc_to_string(h.name)[..] {
             "ftyp" => {
                 let ftyp = try!(read_ftyp(&mut content, &h));
-                println!("{}", ftyp);
+                println!("{:?}", ftyp);
             },
             "moov" => try!(recurse(&mut content, &h, context)),
             "mvhd" => {
                 let mvhd = try!(read_mvhd(&mut content, &h));
-                println!("  {}", mvhd);
+                println!("  {:?}", mvhd);
             },
             "trak" => try!(recurse(&mut content, &h, context)),
             "tkhd" => {
                 let tkhd = try!(read_tkhd(&mut content, &h));
-                println!("  {}", tkhd);
+                println!("  {:?}", tkhd);
             },
             "edts" => try!(recurse(&mut content, &h, context)),
             "elst" => {
                 let elst = try!(read_elst(&mut content, &h));
-                println!("  {}", elst);
+                println!("  {:?}", elst);
             },
             "mdia" => try!(recurse(&mut content, &h, context)),
             "mdhd" => {
                 let mdhd = try!(read_mdhd(&mut content, &h));
-                println!("  {}", mdhd);
+                println!("  {:?}", mdhd);
             },
             "minf" => try!(recurse(&mut content, &h, context)),
             "stbl" => try!(recurse(&mut content, &h, context)),
             "stco" => {
                 let stco = try!(read_stco(&mut content, &h));
-                println!("  {}", stco);
+                println!("  {:?}", stco);
             },
             "co64" => {
                 let co64 = try!(read_co64(&mut content, &h));
-                println!("  {}", co64);
+                println!("  {:?}", co64);
             },
             "stss" => {
                 let stss = try!(read_stss(&mut content, &h));
-                println!("  {}", stss);
+                println!("  {:?}", stss);
             },
             "stsc" => {
                 let stsc = try!(read_stsc(&mut content, &h));
-                println!("  {}", stsc);
+                println!("  {:?}", stsc);
             },
             "stsz" => {
                 let stsz = try!(read_stsz(&mut content, &h));
-                println!("  {}", stsz);
+                println!("  {:?}", stsz);
             },
             "stts" => {
                 let stts = try!(read_stts(&mut content, &h));
-                println!("  {}", stts);
+                println!("  {:?}", stts);
             },
             "hdlr" => {
                 let hdlr = try!(read_hdlr(&mut content, &h));
                 let track_type = match &fourcc_to_string(hdlr.handler_type)[..] {
                     "vide" => Some(TrackType::Video),
                     "soun" => Some(TrackType::Audio),
                     _ => None
                 };
                 // Save track types with recognized types.
                 match track_type {
                     Some(track_type) =>
                          context.tracks.push(Track { track_type: track_type }),
                     None => println!("unknown track type!"),
                 };
+                println!("  {:?}", hdlr);
             },
             "stsd" => {
                 let track = &context.tracks[context.tracks.len() - 1];
                 let stsd = try!(read_stsd(&mut content, &h, &track));
-                println!("  {}", stsd);
+                println!("  {:?}", stsd);
             },
             _ => {
                 // Skip the contents of unknown chunks.
-                println!("{} (skipped)", h);
+                println!("{:?} (skipped)", h);
                 try!(skip_box_content(&mut content, &h));
             },
         };
         assert!(content.limit() == 0);
-        println!("read_box context: {}", context);
+        println!("read_box context: {:?}", context);
         Ok(()) // and_then needs a Result to return.
     })
 }
 
-/// Entry point for C language callers.
-/// Take a buffer and call read_box() on it,
-/// returning the number of detected tracks.
-#[no_mangle]
-pub extern fn read_box_from_buffer(buffer: *const u8, size: usize) -> i32 {
-    use std::slice;
-    use std::thread;
-    use std::i32;
-
-    // Validate arguments from C.
-    if buffer.is_null() || size < 8 {
-        return -1;
-    }
-
-    // Wrap the buffer we've been give in a slice.
-    let b = unsafe { slice::from_raw_parts(buffer, size) };
-    let mut c = Cursor::new(b);
-
-    // Parse in a subthread.
-    let task = thread::spawn(move || {
-        let mut context = MediaContext { tracks: Vec::new() };
-        loop {
-            match read_box(&mut c, &mut context) {
-                Ok(_) => {},
-                Err(byteorder::Error::UnexpectedEOF) => { break },
-                Err(e) => { panic!(e) },
-            }
-        }
-        // Make sure the track count fits in an i32 so we can use
-        // negative values for failure.
-        assert!(context.tracks.len() < i32::MAX as usize);
-        context.tracks.len() as i32
-    });
-    // Catch any panics.
-    task.join().unwrap_or(-1)
-}
-
 /// Parse an ftyp box.
 pub fn read_ftyp<T: ReadBytesExt>(src: &mut T, head: &BoxHeader) -> byteorder::Result<FileTypeBox> {
-    let major = try!(be_u32(src));
+    let major = FourCC(try!(be_u32(src)));
     let minor = try!(be_u32(src));
     let brand_count = (head.size - 8 - 8) / 4;
     let mut brands = Vec::new();
     for _ in 0..brand_count {
-        brands.push(try!(be_u32(src)));
+        brands.push(FourCC(try!(be_u32(src))));
     }
     Ok(FileTypeBox{
         name: head.name,
         size: head.size,
         major_brand: major,
         minor_version: minor,
         compatible_brands: brands,
     })
@@ -650,17 +660,17 @@ pub fn read_stts<T: ReadBytesExt>(src: &
 
 /// Parse a hdlr box.
 pub fn read_hdlr<T: ReadBytesExt + BufRead>(src: &mut T, head: &BoxHeader) -> byteorder::Result<HandlerBox> {
     let (_, _) = try!(read_fullbox_extra(src));
 
     // Skip uninteresting fields.
     try!(skip(src, 4));
 
-    let handler_type = try!(be_u32(src));
+    let handler_type = FourCC(try!(be_u32(src)));
 
     // Skip uninteresting fields.
     try!(skip(src, 12));
 
     // TODO(kinetik): Find a copy of ISO/IEC 14496-1 to work out how strings are encoded.
     // As a hack, just consume the rest of the box.
     try!(skip_remaining_box_content(src, head));
 
@@ -769,24 +779,24 @@ pub fn read_stsd<T: ReadBytesExt + BufRe
     Ok(SampleDescriptionBox{
         name: head.name,
         size: head.size,
         descriptions: descriptions,
     })
 }
 
 /// Convert the iso box type or other 4-character value to a string.
-fn fourcc_to_string(name: u32) -> String {
+fn fourcc_to_string(name: FourCC) -> String {
     let u32_to_vec = |u| {
         vec!((u >> 24 & 0xffu32) as u8,
              (u >> 16 & 0xffu32) as u8,
              (u >>  8 & 0xffu32) as u8,
              (u & 0xffu32) as u8)
     };
-    let name_bytes = u32_to_vec(name);
+    let name_bytes = u32_to_vec(name.0);
     String::from_utf8_lossy(&name_bytes).into_owned()
 }
 
 /// Skip a number of bytes that we don't care to parse.
 fn skip<T: BufRead>(src: &mut T, bytes: usize) -> byteorder::Result<usize> {
     let mut bytes_to_skip = bytes;
     while bytes_to_skip > 0 {
         let len = {
@@ -823,181 +833,41 @@ fn be_u16<T: ReadBytesExt>(src: &mut T) 
 fn be_u32<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u32> {
     src.read_u32::<BigEndian>()
 }
 
 fn be_u64<T: ReadBytesExt>(src: &mut T) -> byteorder::Result<u64> {
     src.read_u64::<BigEndian>()
 }
 
-use std::fmt;
-impl fmt::Display for BoxHeader {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}' {} bytes", fourcc_to_string(self.name), self.size)
-    }
-}
-
-impl fmt::Display for FileTypeBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let name = fourcc_to_string(self.name);
-        let brand = fourcc_to_string(self.major_brand);
-        let mut compat = String::from("compatible with");
-        for brand in &self.compatible_brands {
-            let brand_string = fourcc_to_string(*brand);
-            compat.push(' ');
-            compat.push_str(&brand_string);
-        }
-        write!(f, "'{}' {} bytes '{}' v{}\n {}",
-               name, self.size, brand, self.minor_version, compat)
-    }
-}
-
-impl fmt::Display for MovieHeaderBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let name = fourcc_to_string(self.name);
-        write!(f, "'{}' {} bytes duration {}s", name, self.size,
-               (self.duration as f64)/(self.timescale as f64))
-    }
-}
-
-impl fmt::Display for MediaContext {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "Found {} tracks.", self.tracks.len())
-    }
-}
-
-use std::u16;
-impl fmt::Display for TrackHeaderBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let name = fourcc_to_string(self.name);
-        // Dimensions are 16.16 fixed-point.
-        let base = u16::MAX as f64 + 1.0;
-        let width = (self.width as f64) / base;
-        let height = (self.height as f64) / base;
-        let disabled = if self.enabled { "" } else { " (disabled)" };
-        write!(f, "'{}' {} bytes duration {} id {} {}x{}{}",
-               name, self.size, self.duration, self.track_id,
-               width, height, disabled)
-    }
-}
-
-impl fmt::Display for EditListBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.edits {
-            entries.push_str(&format!("\n  duration {} time {} rate {}/{}",
-                                      entry.segment_duration, entry.media_time,
-                                      entry.media_rate_integer, entry.media_rate_fraction));
-        }
-        write!(f, "'{}' {} bytes {}", fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for MediaHeaderBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}' {} bytes timescale {} duration {}",
-               fourcc_to_string(self.name), self.size, self.timescale, self.duration)
-    }
-}
-
-impl fmt::Display for ChunkOffsetBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.offsets {
-            entries.push_str(&format!("\n  offset {}", entry));
-        }
-        write!(f, "'{}' {} bytes {}",
-               fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for SyncSampleBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.samples {
-            entries.push_str(&format!("\n  sample {}", entry));
-        }
-        write!(f, "'{}' {} bytes {}",
-               fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for SampleToChunkBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.samples {
-            entries.push_str(&format!("\n  sample chunk {} {} {}",
-                                      entry.first_chunk, entry.samples_per_chunk, entry.sample_description_index));
-        }
-        write!(f, "'{}' {} bytes {}",
-               fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for SampleSizeBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.sample_sizes {
-            entries.push_str(&format!("\n  sample size {}", entry));
-        }
-        write!(f, "'{}' {} bytes sample size {} {}",
-               fourcc_to_string(self.name), self.size, self.sample_size, entries)
-    }
-}
-
-impl fmt::Display for TimeToSampleBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let mut entries = String::new();
-        for entry in &self.samples {
-            entries.push_str(&format!("\n  sample count {} delta {}", entry.sample_count, entry.sample_delta));
-        }
-        write!(f, "'{}' {} bytes sample {}",
-               fourcc_to_string(self.name), self.size, entries)
-    }
-}
-
-impl fmt::Display for HandlerBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}' {} bytes handler_type '{}'",
-               fourcc_to_string(self.name), self.size, fourcc_to_string(self.handler_type))
-    }
-}
-
-impl fmt::Display for SampleDescriptionBox {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "'{}' {} bytes descriptions {}",
-               fourcc_to_string(self.name), self.size, self.descriptions.len())
-    }
-}
-
 #[test]
 fn test_read_box_header() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 8]; // minimal box length
     write!(&mut test, "test").unwrap(); // box type
     let mut stream = Cursor::new(test);
     let parsed = read_box_header(&mut stream).unwrap();
-    assert_eq!(parsed.name, 1952805748);
+    assert_eq!(parsed.name, FourCC(1952805748));
     assert_eq!(parsed.size, 8);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_box_header_long() {
     use std::io::Cursor;
     let mut test: Vec<u8> = vec![0, 0, 0, 1]; // long box extension code
     test.extend("long".to_string().into_bytes()); // box type
     test.extend(vec![0, 0, 0, 0, 0, 0, 16, 0]); // 64 bit size
     // Skip generating box content.
     let mut stream = Cursor::new(test);
     let parsed = read_box_header(&mut stream).unwrap();
-    assert_eq!(parsed.name, 1819242087);
+    assert_eq!(parsed.name, FourCC(1819242087));
     assert_eq!(parsed.size, 4096);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_ftyp() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 24]; // size
     write!(&mut test, "ftyp").unwrap(); // type
@@ -1005,24 +875,24 @@ fn test_read_ftyp() {
     test.extend(vec![0, 0, 0, 0]);      // minor version
     write!(&mut test, "isom").unwrap(); // compatible brands...
     write!(&mut test, "mp42").unwrap();
     assert_eq!(test.len(), 24);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_ftyp(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1718909296);
+    assert_eq!(parsed.name, FourCC(1718909296));
     assert_eq!(parsed.size, 24);
-    assert_eq!(parsed.major_brand, 1836069938);
+    assert_eq!(parsed.major_brand, FourCC(1836069938));
     assert_eq!(parsed.minor_version, 0);
     assert_eq!(parsed.compatible_brands.len(), 2);
-    assert_eq!(parsed.compatible_brands[0], 1769172845);
+    assert_eq!(parsed.compatible_brands[0], FourCC(1769172845));
     assert_eq!(fourcc_to_string(parsed.compatible_brands[1]), "mp42");
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_elst_v0() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 28]; // size
     write!(&mut test, "elst").unwrap(); // type
@@ -1032,24 +902,24 @@ fn test_read_elst_v0() {
                      5, 6, 7, 8,
                      9, 10,
                      11, 12]);
     assert_eq!(test.len(), 28);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_elst(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1701606260);
+    assert_eq!(parsed.name, FourCC(1701606260));
     assert_eq!(parsed.size, 28);
     assert_eq!(parsed.edits.len(), 1);
     assert_eq!(parsed.edits[0].segment_duration, 16909060);
     assert_eq!(parsed.edits[0].media_time, 84281096);
     assert_eq!(parsed.edits[0].media_rate_integer, 2314);
     assert_eq!(parsed.edits[0].media_rate_fraction, 2828);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_elst_v1() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 56]; // size
     write!(&mut test, "elst").unwrap(); // type
@@ -1063,24 +933,24 @@ fn test_read_elst_v1() {
                      5, 6, 7, 8, 5, 6, 7, 8,
                      9, 10,
                      11, 12]);
     assert_eq!(test.len(), 56);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_elst(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1701606260);
+    assert_eq!(parsed.name, FourCC(1701606260));
     assert_eq!(parsed.size, 56);
     assert_eq!(parsed.edits.len(), 2);
     assert_eq!(parsed.edits[1].segment_duration, 72623859723010820);
     assert_eq!(parsed.edits[1].media_time, 361984551075317512);
     assert_eq!(parsed.edits[1].media_rate_integer, 2314);
     assert_eq!(parsed.edits[1].media_rate_fraction, 2828);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_mdhd_v0() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 32]; // size
     write!(&mut test, "mdhd").unwrap(); // type
@@ -1090,21 +960,21 @@ fn test_read_mdhd_v0() {
                      1, 2, 3, 4,
                      5, 6, 7, 8,
                      0, 0, 0, 0]);
     assert_eq!(test.len(), 32);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_mdhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1835296868);
+    assert_eq!(parsed.name, FourCC(1835296868));
     assert_eq!(parsed.size, 32);
     assert_eq!(parsed.timescale, 16909060);
     assert_eq!(parsed.duration, 84281096);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
 
 #[test]
 fn test_read_mdhd_v1() {
     use std::io::Cursor;
     use std::io::Write;
     let mut test: Vec<u8> = vec![0, 0, 0, 44]; // size
     write!(&mut test, "mdhd").unwrap(); // type
@@ -1114,14 +984,14 @@ fn test_read_mdhd_v1() {
                      1, 2, 3, 4,
                      5, 6, 7, 8, 5, 6, 7, 8,
                      0, 0, 0, 0]);
     assert_eq!(test.len(), 44);
 
     let mut stream = Cursor::new(test);
     let header = read_box_header(&mut stream).unwrap();
     let parsed = read_mdhd(&mut stream, &header).unwrap();
-    assert_eq!(parsed.name, 1835296868);
+    assert_eq!(parsed.name, FourCC(1835296868));
     assert_eq!(parsed.size, 44);
     assert_eq!(parsed.timescale, 16909060);
     assert_eq!(parsed.duration, 361984551075317512);
-    println!("box {}", parsed);
+    println!("box {:?}", parsed);
 }
new file mode 100644
--- /dev/null
+++ b/media/libstagefright/binding/capi.rs
@@ -0,0 +1,103 @@
+// C API for mp4parse module.
+// Parses ISO Base Media Format aka video/mp4 streams.
+
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std;
+use std::io::Cursor;
+use byteorder;
+
+// Symbols we need from our rust api.
+use MediaContext;
+use read_box;
+
+/// Allocate an opaque rust-side parser context.
+#[no_mangle]
+pub extern "C" fn mp4parse_new() -> *mut MediaContext {
+    let context = Box::new(MediaContext::new());
+    unsafe {
+        // transmute is unsafe, but context is always valid.
+        std::mem::transmute(context)
+    }
+}
+
+/// Free a rust-side parser context.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_free(context: *mut MediaContext) {
+    assert!(!context.is_null());
+    let _: Box<MediaContext> = std::mem::transmute(context);
+}
+
+/// Feed a buffer through read_box(), returning the number of detected tracks.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_read(context: *mut MediaContext, buffer: *const u8, size: usize) -> i32 {
+    // Validate arguments from C.
+    if context.is_null() {
+        return -1;
+    }
+    if buffer.is_null() || size < 8 {
+        return -1;
+    }
+
+    let mut context: &mut MediaContext = &mut *context;
+
+    // Wrap the buffer we've been give in a slice.
+    let b = std::slice::from_raw_parts(buffer, size);
+    let mut c = Cursor::new(b);
+
+    // Parse in a subthread to catch any panics.
+    let task = std::thread::spawn(move || {
+        loop {
+            match read_box(&mut c, &mut context) {
+                Ok(_) => {},
+                Err(byteorder::Error::UnexpectedEOF) => { break },
+                Err(e) => { panic!(e); },
+            }
+        }
+        // Make sure the track count fits in an i32 so we can use
+        // negative values for failure.
+        assert!(context.tracks.len() < std::i32::MAX as usize);
+        context.tracks.len() as i32
+    });
+    task.join().unwrap_or(-1)
+}
+
+#[test]
+fn new_context() {
+    let context = mp4parse_new();
+    assert!(!context.is_null());
+    unsafe { mp4parse_free(context); }
+}
+
+#[test]
+#[should_panic(expected = "assertion failed")]
+fn free_null_context() {
+    unsafe { mp4parse_free(std::ptr::null_mut()); }
+}
+
+#[test]
+fn arg_validation() {
+    let null_buffer = std::ptr::null();
+    let null_context = std::ptr::null_mut();
+
+    let context = mp4parse_new();
+    assert!(!context.is_null());
+
+    let buffer = vec![0u8; 8];
+
+    unsafe {
+        assert_eq!(-1, mp4parse_read(null_context, null_buffer, 0));
+        assert_eq!(-1, mp4parse_read(context, null_buffer, 0));
+    }
+
+    for size in (0..buffer.len()) {
+        println!("testing buffer length {}", size);
+        unsafe {
+            assert_eq!(-1, mp4parse_read(context, buffer.as_ptr(), size));
+        }
+    }
+
+    unsafe { mp4parse_free(context); }
+}
new file mode 100644
--- /dev/null
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -0,0 +1,23 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#ifndef _MP4PARSE_RUST_H
+#define _MP4PARSE_RUST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct mp4parse_state;
+
+struct mp4parse_state* mp4parse_new(void);
+void mp4parse_free(struct mp4parse_state* state);
+
+int32_t mp4parse_read(struct mp4parse_state* state, uint8_t *buffer, size_t size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif