use std::io::{Seek, SeekFrom, Write};
use oxideav_core::{Error, Muxer, Packet, Result, StreamInfo, WriteSeek};
use crate::muxer::{
build_mdia, build_mvhd, build_tkhd, default_samples_per_chunk, rescale_to_media_ts, wrap_box,
TrackState,
};
use crate::options::{BrandPreset, FragmentCadence, FragmentedOptions, Mp4MuxerOptions};
use crate::sample_entries::sample_entry_for;
#[derive(Clone, Debug)]
struct PendingSample {
data: Vec<u8>,
duration: u32,
flags: u32,
composition_time_offset: i32,
}
struct FragTrackState {
base: TrackState,
track_id: u32,
trex_default_sample_duration: u32,
trex_default_sample_size: u32,
trex_default_sample_flags: u32,
trex_locked: bool,
pending: Vec<PendingSample>,
next_bmdt: u64,
packets_total: u64,
tfra_entries: Vec<TfraEmitEntry>,
}
#[derive(Clone, Copy, Debug)]
struct TfraEmitEntry {
time: u64,
moof_offset: u64,
traf_number: u32,
trun_number: u32,
sample_number: u32,
}
impl FragTrackState {
fn new(base: TrackState, track_id: u32) -> Self {
Self {
base,
track_id,
trex_default_sample_duration: 0,
trex_default_sample_size: 0,
trex_default_sample_flags: 0,
trex_locked: false,
pending: Vec::new(),
next_bmdt: 0,
packets_total: 0,
tfra_entries: Vec::new(),
}
}
fn lock_trex(&mut self, duration: u32, size: u32, flags: u32) {
if self.trex_locked {
return;
}
self.trex_default_sample_duration = duration;
self.trex_default_sample_size = size;
self.trex_default_sample_flags = flags;
self.trex_locked = true;
}
}
const SAMPLE_IS_NON_SYNC: u32 = 0x0001_0000;
const SAMPLE_DEPENDS_ON_NONE: u32 = 0x0200_0000;
fn sample_flags_for(keyframe: bool) -> u32 {
if keyframe {
SAMPLE_DEPENDS_ON_NONE
} else {
SAMPLE_IS_NON_SYNC
}
}
pub(crate) fn open_fragmented(
output: Box<dyn WriteSeek>,
streams: &[StreamInfo],
options: Mp4MuxerOptions,
frag_options: FragmentedOptions,
) -> Result<Box<dyn Muxer>> {
if streams.is_empty() {
return Err(Error::invalid("mp4 muxer: need at least one stream"));
}
let mut tracks = Vec::with_capacity(streams.len());
for (i, s) in streams.iter().enumerate() {
let entry = sample_entry_for(&s.params)?;
let mut base = TrackState::new(s.clone(), entry);
base.samples_per_chunk_target = default_samples_per_chunk(&base.stream);
tracks.push(FragTrackState::new(base, (i as u32) + 1));
}
Ok(Box::new(FragmentedMuxer {
output,
tracks,
options,
frag_options,
sequence_number: 0,
header_written: false,
trailer_written: false,
}))
}
struct FragmentedMuxer {
output: Box<dyn WriteSeek>,
tracks: Vec<FragTrackState>,
options: Mp4MuxerOptions,
frag_options: FragmentedOptions,
sequence_number: u32,
header_written: bool,
trailer_written: bool,
}
impl Muxer for FragmentedMuxer {
fn format_name(&self) -> &str {
match (&self.options.brand, self.options.fragmented.is_some()) {
(BrandPreset::Ismv, true) => "ismv",
(BrandPreset::Mov, _) => "mov",
_ => "mp4",
}
}
fn write_header(&mut self) -> Result<()> {
if self.header_written {
return Err(Error::other("mp4 muxer: write_header called twice"));
}
let ftyp = build_ftyp(&self.options.brand);
self.output.write_all(&ftyp)?;
let moov = build_init_moov(&self.tracks)?;
self.output.write_all(&moov)?;
self.header_written = true;
Ok(())
}
fn write_packet(&mut self, packet: &Packet) -> Result<()> {
if !self.header_written {
return Err(Error::other("mp4 muxer: write_header not called"));
}
let idx = packet.stream_index as usize;
if idx >= self.tracks.len() {
return Err(Error::invalid(format!(
"mp4 muxer: unknown stream index {idx}"
)));
}
let media_ts = self.tracks[idx].base.media_time_scale;
let dur = if let Some(d) = packet.duration {
let v = rescale_to_media_ts(d, packet.time_base, media_ts);
if v > 0 {
v as u32
} else {
1
}
} else if let (Some(prev), Some(cur)) = (
self.tracks[idx].base.prev_pts_in_ts,
packet
.pts
.map(|v| rescale_to_media_ts(v, packet.time_base, media_ts)),
) {
((cur - prev).max(0) as u32).max(1)
} else {
1
};
let cts_off = match (packet.pts, packet.dts) {
(Some(p), Some(d)) => {
let pp = rescale_to_media_ts(p, packet.time_base, media_ts);
let dd = rescale_to_media_ts(d, packet.time_base, media_ts);
(pp - dd) as i32
}
_ => 0,
};
let flags = sample_flags_for(packet.flags.keyframe);
let size = packet.data.len() as u32;
let track = &mut self.tracks[idx];
track.lock_trex(dur, size, flags);
track.pending.push(PendingSample {
data: packet.data.clone(),
duration: dur,
flags,
composition_time_offset: cts_off,
});
let pts_in_ts = packet
.pts
.map(|v| rescale_to_media_ts(v, packet.time_base, media_ts));
if let Some(p) = pts_in_ts {
if track.base.first_pts_in_ts.is_none() {
track.base.first_pts_in_ts = Some(p);
}
track.base.prev_pts_in_ts = Some(p);
} else {
let base = track.base.prev_pts_in_ts.unwrap_or(0);
track.base.prev_pts_in_ts = Some(base + dur as i64);
if track.base.first_pts_in_ts.is_none() {
track.base.first_pts_in_ts = Some(0);
}
}
track.base.cumulative_duration += dur as u64;
track.packets_total += 1;
if self.should_flush(idx, packet.flags.keyframe) {
self.flush_fragment()?;
}
Ok(())
}
fn write_trailer(&mut self) -> Result<()> {
if self.trailer_written {
return Ok(());
}
if !self.header_written {
return Err(Error::other("mp4 muxer: write_trailer before write_header"));
}
if self.tracks.iter().any(|t| !t.pending.is_empty()) {
self.flush_fragment()?;
}
if self.frag_options.emit_random_access_indexes
&& self.tracks.iter().any(|t| !t.tfra_entries.is_empty())
{
self.write_mfra()?;
}
self.output.flush()?;
self.trailer_written = true;
Ok(())
}
}
impl FragmentedMuxer {
fn should_flush(&self, current_track_idx: usize, is_keyframe: bool) -> bool {
match self.frag_options.cadence {
FragmentCadence::EverySeconds(secs) => {
let anchor = 0usize;
if self.tracks[anchor].pending.is_empty() {
return false;
}
let media_ts = self.tracks[anchor].base.media_time_scale as f64;
let pending_ticks: u64 = self.tracks[anchor]
.pending
.iter()
.map(|s| s.duration as u64)
.sum();
(pending_ticks as f64 / media_ts) >= secs
}
FragmentCadence::EveryKeyframe => {
let anchor = 0usize;
if current_track_idx != anchor {
return false;
}
if !is_keyframe {
return false;
}
self.tracks[anchor].pending.len() >= 2
}
FragmentCadence::EveryNPackets(n) => {
if n == 0 {
return false;
}
let anchor = 0usize;
if current_track_idx != anchor {
return false;
}
self.tracks[anchor].pending.len() as u32 >= n
}
}
}
fn flush_fragment(&mut self) -> Result<()> {
let mut detached: Vec<(usize, PendingSample)> = Vec::new();
if matches!(self.frag_options.cadence, FragmentCadence::EveryKeyframe) {
let anchor = 0usize;
if let Some(last) = self.tracks[anchor].pending.last() {
if last.flags & SAMPLE_IS_NON_SYNC == 0 {
let s = self.tracks[anchor].pending.pop().unwrap();
self.tracks[anchor].base.cumulative_duration = self.tracks[anchor]
.base
.cumulative_duration
.saturating_sub(s.duration as u64);
detached.push((anchor, s));
}
}
}
if self.tracks.iter().all(|t| t.pending.is_empty()) {
for (idx, s) in detached {
self.tracks[idx].base.cumulative_duration += s.duration as u64;
self.tracks[idx].pending.push(s);
}
return Ok(());
}
self.sequence_number += 1;
let seq = self.sequence_number;
let moof = build_moof(seq, &self.tracks)?;
let moof_size = moof.len() as u64;
let mut mdat_payload: Vec<u8> = Vec::new();
for t in &self.tracks {
for s in &t.pending {
mdat_payload.extend_from_slice(&s.data);
}
}
let mdat = wrap_box(b"mdat", &mdat_payload);
let mdat_size = mdat.len() as u64;
if self.frag_options.emit_random_access_indexes {
let styp_size: u64 = self
.frag_options
.styp
.as_ref()
.map(|b| build_styp(b).len() as u64)
.unwrap_or(0);
let subsegment_size = styp_size + moof_size + mdat_size;
let anchor_idx = self
.tracks
.iter()
.position(|t| !t.pending.is_empty())
.unwrap_or(0);
let ept = self.tracks[anchor_idx].next_bmdt;
let timescale = self.tracks[anchor_idx].base.media_time_scale;
let frag_dur_anchor: u64 = self.tracks[anchor_idx]
.pending
.iter()
.map(|s| s.duration as u64)
.sum();
let subseg_dur_u32 = frag_dur_anchor.min(u32::MAX as u64) as u32;
let starts_sap = self.tracks[anchor_idx]
.pending
.first()
.map(|s| s.flags & SAMPLE_IS_NON_SYNC == 0)
.unwrap_or(false);
let sidx = build_sidx(
self.tracks[anchor_idx].track_id,
timescale,
ept,
subsegment_size,
subseg_dur_u32,
starts_sap,
);
self.output.write_all(&sidx)?;
}
if let Some(brand) = &self.frag_options.styp {
let styp = build_styp(brand);
self.output.write_all(&styp)?;
}
let moof_offset = self.output.stream_position()?;
self.output.write_all(&moof)?;
self.output.write_all(&mdat)?;
for t in self.tracks.iter_mut() {
if t.pending.is_empty() {
continue;
}
let mut dts_in_frag: u64 = 0;
for (k, s) in t.pending.iter().enumerate() {
let is_sync = s.flags & SAMPLE_IS_NON_SYNC == 0;
if is_sync {
t.tfra_entries.push(TfraEmitEntry {
time: t.next_bmdt + dts_in_frag,
moof_offset,
traf_number: 1,
trun_number: 1,
sample_number: (k as u32) + 1,
});
}
dts_in_frag += s.duration as u64;
}
}
for t in self.tracks.iter_mut() {
let frag_dur: u64 = t.pending.iter().map(|s| s.duration as u64).sum();
t.next_bmdt += frag_dur;
t.pending.clear();
}
for (idx, s) in detached {
self.tracks[idx].base.cumulative_duration += s.duration as u64;
self.tracks[idx].pending.push(s);
}
let _ = moof_size;
Ok(())
}
fn write_mfra(&mut self) -> Result<()> {
let mut mfra_body: Vec<u8> = Vec::new();
for t in &self.tracks {
if t.tfra_entries.is_empty() {
continue;
}
mfra_body.extend_from_slice(&build_tfra(t.track_id, &t.tfra_entries));
}
let mfra_total_size: u64 = 8 + mfra_body.len() as u64 + 16;
let mfro = build_mfro(mfra_total_size as u32);
mfra_body.extend_from_slice(&mfro);
let mfra = wrap_box(b"mfra", &mfra_body);
debug_assert_eq!(mfra.len() as u64, mfra_total_size);
self.output.seek(SeekFrom::End(0))?;
self.output.write_all(&mfra)?;
Ok(())
}
}
fn build_ftyp(brand: &BrandPreset) -> Vec<u8> {
let major = brand.major_brand();
let compat = brand.compatible_brands();
let mut body = Vec::with_capacity(8 + 4 * compat.len());
body.extend_from_slice(&major);
let minor: u32 = match brand {
BrandPreset::Mp4 => 0x0000_0200,
_ => 0,
};
body.extend_from_slice(&minor.to_be_bytes());
for b in &compat {
body.extend_from_slice(b);
}
wrap_box(b"ftyp", &body)
}
fn build_styp(brand: &BrandPreset) -> Vec<u8> {
let major = brand.major_brand();
let compat = brand.compatible_brands();
let mut body = Vec::with_capacity(8 + 4 * compat.len());
body.extend_from_slice(&major);
body.extend_from_slice(&0u32.to_be_bytes()); for b in &compat {
body.extend_from_slice(b);
}
wrap_box(b"styp", &body)
}
fn build_init_moov(tracks: &[FragTrackState]) -> Result<Vec<u8>> {
let movie_timescale: u32 = 1000;
let mut moov_body = Vec::new();
moov_body.extend_from_slice(&build_mvhd(movie_timescale, 0, (tracks.len() as u32) + 1));
for t in tracks {
moov_body.extend_from_slice(&build_trak_init(t.track_id, &t.base, movie_timescale)?);
}
moov_body.extend_from_slice(&build_mvex(tracks));
Ok(wrap_box(b"moov", &moov_body))
}
fn build_trak_init(track_id: u32, t: &TrackState, movie_timescale: u32) -> Result<Vec<u8>> {
let mut body = Vec::new();
body.extend_from_slice(&build_tkhd(track_id, 0, &t.stream));
body.extend_from_slice(&build_mdia(t)?);
let _ = movie_timescale;
Ok(wrap_box(b"trak", &body))
}
fn build_mvex(tracks: &[FragTrackState]) -> Vec<u8> {
let mut body = Vec::new();
for t in tracks {
body.extend_from_slice(&build_trex(
t.track_id,
t.trex_default_sample_duration,
t.trex_default_sample_size,
t.trex_default_sample_flags,
));
}
wrap_box(b"mvex", &body)
}
fn build_trex(track_id: u32, ddur: u32, dsiz: u32, dflg: u32) -> Vec<u8> {
let mut body = Vec::with_capacity(24);
body.extend_from_slice(&[0u8; 4]); body.extend_from_slice(&track_id.to_be_bytes());
body.extend_from_slice(&1u32.to_be_bytes()); body.extend_from_slice(&ddur.to_be_bytes());
body.extend_from_slice(&dsiz.to_be_bytes());
body.extend_from_slice(&dflg.to_be_bytes());
wrap_box(b"trex", &body)
}
fn build_moof(seq: u32, tracks: &[FragTrackState]) -> Result<Vec<u8>> {
let placeholder = build_moof_inner(seq, tracks, |_track_idx, _byte_in_mdat| 0)?;
let moof_size = placeholder.len() as u64;
let mdat_header_size: u64 = 8;
let final_moof = build_moof_inner(seq, tracks, |_track_idx, byte_in_mdat| {
(moof_size + mdat_header_size + byte_in_mdat) as i32
})?;
debug_assert_eq!(final_moof.len() as u64, moof_size, "moof size shifted");
Ok(final_moof)
}
fn build_moof_inner<F>(seq: u32, tracks: &[FragTrackState], offset_fn: F) -> Result<Vec<u8>>
where
F: Fn(usize, u64) -> i32,
{
let mut moof_body = Vec::new();
moof_body.extend_from_slice(&build_mfhd(seq));
let mut byte_in_mdat: u64 = 0;
for (i, t) in tracks.iter().enumerate() {
if t.pending.is_empty() {
continue;
}
let track_first_byte = byte_in_mdat;
let trun_data_offset = offset_fn(i, track_first_byte);
let traf = build_traf(t, trun_data_offset)?;
moof_body.extend_from_slice(&traf);
for s in &t.pending {
byte_in_mdat += s.data.len() as u64;
}
}
Ok(wrap_box(b"moof", &moof_body))
}
fn build_mfhd(seq: u32) -> Vec<u8> {
let mut body = Vec::with_capacity(8);
body.extend_from_slice(&[0u8; 4]); body.extend_from_slice(&seq.to_be_bytes());
wrap_box(b"mfhd", &body)
}
fn build_traf(t: &FragTrackState, trun_data_offset: i32) -> Result<Vec<u8>> {
let defaults = FragmentDefaults::for_track(t);
let mut body = Vec::new();
body.extend_from_slice(&build_tfhd(t, defaults));
body.extend_from_slice(&build_tfdt(t.next_bmdt));
body.extend_from_slice(&build_trun(t, trun_data_offset, defaults));
Ok(wrap_box(b"traf", &body))
}
const TFHD_BASE_DATA_OFFSET_PRESENT: u32 = 0x000001;
const TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT: u32 = 0x000002;
const TFHD_DEFAULT_SAMPLE_DURATION_PRESENT: u32 = 0x000008;
const TFHD_DEFAULT_SAMPLE_SIZE_PRESENT: u32 = 0x000010;
const TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT: u32 = 0x000020;
const TFHD_DEFAULT_BASE_IS_MOOF: u32 = 0x020000;
#[derive(Clone, Copy)]
struct FragmentDefaults {
homogeneous_size: Option<u32>,
homogeneous_duration: Option<u32>,
homogeneous_flags: Option<u32>,
first_sample_distinct: bool,
first_sample_flags: u32,
}
impl FragmentDefaults {
fn for_track(t: &FragTrackState) -> Self {
let homogeneous_size = t
.pending
.first()
.map(|s| s.data.len() as u32)
.filter(|&sz| t.pending.iter().all(|s| s.data.len() as u32 == sz));
let homogeneous_duration = t
.pending
.first()
.map(|s| s.duration)
.filter(|&d| t.pending.iter().all(|s| s.duration == d));
let (homogeneous_flags, first_sample_distinct, first_sample_flags) = match t.pending.len() {
0 => (None, false, 0),
1 => (t.pending.first().map(|s| s.flags), false, 0),
_ => {
let all_same = t
.pending
.first()
.map(|s| s.flags)
.filter(|&f| t.pending.iter().all(|s| s.flags == f));
if all_same.is_some() {
(all_same, false, 0)
} else {
let tail_first = t.pending[1].flags;
let tail_same = t.pending[1..].iter().all(|s| s.flags == tail_first);
if tail_same {
(Some(tail_first), true, t.pending[0].flags)
} else {
(None, false, 0)
}
}
}
};
Self {
homogeneous_size,
homogeneous_duration,
homogeneous_flags,
first_sample_distinct,
first_sample_flags,
}
}
}
fn build_tfhd(t: &FragTrackState, defaults: FragmentDefaults) -> Vec<u8> {
let mut flags = TFHD_DEFAULT_BASE_IS_MOOF;
if defaults.homogeneous_duration.is_some() {
flags |= TFHD_DEFAULT_SAMPLE_DURATION_PRESENT;
}
if defaults.homogeneous_size.is_some() {
flags |= TFHD_DEFAULT_SAMPLE_SIZE_PRESENT;
}
if defaults.homogeneous_flags.is_some() {
flags |= TFHD_DEFAULT_SAMPLE_FLAGS_PRESENT;
}
let mut body = Vec::new();
body.push(0); body.extend_from_slice(&flags.to_be_bytes()[1..4]);
body.extend_from_slice(&t.track_id.to_be_bytes());
let _ = TFHD_BASE_DATA_OFFSET_PRESENT;
let _ = TFHD_SAMPLE_DESCRIPTION_INDEX_PRESENT;
if let Some(d) = defaults.homogeneous_duration {
body.extend_from_slice(&d.to_be_bytes());
}
if let Some(sz) = defaults.homogeneous_size {
body.extend_from_slice(&sz.to_be_bytes());
}
if let Some(f) = defaults.homogeneous_flags {
body.extend_from_slice(&f.to_be_bytes());
}
wrap_box(b"tfhd", &body)
}
fn build_tfdt(bmdt: u64) -> Vec<u8> {
let mut body = Vec::with_capacity(12);
body.push(1); body.extend_from_slice(&[0u8; 3]);
body.extend_from_slice(&bmdt.to_be_bytes());
wrap_box(b"tfdt", &body)
}
const TRUN_DATA_OFFSET_PRESENT: u32 = 0x000001;
const TRUN_FIRST_SAMPLE_FLAGS_PRESENT: u32 = 0x000004;
const TRUN_SAMPLE_DURATION_PRESENT: u32 = 0x000100;
const TRUN_SAMPLE_SIZE_PRESENT: u32 = 0x000200;
const TRUN_SAMPLE_FLAGS_PRESENT: u32 = 0x000400;
const TRUN_SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT: u32 = 0x000800;
fn build_trun(t: &FragTrackState, data_offset: i32, defaults: FragmentDefaults) -> Vec<u8> {
let need_per_sample_dur = defaults.homogeneous_duration.is_none();
let need_per_sample_size = defaults.homogeneous_size.is_none();
let need_per_sample_flags = defaults.homogeneous_flags.is_none();
let need_cts = t.pending.iter().any(|s| s.composition_time_offset != 0);
let mut flags = TRUN_DATA_OFFSET_PRESENT;
if defaults.first_sample_distinct {
flags |= TRUN_FIRST_SAMPLE_FLAGS_PRESENT;
}
if need_per_sample_dur {
flags |= TRUN_SAMPLE_DURATION_PRESENT;
}
if need_per_sample_size {
flags |= TRUN_SAMPLE_SIZE_PRESENT;
}
if need_per_sample_flags {
flags |= TRUN_SAMPLE_FLAGS_PRESENT;
}
if need_cts {
flags |= TRUN_SAMPLE_COMPOSITION_TIME_OFFSETS_PRESENT;
}
let mut body = Vec::new();
body.push(1);
body.extend_from_slice(&flags.to_be_bytes()[1..4]);
body.extend_from_slice(&(t.pending.len() as u32).to_be_bytes());
body.extend_from_slice(&data_offset.to_be_bytes());
if defaults.first_sample_distinct {
body.extend_from_slice(&defaults.first_sample_flags.to_be_bytes());
}
for s in &t.pending {
if need_per_sample_dur {
body.extend_from_slice(&s.duration.to_be_bytes());
}
if need_per_sample_size {
body.extend_from_slice(&(s.data.len() as u32).to_be_bytes());
}
if need_per_sample_flags {
body.extend_from_slice(&s.flags.to_be_bytes());
}
if need_cts {
body.extend_from_slice(&s.composition_time_offset.to_be_bytes());
}
}
wrap_box(b"trun", &body)
}
fn build_sidx(
reference_id: u32,
timescale: u32,
earliest_presentation_time: u64,
referenced_size: u64,
subsegment_duration: u32,
starts_with_sap: bool,
) -> Vec<u8> {
let mut body = Vec::with_capacity(28 + 12);
body.push(1); body.extend_from_slice(&[0u8; 3]); body.extend_from_slice(&reference_id.to_be_bytes());
body.extend_from_slice(×cale.to_be_bytes());
body.extend_from_slice(&earliest_presentation_time.to_be_bytes());
body.extend_from_slice(&0u64.to_be_bytes()); body.extend_from_slice(&0u16.to_be_bytes()); body.extend_from_slice(&1u16.to_be_bytes()); let r0 = (referenced_size.min(0x7FFF_FFFF) as u32) & 0x7FFF_FFFF; body.extend_from_slice(&r0.to_be_bytes());
body.extend_from_slice(&subsegment_duration.to_be_bytes());
let sap_type: u32 = if starts_with_sap { 1 } else { 0 }; let r2 = (if starts_with_sap { 0x8000_0000u32 } else { 0 }) | (sap_type << 28);
body.extend_from_slice(&r2.to_be_bytes());
wrap_box(b"sidx", &body)
}
fn build_tfra(track_id: u32, entries: &[TfraEmitEntry]) -> Vec<u8> {
let mut body = Vec::with_capacity(16 + entries.len() * 19);
body.push(1); body.extend_from_slice(&[0u8; 3]); body.extend_from_slice(&track_id.to_be_bytes());
body.extend_from_slice(&0u32.to_be_bytes());
body.extend_from_slice(&(entries.len() as u32).to_be_bytes());
for e in entries {
body.extend_from_slice(&e.time.to_be_bytes());
body.extend_from_slice(&e.moof_offset.to_be_bytes());
body.push(e.traf_number.min(u8::MAX as u32) as u8);
body.push(e.trun_number.min(u8::MAX as u32) as u8);
body.push(e.sample_number.min(u8::MAX as u32) as u8);
}
wrap_box(b"tfra", &body)
}
fn build_mfro(mfra_total_size: u32) -> Vec<u8> {
let mut body = Vec::with_capacity(8);
body.extend_from_slice(&[0u8; 4]); body.extend_from_slice(&mfra_total_size.to_be_bytes());
wrap_box(b"mfro", &body)
}