use super::super::{AacTrack, BoxHdr, BoxIter, fence_depth, fence_entries, read_u32, read_u64};
use super::{Fmp4Init, unsupported};
use crate::budgets::{BudgetExceeded, BudgetKind, MemoryBudgets};
use crate::error::{AacError, MalformedKind, Result};
struct Tfhd {
track_id: u32,
base_offset: Option<u64>,
desc_index: Option<u32>,
default_duration: Option<u32>,
default_size: Option<u32>,
duration_is_empty: bool,
default_base_is_moof: bool,
}
fn parse_tfhd(body: &[u8]) -> Result<Tfhd> {
let flags = read_u32(body, 0)? & 0x00FF_FFFF;
let track_id = read_u32(body, 4)?;
let mut o = 8usize;
let base_offset = if flags & 0x1 != 0 {
let v = read_u64(body, o)?;
o += 8;
Some(v)
} else {
None
};
let desc_index = if flags & 0x2 != 0 {
let v = read_u32(body, o)?;
o += 4;
Some(v)
} else {
None
};
let default_duration = if flags & 0x8 != 0 {
let v = read_u32(body, o)?;
o += 4;
Some(v)
} else {
None
};
let default_size = if flags & 0x10 != 0 {
let v = read_u32(body, o)?;
o += 4;
Some(v)
} else {
None
};
if flags & 0x20 != 0 {
let _ = read_u32(body, o)?; }
Ok(Tfhd {
track_id,
base_offset,
desc_index,
default_duration,
default_size,
duration_is_empty: flags & 0x1_0000 != 0,
default_base_is_moof: flags & 0x2_0000 != 0,
})
}
fn parse_tfdt(body: &[u8]) -> Result<u64> {
match body.first() {
Some(0) => Ok(u64::from(read_u32(body, 4)?)),
Some(1) => read_u64(body, 4),
_ => Err(AacError::Malformed(MalformedKind::Syntax)),
}
}
#[derive(Default)]
pub(crate) struct FragResolver {
pub(super) frames: Vec<(u64, u32)>,
pub(super) total: u64,
pub(super) dts: u64,
last_seq: Option<u32>,
prev_data_end: Option<u64>,
#[cfg(test)]
pub(super) trace_dts: bool,
#[cfg(test)]
pub(super) sample_dts: Vec<u64>,
}
impl FragResolver {
pub(crate) fn reset(&mut self) {
self.frames.clear();
self.total = 0;
self.dts = 0;
self.last_seq = None;
self.prev_data_end = None;
#[cfg(test)]
{
self.trace_dts = false;
self.sample_dts.clear();
}
}
pub(crate) fn total(&self) -> u64 {
self.total
}
pub(crate) fn add_moof(
&mut self,
moof: &[u8],
moof_pos: u64,
moof_end: u64,
init: &Fmp4Init,
mdats: &[(u64, u64)],
mem: &MemoryBudgets,
) -> Result<()> {
let fence = mdats.iter().copied().find(|&(s, _)| s >= moof_end);
let mut frames = std::mem::take(&mut self.frames);
let res = self.resolve_moof(moof, moof_pos, init, fence, &mut frames, mem);
self.frames = frames;
res
}
pub(crate) fn add_moof_push(
&mut self,
moof: &[u8],
moof_pos: u64,
init: &Fmp4Init,
mdat: (u64, u64),
out: &mut Vec<(u64, u32)>,
mem: &MemoryBudgets,
) -> Result<()> {
self.resolve_moof(moof, moof_pos, init, Some(mdat), out, mem)
}
fn resolve_moof(
&mut self,
moof: &[u8],
moof_pos: u64,
init: &Fmp4Init,
fence: Option<(u64, u64)>,
frames: &mut Vec<(u64, u32)>,
mem: &MemoryBudgets,
) -> Result<()> {
fence_depth(mem, 1)?;
let mut traf: Option<BoxHdr> = None;
for child in BoxIter::new(moof, 0, moof.len()) {
let (hdr, typ) = child?;
match &typ {
b"mfhd" => {
let seq = read_u32(&moof[hdr.content_start..hdr.content_end], 4)?;
if let Some(last) = self.last_seq
&& seq != last.wrapping_add(1)
{
return Err(AacError::Malformed(MalformedKind::Syntax));
}
self.last_seq = Some(seq);
}
b"traf" => {
if traf.is_some() {
return unsupported("multiple track fragments per moof are not supported");
}
traf = Some(hdr);
}
_ => {}
}
}
let traf = traf.ok_or(AacError::Malformed(MalformedKind::Syntax))?;
fence_depth(mem, 2)?;
let mut tfhd = None;
let mut tfdt = None;
for child in BoxIter::new(moof, traf.content_start, traf.content_end) {
let (hdr, typ) = child?;
let body = &moof[hdr.content_start..hdr.content_end];
match &typ {
b"tfhd" => tfhd = Some(parse_tfhd(body)?),
b"tfdt" => tfdt = Some(parse_tfdt(body)?),
b"saiz" | b"saio" | b"senc" => {
return unsupported("encrypted fragments (saiz/saio/senc) are not supported");
}
_ => {}
}
}
let tfhd = tfhd.ok_or(AacError::Malformed(MalformedKind::Syntax))?;
if tfhd.track_id != init.track_id {
return unsupported("a fragment for another track is not supported");
}
if tfhd.desc_index.is_some_and(|d| d != 1) {
return unsupported("a sample-description switch is not supported");
}
if let Some(t) = tfdt {
self.dts = t;
}
let base = match (
tfhd.base_offset,
tfhd.default_base_is_moof,
self.prev_data_end,
) {
(Some(b), _, _) => b,
(None, true, _) => moof_pos,
(None, false, Some(e)) => e,
(None, false, None) => moof_pos,
};
let mut next_off: Option<u64> = None;
for child in BoxIter::new(moof, traf.content_start, traf.content_end) {
let (hdr, typ) = child?;
if &typ == b"trun" {
next_off = Some(self.resolve_trun(
&moof[hdr.content_start..hdr.content_end],
base,
next_off,
&tfhd,
init,
fence,
frames,
mem,
)?);
}
}
if let Some(end) = next_off {
self.prev_data_end = Some(end);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn resolve_trun(
&mut self,
body: &[u8],
base: u64,
next_off: Option<u64>,
tfhd: &Tfhd,
init: &Fmp4Init,
fence: Option<(u64, u64)>,
frames: &mut Vec<(u64, u32)>,
mem: &MemoryBudgets,
) -> Result<u64> {
if body.first().copied().unwrap_or(2) > 1 {
return Err(AacError::Malformed(MalformedKind::Syntax));
}
let flags = read_u32(body, 0)? & 0x00FF_FFFF;
let count = read_u32(body, 4)? as usize;
let mut o = 8usize;
let data_offset = if flags & 0x1 != 0 {
let v = i64::from(read_u32(body, o)? as i32);
o += 4;
v
} else {
0
};
if flags & 0x4 != 0 {
let _ = read_u32(body, o)?; o += 4;
}
if count == 0 {
return Ok(next_off.unwrap_or(base));
}
let (mstart, mend) = fence.ok_or(AacError::Malformed(MalformedKind::Syntax))?;
fence_entries(mem, frames.len() as u64 + count as u64, 16)?;
frames.try_reserve(count).map_err(|_| {
AacError::from(BudgetExceeded {
kind: BudgetKind::Metadata,
observed: (frames.len() as u64)
.saturating_add(count as u64)
.saturating_mul(16),
max: mem.max_metadata_bytes,
})
})?;
let mut off = if flags & 0x1 != 0 {
i64::try_from(base)
.ok()
.and_then(|b| b.checked_add(data_offset))
.and_then(|b| u64::try_from(b).ok())
.ok_or(AacError::Malformed(MalformedKind::Syntax))?
} else {
next_off.unwrap_or(base)
};
let def_dur = tfhd.default_duration.unwrap_or(init.trex_duration);
let def_size = tfhd.default_size.unwrap_or(init.trex_size);
for _ in 0..count {
let dur = if flags & 0x100 != 0 {
let v = read_u32(body, o)?;
o += 4;
v
} else {
def_dur
};
let size = if flags & 0x200 != 0 {
let v = read_u32(body, o)?;
o += 4;
v
} else {
def_size
};
if flags & 0x400 != 0 {
let _ = read_u32(body, o)?; o += 4;
}
if flags & 0x800 != 0 {
let cto = read_u32(body, o)?;
o += 4;
if cto != 0 {
return unsupported("composition-time offsets are not supported");
}
}
if dur == 0 && !tfhd.duration_is_empty {
return Err(AacError::Malformed(MalformedKind::Syntax));
}
let end = off
.checked_add(u64::from(size))
.ok_or(AacError::Malformed(MalformedKind::Syntax))?;
if off < mstart || end > mend {
return Err(AacError::Malformed(MalformedKind::Syntax));
}
frames.push((off, size));
#[cfg(test)]
if self.trace_dts {
self.sample_dts.push(self.dts);
}
off = end;
self.dts = self
.dts
.checked_add(u64::from(dur))
.ok_or(AacError::Malformed(MalformedKind::Syntax))?;
self.total = self
.total
.checked_add(u64::from(dur))
.ok_or(AacError::Malformed(MalformedKind::Syntax))?;
}
Ok(off)
}
pub(crate) fn finish(self, init: Fmp4Init) -> Result<AacTrack> {
if self.frames.is_empty() {
return Err(AacError::format("isomp4: fMP4 fragments carry no samples"));
}
Ok(AacTrack {
asc: init.asc,
total_samples: self.total,
frames: self.frames,
edit_start: init.edit_start,
edit_duration: init.edit_duration,
movie_timescale: init.movie_timescale,
media_timescale: init.media_timescale,
media_duration: self.dts,
has_elst: init.has_elst,
edit_open_end: true,
})
}
}