oxideav-mp4
Pure-Rust MP4 / ISO Base Media File Format container — demuxer
(probe + sample-table expansion + seek) and muxer (moov-at-end by
default, optional faststart rewrite). Three brand presets share one
implementation: mp4, mov (QuickTime), and ismv (Smooth Streaming
ftyp, non-fragmented layout). Zero C dependencies.
Part of the oxideav framework but usable standalone.
Installation
[]
= "0.1"
= "0.1"
= "0.1"
= "0.0"
Quick use
Demux an MP4 and feed packets into a codec
use CodecRegistry;
use ContainerRegistry;
let mut codecs = new;
let mut containers = new;
register;
// ... register whichever codecs you care about (aac, flac, h264, mjpeg, ...)
let input: =
Boxnew;
let mut dmx = containers.open?;
// Sample entries are resolved to concrete codec ids. For `mp4a`/`mp4v`
// tracks the esds `objectTypeIndication` is honoured, so MP3-in-mp4
// comes out as "mp3", MPEG-1 video as "mpeg1video", AAC as "aac", etc.
let stream = &dmx.streams;
let mut dec = codecs.make_decoder?;
loop
# Ok::
Mux packets into an MP4
use WriteSeek;
let f = create?;
let ws: = Boxnew;
let mut mux = open?;
mux.write_header?;
for pkt in packets
mux.write_trailer?;
# Ok::
Faststart (moov-at-front) layout
use ;
let opts = Mp4MuxerOptions ;
let mut mux = open_with_options?;
In faststart mode the muxer buffers mdat in memory and writes
[ftyp][moov][mdat] at write_trailer time, patching chunk offsets so
the file is streamable from the first byte.
Scope
Demuxer
Sample-entry FourCCs resolve to these codec ids:
| FourCC | Codec id |
|---|---|
mp4a |
aac (default); esds OTI refines to mp3 (0x69/0x6B), ac3 (0xA5), eac3 (0xA6), dts (0xA9) |
mp4v |
mpeg4video (default); esds OTI refines to mpeg1video, mpeg2video, h264, h265, mjpeg |
alac |
alac |
fLaC / flac |
flac |
Opus / opus |
opus |
ac-3 / AC-3 |
ac3 (Dolby Digital, ETSI TS 102 366 Annex F) |
ec-3 / EC-3 |
eac3 (Dolby Digital Plus, ETSI TS 102 366 Annex G) |
dtsc / dtsh / dtsl / dtse |
dts (DTS Coherent Acoustics / DTS-HD HR / DTS-HD MA / DTS Express, ETSI TS 102 114) |
ulaw / alaw |
pcm_mulaw / pcm_alaw (G.711) |
avc1 / avc3 |
h264 |
hvc1 / hev1 |
h265 |
vp08 |
vp8 |
vp09 |
vp9 |
av01 |
av1 |
jpeg / mjpa / mjpb |
mjpeg |
s263 / h263 |
h263 |
lpcm / sowt / twos |
pcm_s16le (endianness of twos is not re-swapped) |
tx3g |
mov_text (3GPP TS 26.245 timed text — "movtext") |
text |
text (QuickTime plain text) |
wvtt |
webvtt (W3C WebVTT-in-ISOBMFF) |
stpp |
ttml (XML / TTML subtitle) |
sbtt / stxt |
sbtt / stxt (BMFF §12.5–6 text) |
c608 / c708 |
eia_608 / eia_708 (closed captions) |
encv / enca / enct / encs |
original FourCC recovered from sinf/frma; params.options["protection_scheme"] carries the schm.scheme_type (e.g. cenc, cbcs) |
| any other | mp4:<fourcc> — callers can register their own decoder |
-
Codec-specific config records (
avcC,hvcC,av1C,vpcC,dfLa,dOps,dac3,dec3, esds DSI) are forwarded asextradata. -
Sample-table expansion:
stts,stsc,stsz/stz2,stco/co64,stss.next_packetserves samples in file-offset order. -
Fragmented MP4 (DASH / HLS / Smooth Streaming / CMAF):
mvex/trexper-track defaults plus zero or more trailingmoof+mdatpairs (mfhd,traf,tfhd,tfdt,trun) are stitched onto the initial sample table.default-base-is-moof, per-sample size/duration/flags/composition-time-offset overrides, andtfhd.base_data_offsetare all honoured.styp,sidx, andmfrasegment-index boxes are skipped (segment-precision seek hint consumption is a follow-up). -
Movie extends header (ISO/IEC 14496-12 §8.8.2,
mehd): a sealed fragmented file'smvex/mehdMovieExtendsHeaderBox carries the overall presentationfragment_duration(in the movie timescale, per §8.8.2.3) — including allmooffragments — that an authoring step can write once the fragments are laid down. Both versions are read (v0 32-bit, v1 64-bit widened tou64). When present and non-zero, the value drivesDemuxer::duration_microsand takes precedence overmvhd.duration— sealed fragmented files commonly carrymvhd.duration = 0(the moov has no resident samples that contribute to a non-fragment duration) andmehdis then the only authoritative total. The raw value (in the movie timescale) is also surfaced verbatim viaDemuxer::metadata()as themehd_fragment_durationkey, for tooling wanting the untranslated number (e.g. CMAF live-edge probes confirming a producer's sealed total against their own running tally). Absentmehd, the key is not emitted andduration_microsfalls back tomvhd.durationas before. -
Seek:
seek_to(stream, pts)lands on the nearest sync-sample ≤ pts (or the first keyframe of the stream if none qualify). -
Metadata: 3GPP
udtaboxes (titl/auth/…) and iTunes-stylemeta/ilstare surfaced viaDemuxer::metadata(). -
Extended language tag (ISO/IEC 14496-12 §8.4.6,
elng): a track'smdia/elngExtendedLanguageBox carries a NULL-terminated BCP 47 (RFC 4646) tag richer thanmdhd's packed 3-char ISO 639-2 code (region / script / variant subtags). When present it is surfaced onparams.options["language"](e.g.en-US,zh-Hant-HK,es-419) and, per §8.4.6.1, overrides themdhdlanguage. Absentelng, the option is omitted (callers fall back tomdhd). -
Handler-type recognition (ISO/IEC 14496-12 §8.4.3):
soun→ Audio,vide→ Video,subt/sbtl/text→ Subtitle,meta→ Data. Subtitle sample entries (tx3g,text,wvtt,stpp,sbtt,stxt,c608,c708) come out withMediaType::Subtitleand the per-codec id from the table above; their post-preamble payload (BMFF strings / tx3g header / vttC) is preserved verbatim inparams.extradatafor downstream renderers. -
Protected sample-entry unwrap (ISO/IEC 14496-12 §8.12): when the outer FourCC is
encv/enca/enct/encs, the demuxer walks the innersinfto recover the original codec FourCC fromfrmaand the protection scheme fromschm. The stream surfaces the un-transformed codec id so downstream decoders can be set up normally;params.options["protection_scheme"]carries the four-char scheme type (e.g.cenc,cbcs) so callers know packet payloads are still ciphertext. -
CENC metadata parsing (ISO/IEC 23001-7:2016): the three boxes that carry encryption framing on top of the §8.12 envelope are parsed structurally and surfaced to callers; the AES-128 CTR / CBC decryption step itself lives in the
cenc_ciphermodule (key acquisition stays with the caller).tenc(§8.2 TrackEncryptionBox) — discovered insidesinf/schi. v0 capturesdefault_isProtected,default_Per_Sample_IV_Size, anddefault_KID; v1 adds thedefault_crypt_byte_block/default_skip_byte_blockpattern pair (forcens/cbcsschemes) and thedefault_constant_IVused whenisProtected==1 && IV_size==0. Surfaced onparams.optionsascenc_default_kid(lowercase hex),cenc_default_is_protected,cenc_default_iv_size,cenc_tenc_version, and (v1 only)cenc_default_crypt_byte_block/cenc_default_skip_byte_block/cenc_default_constant_iv.pssh(§8.1 ProtectionSystemSpecificHeaderBox) — collected at moov level. Each entry captures the 16-byte SystemID UUID, optional v1 KID list, and the DRM-system-specific opaqueDatablob. Surfaced viaDemuxer::metadata()aspssh_<n>keys with value"<system_id_hex> <kid_count> <data_len>"; structured records are reachable through the publiccenc::PsshBoxtype for callers that downcast.- moof-level
pssh(§8.1.1 — "Container: Movie (moov) or Movie Fragment (moof)"). pssh boxes that live directly inside amoofare collected per fragment and keyed by the enclosingmfhd.sequence_numberso a downstream DRM layer can honour the §8.1.1 reader rule "examine all Protection System Specific Header boxes in the Movie Box and in the Movie Fragment Box associated with the sample (but not those in other Movie Fragment Boxes)". Surfaced viaDemuxer::metadata()asmoof_pssh_<n>keys with value"systemid=<hex> seq=<mfhd_seq> kids=<n> data=<len>"; structured records are reachable through the publicdemux::MoofPsshRecordtype (one record per box in moof-walk order,version0 + v1 with KID list both supported). A malformed pssh inside a moof is dropped without aborting the fragment, mirroring the moov-level recovery policy. senc(§7.2 SampleEncryptionBox) — collected from everytrafwhose matching track carried atencdefault (so the per-sample IV width is recoverable per §7.2.3). Capturesflags, the per-sampleInitializationVector, and (whenUseSubSampleEncryptionis set) the{BytesOfClearData, BytesOfProtectedData}subsample map. Surfaced viaDemuxer::metadata()assenc_<n>keys with value"track=<idx> seq=<mfhd_seq> samples=<n> flags=0x<hex>"; structured records are reachable through the publiccenc::SencBoxtype.
The standalone parsers (
cenc::parse_tenc/cenc::parse_pssh/cenc::parse_senc) are public for callers that already have the box body in hand from another path (e.g. a non-MP4 carrier of CENC framing).- Typed scheme decision router (§4.2 + §10). The four
scheme_typeFourCCs defined in ISO/IEC 23001-7:2016 §10 —cenc(AES-CTR full / NAL-subsample),cbc1(AES-CBC full / NAL-subsample),cens(AES-CTR pattern subsample),cbcs(AES-CBC pattern subsample) — are exposed as the typedcenc::CencSchemeenum. The two binary axes a downstream decryptor dispatches on — cipher mode (CTR vs CBC,cenc::CipherMode) and pattern-encryption flag — are surfaced viaCencScheme::cipher_mode()andCencScheme::uses_pattern_encryption(). A scheme value bundled with the parsedtencbecomes acenc::CencSchemeDecision— the typed routing slip a future AES layer can pattern-match against — built viaCencSchemeDecision::new(scheme, tenc)with structural validation only (the scheme'srequired_tenc_version()must matchtenc.version; pattern schemes must carry a non-zero(crypt_byte_block, skip_byte_block)pair per §9.6). The track-default IV-supply discipline (§9.1) — per-sample 8/16-byte IV vs constant IV vs no IV whenisProtected == 0— is recovered viaCencSchemeDecision::iv_supply()as the typedcenc::IvSupplyenum. The bundle is a static dispatch contract built from container-side bytes only; the AES execution layer that consumes it iscenc_cipher::decrypt_sample_in_place, with key material (looked up by KID from the namedpssh.SystemIDsystem) supplied by the caller. Unknownscheme_typeFourCCs are preserved verbatim throughCencScheme::Unknown([u8; 4])so a caller carrying a private DRM dialect can still route on its own table. - Typed
CencSampleEncryptionInformationGroupEntryparser (§6). Aseigsample-group entry overrides the track-defaulttencparameters for a group of samples —(crypt_byte_block, skip_byte_block, isProtected, Per_Sample_IV_Size, KID, constant_IV?)— and is the spec's mechanism for mixing encrypted and unencrypted samples in one track, or for rotating keys per scene.cenc::parse_seig(body)decodes one entry's payload (the opaque blob the existingsgpdgrouping_type = *b"seig"surface already preserves) into the typedcenc::SeigEntry, mirroring the validationparse_tencapplies (constant-IV size ∈ {8, 16}, per-sample IV size ∈ {0, 8, 16}).SeigEntry::iv_supply()and::uses_pattern_encryption()answer the same routing questions as the track-default accessors but at the per-group resolution. §6's "clients SHALL ignore additional bytes after the fields defined" rule is honoured — trailing bytes from a future edition do not fail the parse. - Per-sample cipher walker (§9.4 / §9.5 / §9.6).
cenc::plan_sample_cipher(decision, subsamples, sample_len)consumes a parsedCencSchemeDecisionplus the per-sample subsample list (fromparse_senc) and returns the orderedVec<CipherStep>partition of the sample plaintext into contiguous(offset, len, kind, iv_restart)runs an AES layer iterates:Clearruns pass through verbatim andEncryptedruns feed the cipher mode picked byCencSchemeDecision::cipher_mode(CTR vs CBC). The walker bakes in every §10-registered structural rule — full-sample CTR (§9.4.2) keeps the trailing partial block encrypted, full-sample CBC (§9.4.3) and§9.7whole-block leave it in the clear, subsample non-pattern isClear(BytesOfClearData) + Encrypted(BytesOfProtectedData)per subsample with a continuous chain across them (§9.5.1), subsample pattern walkscrypt_byte_block * 16encrypted +skip_byte_block * 16clear with the trailing partialcrypt_byte_blockreset to clear (§9.6), and thecbcsIV restart per subsample (§9.5.1) lights up only on the firstEncryptedstep of each subsample forcbcs(notcenc/cbc1/cens). §9.5.1 invariants are enforced —Σ (clear+protected) == sample_len, no both-zero subsample, no subsample running past sample_len.IvSupply::NoneandCencScheme::Unknownare rejected so a caller routes through its own "no cipher" / private-dialect path explicitly. - AES-128 CTR / CBC cipher driver (§9.3 / §9.4–§9.7, module
cenc_cipher).cenc_cipher::decrypt_sample_in_place(decision, key, per_sample_iv, subsamples, data)resolves the IV discipline from theCencSchemeDecision(per-sample IV length-checked againsttenc; constant IV pulled fromtenc.default_constant_IV; supplying both is a §9.2 error), builds theCipherStepplan, and executes it in place against AES-128 (theaes/ctr/cbcprimitive crates) — all four §10 schemes decrypt. The §9.1 IV expansion (8-byte IV → bytes 0..8, bytes 8..16 zero) iscenc_cipher::expand_iv; the §9.3 counter is the low 64 bits big-endian, starting at the expanded-IV value and wrapping to zero without carrying into bytes 0..8. §9.5.1 continuity is honoured: one continuous keystream / cipher chain spans the concatenated protected runs (a partial CTR block ending one subsample's protected range continues in the next;censskip blocks consume no keystream; thecbc1chain crosses clear gaps), whilecbcsreseeds its chain from the constant IV at each subsample'siv_restartstep and chains across skip runs within the subsample. Encrypted CBC runs that are not a multiple of 16 bytes are rejected (§9.4.3 / §10.2 — partial blocks are never CBC-encrypted). The step-level engine is public ascenc_cipher::decrypt_steps_in_place(mode, key, iv, steps, data)forseig-overridden sample groups where the caller assembles its own plan/IV pairing. Tested against a FIPS-197 known-answer AES block anchor, first-principles ECB-built §9.3 keystreams (including the 64-bit counter wrap), and synthetic known-key round-trips for every scheme × subsample × pattern shape (cens 2:1 counter continuity, cbcs 1:9 stride with chain-over-skip + per-subsample restart, §9.7 whole-block audio).
-
Track references (ISO/IEC 14496-12 §8.3.3,
tref): each typedTrackReferenceTypeBoxinsidetrak/trefis parsed and the resulting(reference_type → track_IDs)pairs are surfaced onparams.optionsastref_<type>keys whose value is a space-separated list of referencedtrack_IDs (e.g.tref_chap = "3",tref_subt = "10 11",tref_cdsc = "2"). Useful for wiring subtitle→video (subt), chapter (chap), content description (cdsc), font (font), hint (hint), depth / parallax auxiliary video (vdep/vplx), and hint dependency (hind) relationships.track_ID = 0entries (spec-prohibited) are dropped. -
Track groups (ISO/IEC 14496-12 §8.3.4,
trgr): eachTrackGroupTypeBoxchild insidetrak/trgris parsed as a(track_group_type, track_group_id)pair — the child's FourCC names the grouping (msrcis the spec-named example, for multi-source presentations: tracks sharing atrack_group_idunder themsrctype originate from the same source, e.g. one participant's audio + video in a recorded video call) and the 32-bittrack_group_ididentifies the group within the file. Two tracks carrying the same(type, id)pair belong to the same group (§8.3.4.3); track groups are not dependency relationships (usetreffor those). Each child is surfaced onparams.optionsastrgr_<n>(0-based encounter index) with value"<type> <id>". The spec leaves the door open for two children of the same type on one track (unliketrefwhich caps at one perreference_type) so both encounter-order copies are preserved. Bytes trailing the 32-bit id inside a child are reserved for per-track_group_typeextensions in derived specs and silently ignored at this layer; a child whoseversionfield is non-zero (§8.3.4.2 pins it to 0) is also silently skipped, so unknown extensions never mis-parse. Absenttrgr, none of the keys are emitted. -
Track Kind (ISO/IEC 14496-12 §8.10.4,
kind): eachKindBoxinside a track-leveludtais parsed as a(schemeURI, value)pair (both NULL-terminated C strings; an absent value is allowed, meaning the URI alone identifies the kind). Multiplekindboxes per track are supported — the spec explicitly allows several schemes co-labelling the same track (e.g. one DASH roleurn:mpeg:dash:role:2011 mainplus one iTunes-scheme tag). Each entry is surfaced onparams.optionsaskind_<n>(0-based encounter index); the value is the URI alone when no name follows, or"URI value"(space-separated, mirroring thetref_<type>convention) when both are present. -
Track copyright (ISO/IEC 14496-12 §8.10.2,
cprt): eachCopyrightBoxinside a track-leveludtais parsed as a typed record carrying a 3-letter ISO 639-2/T language code and a decoded notice string. The 16-bit packed language word (§8.10.2.3 — bit 15 pad + three 5-bit characters at(ASCII - 0x60)) is decoded back to lowercase ASCII; the notice C string is UTF-8 by default and UTF-16BE when it opens with the byte-order mark (0xFE 0xFF), with the trailing NUL terminator stripped. Multiplecprtboxes per track are supported — the spec's "Quantity: Zero or more" covers the multilingual case (one box per language). Each record is surfaced onparams.optionsas the paircopyright_<n>(the notice; key omitted when the notice is empty) andcopyright_<n>_lang(the 3-letter tag; emitted even with an empty notice so callers can tell "empty notice ineng" from "nocprt"). A box body shorter than the 6-byte FullBox + language word minimum or an unknown FullBox version (§8.10.2.2 pins it to 0) is silently dropped — the box is informational and a malformed entry never aborts the open. Distinct from the 3GPP TS 26.244cprtshape lumped under the generic file-wide metadata channel, where the language code is not surfaced separately. -
Track selection (ISO/IEC 14496-12 §8.10.3,
tsel): the optionalTrackSelectionBoxinside a track-leveludtacarries two media-selection signals —switch_group(signed 32-bit, §8.10.3.4) groups tracks that are interchangeable during playback (e.g. bitrate-adaptive renditions of the same stream) within the alternate group declared ontkhd(§8.3.2), andattribute_list(§8.10.3.5) is a list of FourCC tags drawn from the descriptive set (tesc= temporal scalability,fgsc/cgsc= SNR scalability,spsc= spatial,resc= region-of-interest,vwsc= view) and differentiating set (bitr= bitrate,cdec= codec,lang= language, …) describing what the track offers. Surfaced onparams.optionsastsel_switch_group(the signed integer, emitted even when zero so callers can tell present-but-zero from absent) andtsel_attributes(space-separated FourCCs, mirroring thetref_<type>convention; omitted when the list is empty). A body shorter than the 8-byte FullBox +switch_groupminimum or an unknown FullBox version is silently dropped —tselis informational and a malformed entry never aborts the open. Container preserves the raw FourCCs; consumer mapping (e.g. a player selecting by language vs. bitrate within an alternate group) is delegated. Absenttsel, no keys are emitted. -
Sub tracks (ISO/IEC 14496-12 §8.14,
strk/stri/strd/stsg): a track-leveludtamay carry zero or morestrkSub Track boxes (§8.14.3), each assigning part of the track to the same alternate / switch groups that whole tracks use (§8.3.2 / §8.10.3) — the mechanism for selecting among layered-codec alternatives (SVC / MVC temporal, spatial, SNR, or view layers) that don't map onto track boundaries (§8.14.1). Eachstrk's mandatorystri(Sub Track Information, §8.14.4) carriesswitch_group(signed 16-bit),alternate_group(signed 16-bit),sub_track_ID(32-bit), and anattribute_list[]of FourCCs from §8.14.4.3's descriptive (tesc/fgsc/cgsc/spsc/resc/vwsc) and differentiating (bitr/frar/nvws/ …) vocabulary. The mandatorystrd(Sub Track Definition, §8.14.5) is walked one level for itsstsg(Sub Track Sample Group, §8.14.6) children — each names agrouping_typeshared with the track'ssbgp/sgpd(§8.9) plus thesgpddescription indices that make up the sub track. Each sub track is surfaced onparams.optionsassubtrack_<n>(0-based encounter index) with value"id=<sub_track_ID> switch=<switch_group> alt=<alternate_group> [attrs=<fourcc...>] [stsg=<grouping_type>:<idx>,<idx>;...]"— the three fixed fields are always present (0 is meaningful — it distinguishes present-but-unassigned from a missing field), theattrs=andstsg=blocks are omitted when empty, and FourCCs with non-printable bytes fall back to 8-digit hex (matching thetsel_attributesconvention). Astrkmissing its mandatorystricontributes no sub track; an unknown FullBox version onstri/stsg, a too-shortstri, or astsgwhose declareditem_countoverruns the body are rejected — the boxes are informational and a malformed entry never aborts the open. Absentstrk, no keys are emitted. -
Composition-to-decode (ISO/IEC 14496-12 §8.6.1.4,
cslg): a track'sstbl/cslgCompositionToDecodeBox documents the composition↔decode timeline relationship implied by a signed (v1)ctts— the DTS shift that guaranteesCTS ≥ DTS, the least / greatest composition offsets, and the composition start / end times. Both v0 (32-bit) and v1 (64-bit) layouts are read (widened toi64). The five fields are surfaced onparams.optionsascslg_composition_to_dts_shift,cslg_least_decode_to_display_delta,cslg_greatest_decode_to_display_delta,cslg_composition_start_time, andcslg_composition_end_time(decimal strings in the media timescale;composition_end_time = 0means "unknown" per §8.6.1.4.3). Absentcslg, none of the keys are emitted. -
Shadow sync samples (ISO/IEC 14496-12 §8.6.3,
stsh): a track'sstbl/stshShadowSyncSampleBox is an optional seek hint — a table of(shadowed_sample_number, sync_sample_number)pairs naming a sync sample (key frame) that can be decoded in place of a non-sync sample when seeking to or before it. Each pair is surfaced onparams.optionsasstsh_<n>(0-based encounter index) with value"shadowed sync"(both 1-based sample numbers, space-separated). The table is purely a seek optimisation — it is ignored in normal forward play and a track decodes correctly without it. Absentstsh, none of the keys are emitted. -
Sample degradation priority (ISO/IEC 14496-12 §8.5.3,
stdp): a track'sstbl/stdpDegradationPriorityBox is an optional per-sample table of 16-bitpriorityvalues, one per sample (thesample_countis implicit fromstsz/stz2, mirroringsdtp). The exact meaning and value range ofpriorityare owned by derived specifications (§8.5.3.1 / §8.5.3.3 "Specifications derived from this define the exact meaning and acceptable range of thepriorityfield"), so the container preserves the raw u16 without interpreting it. The demuxer surfaces a small summary onparams.optionsrather than the per-sample table (which would dominate the options map for a typical track) — four keys per track-with-stdp:stdp_count(total entries),stdp_min/stdp_max(value spread), andstdp_sum(a u64 — a u16 priority × 2^32 samples fits comfortably; consumers compute the mean fromsum / count). A renderer dropping samples under bitrate / CPU pressure consults the carrying spec for the priority ordering. Absentstdp, none of the keys are emitted. -
Sample padding bits (ISO/IEC 14496-12 §8.7.6,
padb): a track'sstbl/padbPaddingBitsBox is an optional table recording, per sample, how many bits at the tail of the sample's last byte are padding (a value in0..=7). On the wire two samples share a byte: each nibble isbit(1) reserved=0; bit(3) pad. Unlikesdtp/stdpthe box declares its ownsample_count(independent ofstsz/stz2), so the table is self-contained — the trailing unused nibble whensample_countis odd is dropped during parse. The reserved high bit of each nibble (required zero by §8.7.6.2) is masked off so a producer slip on the reserved bit does not corrupt the surfaced pad count. The demuxer exposes a summary onparams.optionsrather than the per-sample table — four keys per track-with-padb:padb_count(total entries),padb_max(largest pad count seen,0..=7),padb_nonzero_count(samples whose pad count is non-zero — the count that actually matters to a bitstream consumer; an all-zeropadbmeans the track is byte-aligned), andpadb_hist(eight-bucket histogramn0:n1:n2:n3:n4:n5:n6:n7wherenKis the number of samples whose pad count isK, decimal). The histogram fully captures the value distribution in one short string; consumers can recover any aggregate without scanning. Absentpadb, none of the keys are emitted. -
Sample dependency hints (ISO/IEC 14496-12 §8.6.4,
sdtp): a track'sstbl/sdtpSampleDependencyTypeBox is a per-sample table of four 2-bit fields —is_leading,sample_depends_on,sample_is_depended_on,sample_has_redundancy— packed one byte per sample (thesample_countis implicit fromstsz/stz2). The table feeds trick-mode playback (drop disposable samples on fast-forward) and refines random-access roll-forward (a sample markedsample_depends_on = 2is an I-picture without needing thestssto mark it). The raw per-sample 2-bit values are decoded and stored on the track; the demuxer surfaces a small summary onparams.optionsas five keys —sdtp_count,sdtp_leading_count(samples withis_leading ∈ {1, 3}),sdtp_independent_count(samples withsample_depends_on = 2),sdtp_disposable_count(samples withsample_is_depended_on = 2), andsdtp_redundant_count(samples withsample_has_redundancy = 1). Absentsdtp, none of the keys are emitted (the demuxer falls back tostssfor keyframe detection, as before). -
Multiple sample descriptions (ISO/IEC 14496-12 §8.5.2,
stsd): everySampleEntryin the box is now parsed, not just the first. Entry[0]still drives active decode dispatch (its FourCC is the stream's resolvedcodec), and its codec-specific tail (audio / video preamble + child config boxes) is parsed as before. The additional entries — present when a track switches description mid-stream via a per-chunkstsc.sample_description_index ≥ 2or a fragment'stfhd/trexsample_description_index— are recorded with their FourCC and §8.5.2.2data_reference_index. Whenentry_count > 1the demuxer surfaces them onparams.options:stsd_count(total entries) plusstsd_<n>for each entry, where<n>is the 1-based index matching the spec'ssample_description_index(so astsc/tfhdindex valueklooks up directly asstsd_<k>). Each value is<fourcc> dref=<data_reference_index>. A single-description track (the overwhelming common case) emits none of these keys — the activecodecalready carries that information. A declaredentry_countlarger than the bytes actually present stops at what could be read rather than failing the box or inventing entries. -
Data references (ISO/IEC 14496-12 §8.7.1–2,
dinf/dref/url/urn): a track'sminf/dinf/drefDataReferenceBox is the table of media-data locations that every sample entry's 1-baseddata_reference_index(§8.5.2.2) indexes into — it declares whether a description's samples are self-contained in this file or stored in an external resource. EachDataEntryBoxis aurl(with the §8.7.2.3 self-contained flag, or a NULL-terminatedlocationURL when external) or aurn(a NULL-terminatednameplus an optionallocation); a non-url/urnchild is non-conforming and dropped. The overwhelmingly common single self-containedurlcase is surfaced compactly onparams.optionsasdref_self_contained = "true"with no per-entry keys (a one-lookup "no external resources" check). When the table has more than one entry, or any entry is not self-contained (a split-source track), the full table surfaces:dref_count(total entries),dref_self_contained("true" only when every entry is self-contained), anddref_<n>(1-based, matchingdata_reference_index) =<kind> self=<true|false>[ name=<urn>] [ loc=<url>]. A forgedentry_countcannot over-allocate (the smallest child is its 8-byte box header) and a child overrunning the body ends the walk at what was read; an unknown FullBox version is tolerated. A malformeddrefis dropped without failing theminfparse (the file still demuxes against the single-source default). Absent / unparseabledref, none of the keys are emitted. -
Sample groups (ISO/IEC 14496-12 §8.9,
sbgp+sgpd): a track'sstbl/sbgp(SampleToGroupBox §8.9.2) run-length map andstbl/sgpd(SampleGroupDescriptionBox §8.9.3) per-group entries are parsed. Several of each are accumulated — one pair pergrouping_type(roll,rap,sync,alst,prol, …). Eachsbgpis surfaced onparams.optionsassbgp_<n>(0-based encounter index): the grouping type, an optionalparam=<P>(v1grouping_type_parameter), then space-separatedcount:indexrun-length pairs (group_description_index0 = "no group of this type"; an index ≥ 0x10001 is a movie-fragment-local reference per §8.9.4, kept verbatim — the demuxer does not resolve fragment-local groups). Eachsgpdis surfaced assgpd_<n>: the grouping type, an optionaldefault=<D>(v2default_sample_description_index), then the per-group entry payloads as lowercase hex. Entry sizing honours §8.9.3.2 (v1 fixeddefault_length, v1 per-entrydescription_length, or the v0 deprecated no-length-signalling case captured as one combined blob). The entry payloads are grouping-type-specific and not interpreted by the container — they are surfaced verbatim for a layer that knows thegrouping_typesemantics. Absent both boxes, none of the keys are emitted. -
Compact sample-to-group (ISO/IEC 14496-12:2020 §8.9.5,
csgp): the compact alternative tosbgpis parsed. TheFullBox.flagsfield is overloaded to carry three 2-bit width selectors (index_size_code,count_size_code,pattern_size_code, each mapped to a field width viawidth = 4 << code→ 4/8/16/32 bits) plus agrouping_type_parameter_presentbit. The body's variable-width fields are bit-packed (no byte alignment between 4-/8-bit fields) and read MSB-first: apattern_count-long array of(pattern_length, sample_count)followed by the per-patternsample_group_description_indexrun. Eachcsgpis accumulated and surfaced onparams.optionsascsgp_<n>: the grouping type, an optionalparam=<P>, then onecount*idx0,idx1,…token per pattern (count=sample_count, the index list = the pattern's per-sample indices; 0 = "no group", fragment-local high-bit kept verbatim). It sharesgrouping_typewith the matchingsgpd_<m>. Absentcsgp, no keys are emitted. -
Sub-sample information (ISO/IEC 14496-12 §8.7.7,
subs): a track'sstbl/subsSubSampleInformationBox is an optional sparse table describing how selected samples decompose into smaller, semantically-meaningful sub-samples (e.g. NAL units / parameter sets for H.264 per ISO/IEC 14496-15, or arbitrary segment boundaries for codecs that define their own sub-sample contract). Each entry carries a sample-number delta from the previous entry, then a list of(subsample_size, subsample_priority, discardable, codec_specific_parameters)rows. Version 0 storessubsample_sizeas 16-bit; version 1 widens it to 32-bit (both layouts are read and normalised tou32).flagsdistinguishes co-residentsubsboxes with different per-codec semantics (§8.7.7.1). The container preserves the carried codec's interpretation ofsubsample_priority/discardable/codec_specific_parametersverbatim — those small ints are opaque at this layer. Eachsubsencountered on a track is surfaced onparams.optionsassubs_<n>(0-based encounter index); the value starts with"v<version> flags=<f>"and is followed by one space-separateddelta=<d>[:size,priority,discardable,csp[;...]]block per entry (decimal for everything exceptcsp, which is lowercase 8-digit hex). The trailing colon and per-sub-sample list are omitted when an entry hassubsample_count = 0. Absentsubs, no keys are emitted. -
Sample auxiliary information sizes + offsets (ISO/IEC 14496-12 §8.7.8–9,
saiz+saio): a track'sstbl/saiz+stbl/saiopair documents where per-sample auxiliary-information records live in the file, keyed by(aux_info_type, aux_info_type_parameter). The most common consumer is CENC: whensencis absent, per-sample IVs + subsample maps (ISO/IEC 23001-7) are carried as an auxiliary-information stream of typecenc/cbc1/cens/cbcs, and thesaiz+saiopair points to the bytes in the mdat. Both versions (v0 32-bit / v1 64-bitsaiooffsets) are read; the optionalaux_info_typeblock (gated byflags & 1) is captured when present and surfaced asNone/Noneotherwise so callers can apply §8.7.8.3's implied-value rule against the sample-entry FourCC / sinf scheme themselves.saiz.default_sample_info_sizenon-zero collapses the per-sample table; v1saiooffsets that exceed 32 bits round-trip asu64. Eachsaiz/saioencountered on a track is surfaced onparams.optionsassaiz_<n>/saio_<n>(0-based encounter index):saiz_<n> = "[type=<fourcc>] [param=<P>] default_size=<D> count=<N> [sizes=<s0>,<s1>,…]"saio_<n> = "v<version> [type=<fourcc>] [param=<P>] offsets=<o0>,<o1>,…"
The
type=/param=blocks are omitted when the FullBoxflags & 1bit was zero on disk; thesizes=block is omitted whendefault_sample_info_size != 0. Offsets are decimal; instblthey are absolute file positions. For movie-fragment carriage (§8.8.14) the parser also surfaces a per-trafsummary asfrag_sai_<n>throughDemuxer::metadata()("track=<t> seq=<s> saiz=<n> saio=<m>"), and the structured per-fragment records (with offsets preserved astfhd.base_data_offset-relative per §8.8.14) are reachable through the publicMp4Demuxer::sai_records()accessor on the demuxer (downcast). Absentsaiz/saio, no keys are emitted andsai_records()is empty. -
Producer reference time (ISO/IEC 14496-12 §8.16.5,
prft): a top-level FullBox carrying a UTC wall-clock instant in NTP 64-bit format (RFC 5905 — high 32 bits = seconds since 1900-01-01 UTC, low 32 bits = fractional seconds) correlated with a media time on one reference track's media clock. Used by low-latency DASH / CMAF live streams so a consumer can match production wall-clock against media presentation time (and bound buffer occupancy without out-of-band timing signals). Eachprftencountered during the top-level walk is surfaced onDemuxer::metadata()asprft_<n>(0-based file order); the value is three space-separated decimal integers"reference_track_ID ntp_timestamp media_time". Both v0 (32-bitmedia_time) and v1 (64-bitmedia_time) layouts are read; v1media_timeis widened tou64so callers see one type regardless. Absentprft, no keys are emitted. The structured record is also reachable via the publicoxideav_mp4::demux::parse_prft_box(&[u8])entry point for tooling that wants the typedPrftRecord(reference_track_id,ntp_timestamp,media_time,version) directly. -
Progressive download information (ISO/IEC 14496-12 §8.1.3,
pdin): a top-levelFullBox(version = 0, flags = 0)whose body is a sequence of(rate, initial_delay)u32 pairs (big-endian) — for each effective downloadrate(bytes/second) the box hints the suggested initial playbackinitial_delay(milliseconds) that lets the rest of the file arrive ahead of its playback deadline (§8.1.3.3). A receiver estimates its observed throughput, finds the bracketing pair, and linearly interpolates the delay (or extrapolates from the first / last entry when the observed rate sits outside the recorded range). §8.1.3.1 fixes quantity at zero or one per file and recommends it be placed as early as possible; the top-level walker captures the first instance and ignores any subsequent copies. The parsed table is surfaced onDemuxer::metadata()aspdin_count(total number of pairs, decimal) pluspdin_<n>(one per pair,"rate initial_delay"decimal space-separated, 0-based in file order); an explicitly empty box (preamble only, zero pairs — a producer signalling "no hints available") emitspdin_count = 0and nopdin_<n>keys. Absentpdin, none of the keys are emitted (the demuxer signals "no box" by omission rather than by a zero count). The structured record is also reachable via the publicoxideav_mp4::demux::parse_pdin_box(&[u8])entry point for tooling that already has the box's payload bytes in hand (a DASH packager, a manifest emitter, a fixture validator), and viaMp4Demuxer::pdin_entries()(downcast) for callers holding the demuxer trait object. A body whose post-preamble length is not a multiple of 8 (one u32 pair per entry) is rejected outright per §8.1.3.2 — silently rounding to the nearest pair would mask a producer-side framing bug; a body shorter than the 4-byte FullBox preamble likewise fails the open. The version byte is tolerated even if the producer wrote a non-zero value (the spec pins it to 0 but the payload layout is unambiguous), matchingparse_padb/parse_stdpposture for FullBox version-bit slips. -
Level assignment (ISO/IEC 14496-12 §8.8.13,
leva): aFullBox(0, 0)insidemvexwhose body opens withunsigned int(8) level_countand is followed bylevel_countper-level entries. Each entry maps one fragment level to atrack_id(u32) with apadding_flag(top bit of the packed §8.8.13.2 byte) and a 7-bitassignment_typediscriminator: 0 selects a sample grouping bygrouping_type(u32), 1 addsgrouping_type_parameter(u32), 2/3 carry no further tail (level by track / by track-subsegment), 4 names asub_track_id(u32) for sub-track scope. Levels specify subsets of subsequent movie fragments — samples mapped to levelnmay depend on any levelm ≤ nbut never on levelp > n(§8.8.13.1). The §8.8.13 level table cannot be specified for the initial movie; the box applies to all moof fragments that follow. In DASH (ISO/IEC 23009-1) each subsegment indexed by asidx(§8.16.3) is a "fraction" and data for each level shall appear contiguously within it in increasing level order;padding_flag = 1declares that a conforming fraction can be formed by concatenating any positive integer number of levels and padding the lastmdatto its declared size. §8.8.13.1 fixes quantity at zero or one per file; themvexwalker captures the first instance and ignores any subsequent copies. The parsed table is surfaced onDemuxer::metadata()asleva_count(total entry count, decimal) plusleva_<n>per-entry strings of the shape"<track_id> pad=<0|1> at=<assignment_type> [grouping_type=<u32>] [grouping_type_parameter=<u32>] [sub_track_id=<u32>]"— the trailing tokens are emitted only when theirassignment_typevariant uses them. The structured record is also reachable via the publicoxideav_mp4::demux::parse_leva_box(&[u8])entry point for tooling that already has the box's payload bytes in hand (a DASH packager, a manifest emitter, a scalable-bitstream layer picker), and viaMp4Demuxer::leva_entries() -> Option<&[LevaEntry]>(downcast) for callers holding the demuxer trait object. A truncated entry header (less than 5 bytes fortrack_id+ padding/assignment) or a truncated variant tail is rejected at parse time — a partial table would lie about which levels exist. A malformedlevareachingparse_mvexis dropped silently so a producer slip cannot brickopen()(the box is informational — playback proceeds without level scoping). The §8.8.13.3 minimumlevel_count ≥ 2is not enforced (the demuxer carries whatever the producer wrote so a validator can flag the short table). Reservedassignment_typevalues (> 4) are surfaced as 5-byte header entries with the variant-specific fields zero — the spec leaves their tail undefined so consuming any extra bytes would desynchronise the loop. The version byte is tolerated even if the producer wrote a non-zero value, matchingparse_pdin/parse_padb/parse_stdpposture for FullBox version-bit slips. -
Track extension properties (ISO/IEC 14496-12 §8.8.15,
trep): aFullBox(0, 0)insidemvexwhose body opens withunsigned int(32) track_idand is followed by "any number of boxes" (§8.8.15.2) — e.g. anassp(Alternative Startup Sequence Properties Box, §8.8.16). The box documents or summarises characteristics of one track in the subsequent movie fragments. §8.8.15.1 fixes quantity at zero or more permvex(zero or one per track); themvexwalker collects every instance in file order. The nested child boxes are recorded by type + payload length only (TrepChild) — this crate does not recurse into them, leaving their semantics to a downstream consumer that recognises the specific child. Eachtrepis surfaced onDemuxer::metadata()astrep_<n>(0-based file order) with value"<track_id> children=<k>[ <fourcc>...]"— the track ID, the child count, and each child's four-character type in order. The structured record is reachable via the publicoxideav_mp4::demux::parse_trep_box(&[u8])entry point (for tooling holding the box's payload bytes) and viaMp4Demuxer::treps() -> &[TrepRecord](downcast). Atrepshorter than its 4-byte FullBox preamble or with a truncatedtrack_idis rejected at parse time; a malformedtrepreachingparse_mvexis dropped silently so a producer slip cannot brickopen()(the box is informational). A child box whose declared size overruns the remainingtrepbody is clamped to the bytes available and ends the child walk; a trailing partial child header (fewer than 8 bytes) ends the walk cleanly. The 64-bitlargesizechild form (size == 1) is read via its 16-byte header. The version byte is tolerated even if non-zero, matching theparse_levaposture. -
Subsegment index (ISO/IEC 14496-12 §8.16.4,
ssix): the top-levelFullBox(version = 0, flags = 0)that maps levels (as assigned by the §8.8.13levabox above) to byte ranges of the subsegments indexed by the immediately precedingsidx(§8.16.4.1 placement: zero or one per leaf-onlysidx, as its next box;subsegment_countshall equal that sidx'sreference_count), so a client can fetch a partial subsegment — e.g. only the lower temporal levels — by byte range. The §8.16.4.2 body (subsegment_countu32, then per subsegment arange_countu32 followed by packed(level u8, range_size u24)records that partition every byte of the subsegment, level-contiguous and in increasing level order for leaf subsegments) decodes intoSsixRecord→SsixSubsegment→SsixRange. Everyssixmet during the top-level walk is collected in file order and surfaced onDemuxer::metadata()asssix_<n>="<subsegment_count> <total_range_count>"(shape summary — the full table is reachable via the publicoxideav_mp4::demux::parse_ssix_box(&[u8])entry point or theMp4Demuxer::ssixes()accessor, downcast). Absentssix, no keys are emitted; likeleva, a malformed instance is dropped without failingopen()(the box is informational — this demuxer seeks viasidx/tfraand never fetches partial subsegments). Only version 0 is defined and any other version byte is rejected rather than mis-read; both u32 counts are validated against the bytes actually present before any allocation. The §8.16.4.1 writer-siderange_count ≥ 2minimum is not enforced on read. The matchingbuild_ssix_box(&SsixRecord)builder emits a complete[size]['ssix']box for segment-index emitters pairing it with asidx, rejectingrange_sizevalues above the 24-bit wire field's 0xFF_FFFF ceiling instead of silently masking them.
Muxer
Only codecs with an mp4 sample-entry packaging are accepted. Codec
knowledge is confined to sample_entries::sample_entry_for; the rest
of the muxer appends opaque packet bytes.
Supported encode codec ids (produced sample entry FourCC in parentheses):
pcm_s16le→sowtflac→fLaCwithdfLaconfig (requires STREAMINFO extradata)aac→mp4awithesds(requires AudioSpecificConfig extradata)h264→avc1withavcC(requires AVCConfigurationRecord extradata)mjpeg→jpegmov_text→tx3g(3GPP TS 26.245 timed text) —texthandler +nmhdwebvtt→wvtt(BMFF §12.6.3.2 XMLSubtitleSampleEntry sibling) —subthandler +sthdttml→stpp(BMFF §12.6.3.2 XMLSubtitleSampleEntry) —subt+sthdsbtt→sbtt(BMFF §12.6.3.2 TextSubtitleSampleEntry) —subt+sthdstxt→stxt(BMFF §12.5.3.2 SimpleTextSampleEntry) —subt+sthd
For the subtitle codecs the muxer accepts the demuxer's surfaced
extradata verbatim (the post-preamble sample-entry payload: tx3g's
18-byte header, vttC config, stpp namespace strings, sbtt/stxt MIME
strings), so a demux → mux round-trip preserves the inner config.
Other codec ids fail with Error::Unsupported at open, never at
write_packet time.
Edit lists (edts/elst, ISO/IEC 14496-12 §8.6.5–6) are emitted
per-track when the first packet has a positive presentation timestamp:
a leading empty edit (media_time = -1) of the start delay (in the
movie timescale) followed by a media_time = 0 segment for the track
duration, so a player offsets the track start instead of beginning at
presentation time 0. Version 0 (32-bit) by default, auto-promoting to
version 1 (64-bit) for over-32-bit durations. Tracks starting at PTS 0
get no edts. Controlled by Mp4MuxerOptions::write_edit_list
(default true).
Chunk offsets auto-promote from stco (32-bit) to co64 (64-bit) when
any offset exceeds 4 GiB. The mdat box header stays 32-bit — files
whose mdat payload exceeds 4 GiB fail at write_trailer.
Fragmented / DASH / CMAF segment writing
The dash, cmaf, and ismv registry entries select the fragmented
muxer (oxideav_mp4::frag::open_fragmented_typed). It emits an init
segment (ftyp + moov with mvex/trex per track) followed by one
styp? + sidx? + moof + mdat segment per fragment cadence boundary, and
a trailing mfra/tfra/mfro random-access index. The per-segment
styp (ISO/IEC 14496-12 §8.16.2 Segment Type Box) is controlled by
FragmentedOptions::styp.
For caller-driven per-segment control, the
FragmentedMuxer::write_fragmented_segment_with_styp(major_brand, compat_brands) inherent method marks the next emitted segment's
styp to use the given DASH/CMAF (major, compat) pair, overriding the
preset for one segment (then consumed). The stateless byte builder is
also exposed via the public oxideav_mp4::styp module —
build_styp(major, compat) / write_styp(writer, major, compat) —
mirroring the read-side parse_styp in oxideav-mov so a producer
round-tripping a parsed Styp can emit the same byte sequence.
Sample-group muxing
Sample groups (sbgp / sgpd, ISO/IEC 14496-12 §8.9.2 / §8.9.3) are
emitted per track via Mp4MuxerOptions::track_sample_groups. Each
entry's sbgp and sgpd Vecs are placed at the end of the target
track's stbl body after the chunk-offset table; sgpd is written
before sbgp so the description table the per-sample index references
is declared first (§8.5.1 ordering). The
oxideav_mp4::sample_groups::{SampleToGroup, SampleGroupDescription, build_sbgp, build_sgpd} API also stands alone for callers that want
to assemble the raw boxes themselves.
The version pick for sgpd is automatic per §8.9.3.2: a Some(_)
default_sample_description_index with shared-length entries → v2
(no per-entry length); shared-length entries alone → v1 with fixed
default_length; mixed-length entries → v1 with per-entry
description_length. The deprecated version-0 "no length signalling"
form is not emitted. The grouping-type-specific entry payload itself
is opaque to the container — callers supply already-serialised
Vec<u8> per entry.
sbgp chooses v0 (no grouping_type_parameter) or v1 (Some(_)) per
§8.9.2; a group_description_index ≥ 0x10001 (movie-fragment-local
per §8.9.4) is written verbatim, the muxer does not resolve it.
Seek strategy
seek_to(stream, pts) tries three strategies in order, picking the
first that applies:
tfrafast-path (ISO/IEC 14496-12 §8.8.11). If the file has a trailingmfrawhosetfraindexes the requested track, the demuxer binary-searches thetfratime table for the largesttime ≤ pts, translates the result to amoof_offset, and snaps to the first keyframe at-or-after that offset. O(log N) ontfra+ a one-fragment-bounded sample-list scan.sidxfast-path (ISO/IEC 14496-12 §8.16.3). If notfracovers the track but the file carries one or moresidxboxes whosereference_idmatches the track'strack_ID, the demuxer walks every matchingsidx, expands its references into virtual(EPT, byte_offset)anchors, and picks the latest anchor whose decode-time start is at-or-beforepts(translated from the track's media timescale into the sidx timescale per §8.16.3). Both on-the-wire shapes are handled: a singlesidxindexing every subsegment (DASH on-demand profile) and onesidxper subsegment (DASH live profile / what our own muxer emits). Hierarchical (nested) sidx references are walked for byte-range accounting only — they don't carry a media-time anchor we can land on. Timescale conversion usesu128arithmetic so the multiply doesn't overflow for long-duration tracks even when the track's media timescale and the sidx's timescale differ (per the spec-permitted but DASH-IF-deprecated case).- Linear scan fallback. Walks the sample table picking the
last keyframe at-or-before
pts. This is the unconditional safety net — when neither index applies, or when the indexed offset doesn't resolve cleanly (corrupt index, mdat layout the file lied about),seek_tostill returns a correct cursor.
Not (yet) supported
- Fragmented-MP4 muxing — the demuxer reads
moof+mdatsegments, but the muxer only emits a single moov-at-end (or faststart) shape. - In-pipeline CENC decryption — the AES-128 CTR / CBC driver
exists (
cenc_cipher::decrypt_sample_in_place, all four §10 schemes), but the demuxer does not invoke it automatically:read_packetstill yields ciphertext payloads and the caller — the party holding the content key for the sample's KID — calls the driver per sample. Key acquisition (DRM license exchange against thepssh.SystemIDblob) is out of scope by design. The mdat-resident auxiliary-information bytes that thesaiooffsets name are not pre-fetched — a CENC consumer reading them seeks the input itself using the surfaced offsets. - Per-sample selection of a non-active sample description. All
stsdentries are now parsed and surfaced (see §8.5.2 above), but active decode always uses entry[0]: a per-chunkstsc.sample_description_index ≥ 2or a fragment'stfhd.sample_description_indexoverride is recorded/surfaced but not yet honoured to re-dispatch the codec mid-stream. - mdat payloads larger than 4 GiB (the 32-bit box header is not
promoted to
largesize).
Container registry
let mut reg = new;
register;
Registers:
- Demuxer
"mp4"(also serving.mp4,.m4a,.m4v,.3gp,.mov,.ismv). - Muxers
"mp4","mov","ismv". - A content probe that recognises
ftyp/wide+ftyp/moov.
Fuzzing
A cargo-fuzz target exercises the BMFF box-tree walker on
arbitrary bytes:
The target opens, drains up to 256 packets, and re-seeks; it asserts
nothing panics, aborts, or OOMs. Seed corpus + regression artefacts
live at fuzz/corpus/demux/. The fuzz crate has its own [workspace]
and a committed Cargo.lock for reproducibility.
Pinned regressions worth calling out:
- Extended-size u64 overflow — a
size=1 largesize=u64::MAXextended box anchored at a non-zero file offset used to overflow every downstreambody_start + payload_sizearithmetic site (the §8.16.3sidxend-anchor computation is the most exposed example).read_box_headernowchecked_addsstart + total_sizeand rejects the header before any caller computes a derived end byte. Replayed bytests/largesize_overflow.rsand two boundary unit tests insrc/boxes.rs. Companion to oxideav-mov's round 187 fix on the QTFF atom walker.
License
MIT — see LICENSE.