transmux — any-to-any media container muxing hub
Demux any supported container into one neutral in-memory IR (Media/Track) and
mux from it into any supported container — so every {input} → {output} composes,
with one documented exception: WebM's VP8/Vorbis tracks have no ISOBMFF sample
entry in this crate, so they don't participate in the fMP4/CMAF mux path (see
the codec table below) — only WebM → WebM/MKV round-trips them.
Built to spec (ISO/IEC 14496-12, 13818-1, 23009-1; RFC 8216/3550; [MS-SSTR]).
No transcode, no codec bitstream en/decode. no_std + alloc.
The spokes are the broadcast_common inverse-pair traits Unpackage (container
→ IR) and Package (IR → container):
Demux → IR (Unpackage) |
IR → mux (Package) |
|---|---|
MPEG-2 TS (TsDemux, or streaming StreamingTsDemux) |
CMAF/fMP4 (CmafMux) · progressive MP4 (ProgressiveMux) |
fMP4/CMAF (Fmp4Demux) · progressive MP4 (ProgressiveDemux) |
MPEG-2 TS (TsMux) |
MPEG Program Stream (PsDemux) |
CMAF-HLS (HlsPackager) · TS-HLS (TsHlsPackager, batch or streaming StreamingTsHlsSegmenter) |
WebM/Matroska (WebmDemux; laced blocks are a hard error, not silently skipped — real ffmpeg/mkvmerge output has no lacing) |
DASH MPD (DashPackager) · LL-DASH (LlDashPackager) · Smooth (SmoothPackager; H.264 + AAC-LC only, every other codec rejected) · Matroska (MkvMux) |
RTMP chunk stream (RtmpDemux) |
RTMP chunk stream (RtmpMux) |
Plus transforms — resegment/trim/track-select (Repackage), streaming CMAF
(Segmenter), IR timeline conditioning — PTS/DTS rebase-to-zero / offset /
discontinuity-gap insertion (rebase_to_zero, apply_offset,
insert_discontinuity_gap, over each Track::start_decode_time anchor) —
CENC/CBCS decrypt (CencDecryptor) and encrypt (CencEncryptor, plus
DASH/HLS DRM signalling), and RTP de/packetise + SDP
(RtpPacketiser/RtpDepacketiser).
Every Sample's dts/pts are absolute, optional ticks in the track's
own media timescale (Option<i64>; None only for a genuinely
timestamp-less, section-carried sample), duration is Option<u32>, and
data is a bytes::Bytes (cheap fan-out, not a copy). 33-bit MPEG-2 Systems
(ISO/IEC 13818-1 §2.4.3.7) and 32-bit RTP (RFC 3550 §5.1) rollover is
unwrapped once, at the demux edge — never re-derived downstream, and no
longer a caller-invoked transform. The IR types (Media, Track,
TrackSpec, Sample, DemuxEvent, …) live in transmux::ir and are
re-exported at the crate root for convenience.
Scope — container muxing only
transmux packages coded media; it never encodes or decodes it. It parses
codec config/parameter headers (SPS/PPS/VPS, AAC ASC, syncframe BSI, EBML/box
config) only far enough to build the container boxes and derive metadata
(dimensions, profile, sample rate, codecs= MIME). Compressed samples are
opaque payloads copied through byte-for-byte — decode/encode is the caller's
job (WebCodecs, FFmpeg, hardware).
Every box has a symmetric Parse / Serialize with byte-identical round-trip
coverage against real fixtures; every demux/mux spoke is gated on byte-exact
round-trips through the IR.
Feature matrix
Container — ISOBMFF / CMAF boxes
| Group | Boxes | Status |
|---|---|---|
| File structure | ftyp, styp, mdat, free/skip, uuid |
✅ |
| Movie | moov · mvhd · trak · tkhd · mdia · mdhd · hdlr · minf · vmhd/smhd · dinf/dref |
✅ |
| Sample tables | stbl · stsd · stts · ctts · cslg · stsc · stsz · stco/co64 · stss · stsh |
✅ |
| Edit / fragment-init | edts/elst, mvex/trex |
✅ |
| Movie fragments | moof · mfhd · traf · tfhd · tfdt · trun |
✅ |
| Random access / index | sidx, mfra/tfra/mfro |
✅ |
| Inband events / refs | emsg, tref |
✅ |
| Encryption (CENC) | senc/saiz/saio/tenc/pssh/sinf/schm/frma |
✅ |
Multi-DRM pssh init data |
Widevine (proto) · PlayReady (PRO/WRMHEADER) · FairPlay (skd://) — drm module |
✅ |
| Sample-entry ext | colr (HDR), pasp, clap |
✅ |
| Live / grouping | prft, sbgp/sgpd, subs |
✅ |
Codecs — sample entries + config (header parse only)
| Codec | Sample entry | Config box / decode | RFC 6381 | Status |
|---|---|---|---|---|
| H.264 / AVC | avc1 (avc2/avc3/avc4) |
avcC + SPS/PPS decode (profile/level/chroma/bit-depth/dims/interlaced) |
avc1.PPCCLL |
✅ |
| H.265 / HEVC | hvc1 (hev1) |
hvcC + VPS/SPS + profile_tier_level decode |
hvc1.… |
✅ |
| AAC | mp4a |
esds / ES_Descriptor + AudioSpecificConfig (+ADTS) |
mp4a.40.N |
✅ |
| AC-3 | ac-3 |
dac3 + syncframe BSI parse |
ac-3 |
✅ |
| E-AC-3 | ec-3 |
dec3 + syncframe BSI parse |
ec-3 |
✅ |
| HE-AAC (SBR/PS) | mp4a |
explicit-SBR/PS ASC | mp4a.40.5/29 |
✅ |
| AC-4 | ac-4 |
dac4 |
ac-4 |
✅ |
| MPEG-H 3D | mha1/mhm1 |
mhaC (record decode) |
— | ✅ |
| AV1 | av01 |
av1C |
av01.… |
✅ |
| Opus / FLAC / VP9 | Opus/fLaC/vp09 |
dOps/dfLa/vpcC |
— | ✅ |
| DTS | dtsc/dtsh/dtsl/dtse |
ddts + core-substream frame-header parse (TS) |
dtsc |
✅ |
| H.266 / VVC | vvc1/vvi1 |
vvcC + SPS/profile_tier_level decode |
vvc1.… |
✅ |
| VP8 | (WebM V_VP8) |
keyframe-header dims (RFC 6386) | — | 🟡 |
| Vorbis | (WebM A_VORBIS) |
CodecPrivate id-header decode |
— | 🟡 |
| MPEG-2 video (H.262) | mp4v / TS 0x02 |
esds + sequence-header dims |
mp4v.61 |
✅ |
| MPEG-1/2 audio (MP1/2/3) | mp4a / TS 0x03/0x04 |
esds + frame-header decode |
mp4a.6B/69 |
✅ |
Text / captions
| Format | Sample entry | Status |
|---|---|---|
| WebVTT / TTML | wvtt / stpp |
✅ |
| CEA-608/708 (in-band SEI) | SEI extraction | ⬜ #430 |
Pipeline & packaging
| Feature | API | Status |
|---|---|---|
| Init segment | build_init_segment (ftyp + fragmented-init moov) |
✅ |
| Media segment (batch) | build_media_segment (styp + moof + mdat) from TrackSpec / Sample |
✅ |
| Streaming segmenter | Segmenter — push samples → take_ready segments, keyframe-cut at a target duration |
✅ |
| LL-HLS segmenter | LlHlsSegmenter — with_part_target + take_ready_parts → partial-segment CMAF chunks (RFC 8216bis §4.4.4.9) before the segment closes |
✅ |
| Streaming TS-HLS segmenter | StreamingTsHlsSegmenter — push→Option<TsSegment> (keyframe-cut .ts segments) + rolling media playlist for live input; shares the batch TsHlsPackager cut logic |
✅ |
| NAL conversion | Annex B ↔ length-prefixed (annexb_to_length_prefixed / length_prefixed_to_annexb) |
✅ |
| NAL keyframe classification | nal_unit_type / is_keyframe_nal / access_unit_is_keyframe (NalCodec AVC/HEVC/VVC) |
✅ |
| RTCP control packets | RtcpPacket — SR/RR/SDES/BYE/APP + CompoundPacket (RFC 3550 §6) |
✅ |
| HLS playlists | MediaPlaylist / MasterPlaylist (RFC 8216); #EXT-X-DISCONTINUITY / #EXT-X-DISCONTINUITY-SEQUENCE (RFC 8216 §4.3.4.3/§4.3.3.3) |
✅ |
| LL-HLS playlist directives | MediaPlaylist::low_latency (LowLatencyConfig) → #EXT-X-SERVER-CONTROL · #EXT-X-PART-INF · #EXT-X-PART · #EXT-X-PRELOAD-HINT (RFC 8216bis §4.4.3.7/§4.4.3.8/§4.4.4.9/§4.4.5.3); MediaPlaylist::open_segment (hls::OpenSegment) renders an in-progress live-edge segment as trailing #EXT-X-PART lines with no #EXTINF (RFC 8216bis §4.4.4.9) |
✅ |
Hub spokes (Unpackage / Package)
| Spoke | Type | API | Status |
|---|---|---|---|
| TS demux | Unpackage |
TsDemux (PAT→PMT, PES, in-band config: H.264 avcC · H.265 hvcC · MPEG-2 video esds · AAC/MPEG audio esds · AC-3/E-AC-3 · DTS core ddts); every other stream_type carried as an opaque Data track (PES or reassembled sections) — nothing dropped. This "nothing dropped" guarantee is TS-specific: it does not extend to the FLV demux below, which silently skips non-AVC video and non-AAC audio. is_sync marks IDR and open-GOP RAPs (recovery-point SEI / SPS-led I-frame) so Segmenter anchors broadcast H.264. Every track carries TrackSpec::source_pid + es_info_descriptors (verbatim PMT ES_info) for player track-selection/labeling |
✅ |
| TS demux (streaming) | feed/poll_event/finish |
StreamingTsDemux (event-driven incremental core; TsDemux is a batch wrapper over it) |
✅ |
| fMP4 demux | Unpackage |
Fmp4Demux (moov/moof → IR, all codecs) |
✅ |
| Progressive MP4 demux | Unpackage |
ProgressiveDemux (non-fragmented moov sample tables: stts/ctts/stss/stsz/stsc+stco/co64 → IR; sidx v0/v1) |
✅ |
| MPEG-PS demux | Unpackage |
PsDemux |
✅ |
| WebM demux | Unpackage |
WebmDemux (EBML). Lacing is not supported — a laced block is a hard error, not silently skipped (real ffmpeg/mkvmerge output has no lacing) |
✅ |
| CMAF / progressive / TS / MKV mux | Package |
CmafMux · ProgressiveMux · TsMux · MkvMux (the exact inverse of WebmDemux, same CodecID mapping) |
✅ |
| DASH / LL-DASH / Smooth | Package |
DashPackager (static + dynamic/live MPD; $Number$ or $Time$/SegmentTimeline addressing; Role/@lang/InbandEventStream; auto ContentProtection from Track::encryption + caller-supplied per-DRM-system cenc:pssh) · LlDashPackager · SmoothPackager (H.264 video + AAC-LC audio only — every other codec is rejected) |
✅ |
| TS-HLS | Package |
TsHlsPackager (batch); StreamingTsHlsSegmenter (live: push→TsSegment, rolling media playlist with sliding window + advancing #EXT-X-MEDIA-SEQUENCE) |
✅ |
| Repackage (resegment/trim/select) | — | Repackage |
✅ |
| IR timeline conditioning (rebase / offset / gap) | — | rebase_to_zero · apply_offset · insert_discontinuity_gap (over Track::start_decode_time; 33-bit MPEG-2/32-bit RTP wrap unroll happens once at the demux edge, not as a caller transform) |
✅ |
| IR timeline splice / concat → SSAI | — | concat · splice_insert (keyframe-snapped via snap_to_preceding_sync, → SpliceResult with discontinuity_points) |
✅ |
| CENC/CBCS decrypt + encrypt | Decrypt/Encrypt |
CencDecryptor · CencEncryptor (cenc AES-CTR, cbcs AES-CBC pattern; populates Track::encryption for protect_init_segment/protect_media_segment's sinf/senc/saio/saiz emission) |
✅ |
| CENC/CBCS DRM signalling | — | DASH: DashPackager::content_protection auto-derives the generic-CENC ContentProtection from Track::encryption. HLS: cenc_ext_x_key renders #EXT-X-KEY for cbcs (cenc/CTR has no HLS METHOD — DASH-only) |
✅ |
| HLS Sample-AES / AES-128 encrypt+decrypt | — | sample_aes (h264_encrypt_nal · aac_encrypt_frame · ac3_encrypt_frame · aes128_encrypt_segment · ExtXKey; feature sample-aes) |
✅ |
| fMP4/CMAF conformance validator | — | validate_init_segment / validate_media_segment / validate_cmaf_track (ISO 14496-12 + CMAF structural checks → ConformanceIssue) |
✅ |
| RTP de/packetise + SDP | Package/Unpackage |
RtpPacketiser / RtpDepacketiser |
✅ |
| RTP streaming depayload (live) | — | RtpStreamDepacketiser (push/flush → timed Samples: per-AU duration from RTP-timestamp deltas, is_sync from IDR; v1 low-delay H.264 / 1 AU-per-packet AAC / in-order feed) |
✅ |
| SDP fmtp → codec config | — | rtp_sdp::fmtp_param (generic key=value fmtp lookup) · rtp_sdp::avc_config_from_fmtp (full fmtp line → avcC) · rtp_sdp::avc_config_from_sprop (RFC 6184 §8.1 sprop-parameter-sets → avcC) · rtp_sdp::aac_config_from_fmtp (full RFC 3640 §4.1 fmtp line → esds) · rtp_sdp::aac_config_from_asc_hex (value-level config= hex → esds; the old value-level behavior of aac_config_from_fmtp) · rtp_sdp::rtpmap_clock_rate (rtpmap → RTP clock rate) |
✅ |
| KLV metadata (SMPTE ST 336 / MISB ST 0601) | — | KlvItem · UasLocalSet (BER length + BER-OID tags, tag 2 precision timestamp, tag 1 CRC-16/CCITT checksum) |
✅ |
| KLV-over-RTP | — | packetise_klv / depacketise_klv (RFC 6597 smpte336m, timestamp-shared fragmentation, marker on last) |
✅ |
| RTMP transport (carries FLV A/V) | Unpackage/Package |
RtmpDemux / RtmpMux (chunk stream, AMF0, → FLV spoke) |
✅ |
| FLV demux/mux | Unpackage/Package |
FlvDemux / FlvMux (H.264 + AAC, Adobe FLV v10.1 Annex E). FlvDemux silently skips non-AVC video and non-AAC audio tags — no track, no error (unlike the TS demux's "nothing dropped" opaque-Data fallback above) |
✅ |
| I-frame trick-play track | — | derive_iframe_track / append_iframe_track (sync-sample-only, timeline-conserving) |
✅ |
✅ = implemented + round-trip-tested · ⬜ = planned (issue linked) ·
🟡 = implemented + round-trip-tested for {WebM} → IR → {WebM}/MKV only — no
ISOBMFF sample entry in this crate, so it does not participate in the
fMP4/CMAF/progressive-MP4/DASH/LL-DASH/CMAF-HLS/LL-HLS/Smooth mux path
(fails with Error::UnsupportedCodec)
Quick start
use ;
// Describe each track (codec config synthesised by the caller from SPS/PPS, ADTS…).
let tracks: = /* … */ Vecnew;
let init = build_init_segment?; // ftyp + moov
// Feed samples per fragment.
let video: = /* … */ Vecnew;
let media = build_media_segment?;
// styp + moof + mdat
# Ok::
For a live/streaming source, Segmenter owns the segment-cutting state machine:
use ;
#
# if false
# Ok::
Codec metadata comes straight from the parsed headers — no external SPS parser:
# if false
tests/ts_to_cmaf.rs demonstrates the full path end-to-end: demux a real
H.264+AAC TS, synthesise avcC/esds, and emit byte-identical-config CMAF.
Command-line packager (cli feature)
The optional cli feature builds a transmux binary that wires the demux and
mux spokes into an any-to-any packager: it autodetects the input container from
its leading bytes, runs it through the [Media] hub IR, and writes the chosen
output format. The library itself stays no_std; only this feature pulls
clap + std. It follows the workspace CLI standard
(docs/CLI-STANDARD.md) — clap derive, named flags,
auto --help/--version.
$ cargo install transmux --features cli # or: cargo run -p transmux --features cli --
# TS → CMAF (autodetected input; format inferred from the .cmaf extension)
$ transmux in.ts -o out.cmaf
# MP4 → CMAF-HLS, 4-second segments
$ transmux in.mp4 -o out.m3u8 -f hls --segment-duration 4
# WebM → progressive single-file MP4
$ transmux in.webm -o out.mp4 -f progressive
# PS → DASH MPD (low-latency chunked variant)
$ transmux in.ps -o out.mpd -f dash --ll
| Flag | Meaning |
|---|---|
<IN> / -i, --input <PATH> |
input file (container autodetected) |
-o, --output <PATH> |
output file / playlist / manifest path |
-f, --format <FMT> |
cmaf | hls | ts-hls | dash | ts | progressive (else inferred from the output extension) |
--segment-duration <SECS> |
target segment duration (default 6) |
--ll |
low-latency variant where supported (LL-DASH) |
--tracks <IDS> |
restrict to these track IDs (comma-separated) |
--decrypt / --key <KID:KEY> |
decrypt CENC input (requires the cenc feature) |
Input autodetect signatures: MPEG-TS (0x47 sync at 0 and +188), MP4/CMAF
(ftyp/styp/moov/moof box at offset 4), MPEG-PS (00 00 01 BA),
WebM/Matroska (EBML 1A 45 DF A3), FLV ("FLV").
Spec grounding
Every layout cites its source (ISO/IEC 14496-12 boxes; 14496-1 §7.2.6 esds;
ITU-T H.264 §7.3.2.1.1 / H.265 §7.3.2.2 SPS; ETSI TS 102 366 Annex F AC-3/E-AC-3).
Codec-config tests decode real ffmpeg-encoded fixtures and assert against an
independent oracle (trace_headers fields; ffmpeg's own MP4-muxer config boxes).
License
MIT OR Apache-2.0.