use crate::error::{Error, Result};
use alloc::vec::Vec;
use broadcast_common::Serialize;
const BOX_HEADER_SIZE: usize = 8;
const LARGESIZE_SIZE: usize = 8;
const FULLBOX_EXTRA_SIZE: usize = 4;
pub const TFHD_BASE_DATA_OFFSET_PRESENT: u32 = 0x000001;
pub const TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT: u32 = 0x000002;
pub const TFHD_DEFAULT_SAMPLE_DURATION_PRESENT: u32 = 0x000008;
pub const TFHD_DEFAULT_SAMPLE_SIZE_PRESENT: u32 = 0x000010;
pub const TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT: u32 = 0x000020;
pub const TFHD_DURATION_IS_EMPTY: u32 = 0x010000;
pub const TFHD_DEFAULT_BASE_IS_MOOF: u32 = 0x020000;
pub const TRUN_DATA_OFFSET_PRESENT: u32 = 0x000001;
pub const TRUN_FIRST_SAMPLE_FLAGS_PRESENT: u32 = 0x000004;
pub const TRUN_SAMPLE_DURATION_PRESENT: u32 = 0x000100;
pub const TRUN_SAMPLE_SIZE_PRESENT: u32 = 0x000200;
pub const TRUN_SAMPLE_FLAGS_PRESENT: u32 = 0x000400;
pub const TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT: u32 = 0x000800;
pub(crate) const SAMPLE_FLAGS_SYNC: u32 = 0x0200_0000;
pub(crate) const SAMPLE_FLAGS_NON_SYNC: u32 = 0x0101_0000;
pub(crate) const SAMPLE_FLAG_IS_NON_SYNC: u32 = 0x0001_0000;
pub(crate) struct MediaRunSample {
pub duration: u32,
pub size: usize,
pub is_sync: bool,
pub composition_offset: i32,
}
pub(crate) fn media_fragment_run(
track_id: u32,
samples: impl Iterator<Item = MediaRunSample> + Clone,
) -> Result<(TrackFragmentHeaderBox, TrackFragmentRunBox)> {
let any_cts = samples.clone().any(|s| s.composition_offset != 0);
let mut run_samples = Vec::new();
for s in samples {
run_samples.push(TrunSample {
sample_duration: Some(s.duration),
sample_size: Some(u32::try_from(s.size).map_err(|_| {
Error::InvalidInput("sample larger than the 32-bit trun sample_size field")
})?),
sample_flags: Some(if s.is_sync {
SAMPLE_FLAGS_SYNC
} else {
SAMPLE_FLAGS_NON_SYNC
}),
sample_composition_time_offset: any_cts.then_some(i64::from(s.composition_offset)),
});
}
let mut tr_flags = TRUN_DATA_OFFSET_PRESENT
| TRUN_SAMPLE_DURATION_PRESENT
| TRUN_SAMPLE_SIZE_PRESENT
| TRUN_SAMPLE_FLAGS_PRESENT;
let version = if any_cts {
tr_flags |= TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT;
1u8
} else {
0u8
};
let trun = TrackFragmentRunBox {
version,
tr_flags,
data_offset: Some(0),
first_sample_flags: None,
samples: run_samples,
};
let tfhd = TrackFragmentHeaderBox {
flags: TFHD_DEFAULT_BASE_IS_MOOF,
track_id,
base_data_offset: None,
sample_description_index: None,
default_sample_duration: None,
default_sample_size: None,
default_sample_flags: None,
};
Ok((tfhd, trun))
}
fn read_ver_flags(body: &[u8]) -> Result<(u8, u32)> {
if body.len() < 4 {
return Err(Error::BufferTooShort {
need: 4,
have: body.len(),
what: "FullBox version/flags",
});
}
let ver = body[0];
let flags = u32::from_be_bytes([0, body[1], body[2], body[3]]);
Ok((ver, flags))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MovieFragmentHeaderBox {
pub sequence_number: u32,
}
impl MovieFragmentHeaderBox {
pub fn new(sequence_number: u32) -> Self {
Self { sequence_number }
}
pub fn parse_body(body: &[u8]) -> Result<Self> {
let (_ver, _flags) = read_ver_flags(body)?;
let payload = &body[FULLBOX_EXTRA_SIZE..];
if payload.len() < 4 {
return Err(Error::BufferTooShort {
need: 4,
have: payload.len(),
what: "mfhd.seq",
});
}
Ok(Self {
sequence_number: u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]),
})
}
}
impl Serialize for MovieFragmentHeaderBox {
type Error = Error;
fn serialized_len(&self) -> usize {
BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let mut c = 0;
buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
c += 4;
buf[c..c + 4].copy_from_slice(b"mfhd");
c += 4;
buf[c..c + 4].copy_from_slice(&[0, 0, 0, 0]);
c += 4;
buf[c..c + 4].copy_from_slice(&self.sequence_number.to_be_bytes());
Ok(c + 4)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TrackFragmentHeaderBox {
pub flags: u32,
pub track_id: u32,
pub base_data_offset: Option<u64>,
pub sample_description_index: Option<u32>,
pub default_sample_duration: Option<u32>,
pub default_sample_size: Option<u32>,
pub default_sample_flags: Option<u32>,
}
impl TrackFragmentHeaderBox {
pub fn parse_body(body: &[u8]) -> Result<Self> {
let (_ver, flags) = read_ver_flags(body)?;
let mut c = FULLBOX_EXTRA_SIZE;
if body.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: body.len(),
what: "tfhd.track_id",
});
}
let tid = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
c += 4;
let v_bdo = if flags & TFHD_BASE_DATA_OFFSET_PRESENT != 0 {
if body.len() < c + 8 {
return Err(Error::BufferTooShort {
need: c + 8,
have: body.len(),
what: "tfhd.bdo",
});
}
let v = u64::from_be_bytes([
body[c],
body[c + 1],
body[c + 2],
body[c + 3],
body[c + 4],
body[c + 5],
body[c + 6],
body[c + 7],
]);
c += 8;
Some(v)
} else {
None
};
let v_sdi = if flags & TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT != 0 {
if body.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: body.len(),
what: "tfhd.sdi",
});
}
let v = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
c += 4;
Some(v)
} else {
None
};
let v_dsd = if flags & TFHD_DEFAULT_SAMPLE_DURATION_PRESENT != 0 {
if body.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: body.len(),
what: "tfhd.dsd",
});
}
let v = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
c += 4;
Some(v)
} else {
None
};
let v_dss = if flags & TFHD_DEFAULT_SAMPLE_SIZE_PRESENT != 0 {
if body.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: body.len(),
what: "tfhd.dss",
});
}
let v = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
c += 4;
Some(v)
} else {
None
};
let v_dsf = if flags & TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT != 0 {
if body.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: body.len(),
what: "tfhd.dsf",
});
}
let v = u32::from_be_bytes([body[c], body[c + 1], body[c + 2], body[c + 3]]);
Some(v)
} else {
None
};
Ok(TrackFragmentHeaderBox {
flags,
track_id: tid,
base_data_offset: v_bdo,
sample_description_index: v_sdi,
default_sample_duration: v_dsd,
default_sample_size: v_dss,
default_sample_flags: v_dsf,
})
}
}
impl TrackFragmentHeaderBox {
pub fn effective_flags(&self) -> u32 {
let non_presence = self.flags
& !(TFHD_BASE_DATA_OFFSET_PRESENT
| TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT
| TFHD_DEFAULT_SAMPLE_DURATION_PRESENT
| TFHD_DEFAULT_SAMPLE_SIZE_PRESENT
| TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT);
let mut bits = non_presence;
if self.base_data_offset.is_some() {
bits |= TFHD_BASE_DATA_OFFSET_PRESENT;
}
if self.sample_description_index.is_some() {
bits |= TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT;
}
if self.default_sample_duration.is_some() {
bits |= TFHD_DEFAULT_SAMPLE_DURATION_PRESENT;
}
if self.default_sample_size.is_some() {
bits |= TFHD_DEFAULT_SAMPLE_SIZE_PRESENT;
}
if self.default_sample_flags.is_some() {
bits |= TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT;
}
bits
}
}
impl Serialize for TrackFragmentHeaderBox {
type Error = Error;
fn serialized_len(&self) -> usize {
let mut n = BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4;
if self.base_data_offset.is_some() {
n += 8;
}
if self.sample_description_index.is_some() {
n += 4;
}
if self.default_sample_duration.is_some() {
n += 4;
}
if self.default_sample_size.is_some() {
n += 4;
}
if self.default_sample_flags.is_some() {
n += 4;
}
n
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let flags = self.effective_flags();
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let len = broadcast_common::len::fit_u32(need, "tfhd size")?;
let mut c = 0;
buf[c..c + 4].copy_from_slice(&len.to_be_bytes());
c += 4;
buf[c..c + 4].copy_from_slice(b"tfhd");
c += 4;
buf[c] = 0;
let fb = flags.to_be_bytes();
buf[c + 1] = fb[1];
buf[c + 2] = fb[2];
buf[c + 3] = fb[3];
c += 4;
buf[c..c + 4].copy_from_slice(&self.track_id.to_be_bytes());
c += 4;
if let Some(v) = self.base_data_offset {
buf[c..c + 8].copy_from_slice(&v.to_be_bytes());
c += 8;
}
if let Some(v) = self.sample_description_index {
buf[c..c + 4].copy_from_slice(&v.to_be_bytes());
c += 4;
}
if let Some(v) = self.default_sample_duration {
buf[c..c + 4].copy_from_slice(&v.to_be_bytes());
c += 4;
}
if let Some(v) = self.default_sample_size {
buf[c..c + 4].copy_from_slice(&v.to_be_bytes());
c += 4;
}
if let Some(v) = self.default_sample_flags {
buf[c..c + 4].copy_from_slice(&v.to_be_bytes());
c += 4;
}
Ok(c)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TrackFragmentBaseMediaDecodeTimeBox {
version: u8,
v0: u32,
v1: u64,
}
impl TrackFragmentBaseMediaDecodeTimeBox {
pub fn new_v0(t: u32) -> Self {
Self {
version: 0,
v0: t,
v1: t as u64,
}
}
pub fn new_v1(t: u64) -> Self {
Self {
version: 1,
v0: t as u32,
v1: t,
}
}
pub fn base_media_decode_time(&self) -> u64 {
self.v1
}
pub fn version(&self) -> u8 {
self.version
}
pub fn parse_body(body: &[u8]) -> Result<Self> {
let (ver, _flags) = read_ver_flags(body)?;
let payload = &body[FULLBOX_EXTRA_SIZE..];
if ver == 0 {
if payload.len() < 4 {
return Err(Error::BufferTooShort {
need: 4,
have: payload.len(),
what: "tfdt.v0",
});
}
let v = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
Ok(Self {
version: ver,
v0: v,
v1: v as u64,
})
} else {
if payload.len() < 8 {
return Err(Error::BufferTooShort {
need: 8,
have: payload.len(),
what: "tfdt.v1",
});
}
let v = u64::from_be_bytes([
payload[0], payload[1], payload[2], payload[3], payload[4], payload[5], payload[6],
payload[7],
]);
Ok(Self {
version: ver,
v0: v as u32,
v1: v,
})
}
}
}
impl Serialize for TrackFragmentBaseMediaDecodeTimeBox {
type Error = Error;
fn serialized_len(&self) -> usize {
BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + if self.version == 0 { 4 } else { 8 }
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let mut c = 0;
buf[c..c + 4].copy_from_slice(&(need as u32).to_be_bytes());
c += 4;
buf[c..c + 4].copy_from_slice(b"tfdt");
c += 4;
buf[c] = self.version;
buf[c + 1] = 0;
buf[c + 2] = 0;
buf[c + 3] = 0;
c += 4;
if self.version == 0 {
buf[c..c + 4].copy_from_slice(&self.v0.to_be_bytes());
c += 4;
} else {
buf[c..c + 8].copy_from_slice(&self.v1.to_be_bytes());
c += 8;
}
Ok(c)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TrunSample {
pub sample_duration: Option<u32>,
pub sample_size: Option<u32>,
pub sample_flags: Option<u32>,
pub sample_composition_time_offset: Option<i64>,
}
impl TrunSample {
pub const fn new() -> Self {
Self {
sample_duration: None,
sample_size: None,
sample_flags: None,
sample_composition_time_offset: None,
}
}
}
impl Default for TrunSample {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TrackFragmentRunBox {
pub version: u8,
pub tr_flags: u32,
pub data_offset: Option<i32>,
pub first_sample_flags: Option<u32>,
pub samples: Vec<TrunSample>,
}
impl TrackFragmentRunBox {
pub fn parse_body(body: &[u8]) -> Result<Self> {
let (ver, tr_flags) = read_ver_flags(body)?;
let payload = &body[FULLBOX_EXTRA_SIZE..];
if payload.len() < 4 {
return Err(Error::BufferTooShort {
need: 4,
have: payload.len(),
what: "trun.sc",
});
}
let mut c = 0usize;
let sc = u32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]) as usize;
c += 4;
let data_offset = if tr_flags & TRUN_DATA_OFFSET_PRESENT != 0 {
if payload.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: payload.len(),
what: "trun.do",
});
}
let v =
i32::from_be_bytes([payload[c], payload[c + 1], payload[c + 2], payload[c + 3]]);
c += 4;
Some(v)
} else {
None
};
let fsf = if tr_flags & TRUN_FIRST_SAMPLE_FLAGS_PRESENT != 0 {
if payload.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: payload.len(),
what: "trun.fsf",
});
}
let v =
u32::from_be_bytes([payload[c], payload[c + 1], payload[c + 2], payload[c + 3]]);
c += 4;
Some(v)
} else {
None
};
let has_dur = tr_flags & TRUN_SAMPLE_DURATION_PRESENT != 0;
let has_sz = tr_flags & TRUN_SAMPLE_SIZE_PRESENT != 0;
let has_flg = tr_flags & TRUN_SAMPLE_FLAGS_PRESENT != 0;
let has_cto = tr_flags & TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT != 0;
let entry_size = [has_dur, has_sz, has_flg, has_cto]
.iter()
.filter(|&&present| present)
.count()
* 4;
let mut samples = Vec::with_capacity(crate::init_segment::bounded_entry_count(
payload.len().saturating_sub(c),
entry_size,
sc,
));
for _ in 0..sc {
let mut s = TrunSample::new();
if has_dur {
if payload.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: payload.len(),
what: "trun.dur",
});
}
s.sample_duration = Some(u32::from_be_bytes([
payload[c],
payload[c + 1],
payload[c + 2],
payload[c + 3],
]));
c += 4;
}
if has_sz {
if payload.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: payload.len(),
what: "trun.sz",
});
}
s.sample_size = Some(u32::from_be_bytes([
payload[c],
payload[c + 1],
payload[c + 2],
payload[c + 3],
]));
c += 4;
}
if has_flg {
if payload.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: payload.len(),
what: "trun.flg",
});
}
s.sample_flags = Some(u32::from_be_bytes([
payload[c],
payload[c + 1],
payload[c + 2],
payload[c + 3],
]));
c += 4;
}
if has_cto {
if payload.len() < c + 4 {
return Err(Error::BufferTooShort {
need: c + 4,
have: payload.len(),
what: "trun.cto",
});
}
if ver == 1 {
s.sample_composition_time_offset = Some(i64::from(i32::from_be_bytes([
payload[c],
payload[c + 1],
payload[c + 2],
payload[c + 3],
])));
} else {
s.sample_composition_time_offset = Some(i64::from(u32::from_be_bytes([
payload[c],
payload[c + 1],
payload[c + 2],
payload[c + 3],
])));
}
c += 4;
}
samples.push(s);
}
Ok(TrackFragmentRunBox {
version: ver,
tr_flags,
data_offset,
first_sample_flags: fsf,
samples,
})
}
pub fn record_stride(flags: u32) -> usize {
let mut n = 0u32;
if flags & TRUN_SAMPLE_DURATION_PRESENT != 0 {
n += 1;
}
if flags & TRUN_SAMPLE_SIZE_PRESENT != 0 {
n += 1;
}
if flags & TRUN_SAMPLE_FLAGS_PRESENT != 0 {
n += 1;
}
if flags & TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT != 0 {
n += 1;
}
(n * 4) as usize
}
}
impl TrackFragmentRunBox {
pub fn effective_flags(&self) -> u32 {
let non_presence = self.tr_flags
& !(TRUN_DATA_OFFSET_PRESENT
| TRUN_FIRST_SAMPLE_FLAGS_PRESENT
| TRUN_SAMPLE_DURATION_PRESENT
| TRUN_SAMPLE_SIZE_PRESENT
| TRUN_SAMPLE_FLAGS_PRESENT
| TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT);
let mut bits = non_presence;
if self.data_offset.is_some() {
bits |= TRUN_DATA_OFFSET_PRESENT;
}
if self.first_sample_flags.is_some() {
bits |= TRUN_FIRST_SAMPLE_FLAGS_PRESENT;
}
if self.samples.is_empty() {
return bits | self.tr_flags;
}
let all = |f: fn(&TrunSample) -> bool| self.samples.iter().all(f);
if all(|s| s.sample_duration.is_some()) {
bits |= TRUN_SAMPLE_DURATION_PRESENT;
}
if all(|s| s.sample_size.is_some()) {
bits |= TRUN_SAMPLE_SIZE_PRESENT;
}
if all(|s| s.sample_flags.is_some()) {
bits |= TRUN_SAMPLE_FLAGS_PRESENT;
}
if all(|s| s.sample_composition_time_offset.is_some()) {
bits |= TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT;
}
bits
}
fn check_sample_fields(&self, flags: u32) -> Result<()> {
type FieldCheck = (u32, fn(&TrunSample) -> bool);
let checks: [FieldCheck; 4] = [
(TRUN_SAMPLE_DURATION_PRESENT, |s: &TrunSample| {
s.sample_duration.is_some()
}),
(TRUN_SAMPLE_SIZE_PRESENT, |s: &TrunSample| {
s.sample_size.is_some()
}),
(TRUN_SAMPLE_FLAGS_PRESENT, |s: &TrunSample| {
s.sample_flags.is_some()
}),
(
TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT,
|s: &TrunSample| s.sample_composition_time_offset.is_some(),
),
];
for (bit, present) in checks {
if self.samples.iter().any(present) && !self.samples.iter().all(present) {
return Err(Error::InvalidInput(
"trun samples disagree on a per-sample field; a trun record is fixed-width",
));
}
if flags & bit != 0 && !self.samples.iter().all(present) {
return Err(Error::InvalidInput(
"trun flags declare a per-sample field that a sample does not carry",
));
}
}
Ok(())
}
}
impl Serialize for TrackFragmentRunBox {
type Error = Error;
fn serialized_len(&self) -> usize {
let flags = self.effective_flags();
let mut n = BOX_HEADER_SIZE + FULLBOX_EXTRA_SIZE + 4;
if flags & TRUN_DATA_OFFSET_PRESENT != 0 {
n += 4;
}
if flags & TRUN_FIRST_SAMPLE_FLAGS_PRESENT != 0 {
n += 4;
}
n + self.samples.len() * Self::record_stride(flags)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let flags = self.effective_flags();
self.check_sample_fields(flags)?;
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let len = broadcast_common::len::fit_u32(need, "trun size")?;
let mut c = 0;
buf[c..c + 4].copy_from_slice(&len.to_be_bytes());
c += 4;
buf[c..c + 4].copy_from_slice(b"trun");
c += 4;
let needs_v1 = self
.samples
.iter()
.any(|s| s.sample_composition_time_offset.is_some_and(|v| v < 0));
let version = if needs_v1 { 1 } else { self.version };
buf[c] = version;
let fb = flags.to_be_bytes();
buf[c + 1] = fb[1];
buf[c + 2] = fb[2];
buf[c + 3] = fb[3];
c += 4;
let sample_count = broadcast_common::len::fit_u32(self.samples.len(), "sample_count")?;
buf[c..c + 4].copy_from_slice(&sample_count.to_be_bytes());
c += 4;
if let Some(v) = self.data_offset {
buf[c..c + 4].copy_from_slice(&v.to_be_bytes());
c += 4;
}
if let Some(v) = self.first_sample_flags {
buf[c..c + 4].copy_from_slice(&v.to_be_bytes());
c += 4;
}
let has_dur = flags & TRUN_SAMPLE_DURATION_PRESENT != 0;
let has_sz = flags & TRUN_SAMPLE_SIZE_PRESENT != 0;
let has_flg = flags & TRUN_SAMPLE_FLAGS_PRESENT != 0;
let has_cto = flags & TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT != 0;
for s in &self.samples {
if has_dur {
buf[c..c + 4].copy_from_slice(&s.sample_duration.unwrap_or(0).to_be_bytes());
c += 4;
}
if has_sz {
buf[c..c + 4].copy_from_slice(&s.sample_size.unwrap_or(0).to_be_bytes());
c += 4;
}
if has_flg {
buf[c..c + 4].copy_from_slice(&s.sample_flags.unwrap_or(0).to_be_bytes());
c += 4;
}
if has_cto {
let offset = s.sample_composition_time_offset.unwrap_or(0);
if version == 1 {
let signed = i32::try_from(offset).map_err(|_| {
Error::InvalidInput(
"trun v1 sample_composition_time_offset leaves the signed 32-bit range",
)
})?;
buf[c..c + 4].copy_from_slice(&signed.to_be_bytes());
} else {
let unsigned = u32::try_from(offset).map_err(|_| {
Error::InvalidInput(
"trun v0 sample_composition_time_offset leaves the unsigned 32-bit range",
)
})?;
buf[c..c + 4].copy_from_slice(&unsigned.to_be_bytes());
}
c += 4;
}
}
Ok(c)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct OpaqueChild {
pub box_type: [u8; 4],
pub data: Vec<u8>,
pub largesize: bool,
pub to_end: bool,
}
impl OpaqueChild {
pub(crate) fn from_child(child: &crate::init_segment::ChildBox<'_>) -> Self {
Self {
box_type: child.four_cc,
data: child.payload.to_vec(),
largesize: child.largesize,
to_end: child.to_end,
}
}
pub fn new(box_type: [u8; 4], data: Vec<u8>) -> Self {
Self {
box_type,
data,
largesize: false,
to_end: false,
}
}
}
impl Serialize for OpaqueChild {
type Error = Error;
fn serialized_len(&self) -> usize {
self.header_len() + self.data.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
self.serialize_child_into(buf, true)
}
}
impl OpaqueChild {
pub(crate) fn serialize_child_into(&self, buf: &mut [u8], is_last: bool) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let mut c = 0usize;
if self.to_end && is_last {
buf[c..c + 4].copy_from_slice(&0u32.to_be_bytes());
c += 4;
buf[c..c + 4].copy_from_slice(&self.box_type);
c += 4;
} else if self.largesize {
buf[c..c + 4].copy_from_slice(&1u32.to_be_bytes());
c += 4;
buf[c..c + 4].copy_from_slice(&self.box_type);
c += 4;
buf[c..c + 8].copy_from_slice(&(need as u64).to_be_bytes());
c += 8;
} else {
let len = broadcast_common::len::fit_u32(need, "opaque child size")?;
buf[c..c + 4].copy_from_slice(&len.to_be_bytes());
c += 4;
buf[c..c + 4].copy_from_slice(&self.box_type);
c += 4;
}
buf[c..c + self.data.len()].copy_from_slice(&self.data);
Ok(c + self.data.len())
}
}
impl OpaqueChild {
fn header_len(&self) -> usize {
BOX_HEADER_SIZE + if self.largesize { LARGESIZE_SIZE } else { 0 }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum TrafChild {
Tfhd,
Tfdt,
Trun(usize),
Opaque(OpaqueChild),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum MoofChild {
Mfhd,
Traf(usize),
Opaque(OpaqueChild),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TrackFragmentBox {
pub tfhd: TrackFragmentHeaderBox,
pub tfdt: Option<TrackFragmentBaseMediaDecodeTimeBox>,
pub trun: Vec<TrackFragmentRunBox>,
pub order: Vec<TrafChild>,
}
impl TrackFragmentBox {
pub fn new(
tfhd: TrackFragmentHeaderBox,
tfdt: Option<TrackFragmentBaseMediaDecodeTimeBox>,
trun: Vec<TrackFragmentRunBox>,
) -> Self {
Self {
tfhd,
tfdt,
trun,
order: Vec::new(),
}
}
pub fn parse_body(body: &[u8]) -> Result<Self> {
let mut tfhd: Option<TrackFragmentHeaderBox> = None;
let mut tfdt: Option<TrackFragmentBaseMediaDecodeTimeBox> = None;
let mut trun: Vec<TrackFragmentRunBox> = Vec::new();
let mut order: Vec<TrafChild> = Vec::new();
crate::init_segment::walk_children(body, |child| {
if child.is(b"tfhd") {
tfhd = Some(TrackFragmentHeaderBox::parse_body(child.payload)?);
order.push(TrafChild::Tfhd);
} else if child.is(b"tfdt") {
tfdt = Some(TrackFragmentBaseMediaDecodeTimeBox::parse_body(
child.payload,
)?);
order.push(TrafChild::Tfdt);
} else if child.is(b"trun") {
let parsed = TrackFragmentRunBox::parse_body(child.payload)?;
order.push(TrafChild::Trun(trun.len()));
trun.push(parsed);
} else {
order.push(TrafChild::Opaque(OpaqueChild::from_child(&child)));
}
Ok(())
})?;
let tfhd = tfhd.ok_or(Error::BufferTooShort {
need: 1,
have: 0,
what: "traf missing tfhd",
})?;
Ok(TrackFragmentBox {
tfhd,
tfdt,
trun,
order,
})
}
fn child_order(&self) -> Vec<TrafChild> {
if !self.order.is_empty() {
return self.order.clone();
}
let mut out = Vec::with_capacity(2 + self.trun.len());
out.push(TrafChild::Tfhd);
if self.tfdt.is_some() {
out.push(TrafChild::Tfdt);
}
for i in 0..self.trun.len() {
out.push(TrafChild::Trun(i));
}
out
}
pub fn into_bytes(&self) -> Result<Vec<u8>> {
let mut buf = alloc::vec![0u8; self.serialized_len()];
let n = self.serialize_into(&mut buf)?;
buf.truncate(n);
Ok(buf)
}
}
impl Serialize for TrackFragmentBox {
type Error = Error;
fn serialized_len(&self) -> usize {
let mut n = BOX_HEADER_SIZE;
for child in self.child_order() {
n += match child {
TrafChild::Tfhd => self.tfhd.serialized_len(),
TrafChild::Tfdt => self.tfdt.as_ref().map_or(0, Serialize::serialized_len),
TrafChild::Trun(i) => self.trun.get(i).map_or(0, Serialize::serialized_len),
TrafChild::Opaque(ref o) => o.serialized_len(),
};
}
n
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let len = broadcast_common::len::fit_u32(need, "traf size")?;
buf[..4].copy_from_slice(&len.to_be_bytes());
buf[4..8].copy_from_slice(b"traf");
let mut c = BOX_HEADER_SIZE;
let order = self.child_order();
for (i, child) in order.iter().enumerate() {
let is_last = i + 1 == order.len();
match child {
TrafChild::Tfhd => c += self.tfhd.serialize_into(&mut buf[c..])?,
TrafChild::Tfdt => {
if let Some(ref t) = self.tfdt {
c += t.serialize_into(&mut buf[c..])?;
}
}
TrafChild::Trun(i) => {
if let Some(r) = self.trun.get(*i) {
c += r.serialize_into(&mut buf[c..])?;
}
}
TrafChild::Opaque(o) => c += o.serialize_child_into(&mut buf[c..], is_last)?,
}
}
if c != need {
return Err(Error::InvalidInput(
"traf child order does not account for the whole box",
));
}
Ok(c)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MovieFragmentBox {
pub mfhd: MovieFragmentHeaderBox,
pub traf: Vec<TrackFragmentBox>,
pub order: Vec<MoofChild>,
}
impl MovieFragmentBox {
pub fn new(mfhd: MovieFragmentHeaderBox, traf: Vec<TrackFragmentBox>) -> Self {
Self {
mfhd,
traf,
order: Vec::new(),
}
}
pub fn parse_body(body: &[u8]) -> Result<Self> {
let mut mfhd: Option<MovieFragmentHeaderBox> = None;
let mut traf: Vec<TrackFragmentBox> = Vec::new();
let mut order: Vec<MoofChild> = Vec::new();
crate::init_segment::walk_children(body, |child| {
if child.is(b"mfhd") {
mfhd = Some(MovieFragmentHeaderBox::parse_body(child.payload)?);
order.push(MoofChild::Mfhd);
} else if child.is(b"traf") {
let parsed = TrackFragmentBox::parse_body(child.payload)?;
order.push(MoofChild::Traf(traf.len()));
traf.push(parsed);
} else {
order.push(MoofChild::Opaque(OpaqueChild::from_child(&child)));
}
Ok(())
})?;
let mfhd = mfhd.ok_or(Error::BufferTooShort {
need: 1,
have: 0,
what: "moof missing mfhd",
})?;
if traf.is_empty() {
return Err(Error::BufferTooShort {
need: 1,
have: 0,
what: "moof missing traf",
});
}
Ok(MovieFragmentBox { mfhd, traf, order })
}
fn child_order(&self) -> Vec<MoofChild> {
if !self.order.is_empty() {
return self.order.clone();
}
let mut out = Vec::with_capacity(1 + self.traf.len());
out.push(MoofChild::Mfhd);
for i in 0..self.traf.len() {
out.push(MoofChild::Traf(i));
}
out
}
pub(crate) fn child_order_for_rewrite(&self) -> Vec<MoofChild> {
self.child_order()
}
}
impl Serialize for MovieFragmentBox {
type Error = Error;
fn serialized_len(&self) -> usize {
let mut n = BOX_HEADER_SIZE;
for child in self.child_order() {
n += match child {
MoofChild::Mfhd => self.mfhd.serialized_len(),
MoofChild::Traf(i) => self.traf.get(i).map_or(0, Serialize::serialized_len),
MoofChild::Opaque(ref o) => o.serialized_len(),
};
}
n
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let need = self.serialized_len();
if buf.len() < need {
return Err(Error::OutputBufferTooSmall {
need,
have: buf.len(),
});
}
let len = broadcast_common::len::fit_u32(need, "moof size")?;
buf[..4].copy_from_slice(&len.to_be_bytes());
buf[4..8].copy_from_slice(b"moof");
let mut c = BOX_HEADER_SIZE;
let order = self.child_order();
for (i, child) in order.iter().enumerate() {
let is_last = i + 1 == order.len();
match child {
MoofChild::Mfhd => c += self.mfhd.serialize_into(&mut buf[c..])?,
MoofChild::Traf(i) => {
if let Some(t) = self.traf.get(*i) {
c += t.serialize_into(&mut buf[c..])?;
}
}
MoofChild::Opaque(o) => c += o.serialize_child_into(&mut buf[c..], is_last)?,
}
}
if c != need {
return Err(Error::InvalidInput(
"moof child order does not account for the whole box",
));
}
Ok(c)
}
}
const SENC_ENTRIES_OFFSET: u64 = 16;
const SAIZ_SUBSAMPLE_COUNT_SIZE: usize = 2;
const SAIZ_SUBSAMPLE_ENTRY_SIZE: usize = 6;
const CENC_AUX_INFO_TYPES: [[u8; 4]; 4] = [*b"cenc", *b"cens", *b"cbc1", *b"cbcs"];
fn aux_info_type_of(payload: &[u8], box_type: &[u8; 4]) -> Option<[u8; 4]> {
let _ = box_type;
if payload.len() < 4 {
return None;
}
let version = payload[0];
let flags = u32::from_be_bytes([0, payload[1], payload[2], payload[3]]);
if version == 0 && flags & 0x01 == 0 {
return None;
}
let ty = payload.get(4..8)?;
Some([ty[0], ty[1], ty[2], ty[3]])
}
pub struct FragmentProtection<'a> {
pub track_id: u32,
pub entries: &'a [crate::cenc::SampleEncryptionEntry],
pub per_sample_iv_size: u8,
}
struct CencFragmentBoxes {
senc: crate::cenc::SampleEncryptionBox,
saiz: crate::cenc::SampleAuxInfoSizesBox,
saio: crate::cenc::SampleAuxInfoOffsetsBox,
}
impl CencFragmentBoxes {
fn added_len(&self) -> usize {
self.senc.serialized_len() + self.saiz.serialized_len() + self.saio.serialized_len()
}
}
fn build_cenc_fragment_boxes(p: &FragmentProtection<'_>) -> Result<Option<CencFragmentBoxes>> {
let use_subsamples = p.entries.iter().any(|e| !e.subsamples.is_empty());
if p.per_sample_iv_size == 0 && !use_subsamples {
return Ok(None);
}
let flags = if use_subsamples {
crate::cenc::SENC_FLAG_USE_SUBSAMPLE_ENCRYPTION
} else {
0
};
let senc = crate::cenc::SampleEncryptionBox {
version: 0,
flags,
per_sample_iv_size: p.per_sample_iv_size,
entries: p.entries.to_vec(),
};
let mut sizes = Vec::with_capacity(p.entries.len());
for e in p.entries {
let mut sz = p.per_sample_iv_size as usize;
if use_subsamples {
sz += SAIZ_SUBSAMPLE_COUNT_SIZE + e.subsamples.len() * SAIZ_SUBSAMPLE_ENTRY_SIZE;
}
if sz > u8::MAX as usize {
return Err(Error::InvalidInput(
"protect_media_segment: per-sample aux info size exceeds 255 bytes (saiz sample_info_size is u8)",
));
}
sizes.push(sz as u8);
}
let uniform = sizes
.first()
.copied()
.filter(|first| sizes.iter().all(|s| s == first));
let sample_count = broadcast_common::len::fit_u32(sizes.len(), "saiz sample_count")?;
let saiz = crate::cenc::SampleAuxInfoSizesBox {
version: 0,
flags: 0,
aux_info_type: None,
aux_info_type_parameter: None,
default_sample_info_size: uniform.unwrap_or(0),
sample_count,
sample_info_sizes: if uniform.is_some() { Vec::new() } else { sizes },
};
let saio = crate::cenc::SampleAuxInfoOffsetsBox {
version: 0,
flags: 0,
aux_info_type: None,
aux_info_type_parameter: None,
offsets: alloc::vec![0u64],
};
Ok(Some(CencFragmentBoxes { senc, saiz, saio }))
}
pub fn protect_media_segment(
media_segment: &[u8],
protections: &[FragmentProtection<'_>],
) -> Result<Vec<u8>> {
if protections.is_empty() {
return Ok(media_segment.to_vec());
}
let mut prefix_len = 0usize;
let mut moof_len = None;
for step in crate::box_types::box_iter(media_segment) {
let (box_ref, consumed) = step?;
if box_ref.header.box_type.is(b"moof") {
moof_len = Some(consumed);
break;
}
prefix_len += consumed;
}
let moof_len = moof_len.ok_or(Error::UnexpectedBox { expected: "moof" })?;
let moof_bytes = &media_segment[prefix_len..prefix_len + moof_len];
let suffix = &media_segment[prefix_len + moof_len..];
let mut moof = MovieFragmentBox::parse_body(&moof_bytes[BOX_HEADER_SIZE..])?;
let mut built: Vec<Option<CencFragmentBoxes>> = (0..moof.traf.len()).map(|_| None).collect();
for p in protections {
let idx = moof
.traf
.iter()
.position(|t| t.tfhd.track_id == p.track_id)
.ok_or(Error::InvalidInput(
"protect_media_segment: track_id not present in moof",
))?;
let sample_count: usize = moof.traf[idx].trun.iter().map(|r| r.samples.len()).sum();
if sample_count != p.entries.len() {
return Err(Error::InvalidInput(
"protect_media_segment: entries.len() must equal the traf's total trun sample count",
));
}
for child in &moof.traf[idx].order {
let TrafChild::Opaque(o) = child else {
continue;
};
let collides = match &o.box_type {
b"senc" => true,
b"saiz" | b"saio" => aux_info_type_of(&o.data, &o.box_type)
.is_none_or(|ty| CENC_AUX_INFO_TYPES.contains(&ty)),
_ => false,
};
if collides {
return Err(Error::InvalidInput(
"protect_media_segment: traf already carries a CENC senc/saiz/saio",
));
}
}
built[idx] = build_cenc_fragment_boxes(p)?;
}
let base_lens: Vec<usize> = moof.traf.iter().map(|t| t.serialized_len()).collect();
let final_lens: Vec<usize> = base_lens
.iter()
.zip(&built)
.map(|(&base, b)| base + b.as_ref().map(CencFragmentBoxes::added_len).unwrap_or(0))
.collect();
let order = moof.child_order_for_rewrite();
let new_moof_len = BOX_HEADER_SIZE
+ order
.iter()
.map(|child| match child {
MoofChild::Mfhd => moof.mfhd.serialized_len(),
MoofChild::Traf(i) => final_lens.get(*i).copied().unwrap_or(0),
MoofChild::Opaque(o) => o.serialized_len(),
})
.sum::<usize>();
let delta = new_moof_len as i64 - moof_len as i64;
let mut running = BOX_HEADER_SIZE as u64;
for child in &order {
let traf_index = match child {
MoofChild::Mfhd => {
running += moof.mfhd.serialized_len() as u64;
continue;
}
MoofChild::Opaque(o) => {
running += o.serialized_len() as u64;
continue;
}
MoofChild::Traf(i) => *i,
};
let Some(traf) = moof.traf.get_mut(traf_index) else {
return Err(Error::InvalidInput(
"protect_media_segment: moof child index out of range",
));
};
if let Some(b) = built[traf_index].as_mut() {
let senc_start = running + base_lens[traf_index] as u64;
b.saio.offsets[0] = senc_start + SENC_ENTRIES_OFFSET;
}
running += final_lens[traf_index] as u64;
for run in &mut traf.trun {
if run.tr_flags & TRUN_DATA_OFFSET_PRESENT != 0 {
let current = run.data_offset.ok_or(Error::InvalidInput(
"protect_media_segment: trun declares data_offset but carries none",
))?;
let shifted = i64::from(current)
.checked_add(delta)
.and_then(|v| i32::try_from(v).ok())
.ok_or(Error::InvalidInput(
"protect_media_segment: trun.data_offset leaves the 32-bit field",
))?;
run.data_offset = Some(shifted);
}
}
}
let mut moof_out = alloc::vec![0u8; new_moof_len];
let moof_size = broadcast_common::len::fit_u32(new_moof_len, "moof size")?;
moof_out[0..4].copy_from_slice(&moof_size.to_be_bytes());
moof_out[4..8].copy_from_slice(b"moof");
let mut c = BOX_HEADER_SIZE;
for child in moof.child_order_for_rewrite() {
match child {
MoofChild::Mfhd => c += moof.mfhd.serialize_into(&mut moof_out[c..])?,
MoofChild::Traf(i) => {
let start = c;
let traf = moof.traf.get(i).ok_or(Error::InvalidInput(
"protect_media_segment: moof child index out of range",
))?;
c += traf.serialize_into(&mut moof_out[c..])?;
if let Some(b) = built.get(i).and_then(|b| b.as_ref()) {
c += b.senc.serialize_into(&mut moof_out[c..])?;
c += b.saiz.serialize_into(&mut moof_out[c..])?;
c += b.saio.serialize_into(&mut moof_out[c..])?;
let final_len = broadcast_common::len::fit_u32(c - start, "traf size")?;
moof_out[start..start + 4].copy_from_slice(&final_len.to_be_bytes());
}
}
MoofChild::Opaque(ref o) => c += o.serialize_into(&mut moof_out[c..])?,
}
}
if c != new_moof_len {
return Err(Error::InvalidInput(
"moof length/senc offset consistency check failed",
));
}
let mut out = Vec::with_capacity(prefix_len + new_moof_len + suffix.len());
out.extend_from_slice(&media_segment[..prefix_len]);
out.extend_from_slice(&moof_out);
out.extend_from_slice(suffix);
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn mfhd_round_trip() {
let m = MovieFragmentHeaderBox::new(42);
let b = m.to_bytes();
let p = MovieFragmentHeaderBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.sequence_number, 42);
assert_eq!(b, p.to_bytes());
}
#[test]
fn mfhd_len() {
assert_eq!(MovieFragmentHeaderBox::new(1).serialized_len(), 16);
}
#[test]
fn tfhd_minimal() {
let t = TrackFragmentHeaderBox {
flags: 0,
track_id: 1,
base_data_offset: None,
sample_description_index: None,
default_sample_duration: None,
default_sample_size: None,
default_sample_flags: None,
};
let b = t.to_bytes();
let p = TrackFragmentHeaderBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.track_id, 1);
assert_eq!(b, p.to_bytes());
}
#[test]
fn tfhd_subset() {
let t = TrackFragmentHeaderBox {
flags: TFHD_DEFAULT_SAMPLE_DURATION_PRESENT | TFHD_DEFAULT_SAMPLE_SIZE_PRESENT,
track_id: 7,
base_data_offset: None,
sample_description_index: None,
default_sample_duration: Some(512),
default_sample_size: Some(3933),
default_sample_flags: None,
};
let b = t.to_bytes();
let p = TrackFragmentHeaderBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.default_sample_duration, Some(512));
assert_eq!(p.default_sample_size, Some(3933));
assert_eq!(b, p.to_bytes());
}
#[test]
fn tfhd_full_flags() {
let flags = TFHD_BASE_DATA_OFFSET_PRESENT
| TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT
| TFHD_DEFAULT_SAMPLE_DURATION_PRESENT
| TFHD_DEFAULT_SAMPLE_SIZE_PRESENT
| TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT;
let t = TrackFragmentHeaderBox {
flags,
track_id: 2,
base_data_offset: Some(0x1234567890ABCDEF),
sample_description_index: Some(1),
default_sample_duration: Some(1024),
default_sample_size: Some(256),
default_sample_flags: Some(0x01010000),
};
let b = t.to_bytes();
let p = TrackFragmentHeaderBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.base_data_offset, t.base_data_offset);
assert_eq!(p.flags, flags);
assert_eq!(b, p.to_bytes());
}
#[test]
fn tfhd_flags_are_derived_from_the_options() {
let t = TrackFragmentHeaderBox {
flags: 0,
track_id: 4,
base_data_offset: Some(0x1000),
sample_description_index: Some(1),
default_sample_duration: Some(512),
default_sample_size: Some(99),
default_sample_flags: Some(0x01010000),
};
let b = t.to_bytes();
let p = TrackFragmentHeaderBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.base_data_offset, Some(0x1000), "base must not become 0");
assert_eq!(p.sample_description_index, Some(1));
assert_eq!(p.default_sample_duration, Some(512));
assert_eq!(p.default_sample_size, Some(99));
assert_eq!(p.default_sample_flags, Some(0x01010000));
assert_eq!(
p.flags,
TFHD_BASE_DATA_OFFSET_PRESENT
| TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT
| TFHD_DEFAULT_SAMPLE_DURATION_PRESENT
| TFHD_DEFAULT_SAMPLE_SIZE_PRESENT
| TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT
);
let t = TrackFragmentHeaderBox {
flags: TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT,
track_id: 4,
base_data_offset: None,
sample_description_index: None,
default_sample_duration: None,
default_sample_size: None,
default_sample_flags: None,
};
let b = t.to_bytes();
assert_eq!(b.len(), 16, "no value means no field on the wire");
let p = TrackFragmentHeaderBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.flags, 0, "the unmatched presence bit is not written");
assert_eq!(p.sample_description_index, None);
}
#[test]
fn trun_flags_are_derived_from_the_values() {
let trun = TrackFragmentRunBox {
version: 0,
tr_flags: 0,
data_offset: Some(716),
first_sample_flags: None,
samples: vec![
TrunSample {
sample_duration: Some(512),
sample_size: None,
sample_flags: None,
sample_composition_time_offset: None,
},
TrunSample {
sample_duration: Some(512),
sample_size: None,
sample_flags: None,
sample_composition_time_offset: None,
},
],
};
let b = trun.to_bytes();
let p = TrackFragmentRunBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.data_offset, Some(716), "data_offset must survive");
assert_eq!(
TrackFragmentRunBox::record_stride(p.tr_flags),
4,
"only sample_duration"
);
assert_eq!(p.samples[0].sample_duration, Some(512));
assert_eq!(b, p.to_bytes());
let gapped = TrackFragmentRunBox {
version: 0,
tr_flags: TRUN_SAMPLE_DURATION_PRESENT,
data_offset: None,
first_sample_flags: None,
samples: vec![
TrunSample {
sample_duration: Some(512),
sample_size: None,
sample_flags: None,
sample_composition_time_offset: None,
},
TrunSample {
sample_duration: None,
sample_size: None,
sample_flags: None,
sample_composition_time_offset: None,
},
],
};
let err = gapped.try_to_bytes().unwrap_err();
assert!(
matches!(err, Error::InvalidInput(_)),
"expected InvalidInput, got {err:?}"
);
}
#[test]
fn trun_v0_large_unsigned_composition_offset_round_trips() {
let offset: i64 = 0x8000_0001;
let trun = TrackFragmentRunBox {
version: 0,
tr_flags: 0,
data_offset: None,
first_sample_flags: None,
samples: vec![TrunSample {
sample_duration: None,
sample_size: None,
sample_flags: None,
sample_composition_time_offset: Some(offset),
}],
};
let b = trun.try_to_bytes().expect("serialize");
assert_eq!(b.len(), 20, "stays version 0");
assert_eq!(b[8], 0, "version 0");
assert_eq!(
&b[16..20],
&[0x80, 0x00, 0x00, 0x01],
"the raw unsigned bytes are preserved"
);
let p = TrackFragmentRunBox::parse_body(&b[8..]).expect("parse");
assert_eq!(p.version, 0, "a non-negative offset keeps version 0");
assert_eq!(p.samples[0].sample_composition_time_offset, Some(offset));
assert_eq!(b, p.try_to_bytes().unwrap(), "byte-identical round trip");
}
#[test]
fn trun_v1_negative_offsets_stay_v1_and_out_of_range_errors() {
let neg = TrackFragmentRunBox {
version: 1,
tr_flags: 0,
data_offset: None,
first_sample_flags: None,
samples: vec![TrunSample {
sample_duration: None,
sample_size: None,
sample_flags: None,
sample_composition_time_offset: Some(-1),
}],
};
let b = neg.try_to_bytes().expect("serialize v1");
assert_eq!(b[8], 1, "stays version 1");
let p = TrackFragmentRunBox::parse_body(&b[8..]).expect("parse");
assert_eq!(p.samples[0].sample_composition_time_offset, Some(-1));
let too_big = TrackFragmentRunBox {
version: 1,
tr_flags: 0,
data_offset: None,
first_sample_flags: None,
samples: vec![TrunSample {
sample_duration: None,
sample_size: None,
sample_flags: None,
sample_composition_time_offset: Some(0x8000_0001),
}],
};
let err = too_big.try_to_bytes().unwrap_err();
assert!(
matches!(err, Error::InvalidInput(_)),
"expected InvalidInput, got {err:?}"
);
}
#[test]
fn trun_version_is_promoted_for_a_negative_composition_offset() {
let trun = TrackFragmentRunBox {
version: 0,
tr_flags: 0,
data_offset: None,
first_sample_flags: None,
samples: vec![TrunSample {
sample_duration: None,
sample_size: None,
sample_flags: None,
sample_composition_time_offset: Some(-1),
}],
};
let b = trun.try_to_bytes().expect("serialize");
let p = TrackFragmentRunBox::parse_body(&b[8..]).expect("parse");
assert_eq!(p.version, 1, "a negative offset forces version 1");
assert_eq!(p.samples[0].sample_composition_time_offset, Some(-1));
let trun = TrackFragmentRunBox {
version: 0,
tr_flags: 0,
data_offset: None,
first_sample_flags: None,
samples: vec![TrunSample {
sample_duration: None,
sample_size: None,
sample_flags: None,
sample_composition_time_offset: Some(0),
}],
};
let p = TrackFragmentRunBox::parse_body(&trun.try_to_bytes().unwrap()[8..]).unwrap();
assert_eq!(p.version, 0, "a non-negative offset keeps version 0");
}
#[test]
fn empty_trun_round_trips_its_flag_bits() {
let tr_flags =
TRUN_DATA_OFFSET_PRESENT | TRUN_SAMPLE_DURATION_PRESENT | TRUN_SAMPLE_SIZE_PRESENT;
let trun = TrackFragmentRunBox {
version: 0,
tr_flags,
data_offset: Some(64),
first_sample_flags: None,
samples: Vec::new(),
};
let b = trun.try_to_bytes().expect("serialize");
let p = TrackFragmentRunBox::parse_body(&b[8..]).expect("parse");
assert!(p.samples.is_empty());
assert_eq!(
p.tr_flags, tr_flags,
"the flag bits survive with no samples"
);
assert_eq!(p.data_offset, Some(64));
assert_eq!(b, p.try_to_bytes().unwrap(), "byte-identical round trip");
}
#[test]
fn tfhd_with_both_bases_round_trips() {
let flags = TFHD_DEFAULT_BASE_IS_MOOF | TFHD_BASE_DATA_OFFSET_PRESENT;
let t = TrackFragmentHeaderBox {
flags,
track_id: 1,
base_data_offset: Some(0x40),
sample_description_index: None,
default_sample_duration: None,
default_sample_size: None,
default_sample_flags: None,
};
let b = t
.try_to_bytes()
.expect("a redundant default-base-is-moof is legal");
let p = TrackFragmentHeaderBox::parse_body(&b[8..]).expect("parses");
assert_eq!(p.flags, flags, "the redundant bit is preserved");
assert_eq!(p.base_data_offset, Some(0x40));
assert_eq!(b, p.try_to_bytes().unwrap(), "byte-identical round trip");
}
#[test]
fn tfdt_v0() {
let t = TrackFragmentBaseMediaDecodeTimeBox::new_v0(12345);
let b = t.to_bytes();
assert_eq!(b.len(), 16);
let p = TrackFragmentBaseMediaDecodeTimeBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.base_media_decode_time(), 12345);
assert!(p.version() == 0);
assert_eq!(b, p.to_bytes());
}
#[test]
fn tfdt_v1() {
let t = TrackFragmentBaseMediaDecodeTimeBox::new_v1(0x123456789AB);
let b = t.to_bytes();
assert_eq!(b.len(), 20);
let p = TrackFragmentBaseMediaDecodeTimeBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.base_media_decode_time(), 0x123456789AB);
assert!(p.version() == 1);
assert_eq!(b, p.to_bytes());
}
#[test]
fn trun_subset_flags() {
let tr = TrackFragmentRunBox {
version: 0,
tr_flags: TRUN_DATA_OFFSET_PRESENT
| TRUN_FIRST_SAMPLE_FLAGS_PRESENT
| TRUN_SAMPLE_SIZE_PRESENT
| TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT,
data_offset: Some(716),
first_sample_flags: Some(0x02000000),
samples: vec![
TrunSample {
sample_duration: None,
sample_size: Some(3933),
sample_flags: None,
sample_composition_time_offset: Some(1024),
},
TrunSample {
sample_duration: None,
sample_size: Some(509),
sample_flags: None,
sample_composition_time_offset: Some(2560),
},
],
};
let b = tr.to_bytes();
let p = TrackFragmentRunBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.samples.len(), 2);
assert_eq!(p.samples[0].sample_size, Some(3933));
assert_eq!(p.samples[0].sample_composition_time_offset, Some(1024));
assert_eq!(b, p.to_bytes());
}
#[test]
fn trun_record_stride() {
assert_eq!(
TrackFragmentRunBox::record_stride(
TRUN_SAMPLE_DURATION_PRESENT | TRUN_SAMPLE_SIZE_PRESENT
),
8
);
assert_eq!(
TrackFragmentRunBox::record_stride(
TRUN_SAMPLE_DURATION_PRESENT
| TRUN_SAMPLE_SIZE_PRESENT
| TRUN_SAMPLE_FLAGS_PRESENT
| TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT
),
16
);
}
#[test]
fn trun_mutation_changes_bytes() {
let t = TrackFragmentRunBox {
version: 0,
tr_flags: TRUN_SAMPLE_DURATION_PRESENT | TRUN_SAMPLE_SIZE_PRESENT,
data_offset: None,
first_sample_flags: None,
samples: vec![TrunSample {
sample_duration: Some(1024),
sample_size: Some(256),
sample_flags: None,
sample_composition_time_offset: None,
}],
};
let orig = t.to_bytes();
let m = TrackFragmentRunBox {
version: 0,
tr_flags: TRUN_SAMPLE_DURATION_PRESENT
| TRUN_SAMPLE_SIZE_PRESENT
| TRUN_SAMPLE_FLAGS_PRESENT,
data_offset: None,
first_sample_flags: None,
samples: vec![TrunSample {
sample_duration: Some(1024),
sample_size: Some(256),
sample_flags: Some(0x01000000),
sample_composition_time_offset: None,
}],
};
let mb = m.to_bytes();
assert_ne!(mb, orig);
assert_eq!(mb[10] & 0x04, 0x04);
assert_eq!(orig[10] & 0x04, 0x00);
let m2 = TrackFragmentRunBox {
tr_flags: t.tr_flags,
samples: t.samples.clone(),
..m
};
assert_eq!(m2.to_bytes(), orig);
}
#[test]
fn traf_round_trip() {
let tfhd = TrackFragmentHeaderBox {
flags: TFHD_DEFAULT_SAMPLE_DURATION_PRESENT
| TFHD_DEFAULT_SAMPLE_SIZE_PRESENT
| TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT
| TFHD_DEFAULT_BASE_IS_MOOF,
track_id: 1,
base_data_offset: None,
sample_description_index: None,
default_sample_duration: Some(512),
default_sample_size: Some(3933),
default_sample_flags: Some(0x01010000),
};
let tfdt = TrackFragmentBaseMediaDecodeTimeBox::new_v1(0);
let trun = TrackFragmentRunBox {
version: 0,
tr_flags: TRUN_DATA_OFFSET_PRESENT
| TRUN_FIRST_SAMPLE_FLAGS_PRESENT
| TRUN_SAMPLE_SIZE_PRESENT
| TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT,
data_offset: Some(716),
first_sample_flags: Some(0x02000000),
samples: vec![TrunSample {
sample_duration: None,
sample_size: Some(3933),
sample_flags: None,
sample_composition_time_offset: Some(1024),
}],
};
let traf = TrackFragmentBox::new(tfhd, Some(tfdt), vec![trun]);
let b = traf.to_bytes();
let p = TrackFragmentBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.tfhd.track_id, 1);
assert!(p.tfdt.is_some());
assert_eq!(p.trun.len(), 1);
assert_eq!(b, p.to_bytes());
}
#[test]
fn moof_round_trip() {
let mfhd = MovieFragmentHeaderBox::new(1);
let tfhd = TrackFragmentHeaderBox {
flags: TFHD_DEFAULT_SAMPLE_DURATION_PRESENT
| TFHD_DEFAULT_SAMPLE_SIZE_PRESENT
| TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT
| TFHD_DEFAULT_BASE_IS_MOOF,
track_id: 1,
base_data_offset: None,
sample_description_index: None,
default_sample_duration: Some(512),
default_sample_size: Some(3933),
default_sample_flags: Some(0x01010000),
};
let tfdt = TrackFragmentBaseMediaDecodeTimeBox::new_v1(0);
let trun = TrackFragmentRunBox {
version: 0,
tr_flags: TRUN_DATA_OFFSET_PRESENT
| TRUN_FIRST_SAMPLE_FLAGS_PRESENT
| TRUN_SAMPLE_SIZE_PRESENT
| TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT,
data_offset: Some(716),
first_sample_flags: Some(0x02000000),
samples: vec![TrunSample {
sample_duration: None,
sample_size: Some(3933),
sample_flags: None,
sample_composition_time_offset: Some(1024),
}],
};
let traf = TrackFragmentBox::new(tfhd, Some(tfdt), vec![trun]);
let moof = MovieFragmentBox::new(mfhd, vec![traf]);
let b = moof.to_bytes();
let p = MovieFragmentBox::parse_body(&b[8..]).unwrap();
assert_eq!(p.mfhd.sequence_number, 1);
assert_eq!(p.traf.len(), 1);
assert_eq!(b, p.to_bytes());
}
#[test]
fn traf_without_trun_parses() {
let body: &[u8] = &[
0, 0, 0, 16, b't', b'f', b'h', b'd', 0, 0x01, 0x00, 0x00, 0, 0, 0, 7, ];
let traf = TrackFragmentBox::parse_body(body).expect("trun is optional");
assert_eq!(traf.tfhd.track_id, 7);
assert_eq!(traf.tfhd.flags, TFHD_DURATION_IS_EMPTY);
assert!(traf.trun.is_empty());
let out = traf.to_bytes();
assert_eq!(&out[8..], body, "traf body round-trip");
assert_eq!(&out[..8], &[0, 0, 0, 24, b't', b'r', b'a', b'f']);
}
#[test]
fn traf_preserves_unmodelled_children_in_wire_order() {
let body: &[u8] = &[
0, 0, 0, 16, b't', b'f', b'h', b'd', 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 20, b's', b'g', b'p', b'd', 0, 0, 0, 0, b'r', b'o', b'l', b'l', 0, 0, 0, 0, 0,
0, 0, 16, b't', b'r', b'u', b'n', 0, 0, 0, 0, 0, 0, 0, 0,
];
let traf = TrackFragmentBox::parse_body(body).expect("parses");
assert_eq!(traf.tfhd.track_id, 1);
assert_eq!(traf.trun.len(), 1);
assert_eq!(traf.order.len(), 3);
let out = traf.to_bytes();
assert_eq!(&out[8..], body, "children must round-trip in order");
}
#[test]
fn moof_preserves_unmodelled_children_in_wire_order() {
let body: &[u8] = &[
0, 0, 0, 16, b'm', b'f', b'h', b'd', 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 12, b'm', b'e', b't', b'a', 0, 0, 0, 0, 0, 0, 0, 24, b't', b'r', b'a', b'f', 0, 0, 0, 16, b't', b'f', b'h', b'd', 0, 0, 0, 0, 0, 0, 0, 1,
];
let moof = MovieFragmentBox::parse_body(body).expect("parses");
assert_eq!(moof.mfhd.sequence_number, 3);
assert_eq!(moof.traf.len(), 1);
assert_eq!(moof.order.len(), 3);
let out = moof.to_bytes();
assert_eq!(&out[8..], body, "moof must round-trip byte-identically");
}
#[test]
fn real_fixture_first_moof_byte_identical() {
let data = std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../fixtures/transmux/h264_aac_frag.mp4"
))
.unwrap();
use crate::box_types::parse_box;
let mut remaining: &[u8] = &data;
while !remaining.is_empty() {
let (bx, consumed) = parse_box(remaining).unwrap();
if bx.header.box_type.is(b"moof") {
let moof_bytes = &remaining[..consumed];
let parsed = MovieFragmentBox::parse_body(bx.body).unwrap();
assert_eq!(parsed.mfhd.sequence_number, 1);
assert_eq!(parsed.traf.len(), 2);
assert_eq!(parsed.traf[0].tfhd.track_id, 1);
assert_eq!(parsed.traf[1].tfhd.track_id, 2);
assert!(parsed.traf[0].tfdt.is_some());
assert_eq!(parsed.traf[0].tfdt.unwrap().base_media_decode_time(), 0);
assert_eq!(parsed.traf[0].trun.len(), 1);
assert_eq!(parsed.traf[0].trun[0].samples.len(), 25);
let serialized = parsed.to_bytes();
assert_eq!(serialized.len(), moof_bytes.len());
assert_eq!(
serialized, moof_bytes,
"moof round-trip must be byte-identical"
);
return;
}
if consumed == 0 || consumed >= remaining.len() {
break;
}
remaining = &remaining[consumed..];
}
panic!("moof box not found");
}
#[test]
fn build_cenc_fragment_boxes_omits_senc_for_constant_iv_whole_sample() {
let degenerate_senc_body: [u8; 4] = 2u32.to_be_bytes(); let rejected = crate::cenc::SampleEncryptionBox::parse_body(
°enerate_senc_body,
0,
0, 0, );
assert!(
matches!(rejected, Err(Error::InvalidInput(_))),
"this crate's own senc parser must reject a sample_count with no backing per-sample \
bytes, got {rejected:?}"
);
let entries = alloc::vec![
crate::cenc::SampleEncryptionEntry {
initialization_vector: Vec::new(),
subsamples: Vec::new(),
},
crate::cenc::SampleEncryptionEntry {
initialization_vector: Vec::new(),
subsamples: Vec::new(),
},
];
let protection = FragmentProtection {
track_id: 1,
entries: &entries,
per_sample_iv_size: 0,
};
let built = build_cenc_fragment_boxes(&protection).expect("must not error");
assert!(
built.is_none(),
"a constant-IV whole-sample track has nothing for senc/saiz/saio to carry \
(ISO/IEC 23001-7 §12.2/§12.3) — build_cenc_fragment_boxes must return None, not a \
senc shape this crate's own parser rejects"
);
}
#[test]
fn protect_media_segment_constant_iv_whole_sample_round_trips_with_no_senc() {
const TRACK_ID: u32 = 1;
let tfhd = TrackFragmentHeaderBox {
flags: 0,
track_id: TRACK_ID,
base_data_offset: None,
sample_description_index: None,
default_sample_duration: None,
default_sample_size: None,
default_sample_flags: None,
};
let trun = TrackFragmentRunBox {
version: 0,
tr_flags: TRUN_SAMPLE_SIZE_PRESENT,
data_offset: None,
first_sample_flags: None,
samples: vec![
TrunSample {
sample_duration: None,
sample_size: Some(4),
sample_flags: None,
sample_composition_time_offset: None,
},
TrunSample {
sample_duration: None,
sample_size: Some(4),
sample_flags: None,
sample_composition_time_offset: None,
},
],
};
let traf = TrackFragmentBox::new(tfhd, None, vec![trun]);
let moof = MovieFragmentBox::new(MovieFragmentHeaderBox::new(1), vec![traf]);
let unprotected = moof.to_bytes();
let entries = alloc::vec![
crate::cenc::SampleEncryptionEntry {
initialization_vector: Vec::new(),
subsamples: Vec::new(),
},
crate::cenc::SampleEncryptionEntry {
initialization_vector: Vec::new(),
subsamples: Vec::new(),
},
];
let protection = FragmentProtection {
track_id: TRACK_ID,
entries: &entries,
per_sample_iv_size: 0,
};
let protected = protect_media_segment(&unprotected, &[protection])
.expect("protect_media_segment must not error on a constant-IV whole-sample track");
assert_eq!(
protected, unprotected,
"no senc/saiz/saio bytes must be appended when there is nothing for them to carry"
);
let reparsed = MovieFragmentBox::parse_body(&protected[BOX_HEADER_SIZE..])
.expect("protected output must still parse as a valid moof");
assert_eq!(reparsed.traf.len(), 1);
assert_eq!(reparsed.traf[0].trun[0].samples.len(), 2);
}
}