use std::collections::HashSet;
use std::io::SeekFrom;
use oxideav_core::{
CodecId, CodecParameters, CodecResolver, CodecTag, Error, MediaType, Packet, ProbeContext,
Result, SampleFormat, StreamInfo, TimeBase,
};
use oxideav_core::{Demuxer, ReadSeek};
use crate::boxes::*;
use crate::codec_id::{from_sample_entry, from_sample_entry_with_oti};
pub fn open(mut input: Box<dyn ReadSeek>, codecs: &dyn CodecResolver) -> Result<Box<dyn Demuxer>> {
let mut saw_ftyp = false;
let mut moov: Option<Vec<u8>> = None;
while let Some(hdr) = read_box_header(&mut *input)? {
match hdr.fourcc {
FTYP => {
saw_ftyp = true;
skip_box_body(&mut *input, &hdr)?;
}
MOOV => {
moov = Some(read_box_body(&mut *input, &hdr)?);
break;
}
_ => skip_box_body(&mut *input, &hdr)?,
}
}
if !saw_ftyp {
return Err(Error::invalid("MP4: missing ftyp box"));
}
let moov = moov.ok_or_else(|| Error::invalid("MP4: missing moov box"))?;
let parsed = parse_moov(&moov)?;
if parsed.tracks.is_empty() {
return Err(Error::invalid("MP4: no tracks"));
}
let mut streams: Vec<StreamInfo> = Vec::with_capacity(parsed.tracks.len());
let mut samples: Vec<SampleRef> = Vec::new();
for (i, t) in parsed.tracks.iter().enumerate() {
streams.push(build_stream_info(i as u32, t, codecs));
expand_samples(t, i as u32, &mut samples)?;
}
samples.sort_by_key(|s| s.offset);
let duration_micros: i64 = if parsed.movie_timescale > 0 && parsed.movie_duration > 0 {
(parsed.movie_duration as i128 * 1_000_000 / parsed.movie_timescale as i128) as i64
} else {
0
};
Ok(Box::new(Mp4Demuxer {
input,
streams,
samples,
cursor: 0,
metadata: parsed.metadata,
duration_micros,
}))
}
#[derive(Default)]
struct ParsedMoov {
tracks: Vec<Track>,
movie_timescale: u32,
movie_duration: u64,
metadata: Vec<(String, String)>,
}
#[derive(Clone, Debug)]
struct Track {
media_type: MediaType,
codec_id_fourcc: [u8; 4],
timescale: u32,
duration: Option<u64>,
channels: Option<u16>,
sample_rate: Option<u32>,
sample_size_bits: Option<u16>,
width: Option<u32>,
height: Option<u32>,
extradata: Vec<u8>,
esds_oti: Option<u8>,
stts: Vec<(u32, u32)>, stsc: Vec<(u32, u32, u32)>, stsz: Vec<u32>, chunk_offsets: Vec<u64>, stss: Vec<u32>,
}
fn parse_moov(moov: &[u8]) -> Result<ParsedMoov> {
let mut out = ParsedMoov::default();
let mut cur = std::io::Cursor::new(moov);
let end = moov.len() as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur)? {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
match hdr.fourcc {
TRAK => {
let body = read_bytes_vec(&mut cur, psz)?;
if let Some(t) = parse_trak(&body)? {
out.tracks.push(t);
}
}
MVHD => {
let body = read_bytes_vec(&mut cur, psz)?;
parse_mvhd(&body, &mut out)?;
}
UDTA => {
let body = read_bytes_vec(&mut cur, psz)?;
parse_udta(&body, &mut out.metadata);
}
META => {
let body = read_bytes_vec(&mut cur, psz)?;
parse_meta(&body, &mut out.metadata);
}
_ => {
cur.set_position(cur.position() + psz as u64);
}
}
}
Ok(out)
}
fn parse_mvhd(body: &[u8], out: &mut ParsedMoov) -> Result<()> {
if body.is_empty() {
return Err(Error::invalid("MP4: mvhd empty"));
}
let version = body[0];
let (timescale, duration) = if version == 0 {
if body.len() < 20 {
return Err(Error::invalid("MP4: mvhd v0 too short"));
}
let ts = u32::from_be_bytes([body[12], body[13], body[14], body[15]]);
let du = u32::from_be_bytes([body[16], body[17], body[18], body[19]]) as u64;
(ts, du)
} else {
if body.len() < 32 {
return Err(Error::invalid("MP4: mvhd v1 too short"));
}
let ts = u32::from_be_bytes([body[20], body[21], body[22], body[23]]);
let du = u64::from_be_bytes([
body[24], body[25], body[26], body[27], body[28], body[29], body[30], body[31],
]);
(ts, du)
};
out.movie_timescale = timescale;
out.movie_duration = duration;
Ok(())
}
fn parse_udta(body: &[u8], metadata: &mut Vec<(String, String)>) {
let mut cur = std::io::Cursor::new(body);
let end = body.len() as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur).ok().flatten() {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
if cur.position() as usize + psz > body.len() {
break;
}
let start = cur.position() as usize;
cur.set_position((start + psz) as u64);
let payload = &body[start..start + psz];
match &hdr.fourcc {
b"meta" => parse_meta(payload, metadata),
b"titl" | b"auth" | b"cprt" | b"dscp" | b"gnre" | b"albm" | b"yrrc"
if payload.len() >= 6 =>
{
let key = match &hdr.fourcc {
b"titl" => "title",
b"auth" => "artist",
b"cprt" => "copyright",
b"dscp" => "description",
b"gnre" => "genre",
b"albm" => "album",
b"yrrc" => "date",
_ => unreachable!(),
};
let s = decode_utf8_or_utf16(&payload[6..]);
if !s.is_empty() {
metadata.push((key.into(), s));
}
}
_ => {}
}
}
}
fn parse_meta(body: &[u8], metadata: &mut Vec<(String, String)>) {
if body.len() < 4 {
return;
}
let mut cur = std::io::Cursor::new(&body[4..]);
let end = body.len() as u64 - 4;
while cur.position() < end {
let hdr = match read_box_header(&mut cur).ok().flatten() {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
let start = cur.position() as usize;
if start + psz > (body.len() - 4) {
break;
}
cur.set_position((start + psz) as u64);
if hdr.fourcc == ILST {
parse_ilst(&body[4 + start..4 + start + psz], metadata);
}
}
}
fn parse_ilst(body: &[u8], metadata: &mut Vec<(String, String)>) {
let mut cur = std::io::Cursor::new(body);
let end = body.len() as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur).ok().flatten() {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
let start = cur.position() as usize;
if start + psz > body.len() {
break;
}
cur.set_position((start + psz) as u64);
let item = &body[start..start + psz];
let key = ilst_key_for(&hdr.fourcc);
if key.is_none() {
continue;
}
let key = key.unwrap();
let mut sub = std::io::Cursor::new(item);
let sub_end = item.len() as u64;
while sub.position() < sub_end {
let sh = match read_box_header(&mut sub).ok().flatten() {
Some(h) => h,
None => break,
};
let sub_psz = sh.payload_size().unwrap_or(0) as usize;
let sub_start = sub.position() as usize;
if sub_start + sub_psz > item.len() {
break;
}
sub.set_position((sub_start + sub_psz) as u64);
if sh.fourcc == DATA {
let data_body = &item[sub_start..sub_start + sub_psz];
if data_body.len() > 8 {
let value = String::from_utf8_lossy(&data_body[8..]).trim().to_string();
if !value.is_empty() {
metadata.push((key.into(), value));
}
}
}
}
}
}
fn ilst_key_for(fourcc: &[u8; 4]) -> Option<&'static str> {
match fourcc {
b"\xa9nam" => Some("title"),
b"\xa9ART" => Some("artist"),
b"\xa9alb" => Some("album"),
b"\xa9cmt" => Some("comment"),
b"\xa9gen" => Some("genre"),
b"\xa9day" => Some("date"),
b"\xa9wrt" => Some("composer"),
b"\xa9too" => Some("encoder"),
b"\xa9cpy" | b"cprt" => Some("copyright"),
b"\xa9lyr" => Some("lyrics"),
b"aART" => Some("album_artist"),
b"trkn" => Some("track"),
b"disk" => Some("disc"),
b"desc" => Some("description"),
_ => None,
}
}
fn decode_utf8_or_utf16(buf: &[u8]) -> String {
if buf.len() >= 2 && buf[0] == 0xFE && buf[1] == 0xFF {
let pairs = buf[2..].chunks_exact(2);
let units: Vec<u16> = pairs.map(|p| u16::from_be_bytes([p[0], p[1]])).collect();
return String::from_utf16_lossy(&units)
.trim_end_matches('\0')
.trim()
.to_string();
}
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8_lossy(&buf[..end]).trim().to_string()
}
fn parse_trak(body: &[u8]) -> Result<Option<Track>> {
let mut t = Track {
media_type: MediaType::Unknown,
codec_id_fourcc: [0; 4],
timescale: 0,
duration: None,
channels: None,
sample_rate: None,
sample_size_bits: None,
width: None,
height: None,
extradata: Vec::new(),
esds_oti: None,
stts: Vec::new(),
stsc: Vec::new(),
stsz: Vec::new(),
chunk_offsets: Vec::new(),
stss: Vec::new(),
};
let mut has_media = false;
let mut cur = std::io::Cursor::new(body);
let end = body.len() as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur)? {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
match hdr.fourcc {
MDIA => {
let sub = read_bytes_vec(&mut cur, psz)?;
parse_mdia(&sub, &mut t)?;
has_media = true;
}
_ => {
cur.set_position(cur.position() + psz as u64);
}
}
}
if has_media {
Ok(Some(t))
} else {
Ok(None)
}
}
fn parse_mdia(body: &[u8], t: &mut Track) -> Result<()> {
let mut cur = std::io::Cursor::new(body);
let end = body.len() as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur)? {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
match hdr.fourcc {
MDHD => {
let b = read_bytes_vec(&mut cur, psz)?;
parse_mdhd(&b, t)?;
}
HDLR => {
let b = read_bytes_vec(&mut cur, psz)?;
parse_hdlr(&b, t)?;
}
MINF => {
let b = read_bytes_vec(&mut cur, psz)?;
parse_minf(&b, t)?;
}
_ => cur.set_position(cur.position() + psz as u64),
}
}
Ok(())
}
fn parse_mdhd(body: &[u8], t: &mut Track) -> Result<()> {
if body.len() < 24 {
return Err(Error::invalid("MP4: mdhd too short"));
}
let version = body[0];
let (timescale, duration) = if version == 0 {
let ts = u32::from_be_bytes([body[12], body[13], body[14], body[15]]);
let du = u32::from_be_bytes([body[16], body[17], body[18], body[19]]) as u64;
(ts, du)
} else {
if body.len() < 32 {
return Err(Error::invalid("MP4: mdhd v1 too short"));
}
let ts = u32::from_be_bytes([body[20], body[21], body[22], body[23]]);
let du = u64::from_be_bytes([
body[24], body[25], body[26], body[27], body[28], body[29], body[30], body[31],
]);
(ts, du)
};
t.timescale = timescale;
t.duration = Some(duration);
Ok(())
}
fn parse_hdlr(body: &[u8], t: &mut Track) -> Result<()> {
if body.len() < 12 {
return Err(Error::invalid("MP4: hdlr too short"));
}
let mut handler = [0u8; 4];
handler.copy_from_slice(&body[8..12]);
t.media_type = match &handler {
h if *h == HANDLER_SOUN => MediaType::Audio,
h if *h == HANDLER_VIDE => MediaType::Video,
_ => MediaType::Data,
};
Ok(())
}
fn parse_minf(body: &[u8], t: &mut Track) -> Result<()> {
let mut cur = std::io::Cursor::new(body);
let end = body.len() as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur)? {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
match hdr.fourcc {
STBL => {
let sub = read_bytes_vec(&mut cur, psz)?;
parse_stbl(&sub, t)?;
}
_ => cur.set_position(cur.position() + psz as u64),
}
}
Ok(())
}
fn parse_stbl(body: &[u8], t: &mut Track) -> Result<()> {
let mut cur = std::io::Cursor::new(body);
let end = body.len() as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur)? {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
let b = read_bytes_vec(&mut cur, psz)?;
match hdr.fourcc {
STSD => parse_stsd(&b, t)?,
STTS => t.stts = parse_stts(&b)?,
STSC => t.stsc = parse_stsc(&b)?,
STSZ => t.stsz = parse_stsz(&b)?,
STZ2 => t.stsz = parse_stz2(&b)?,
STCO => t.chunk_offsets = parse_stco(&b)?,
CO64 => t.chunk_offsets = parse_co64(&b)?,
STSS => t.stss = parse_stss(&b)?,
_ => {}
}
}
Ok(())
}
fn parse_stsd(body: &[u8], t: &mut Track) -> Result<()> {
if body.len() < 8 {
return Err(Error::invalid("MP4: stsd too short"));
}
let entry_count = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
if entry_count == 0 {
return Ok(());
}
let mut cur = std::io::Cursor::new(&body[8..]);
let hdr = match read_box_header(&mut cur)? {
Some(h) => h,
None => return Err(Error::invalid("MP4: stsd first entry missing")),
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
let entry = read_bytes_vec(&mut cur, psz)?;
t.codec_id_fourcc = hdr.fourcc;
parse_sample_entry(&entry, t)?;
Ok(())
}
fn parse_sample_entry(entry: &[u8], t: &mut Track) -> Result<()> {
if entry.len() < 8 {
return Ok(());
}
match t.media_type {
MediaType::Audio => parse_audio_sample_entry(entry, t),
MediaType::Video => parse_video_sample_entry(entry, t),
_ => Ok(()),
}
}
fn parse_audio_sample_entry(entry: &[u8], t: &mut Track) -> Result<()> {
if entry.len() < 28 {
return Ok(());
}
let channels = u16::from_be_bytes([entry[16], entry[17]]);
let sample_size = u16::from_be_bytes([entry[18], entry[19]]);
let sample_rate = u32::from_be_bytes([entry[24], entry[25], entry[26], entry[27]]) >> 16;
t.channels = Some(channels);
t.sample_size_bits = Some(sample_size);
t.sample_rate = Some(sample_rate);
let mut cur = std::io::Cursor::new(&entry[28..]);
let end = (entry.len() - 28) as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur)? {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
let body = read_bytes_vec(&mut cur, psz)?;
match &hdr.fourcc {
b"dfLa" if body.len() > 4 => {
t.extradata = body[4..].to_vec();
}
b"dOps" if body.len() >= 11 => {
let mut oh = Vec::with_capacity(body.len() + 8);
oh.extend_from_slice(b"OpusHead");
oh.extend_from_slice(&body);
t.extradata = oh;
}
b"esds" if body.len() >= 4 => {
if let Some(parsed) = parse_esds(&body[4..]) {
if !parsed.dsi.is_empty() {
t.extradata = parsed.dsi;
}
t.esds_oti = parsed.oti;
}
}
_ => {}
}
}
Ok(())
}
#[derive(Default)]
struct EsdsInfo {
dsi: Vec<u8>,
oti: Option<u8>,
}
fn parse_esds(buf: &[u8]) -> Option<EsdsInfo> {
let mut info = EsdsInfo::default();
let mut cur = 0usize;
let (tag, len, hdr_bytes) = read_descr(buf, cur)?;
if tag != 0x03 {
return None;
}
cur += hdr_bytes;
let es_end = cur.checked_add(len)?;
if es_end > buf.len() {
return None;
}
if cur + 3 > es_end {
return None;
}
let flags = buf[cur + 2];
cur += 3;
if flags & 0x80 != 0 {
cur = cur.checked_add(2)?; }
if flags & 0x40 != 0 {
if cur >= es_end {
return None;
}
let url_len = buf[cur] as usize;
cur = cur.checked_add(1 + url_len)?;
}
if flags & 0x20 != 0 {
cur = cur.checked_add(2)?; }
while cur < es_end {
let (sub_tag, sub_len, sub_hdr) = read_descr(buf, cur)?;
cur += sub_hdr;
let sub_end = cur.checked_add(sub_len)?;
if sub_end > es_end {
return None;
}
if sub_tag == 0x04 {
if sub_len < 13 {
return None;
}
info.oti = Some(buf[cur]);
if sub_len > 13 {
let mut inner = cur + 13;
while inner < sub_end {
let (dsi_tag, dsi_len, dsi_hdr) = read_descr(buf, inner)?;
inner += dsi_hdr;
let dsi_end = inner.checked_add(dsi_len)?;
if dsi_end > sub_end {
return None;
}
if dsi_tag == 0x05 {
info.dsi = buf[inner..dsi_end].to_vec();
break;
}
inner = dsi_end;
}
}
}
cur = sub_end;
}
Some(info)
}
#[cfg(test)]
fn parse_esds_dsi(buf: &[u8]) -> Option<Vec<u8>> {
let info = parse_esds(buf)?;
if info.dsi.is_empty() {
None
} else {
Some(info.dsi)
}
}
fn read_descr(buf: &[u8], off: usize) -> Option<(u8, usize, usize)> {
if off >= buf.len() {
return None;
}
let tag = buf[off];
let mut len: usize = 0;
let mut consumed = 1usize;
for _ in 0..4 {
let p = off + consumed;
if p >= buf.len() {
return None;
}
let b = buf[p];
consumed += 1;
len = (len << 7) | (b & 0x7F) as usize;
if b & 0x80 == 0 {
return Some((tag, len, consumed));
}
}
None
}
fn parse_video_sample_entry(entry: &[u8], t: &mut Track) -> Result<()> {
if entry.len() < 28 {
return Ok(());
}
let width = u16::from_be_bytes([entry[24], entry[25]]);
let height = u16::from_be_bytes([entry[26], entry[27]]);
t.width = Some(width as u32);
t.height = Some(height as u32);
if entry.len() <= 78 {
return Ok(());
}
let mut cur = std::io::Cursor::new(&entry[78..]);
let end = (entry.len() - 78) as u64;
while cur.position() < end {
let hdr = match read_box_header(&mut cur)? {
Some(h) => h,
None => break,
};
let psz = hdr.payload_size().unwrap_or(0) as usize;
let body = read_bytes_vec(&mut cur, psz)?;
match &hdr.fourcc {
b"avcC" => t.extradata = body,
b"hvcC" => t.extradata = body,
b"av1C" => t.extradata = body,
b"vpcC" => t.extradata = body,
b"esds" if body.len() >= 4 => {
if let Some(parsed) = parse_esds(&body[4..]) {
if !parsed.dsi.is_empty() {
t.extradata = parsed.dsi;
}
t.esds_oti = parsed.oti;
}
}
_ => {}
}
}
Ok(())
}
fn parse_stts(body: &[u8]) -> Result<Vec<(u32, u32)>> {
if body.len() < 8 {
return Err(Error::invalid("MP4: stts too short"));
}
let count = u32::from_be_bytes([body[4], body[5], body[6], body[7]]) as usize;
let mut out = Vec::with_capacity(count);
let mut off = 8;
for _ in 0..count {
if off + 8 > body.len() {
return Err(Error::invalid("MP4: stts truncated"));
}
let cnt = u32::from_be_bytes([body[off], body[off + 1], body[off + 2], body[off + 3]]);
let dlt = u32::from_be_bytes([body[off + 4], body[off + 5], body[off + 6], body[off + 7]]);
out.push((cnt, dlt));
off += 8;
}
Ok(out)
}
fn parse_stsc(body: &[u8]) -> Result<Vec<(u32, u32, u32)>> {
if body.len() < 8 {
return Err(Error::invalid("MP4: stsc too short"));
}
let count = u32::from_be_bytes([body[4], body[5], body[6], body[7]]) as usize;
let mut out = Vec::with_capacity(count);
let mut off = 8;
for _ in 0..count {
if off + 12 > body.len() {
return Err(Error::invalid("MP4: stsc truncated"));
}
let fc = u32::from_be_bytes([body[off], body[off + 1], body[off + 2], body[off + 3]]);
let spc = u32::from_be_bytes([body[off + 4], body[off + 5], body[off + 6], body[off + 7]]);
let sdi =
u32::from_be_bytes([body[off + 8], body[off + 9], body[off + 10], body[off + 11]]);
out.push((fc, spc, sdi));
off += 12;
}
Ok(out)
}
fn parse_stsz(body: &[u8]) -> Result<Vec<u32>> {
if body.len() < 12 {
return Err(Error::invalid("MP4: stsz too short"));
}
let uniform = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
let count = u32::from_be_bytes([body[8], body[9], body[10], body[11]]) as usize;
if uniform != 0 {
return Ok(vec![uniform; count]);
}
let mut out = Vec::with_capacity(count);
let mut off = 12;
for _ in 0..count {
if off + 4 > body.len() {
return Err(Error::invalid("MP4: stsz truncated"));
}
out.push(u32::from_be_bytes([
body[off],
body[off + 1],
body[off + 2],
body[off + 3],
]));
off += 4;
}
Ok(out)
}
fn parse_stz2(body: &[u8]) -> Result<Vec<u32>> {
if body.len() < 12 {
return Err(Error::invalid("MP4: stz2 too short"));
}
let field_size = body[7];
let count = u32::from_be_bytes([body[8], body[9], body[10], body[11]]) as usize;
let mut out = Vec::with_capacity(count);
let off = 12;
match field_size {
4 => {
for i in 0..count {
if off + i / 2 >= body.len() {
return Err(Error::invalid("MP4: stz2 4-bit truncated"));
}
let b = body[off + i / 2];
let v = if i % 2 == 0 { b >> 4 } else { b & 0x0F };
out.push(v as u32);
}
}
8 => {
if off + count > body.len() {
return Err(Error::invalid("MP4: stz2 8-bit truncated"));
}
for i in 0..count {
out.push(body[off + i] as u32);
}
}
16 => {
if off + count * 2 > body.len() {
return Err(Error::invalid("MP4: stz2 16-bit truncated"));
}
for i in 0..count {
out.push(u16::from_be_bytes([body[off + 2 * i], body[off + 2 * i + 1]]) as u32);
}
}
_ => return Err(Error::invalid("MP4: stz2 invalid field size")),
}
Ok(out)
}
fn parse_stss(body: &[u8]) -> Result<Vec<u32>> {
if body.len() < 8 {
return Err(Error::invalid("MP4: stss too short"));
}
let count = u32::from_be_bytes([body[4], body[5], body[6], body[7]]) as usize;
let mut out = Vec::with_capacity(count);
let mut off = 8;
for _ in 0..count {
if off + 4 > body.len() {
return Err(Error::invalid("MP4: stss truncated"));
}
out.push(u32::from_be_bytes([
body[off],
body[off + 1],
body[off + 2],
body[off + 3],
]));
off += 4;
}
Ok(out)
}
fn parse_stco(body: &[u8]) -> Result<Vec<u64>> {
if body.len() < 8 {
return Err(Error::invalid("MP4: stco too short"));
}
let count = u32::from_be_bytes([body[4], body[5], body[6], body[7]]) as usize;
let mut out = Vec::with_capacity(count);
let mut off = 8;
for _ in 0..count {
if off + 4 > body.len() {
return Err(Error::invalid("MP4: stco truncated"));
}
out.push(
u32::from_be_bytes([body[off], body[off + 1], body[off + 2], body[off + 3]]) as u64,
);
off += 4;
}
Ok(out)
}
fn parse_co64(body: &[u8]) -> Result<Vec<u64>> {
if body.len() < 8 {
return Err(Error::invalid("MP4: co64 too short"));
}
let count = u32::from_be_bytes([body[4], body[5], body[6], body[7]]) as usize;
let mut out = Vec::with_capacity(count);
let mut off = 8;
for _ in 0..count {
if off + 8 > body.len() {
return Err(Error::invalid("MP4: co64 truncated"));
}
out.push(u64::from_be_bytes([
body[off],
body[off + 1],
body[off + 2],
body[off + 3],
body[off + 4],
body[off + 5],
body[off + 6],
body[off + 7],
]));
off += 8;
}
Ok(out)
}
#[derive(Clone, Copy, Debug)]
struct SampleRef {
track_idx: u32,
offset: u64,
size: u32,
pts: i64,
duration: i64,
keyframe: bool,
}
fn expand_samples(t: &Track, track_idx: u32, out: &mut Vec<SampleRef>) -> Result<()> {
if t.stsz.is_empty() {
return Ok(());
}
let n_samples = t.stsz.len();
let mut pts = Vec::with_capacity(n_samples);
{
let mut i = 0;
let mut t_accum: i64 = 0;
for &(count, delta) in &t.stts {
for _ in 0..count {
if i >= n_samples {
break;
}
pts.push((t_accum, delta as i64));
t_accum += delta as i64;
i += 1;
}
}
while pts.len() < n_samples {
pts.push((t_accum, 0));
}
}
let mut chunk_of_sample = Vec::with_capacity(n_samples);
let mut sample_within_chunk = Vec::with_capacity(n_samples);
{
let mut sample_i = 0;
let mut chunk_i = 1u32;
for entry_i in 0..t.stsc.len() {
let (fc, spc, _sdi) = t.stsc[entry_i];
let next_fc = t
.stsc
.get(entry_i + 1)
.map(|e| e.0)
.unwrap_or(t.chunk_offsets.len() as u32 + 1);
let mut ch = chunk_i.max(fc);
while ch < next_fc && sample_i < n_samples {
for s_in_ch in 0..spc {
if sample_i >= n_samples {
break;
}
chunk_of_sample.push(ch);
sample_within_chunk.push(s_in_ch);
sample_i += 1;
}
ch += 1;
}
chunk_i = ch;
}
while sample_within_chunk.len() < n_samples {
chunk_of_sample.push(*chunk_of_sample.last().unwrap_or(&1));
sample_within_chunk.push(0);
}
}
let stss_all_keyframes = t.stss.is_empty();
let stss_set: std::collections::HashSet<u32> = t.stss.iter().copied().collect();
for i in 0..n_samples {
let chunk = chunk_of_sample[i] as usize;
if chunk == 0 || chunk > t.chunk_offsets.len() {
return Err(Error::invalid(format!(
"MP4: chunk index {chunk} out of range (track {track_idx})"
)));
}
let chunk_off = t.chunk_offsets[chunk - 1];
let chunk_start_sample = i - sample_within_chunk[i] as usize;
let mut preceding: u64 = 0;
for j in chunk_start_sample..i {
preceding += t.stsz[j] as u64;
}
let size = t.stsz[i];
let (pts_v, dur) = pts[i];
let one_based = (i as u32) + 1;
let keyframe = stss_all_keyframes || stss_set.contains(&one_based);
out.push(SampleRef {
track_idx,
offset: chunk_off + preceding,
size,
pts: pts_v,
duration: dur,
keyframe,
});
}
Ok(())
}
fn build_ctx<'a>(tag: &'a CodecTag, t: &'a Track) -> ProbeContext<'a> {
let mut ctx = ProbeContext::new(tag);
if !t.extradata.is_empty() {
ctx = ctx.header(&t.extradata);
}
if let Some(b) = t.sample_size_bits {
ctx = ctx.bits(b);
}
if let Some(c) = t.channels {
ctx = ctx.channels(c);
}
if let Some(sr) = t.sample_rate {
ctx = ctx.sample_rate(sr);
}
if let Some(w) = t.width {
ctx = ctx.width(w);
}
if let Some(h) = t.height {
ctx = ctx.height(h);
}
ctx
}
fn build_stream_info(index: u32, t: &Track, codecs: &dyn CodecResolver) -> StreamInfo {
let codec_id = {
let mut resolved: Option<CodecId> = None;
if let Some(oti) = t.esds_oti {
let tag = CodecTag::mp4_object_type(oti);
let ctx = build_ctx(&tag, t);
resolved = codecs.resolve_tag(&ctx);
}
if resolved.is_none() {
let tag = CodecTag::fourcc(&t.codec_id_fourcc);
let ctx = build_ctx(&tag, t);
resolved = codecs.resolve_tag(&ctx);
}
resolved.unwrap_or_else(|| match t.esds_oti {
Some(oti) => from_sample_entry_with_oti(&t.codec_id_fourcc, oti),
None => from_sample_entry(&t.codec_id_fourcc),
})
};
let mut params = match t.media_type {
MediaType::Audio => CodecParameters::audio(codec_id),
MediaType::Video => CodecParameters::video(codec_id),
_ => {
let mut p = CodecParameters::audio(codec_id);
p.media_type = MediaType::Data;
p
}
};
params.channels = t.channels;
params.sample_rate = t.sample_rate;
params.sample_format = match (params.codec_id.as_str(), t.sample_size_bits) {
("flac", Some(8)) => Some(SampleFormat::U8),
("flac", Some(16)) => Some(SampleFormat::S16),
("flac", Some(24)) => Some(SampleFormat::S24),
("flac", Some(32)) => Some(SampleFormat::S32),
("pcm_s16le", _) => Some(SampleFormat::S16),
_ => None,
};
params.width = t.width;
params.height = t.height;
params.extradata = t.extradata.clone();
let timescale = if t.timescale == 0 { 1 } else { t.timescale };
StreamInfo {
index,
time_base: TimeBase::new(1, timescale as i64),
duration: t.duration.map(|d| d as i64),
start_time: Some(0),
params,
}
}
struct Mp4Demuxer {
input: Box<dyn ReadSeek>,
streams: Vec<StreamInfo>,
samples: Vec<SampleRef>,
cursor: usize,
metadata: Vec<(String, String)>,
duration_micros: i64,
}
impl Demuxer for Mp4Demuxer {
fn format_name(&self) -> &str {
"mp4"
}
fn streams(&self) -> &[StreamInfo] {
&self.streams
}
fn next_packet(&mut self) -> Result<Packet> {
if self.cursor >= self.samples.len() {
return Err(Error::Eof);
}
let s = self.samples[self.cursor];
self.cursor += 1;
self.input.seek(SeekFrom::Start(s.offset))?;
let mut data = vec![0u8; s.size as usize];
self.input.read_exact(&mut data)?;
let stream = &self.streams[s.track_idx as usize];
let mut pkt = Packet::new(s.track_idx, stream.time_base, data);
pkt.pts = Some(s.pts);
pkt.dts = Some(s.pts);
pkt.duration = Some(s.duration);
pkt.flags.keyframe = s.keyframe;
Ok(pkt)
}
fn seek_to(&mut self, stream_index: u32, pts: i64) -> Result<i64> {
if stream_index as usize >= self.streams.len() {
return Err(Error::invalid(format!(
"MP4: stream index {stream_index} out of range"
)));
}
let mut best_cursor: Option<usize> = None;
let mut best_pts: i64 = 0;
for (i, s) in self.samples.iter().enumerate() {
if s.track_idx != stream_index || !s.keyframe {
continue;
}
if s.pts <= pts {
if best_cursor.is_none() || s.pts >= best_pts {
best_cursor = Some(i);
best_pts = s.pts;
}
} else {
break;
}
}
if best_cursor.is_none() {
for (i, s) in self.samples.iter().enumerate() {
if s.track_idx == stream_index && s.keyframe {
best_cursor = Some(i);
best_pts = s.pts;
break;
}
}
}
let cursor = best_cursor.ok_or_else(|| {
Error::unsupported(format!(
"MP4: no keyframes in stream {stream_index} to seek to"
))
})?;
self.cursor = cursor;
Ok(best_pts)
}
fn metadata(&self) -> &[(String, String)] {
&self.metadata
}
fn duration_micros(&self) -> Option<i64> {
if self.duration_micros > 0 {
Some(self.duration_micros)
} else {
None
}
}
}
use std::io::Read;
fn read_bytes_vec<R: Read + ?Sized>(r: &mut R, n: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; n];
r.read_exact(&mut buf)?;
Ok(buf)
}
#[allow(dead_code)]
fn _unused() -> (HashSet<u32>, SeekFrom) {
(HashSet::new(), SeekFrom::Start(0))
}
#[cfg(test)]
mod tests {
use super::parse_esds_dsi;
fn build_esds_payload(asc: &[u8]) -> Vec<u8> {
let mut dsi = Vec::new();
dsi.push(0x05);
dsi.push(asc.len() as u8);
dsi.extend_from_slice(asc);
let mut dcd = vec![
0x04,
(13 + dsi.len()) as u8,
0x40, (0x05 << 2) | 0x01, ];
dcd.extend_from_slice(&[0, 0, 0]); dcd.extend_from_slice(&[0, 0, 0, 0]); dcd.extend_from_slice(&[0, 0, 0, 0]); dcd.extend_from_slice(&dsi);
let slc = vec![0x06, 0x01, 0x02];
let mut esd = Vec::new();
esd.push(0x03);
esd.push((3 + dcd.len() + slc.len()) as u8);
esd.extend_from_slice(&[0, 0, 0]); esd.extend_from_slice(&dcd);
esd.extend_from_slice(&slc);
esd
}
#[test]
fn extracts_asc_from_esds() {
let asc = [0x12, 0x10];
let payload = build_esds_payload(&asc);
let got = parse_esds_dsi(&payload).expect("dsi");
assert_eq!(got, asc);
}
#[test]
fn handles_ber_multi_byte_length() {
let asc = [0x11, 0x90];
let mut body = Vec::new();
body.extend_from_slice(&[0, 0, 0]);
let mut dsi = vec![0x05, asc.len() as u8];
dsi.extend_from_slice(&asc);
let mut dcd = vec![0x04, (13 + dsi.len()) as u8, 0x40, (0x05 << 2) | 0x01];
dcd.extend_from_slice(&[0, 0, 0]);
dcd.extend_from_slice(&[0, 0, 0, 0]);
dcd.extend_from_slice(&[0, 0, 0, 0]);
dcd.extend_from_slice(&dsi);
body.extend_from_slice(&dcd);
body.extend_from_slice(&[0x06, 0x01, 0x02]);
let body_len = body.len();
assert!(body_len < 128);
let hi = (body_len >> 7) as u8 | 0x80;
let lo = (body_len & 0x7F) as u8;
let mut payload = vec![0x03, hi, lo];
payload.extend_from_slice(&body);
let got = parse_esds_dsi(&payload).expect("dsi");
assert_eq!(got, asc);
}
#[test]
fn rejects_non_es_descriptor() {
let payload = vec![0x04, 0x01, 0x00];
assert!(parse_esds_dsi(&payload).is_none());
}
}