Bug 1340980 - update rust mp4 parser. r=kinetik draft
authorAlfredo.Yang <ayang@mozilla.com>
Mon, 20 Feb 2017 10:58:10 +0800
changeset 487836 d4ea066eb6acbaa648851fd2827e145b84004a14
parent 487788 1302072a9f42a9086b1cef7455a84c9374956f95
child 546570 f5dc533732b2f9778dcb2b8d201fe67e6343e02e
push id46353
push userbmo:ayang@mozilla.com
push dateWed, 22 Feb 2017 07:04:49 +0000
reviewerskinetik
bugs1340980
milestone54.0a1
Bug 1340980 - update rust mp4 parser. r=kinetik MozReview-Commit-ID: HrRkz8Sk0v7
media/libstagefright/binding/DecoderData.cpp
media/libstagefright/binding/include/mp4parse.h
media/libstagefright/binding/mp4parse-cargo.patch
media/libstagefright/binding/mp4parse/Cargo.toml
media/libstagefright/binding/mp4parse/src/boxes.rs
media/libstagefright/binding/mp4parse/src/lib.rs
media/libstagefright/binding/mp4parse/src/tests.rs
media/libstagefright/binding/mp4parse/tests/public.rs
media/libstagefright/binding/mp4parse_capi/src/lib.rs
media/libstagefright/binding/update-rust.sh
--- a/media/libstagefright/binding/DecoderData.cpp
+++ b/media/libstagefright/binding/DecoderData.cpp
@@ -187,17 +187,17 @@ MP4VideoInfo::Update(const MetaData* aMe
     }
   }
 
 }
 
 #ifdef MOZ_RUST_MP4PARSE
 static void
 UpdateTrackProtectedInfo(mozilla::TrackInfo& aConfig,
-                         const mp4parser_sinf_info& aSinf)
+                         const mp4parse_sinf_info& aSinf)
 {
   if (aSinf.is_encrypted != 0) {
     aConfig.mCrypto.mValid = true;
     aConfig.mCrypto.mMode = aSinf.is_encrypted;
     aConfig.mCrypto.mIVSize = aSinf.iv_size;
     aConfig.mCrypto.mKeyId.AppendElements(aSinf.kid.data, aSinf.kid.length);
   }
 }
--- a/media/libstagefright/binding/include/mp4parse.h
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -43,47 +43,57 @@ typedef enum mp4parse_codec {
 typedef struct mp4parse_track_info {
 	mp4parse_track_type track_type;
 	mp4parse_codec codec;
 	uint32_t track_id;
 	uint64_t duration;
 	int64_t media_time;
 } mp4parse_track_info;
 
+typedef struct mp4parse_indice {
+	uint64_t start_offset;
+	uint64_t end_offset;
+	uint64_t start_composition;
+	uint64_t end_composition;
+	uint64_t start_decode;
+	bool sync;
+} mp4parse_indice;
+
 typedef struct mp4parse_byte_data {
 	uint32_t length;
 	uint8_t const* data;
+	mp4parse_indice const* indices;
 } mp4parse_byte_data;
 
 typedef struct mp4parse_pssh_info {
 	mp4parse_byte_data data;
 } mp4parse_pssh_info;
 
-typedef struct mp4parser_sinf_info {
+typedef struct mp4parse_sinf_info {
 	uint32_t is_encrypted;
 	uint8_t iv_size;
 	mp4parse_byte_data kid;
-} mp4parser_sinf_info;
+} mp4parse_sinf_info;
 
 typedef struct mp4parse_track_audio_info {
 	uint16_t channels;
 	uint16_t bit_depth;
 	uint32_t sample_rate;
 	uint16_t profile;
 	mp4parse_byte_data codec_specific_config;
-	mp4parser_sinf_info protected_data;
+	mp4parse_sinf_info protected_data;
 } mp4parse_track_audio_info;
 
 typedef struct mp4parse_track_video_info {
 	uint32_t display_width;
 	uint32_t display_height;
 	uint16_t image_width;
 	uint16_t image_height;
 	mp4parse_byte_data extra_data;
-	mp4parser_sinf_info protected_data;
+	mp4parse_sinf_info protected_data;
 } mp4parse_track_video_info;
 
 typedef struct mp4parse_fragment_info {
 	uint64_t fragment_duration;
 } mp4parse_fragment_info;
 
 typedef struct mp4parse_parser mp4parse_parser;
 
@@ -111,16 +121,18 @@ mp4parse_error mp4parse_get_track_count(
 mp4parse_error mp4parse_get_track_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_info* info);
 
 /// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
 mp4parse_error mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_audio_info* info);
 
 /// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
 mp4parse_error mp4parse_get_track_video_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_video_info* info);
 
+mp4parse_error mp4parse_get_indice_table(mp4parse_parser* parser, uint32_t track_id, mp4parse_byte_data* indices);
+
 /// Fill the supplied `mp4parse_fragment_info` with metadata from fragmented file.
 mp4parse_error mp4parse_get_fragment_info(mp4parse_parser* parser, mp4parse_fragment_info* info);
 
 /// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
 mp4parse_error mp4parse_is_fragmented(mp4parse_parser* parser, uint32_t track_id, uint8_t* fragmented);
 
 /// Get 'pssh' system id and 'pssh' box content for eme playback.
 ///
--- a/media/libstagefright/binding/mp4parse-cargo.patch
+++ b/media/libstagefright/binding/mp4parse-cargo.patch
@@ -22,21 +22,24 @@ index ff9422c..814c4c6 100644
 -
  # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
  [profile.release]
  debug-assertions = true
 diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 index aeeebc65..5c0836a 100644
 --- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 +++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
-@@ -18,18 +18,12 @@ exclude = [
+@@ -18,21 +18,12 @@ exclude = [
    "*.mp4",
  ]
  
 -build = "build.rs"
+-
+-[badges]
+-travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
 +build = false
 
  [dependencies]
  byteorder = "1.0.0"
  "mp4parse" = {version = "0.6.0", path = "../mp4parse"}
  
 -[build-dependencies]
 -rusty-cheddar = "0.3.2"
--- a/media/libstagefright/binding/mp4parse/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
@@ -5,24 +5,28 @@ authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
 documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
 license = "MPL-2.0"
+categories = ["multimedia::video"]
 
 repository = "https://github.com/mozilla/mp4parse-rust"
 
 # Avoid complaints about trying to package test files.
 exclude = [
   "*.mp4",
 ]
 
+[badges]
+travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
+
 [dependencies]
 byteorder = "1.0.0"
 bitreader = { version = "0.2.0" }
 
 [dev-dependencies]
 test-assembler = "0.1.2"
 
 # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
--- a/media/libstagefright/binding/mp4parse/src/boxes.rs
+++ b/media/libstagefright/binding/mp4parse/src/boxes.rs
@@ -129,9 +129,11 @@ box_database!(
     MovieExtendsBox                   0x6d766578, // "mvex"
     MovieExtendsHeaderBox             0x6d656864, // "mehd"
     QTWaveAtom                        0x77617665, // "wave" - quicktime atom
     ProtectionSystemSpecificHeaderBox 0x70737368, // "pssh"
     SchemeInformationBox              0x73636869, // "schi"
     TrackEncryptionBox                0x74656e63, // "tenc"
     ProtectionSchemeInformationBox    0x73696e66, // "sinf"
     OriginalFormatBox                 0x66726d61, // "frma"
+    MP3AudioSampleEntry               0x2e6d7033, // ".mp3" - from F4V.
+    CompositionOffsetBox              0x63747473, // "ctts"
 );
--- a/media/libstagefright/binding/mp4parse/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse/src/lib.rs
@@ -146,56 +146,74 @@ struct Edit {
 #[derive(Debug)]
 struct MediaHeaderBox {
     timescale: u32,
     duration: u64,
 }
 
 // Chunk offset box 'stco' or 'co64'
 #[derive(Debug)]
-struct ChunkOffsetBox {
-    offsets: Vec<u64>,
+pub struct ChunkOffsetBox {
+    pub offsets: Vec<u64>,
 }
 
 // Sync sample box 'stss'
 #[derive(Debug)]
-struct SyncSampleBox {
-    samples: Vec<u32>,
+pub struct SyncSampleBox {
+    pub samples: Vec<u32>,
 }
 
 // Sample to chunk box 'stsc'
 #[derive(Debug)]
-struct SampleToChunkBox {
-    samples: Vec<SampleToChunk>,
+pub struct SampleToChunkBox {
+    pub samples: Vec<SampleToChunk>,
 }
 
 #[derive(Debug)]
-struct SampleToChunk {
-    first_chunk: u32,
-    samples_per_chunk: u32,
-    sample_description_index: u32,
+pub struct SampleToChunk {
+    pub first_chunk: u32,
+    pub samples_per_chunk: u32,
+    pub sample_description_index: u32,
 }
 
 // Sample size box 'stsz'
 #[derive(Debug)]
-struct SampleSizeBox {
-    sample_size: u32,
-    sample_sizes: Vec<u32>,
+pub struct SampleSizeBox {
+    pub sample_size: u32,
+    pub sample_sizes: Vec<u32>,
 }
 
 // Time to sample box 'stts'
 #[derive(Debug)]
-struct TimeToSampleBox {
-    samples: Vec<Sample>,
+pub struct TimeToSampleBox {
+    pub samples: Vec<Sample>,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Sample {
+    pub sample_count: u32,
+    pub sample_delta: u32,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum TimeOffsetVersion {
+    Version0(u32),
+    Version1(i32),
+}
+
+#[derive(Debug, Clone)]
+pub struct TimeOffset {
+    pub sample_count: u32,
+    pub time_offset: TimeOffsetVersion,
 }
 
 #[derive(Debug)]
-struct Sample {
-    sample_count: u32,
-    sample_delta: u32,
+pub struct CompositionOffsetBox {
+    pub samples: Vec<TimeOffset>,
 }
 
 // Handler reference box 'hdlr'
 #[derive(Debug)]
 struct HandlerBox {
     handler_type: FourCC,
 }
 
@@ -223,16 +241,17 @@ pub struct ES_Descriptor {
 }
 
 #[allow(non_camel_case_types)]
 #[derive(Debug, Clone)]
 pub enum AudioCodecSpecific {
     ES_Descriptor(ES_Descriptor),
     FLACSpecificBox(FLACSpecificBox),
     OpusSpecificBox(OpusSpecificBox),
+    MP3,
 }
 
 #[derive(Debug, Clone)]
 pub struct AudioSampleEntry {
     data_reference_index: u16,
     pub channelcount: u16,
     pub samplesize: u16,
     pub samplerate: u32,
@@ -391,16 +410,17 @@ pub struct TrackTimeScale(pub u64, pub u
 /// A time to be scaled by the track's local (mdhd) timescale.
 /// Members are time in scale units and the track id.
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub struct TrackScaledTime(pub u64, pub usize);
 
 /// A fragmented file contains no sample data in stts, stsc, and stco.
 #[derive(Debug, Default)]
 pub struct EmptySampleTableBoxes {
+    // TODO: Track has stts, stsc and stco, this structure can be discarded.
     pub empty_stts : bool,
     pub empty_stsc : bool,
     pub empty_stco : bool,
 }
 
 /// Check boxes contain data.
 impl EmptySampleTableBoxes {
     pub fn all_empty(&self) -> bool {
@@ -416,16 +436,22 @@ pub struct Track {
     pub media_time: Option<TrackScaledTime>,
     pub timescale: Option<TrackTimeScale>,
     pub duration: Option<TrackScaledTime>,
     pub track_id: Option<u32>,
     pub codec_type: CodecType,
     pub empty_sample_boxes: EmptySampleTableBoxes,
     pub data: Option<SampleEntry>,
     pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
+    pub stts: Option<TimeToSampleBox>,
+    pub stsc: Option<SampleToChunkBox>,
+    pub stsz: Option<SampleSizeBox>,
+    pub stco: Option<ChunkOffsetBox>,   // It is for stco or co64.
+    pub stss: Option<SyncSampleBox>,
+    pub ctts: Option<CompositionOffsetBox>,
 }
 
 impl Track {
     fn new(id: usize) -> Track {
         Track { id: id, ..Default::default() }
     }
 }
 
@@ -835,40 +861,51 @@ fn read_stbl<T: Read>(f: &mut BMFFBox<T>
     while let Some(mut b) = iter.next_box()? {
         match b.head.name {
             BoxType::SampleDescriptionBox => {
                 let stsd = read_stsd(&mut b, track)?;
                 log!("{:?}", stsd);
             }
             BoxType::TimeToSampleBox => {
                 let stts = read_stts(&mut b)?;
+                log!("{:?}", stts);
                 track.empty_sample_boxes.empty_stts = stts.samples.is_empty();
-                log!("{:?}", stts);
+                track.stts = Some(stts);
             }
             BoxType::SampleToChunkBox => {
                 let stsc = read_stsc(&mut b)?;
+                log!("{:?}", stsc);
                 track.empty_sample_boxes.empty_stsc = stsc.samples.is_empty();
-                log!("{:?}", stsc);
+                track.stsc = Some(stsc);
             }
             BoxType::SampleSizeBox => {
                 let stsz = read_stsz(&mut b)?;
                 log!("{:?}", stsz);
+                track.stsz = Some(stsz);
             }
             BoxType::ChunkOffsetBox => {
                 let stco = read_stco(&mut b)?;
                 track.empty_sample_boxes.empty_stco = stco.offsets.is_empty();
                 log!("{:?}", stco);
+                track.stco = Some(stco);
             }
             BoxType::ChunkLargeOffsetBox => {
                 let co64 = read_co64(&mut b)?;
                 log!("{:?}", co64);
+                track.stco = Some(co64);
             }
             BoxType::SyncSampleBox => {
                 let stss = read_stss(&mut b)?;
                 log!("{:?}", stss);
+                track.stss = Some(stss);
+            }
+            BoxType::CompositionOffsetBox => {
+                let ctts = read_ctts(&mut b)?;
+                log!("{:?}", ctts);
+                track.ctts = Some(ctts);
             }
             _ => skip_box_content(&mut b)?,
         };
         check_parser_state!(b.content);
     }
     Ok(())
 }
 
@@ -1110,16 +1147,55 @@ fn read_stsc<T: Read>(src: &mut BMFFBox<
     // Padding could be added in some contents.
     skip_box_remain(src)?;
 
     Ok(SampleToChunkBox {
         samples: samples,
     })
 }
 
+fn read_ctts<T: Read>(src: &mut BMFFBox<T>) -> Result<CompositionOffsetBox> {
+    let (version, _) = read_fullbox_extra(src)?;
+
+    let counts = be_u32(src)?;
+
+    if src.bytes_left() < (counts as usize * 8) {
+        return Err(Error::InvalidData("insufficient data in 'ctts' box"));
+    }
+
+    let mut offsets = Vec::new();
+    for _ in 0..counts {
+        let (sample_count, time_offset) = match version {
+            0 => {
+                let count = be_u32(src)?;
+                let offset = TimeOffsetVersion::Version0(be_u32(src)?);
+                (count, offset)
+            },
+            1 => {
+                let count = be_u32(src)?;
+                let offset = TimeOffsetVersion::Version1(be_i32(src)?);
+                (count, offset)
+            },
+            _ => {
+                return Err(Error::InvalidData("unsupported version in 'ctts' box"));
+            }
+        };
+        offsets.push(TimeOffset {
+            sample_count: sample_count,
+            time_offset: time_offset,
+        });
+    }
+
+    skip_box_remain(src)?;
+
+    Ok(CompositionOffsetBox {
+        samples: offsets,
+    })
+}
+
 /// Parse a stsz box.
 fn read_stsz<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleSizeBox> {
     let (_, _) = read_fullbox_extra(src)?;
     let sample_size = be_u32(src)?;
     let sample_count = be_u32(src)?;
     let mut sample_sizes = Vec::new();
     if sample_size == 0 {
         for _ in 0..sample_count {
@@ -1428,17 +1504,16 @@ fn read_dfla<T: Read>(src: &mut BMFFBox<
         let block = read_flac_metadata(src)?;
         blocks.push(block);
     }
     // The box must have at least one meta block, and the first block
     // must be the METADATA_BLOCK_STREAMINFO
     if blocks.is_empty() {
         return Err(Error::InvalidData("FLACSpecificBox missing metadata"));
     } else if blocks[0].block_type != 0 {
-        println!("flac metadata block:\n  {:?}", blocks[0]);
         return Err(Error::InvalidData(
                 "FLACSpecificBox must have STREAMINFO metadata first"));
     } else if blocks[0].data.len() != 34 {
         return Err(Error::InvalidData(
                 "FLACSpecificBox STREAMINFO block is the wrong size"));
     }
     Ok(FLACSpecificBox {
         version: version,
@@ -1597,17 +1672,18 @@ fn read_video_sample_entry<T: Read>(src:
                 }
                 let avcc = read_buf(&mut b.content, avcc_size as usize)?;
                 log!("{:?} (avcc)", avcc);
                 // TODO(kinetik): Parse avcC box?  For now we just stash the data.
                 codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc));
             }
             BoxType::VPCodecConfigurationBox => { // vpcC
                 if (name != BoxType::VP8SampleEntry &&
-                    name != BoxType::VP9SampleEntry) ||
+                    name != BoxType::VP9SampleEntry &&
+                    name != BoxType::ProtectedVisualSampleEntry) ||
                     codec_specific.is_some() {
                         return Err(Error::InvalidData("malformed video sample entry"));
                     }
                 let vpcc = read_vpcc(&mut b)?;
                 codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc));
             }
             BoxType::ProtectionSchemeInformationBox => {
                 if name != BoxType::ProtectedVisualSampleEntry {
@@ -1682,16 +1758,19 @@ fn read_audio_sample_entry<T: Read>(src:
             // Skip uninteresting fields.
             skip(src, 16)?;
         },
         _ => return Err(Error::Unsupported("unsupported non-isom audio sample entry")),
     }
 
     // Skip chan/etc. for now.
     let mut codec_specific = None;
+    if name == BoxType::MP3AudioSampleEntry {
+        codec_specific = Some(AudioCodecSpecific::MP3);
+    }
     let mut protection_info = Vec::new();
     let mut iter = src.box_iter();
     while let Some(mut b) = iter.next_box()? {
         match b.head.name {
             BoxType::ESDBox => {
                 if (name != BoxType::MP4AudioSampleEntry &&
                     name != BoxType::ProtectedAudioSampleEntry) ||
                     codec_specific.is_some() {
@@ -1884,26 +1963,28 @@ fn read_buf<T: ReadBytesExt>(src: &mut T
     Ok(buf)
 }
 
 // TODO(kinetik): Find a copy of ISO/IEC 14496-1 to confirm various string encodings.
 // XXX(kinetik): definition of "null-terminated" string is fuzzy, we have:
 // - zero or more byte strings, with a single null terminating the string.
 // - zero byte strings with no null terminator (i.e. zero space in the box for the string)
 // - length-prefixed strings with no null terminator (e.g. bear_rotate_0.mp4)
+// - multiple byte strings where more than one byte is a null.
 fn read_null_terminated_string<T: ReadBytesExt>(src: &mut T, mut size: usize) -> Result<String> {
     let mut buf = Vec::new();
     while size > 0 {
         let c = src.read_u8()?;
+        size -= 1;
         if c == 0 {
             break;
         }
         buf.push(c);
-        size -= 1;
     }
+    skip(src, size)?;
     String::from_utf8(buf).map_err(From::from)
 }
 
 #[allow(dead_code)]
 fn read_pascal_string<T: ReadBytesExt>(src: &mut T) -> Result<String> {
     let len = src.read_u8()?;
     let buf = read_buf(src, len as usize)?;
     String::from_utf8(buf).map_err(From::from)
--- a/media/libstagefright/binding/mp4parse/src/tests.rs
+++ b/media/libstagefright/binding/mp4parse/src/tests.rs
@@ -879,8 +879,24 @@ fn read_esds() {
     let es = super::read_esds(&mut stream).unwrap();
 
     assert_eq!(es.audio_codec, super::CodecType::AAC);
     assert_eq!(es.audio_object_type, Some(2));
     assert_eq!(es.audio_sample_rate, Some(24000));
     assert_eq!(es.audio_channel_count, Some(6));
     assert_eq!(es.codec_esds, aac_esds);
 }
+
+#[test]
+fn read_null_terminated_string() {
+    let tests = vec![
+        vec![0u8],                         // Short null-terminated string.
+        vec![65u8, 0u8],                   // Normal null-terminated string.
+        vec![],                            // Empty string (no data).
+        vec![4u8, 65u8, 66u8, 67u8, 68u8], // Length-prefixed string, not null-terminated.
+        vec![0u8, 0u8],                    // Doubly null-terminated string.
+    ];
+    for v in tests.iter() {
+        let mut c = Cursor::new(v);
+        super::read_null_terminated_string(&mut c, v.len()).expect("string read failed");
+        assert_eq!(c.position(), v.len() as u64);
+    }
+}
--- a/media/libstagefright/binding/mp4parse/tests/public.rs
+++ b/media/libstagefright/binding/mp4parse/tests/public.rs
@@ -87,16 +87,19 @@ fn public_api() {
                         assert_eq!(flac.blocks[0].data.len(), 34);
                         "FLAC"
                     }
                     mp4::AudioCodecSpecific::OpusSpecificBox(opus) => {
                         // We don't enter in here, we just check if fields are public.
                         assert!(opus.version > 0);
                         "Opus"
                     }
+                    mp4::AudioCodecSpecific::MP3 => {
+                        "MP3"
+                    }
                 }, "ES");
                 assert!(a.samplesize > 0);
                 assert!(a.samplerate > 0);
             }
             Some(mp4::SampleEntry::Unknown) | None => {}
         }
     }
 }
--- a/media/libstagefright/binding/mp4parse_capi/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
@@ -50,16 +50,17 @@ use mp4parse::SampleEntry;
 use mp4parse::AudioCodecSpecific;
 use mp4parse::VideoCodecSpecific;
 use mp4parse::MediaTimeScale;
 use mp4parse::MediaScaledTime;
 use mp4parse::TrackTimeScale;
 use mp4parse::TrackScaledTime;
 use mp4parse::serialize_opus_header;
 use mp4parse::CodecType;
+use mp4parse::Track;
 
 // rusty-cheddar's C enum generation doesn't namespace enum members by
 // prefixing them, so we're forced to do it in our member names until
 // https://github.com/Sean1708/rusty-cheddar/pull/35 is fixed.  Importing
 // the members into the module namespace avoids doubling up on the
 // namespacing on the Rust side.
 use mp4parse_error::*;
 use mp4parse_track_type::*;
@@ -109,71 +110,89 @@ pub struct mp4parse_track_info {
     pub codec: mp4parse_codec,
     pub track_id: u32,
     pub duration: u64,
     pub media_time: i64, // wants to be u64? understand how elst adjustment works
     // TODO(kinetik): include crypto guff
 }
 
 #[repr(C)]
+#[derive(Default, Debug, PartialEq)]
+pub struct mp4parse_indice {
+    pub start_offset: u64,
+    pub end_offset: u64,
+    pub start_composition: u64,
+    pub end_composition: u64,
+    pub start_decode: u64,
+    pub sync: bool,
+}
+
+#[repr(C)]
 pub struct mp4parse_byte_data {
     pub length: u32,
+    // cheddar can't handle generic type, so it needs to be multiple data types here.
     pub data: *const u8,
+    pub indices: *const mp4parse_indice,
 }
 
 impl Default for mp4parse_byte_data {
     fn default() -> Self {
         mp4parse_byte_data {
             length: 0,
-            data: std::ptr::null_mut(),
+            data: std::ptr::null(),
+            indices: std::ptr::null(),
         }
     }
 }
 
 impl mp4parse_byte_data {
     fn set_data(&mut self, data: &Vec<u8>) {
         self.length = data.len() as u32;
         self.data = data.as_ptr();
     }
+    fn set_indices(&mut self, data: &Vec<mp4parse_indice>) {
+        self.length = data.len() as u32;
+        self.indices = data.as_ptr();
+    }
 }
 
 #[repr(C)]
 #[derive(Default)]
 pub struct mp4parse_pssh_info {
     pub data: mp4parse_byte_data,
 }
 
 #[repr(C)]
 #[derive(Default)]
-pub struct mp4parser_sinf_info {
+pub struct mp4parse_sinf_info {
     pub is_encrypted: u32,
     pub iv_size: u8,
     pub kid: mp4parse_byte_data,
 }
 
 #[repr(C)]
 #[derive(Default)]
 pub struct mp4parse_track_audio_info {
     pub channels: u16,
     pub bit_depth: u16,
     pub sample_rate: u32,
     pub profile: u16,
     pub codec_specific_config: mp4parse_byte_data,
-    pub protected_data: mp4parser_sinf_info,
+    pub protected_data: mp4parse_sinf_info,
 }
 
 #[repr(C)]
 #[derive(Default)]
 pub struct mp4parse_track_video_info {
     pub display_width: u32,
     pub display_height: u32,
     pub image_width: u16,
     pub image_height: u16,
     pub extra_data: mp4parse_byte_data,
-    pub protected_data: mp4parser_sinf_info,
+    pub protected_data: mp4parse_sinf_info,
 }
 
 #[repr(C)]
 #[derive(Default)]
 pub struct mp4parse_fragment_info {
     pub fragment_duration: u64,
     // TODO:
     // info in trex box.
@@ -182,16 +201,17 @@ pub struct mp4parse_fragment_info {
 // Even though mp4parse_parser is opaque to C, rusty-cheddar won't let us
 // use more than one member, so we introduce *another* wrapper.
 struct Wrap {
     context: MediaContext,
     io: mp4parse_io,
     poisoned: bool,
     opus_header: HashMap<u32, Vec<u8>>,
     pssh_data: Vec<u8>,
+    sample_table: HashMap<u32, Vec<mp4parse_indice>>,
 }
 
 #[repr(C)]
 #[allow(non_camel_case_types)]
 pub struct mp4parse_parser(Wrap);
 
 impl mp4parse_parser {
     fn context(&self) -> &MediaContext {
@@ -216,16 +236,20 @@ impl mp4parse_parser {
 
     fn opus_header_mut(&mut self) -> &mut HashMap<u32, Vec<u8>> {
         &mut self.0.opus_header
     }
 
     fn pssh_data_mut(&mut self) -> &mut Vec<u8> {
         &mut self.0.pssh_data
     }
+
+    fn sample_table_mut(&mut self) -> &mut HashMap<u32, Vec<mp4parse_indice>> {
+        &mut self.0.sample_table
+    }
 }
 
 #[repr(C)]
 #[derive(Clone)]
 pub struct mp4parse_io {
     pub read: extern fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize,
     pub userdata: *mut std::os::raw::c_void,
 }
@@ -261,16 +285,17 @@ pub unsafe extern fn mp4parse_new(io: *c
         return std::ptr::null_mut();
     }
     let parser = Box::new(mp4parse_parser(Wrap {
         context: MediaContext::new(),
         io: (*io).clone(),
         poisoned: false,
         opus_header: HashMap::new(),
         pssh_data: Vec::new(),
+        sample_table: HashMap::new(),
     }));
 
     Box::into_raw(parser)
 }
 
 /// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
 #[no_mangle]
 pub unsafe extern fn mp4parse_free(parser: *mut mp4parse_parser) {
@@ -396,16 +421,18 @@ pub unsafe extern fn mp4parse_get_track_
             AudioCodecSpecific::FLACSpecificBox(_) =>
                 mp4parse_codec::MP4PARSE_CODEC_FLAC,
             AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC =>
                 mp4parse_codec::MP4PARSE_CODEC_AAC,
             AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 =>
                 mp4parse_codec::MP4PARSE_CODEC_MP3,
             AudioCodecSpecific::ES_Descriptor(_) =>
                 mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
+            AudioCodecSpecific::MP3 =>
+                mp4parse_codec::MP4PARSE_CODEC_MP3,
         },
         Some(SampleEntry::Video(ref video)) => match video.codec_specific {
             VideoCodecSpecific::VPxConfig(_) =>
                 mp4parse_codec::MP4PARSE_CODEC_VP9,
             VideoCodecSpecific::AVCConfig(_) =>
                 mp4parse_codec::MP4PARSE_CODEC_AVC,
         },
         _ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
@@ -531,16 +558,17 @@ pub unsafe extern fn mp4parse_get_track_
                             }
                             (*info).codec_specific_config.length = v.len() as u32;
                             (*info).codec_specific_config.data = v.as_ptr();
                         }
                     }
                 }
             }
         }
+        AudioCodecSpecific::MP3 => (),
     }
 
     match audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
         Some(ref p) => {
             if let Some(ref tenc) = p.tenc {
                 (*info).protected_data.is_encrypted = tenc.is_encrypted;
                 (*info).protected_data.iv_size = tenc.iv_size;
                 (*info).protected_data.kid.set_data(&(tenc.kid));
@@ -610,16 +638,377 @@ pub unsafe extern fn mp4parse_get_track_
             }
         },
         _ => {},
     }
 
     MP4PARSE_OK
 }
 
+#[no_mangle]
+pub unsafe extern fn mp4parse_get_indice_table(parser: *mut mp4parse_parser, track_id: u32, indices: *mut mp4parse_byte_data) -> mp4parse_error {
+    if parser.is_null() || (*parser).poisoned() {
+        return MP4PARSE_ERROR_BADARG;
+    }
+
+    // Initialize fields to default values to ensure all fields are always valid.
+    *indices = Default::default();
+
+    let context = (*parser).context();
+    let tracks = &context.tracks;
+    let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) {
+        Some(t) => t,
+        _ => return MP4PARSE_ERROR_INVALID,
+    };
+
+    let index_table = (*parser).sample_table_mut();
+    match index_table.get(&track_id) {
+        Some(v) => {
+            (*indices).set_indices(v);
+            return MP4PARSE_OK;
+        },
+        _ => {},
+    }
+
+    // Find the track start offset time from 'elst'.
+    // 'media_time' maps start time onward, 'empty_duration' adds time offset
+    // before first frame is displayed.
+    let offset_time =
+        match (&track.empty_duration, &track.media_time, &context.timescale) {
+            (&Some(empty_duration), &Some(media_time), &Some(scale)) => {
+                (empty_duration.0 - media_time.0) as i64 * scale.0 as i64
+            },
+            (&Some(empty_duration), _, &Some(scale)) => {
+                empty_duration.0 as i64 * scale.0 as i64
+            },
+            (_, &Some(media_time), &Some(scale)) => {
+                (0 - media_time.0) as i64 * scale.0 as i64
+            },
+            _ => 0,
+        };
+
+    match create_sample_table(track, offset_time) {
+        Some(v) => {
+            (*indices).set_indices(&v);
+            index_table.insert(track_id, v);
+            return MP4PARSE_OK;
+        },
+        _ => {},
+    }
+
+    MP4PARSE_ERROR_INVALID
+}
+
+// Convert a 'ctts' compact table to full table by iterator,
+// (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ...
+//
+// For example:
+// (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time().
+struct TimeOffsetIterator<'a> {
+    cur_sample_range: std::ops::Range<u32>,
+    cur_offset: i64,
+    ctts_iter: Option<std::slice::Iter<'a, mp4parse::TimeOffset>>,
+}
+
+impl<'a> Iterator for TimeOffsetIterator<'a> {
+    type Item = i64;
+
+    fn next(&mut self) -> Option<i64> {
+        let has_sample = self.cur_sample_range.next()
+            .or_else(|| {
+                // At end of current TimeOffset, find the next TimeOffset.
+                let iter = match self.ctts_iter {
+                    Some(ref mut v) => v,
+                    _ => return None,
+                };
+                let offset_version;
+                self.cur_sample_range = match iter.next() {
+                    Some(v) => {
+                        offset_version = v.time_offset;
+                        (0 .. v.sample_count)
+                    },
+                    _ => {
+                        offset_version = mp4parse::TimeOffsetVersion::Version0(0);
+                        (0 .. 0)
+                    },
+                };
+
+                self.cur_offset = match offset_version {
+                    mp4parse::TimeOffsetVersion::Version0(i) => i as i64,
+                    mp4parse::TimeOffsetVersion::Version1(i) => i as i64,
+                };
+
+                self.cur_sample_range.next()
+            });
+
+        has_sample.and(Some(self.cur_offset))
+    }
+}
+
+impl<'a> TimeOffsetIterator<'a> {
+    fn next_offset_time(&mut self) -> i64 {
+        match self.next() {
+            Some(v) => v as i64,
+            _ => 0,
+        }
+    }
+}
+
+// Convert 'stts' compact table to full table by iterator,
+// (sample_count_with_the_same_time, time) => (time, time, time) ... repeats
+// sample_count_with_the_same_time.
+//
+// For example:
+// (2, 3000), (1, 2999) to (3000, 3000, 2999).
+struct TimeToSampleIteraor<'a> {
+    cur_sample_count: std::ops::Range<u32>,
+    cur_sample_delta: u32,
+    stts_iter: std::slice::Iter<'a, mp4parse::Sample>,
+}
+
+impl<'a> Iterator for TimeToSampleIteraor<'a> {
+    type Item = u32;
+
+    fn next(&mut self) -> Option<u32> {
+        let has_sample = self.cur_sample_count.next()
+            .or_else(|| {
+                self.cur_sample_count = match self.stts_iter.next() {
+                    Some(v) => {
+                        self.cur_sample_delta = v.sample_delta;
+                        (0 .. v.sample_count)
+                    },
+                    _ => (0 .. 0),
+                };
+
+                self.cur_sample_count.next()
+            });
+
+        has_sample.and(Some(self.cur_sample_delta))
+    }
+}
+
+impl<'a> TimeToSampleIteraor<'a> {
+    fn next_delta(&mut self) -> u32 {
+        match self.next() {
+            Some(v) => v,
+            _ => 0,
+        }
+    }
+}
+
+// Convert 'stco' compact table to full table by iterator.
+// (start_chunk_num, sample_number) => (start_chunk_num, sample_number),
+//                                     (start_chunk_num + 1, sample_number),
+//                                     (start_chunk_num + 2, sample_number),
+//                                     ...
+//                                     (next start_chunk_num, next sample_number),
+//                                     ...
+//
+// For example:
+// (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10),
+// (7, 10), (8, 10), (9, 2)
+struct SampleToChunkIterator<'a> {
+    chunks: std::ops::Range<u32>,
+    sample_count: u32,
+    stsc_peek_iter: std::iter::Peekable<std::slice::Iter<'a, mp4parse::SampleToChunk>>,
+    remain_chunk_count: u32, // total chunk number from 'stco'.
+}
+
+impl<'a> Iterator for SampleToChunkIterator<'a> {
+    type Item = (u32, u32);
+
+    fn next(&mut self) -> Option<(u32, u32)> {
+        let has_chunk = self.chunks.next()
+            .or_else(|| {
+                self.chunks = match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) {
+                    (Some(next), Some(peek)) => {
+                        self.sample_count = next.samples_per_chunk;
+                        ((next.first_chunk - 1) .. (peek.first_chunk - 1))
+                    },
+                    (Some(next), None) => {
+                        self.sample_count = next.samples_per_chunk;
+                        // Total chunk number in 'stsc' could be different to 'stco',
+                        // there could be more chunks at the last 'stsc' record.
+                        ((next.first_chunk - 1) .. next.first_chunk + self.remain_chunk_count -1)
+                    },
+                    _ => (0 .. 0),
+                };
+                self.remain_chunk_count -= self.chunks.len() as u32;
+                self.chunks.next()
+            });
+
+        has_chunk.map_or(None, |id| { Some((id, self.sample_count)) })
+    }
+}
+
+// A helper struct to convert track time to us.
+struct PresentationTime {
+    time: i64,
+    scale: TrackTimeScale
+}
+
+impl PresentationTime {
+    fn new(time: i64, scale: TrackTimeScale) -> PresentationTime {
+        PresentationTime {
+            time: time,
+            scale: scale,
+        }
+    }
+
+    fn to_us(&self) -> i64 {
+        let track_time = TrackScaledTime(self.time as u64, self.scale.1);
+        match track_time_to_us(track_time, self.scale) {
+            Some(v) => v as i64,
+            _ => 0,
+        }
+    }
+}
+
+fn create_sample_table(track: &Track, track_offset_time: i64) -> Option<Vec<mp4parse_indice>> {
+    let timescale = match track.timescale {
+        Some(t) => t,
+        _ => return None,
+    };
+
+    let (stsc, stco, stsz, stts) =
+        match (&track.stsc, &track.stco, &track.stsz, &track.stts) {
+            (&Some(ref a), &Some(ref b), &Some(ref c), &Some(ref d)) => (a, b, c, d),
+            _ => return None,
+        };
+
+    // According to spec, no sync table means every sample is sync sample.
+    let has_sync_table = match track.stss {
+        Some(_) => true,
+        _ => false,
+    };
+
+    let mut sample_table = Vec::new();
+    let mut sample_size_iter = stsz.sample_sizes.iter();
+
+    // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample
+    // offset address.
+    let stsc_iter = SampleToChunkIterator {
+        chunks: (0 .. 0),
+        sample_count: 0,
+        stsc_peek_iter: stsc.samples.as_slice().iter().peekable(),
+        remain_chunk_count: stco.offsets.len() as u32,
+    };
+
+    for i in stsc_iter {
+        let chunk_id = i.0 as usize;
+        let sample_counts = i.1;
+        let mut cur_position: u64 = stco.offsets[chunk_id];
+        for _ in 0 .. sample_counts {
+            let start_offset = cur_position;
+            let end_offset = match (stsz.sample_size, sample_size_iter.next()) {
+                (_, Some(t)) => start_offset + *t as u64,
+                (t, _) if t > 0 => start_offset + t as u64,
+                _ => 0,
+            };
+            if end_offset == 0 {
+                return None;
+            }
+            cur_position = end_offset;
+
+            sample_table.push(
+                mp4parse_indice {
+                    start_offset: start_offset,
+                    end_offset: end_offset,
+                    start_composition: 0,
+                    end_composition: 0,
+                    start_decode: 0,
+                    sync: !has_sync_table,
+                }
+            );
+        }
+    }
+
+    // Mark the sync sample in sample_table according to 'stss'.
+    match &track.stss {
+        &Some(ref v) => {
+            for iter in &v.samples {
+                sample_table[(iter - 1) as usize].sync = true;
+            }
+        },
+        _ => {}
+    }
+
+    let ctts_iter = match &track.ctts {
+        &Some(ref v) => Some(v.samples.as_slice().iter()),
+        _ => None,
+    };
+
+    let mut ctts_offset_iter = TimeOffsetIterator {
+        cur_sample_range: (0 .. 0),
+        cur_offset: 0,
+        ctts_iter: ctts_iter,
+    };
+
+    let mut stts_iter = TimeToSampleIteraor {
+        cur_sample_count: (0 .. 0),
+        cur_sample_delta: 0,
+        stts_iter: stts.samples.as_slice().iter(),
+    };
+
+    // sum_delta is the sum of stts_iter delta.
+    // According to sepc:
+    //      decode time => DT(n) = DT(n-1) + STTS(n)
+    //      composition time => CT(n) = DT(n) + CTTS(n)
+    // Note:
+    //      composition time needs to add the track offset time from 'elst' table.
+    let mut sum_delta = PresentationTime::new(0, timescale);
+    for sample in sample_table.as_mut_slice() {
+        let decode_time = sum_delta.to_us();
+        sum_delta.time += stts_iter.next_delta() as i64;
+
+        // ctts_offset is the current sample offset time.
+        let ctts_offset = PresentationTime::new(ctts_offset_iter.next_offset_time(), timescale);
+
+        let start_composition = (decode_time + ctts_offset.to_us() + track_offset_time) as u64;
+        let end_composition = (sum_delta.to_us() + ctts_offset.to_us() + track_offset_time) as u64;
+
+        sample.start_decode = decode_time as u64;
+        sample.start_composition = start_composition;
+        sample.end_composition = end_composition;
+    }
+
+    // Correct composition end time due to 'ctts' causes composition time re-ordering.
+    //
+    // Composition end time is not in specification. However, gecko needs it, so we need to
+    // calculate to correct the composition end time.
+    if track.ctts.is_some() {
+        // Create an index table refers to sample_table and sorted by start_composisiton time.
+        let mut sort_table = Vec::new();
+        sort_table.reserve(sample_table.len());
+        for i in 0 .. sample_table.len() {
+            sort_table.push(i);
+        }
+
+        sort_table.sort_by_key(|i| {
+            match sample_table.get(*i) {
+                Some(v) => {
+                    v.start_composition
+                },
+                _ => 0,
+            }
+        });
+
+        let iter = sort_table.iter();
+        for i in 0 .. (iter.len() - 1) {
+            let current_index = sort_table[i] as usize;
+            let peek_index = sort_table[i + 1] as usize;
+            let next_start_composition_time = sample_table[peek_index].start_composition;
+            let ref mut sample = sample_table[current_index];
+            sample.end_composition = next_start_composition_time;
+        }
+    }
+
+    Some(sample_table)
+}
+
 /// Fill the supplied `mp4parse_fragment_info` with metadata from fragmented file.
 #[no_mangle]
 pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut mp4parse_parser, info: *mut mp4parse_fragment_info) -> mp4parse_error {
     if parser.is_null() || info.is_null() || (*parser).poisoned() {
         return MP4PARSE_ERROR_BADARG;
     }
 
     // Initialize fields to default values to ensure all fields are always valid.
--- a/media/libstagefright/binding/update-rust.sh
+++ b/media/libstagefright/binding/update-rust.sh
@@ -1,13 +1,13 @@
 #!/bin/sh -e
 # Script to update mp4parse-rust sources to latest upstream
 
 # Default version.
-VER=v0.6.0
+VER=6dfc85b277f8a072083b71f23cc05981b22a10bc
 
 # Accept version or commit from the command line.
 if test -n "$1"; then
   VER=$1
 fi
 
 echo "Fetching sources..."
 rm -rf _upstream