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