transmux 0.22.0

Any-to-any media container muxing hub: demux TS, fMP4/CMAF, MPEG-PS, WebM, FLV, or RTMP into one neutral IR and mux to CMAF/fMP4, progressive MP4, TS, DASH, low-latency DASH, HLS, low-latency HLS, Smooth Streaming, or RTMP. CENC/CBCS encrypt+decrypt, SSAI splice, RTP/RTCP, and an fMP4/CMAF conformance validator; parses codec config headers only, samples stay opaque. no_std + alloc.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
//! Per-track codec configuration — [`CodecConfig`] + [`DataCarriage`].
//!
//! Moved out of `pipeline.rs` (media plane step 2a, no-op): same types, same
//! fields, same impls.

use alloc::vec::Vec;

use crate::ac3::{Ac3SpecificBox, Ec3SpecificBox};
use crate::ac4::Ac4SpecificBox;
use crate::av1::Av1ConfigurationBox;
use crate::avc_config::AVCConfigurationBox;
use crate::dts::DtsSpecificBox;
use crate::flac::FlacSpecificBox;
use crate::hevc_config::HEVCConfigurationBox;
use crate::mp4esds::EsdsBox;
use crate::opus::OpusSpecificBox;
use crate::vp9::Vp9ConfigurationBox;
use crate::vvc_config::VvcConfigurationBox;

/// Whether a [`CodecConfig::Data`] elementary stream carries PES packets or
/// PSI/private sections on its PID.
///
/// ISO/IEC 13818-1 §2.4.4.8 / Table 2-34 splits `stream_type` into two
/// carriage families: most types (subtitles, teletext, SMPTE 2038 ANC,
/// metadata, and any unrecognised value) are PES-packetised (§2.4.3.6); a
/// fixed set (`0x05` private_sections, `0x0A`-`0x0D` DSM-CC, `0x14` DSM-CC
/// synchronized download, `0x86` SCTE-35/ANSI-scoped) are carried as raw
/// PSI-style sections instead (§2.4.4), with no PES header at all.
/// Reassembling a section stream with a PES parser (or vice versa) silently
/// yields nothing, so a demuxer/muxer must dispatch on this before touching
/// the payload — see `crate::ts_demux` / `crate::ts_mux` (issue #576).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DataCarriage {
    /// The elementary stream is PES-packetised (ISO/IEC 13818-1 §2.4.3.6).
    Pes,
    /// The elementary stream carries PSI/private sections directly on its
    /// PID (ISO/IEC 13818-1 §2.4.4), with no PES header at all.
    Sections,
}

impl DataCarriage {
    /// A short label for this carriage kind.
    pub fn name(&self) -> &'static str {
        match self {
            DataCarriage::Pes => "pes",
            DataCarriage::Sections => "sections",
        }
    }
}

broadcast_common::impl_spec_display!(DataCarriage);

/// The wire format of a [`CodecConfig::Subtitle`] track (media plane step 2d).
///
/// `Ttml`/`WebVtt` are ISOBMFF sample entries this crate demuxes
/// ([`crate::media::Fmp4Demux`]); `DvbBitmap`/`Teletext` name the PES-carried
/// broadcast subtitle formats a TS demuxer can identify from their PMT
/// descriptors (ETSI EN 300 468) but does not yet classify as
/// `CodecConfig::Subtitle` itself (still `CodecConfig::Data` on that path
/// today) — the variant exists so a future TS-side classifier has a home
/// without another breaking enum change.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SubtitleFormat {
    /// TTML/IMSC XML subtitles (ISO BMFF `stpp` sample entry, ISO/IEC
    /// 14496-30 §7.2).
    Ttml,
    /// WebVTT subtitles (ISO BMFF `wvtt` sample entry, ISO/IEC 14496-30 §9.2).
    WebVtt,
    /// DVB (bitmap) subtitling, carried as a PES stream (ETSI EN 300 743).
    DvbBitmap,
    /// Teletext, carried as a PES stream (ETSI EN 300 706 / EN 300 472).
    Teletext,
}

impl SubtitleFormat {
    /// A short label for this subtitle format.
    pub fn name(&self) -> &'static str {
        match self {
            SubtitleFormat::Ttml => "TTML (stpp)",
            SubtitleFormat::WebVtt => "WebVTT (wvtt)",
            SubtitleFormat::DvbBitmap => "DVB bitmap subtitle",
            SubtitleFormat::Teletext => "Teletext",
        }
    }
}

broadcast_common::impl_spec_display!(SubtitleFormat);

/// Per-track codec configuration for the initialization segment.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum CodecConfig {
    /// H.264/AVC video (`avc1` sample entry with an `avcC` config box).
    Avc {
        /// The `avcC` decoder configuration record.
        config: AVCConfigurationBox,
        /// Coded width in pixels.
        width: u16,
        /// Coded height in pixels.
        height: u16,
    },
    /// H.265/HEVC video (`hvc1`/`hev1` sample entry with an `hvcC` config box) —
    /// ISO/IEC 14496-15:2017 §8.4.
    Hevc {
        /// The `hvcC` decoder configuration record box.
        config: HEVCConfigurationBox,
        /// Coded width in pixels.
        width: u16,
        /// Coded height in pixels.
        height: u16,
    },
    /// H.266/VVC video (`vvc1`/`vvi1` sample entry with a `vvcC` config box) —
    /// ISO/IEC 14496-15:2022 §11.3.3.
    Vvc {
        /// The `vvcC` decoder configuration record box.
        config: VvcConfigurationBox,
        /// Coded width in pixels.
        width: u16,
        /// Coded height in pixels.
        height: u16,
    },
    /// AAC audio (`mp4a` sample entry with an `esds` box).
    Aac {
        /// The `esds` box carrying the AudioSpecificConfig.
        esds: EsdsBox,
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz (stored 16.16 in the sample entry).
        sample_rate: u32,
        /// Sample size in bits (typically 16).
        sample_size: u16,
    },
    /// AC-3 audio (`ac-3` sample entry with a `dac3` box).
    Ac3 {
        /// The `dac3` config box.
        config: Ac3SpecificBox,
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz.
        sample_rate: u32,
        /// Sample size in bits (typically 16).
        sample_size: u16,
    },
    /// E-AC-3 audio (`ec-3` sample entry with a `dec3` box).
    Eac3 {
        /// The `dec3` config box.
        config: Ec3SpecificBox,
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz.
        sample_rate: u32,
        /// Sample size in bits (typically 16).
        sample_size: u16,
    },
    /// AV1 video (`av01` sample entry with an `av1C` box).
    Av1 {
        /// The `av1C` configuration box.
        config: Av1ConfigurationBox,
        /// Coded width in pixels.
        width: u16,
        /// Coded height in pixels.
        height: u16,
    },
    /// VP9 video (`vp09` sample entry with a `vpcC` box).
    Vp9 {
        /// The `vpcC` configuration box.
        config: Vp9ConfigurationBox,
        /// Coded width in pixels.
        width: u16,
        /// Coded height in pixels.
        height: u16,
    },
    /// Opus audio (`Opus` sample entry with a `dOps` box).
    Opus {
        /// The `dOps` config box.
        config: OpusSpecificBox,
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz (stored 16.16; per spec always 48000).
        sample_rate: u32,
        /// Sample size in bits (typically 16).
        sample_size: u16,
    },
    /// FLAC audio (`fLaC` sample entry with a `dfLa` box).
    Flac {
        /// The `dfLa` config box.
        config: FlacSpecificBox,
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz.
        sample_rate: u32,
        /// Sample size in bits.
        sample_size: u16,
    },
    /// AC-4 audio (`ac-4` sample entry with a `dac4` box).
    Ac4 {
        /// The `dac4` config box (opaque `ac4_dsi_v1()`).
        config: Ac4SpecificBox,
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz.
        sample_rate: u32,
        /// Sample size in bits (16 per spec).
        sample_size: u16,
    },
    /// MPEG-H 3D Audio (`mha1` sample entry with an `mhaC` box) — ISO/IEC 23008-3 §20.
    ///
    /// Use `mha1` for raw MHAS frames (config in `mhaC`).  For in-band MHAS
    /// (`mhm1`) the caller should convert the `codec_type` on the resulting
    /// [`MhaSampleEntry`](crate::init_segment::MhaSampleEntry) if needed;
    /// `build_trak` always emits `mha1`.
    MpegH {
        /// The `MHADecoderConfigurationRecord` carried in the `mhaC` box.
        config: crate::mpegh::MHADecoderConfigurationRecord,
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz.
        sample_rate: u32,
        /// Sample size in bits (typically 16).
        sample_size: u16,
    },
    /// MPEG-2 video / H.262 (`mp4v` sample entry with an `esds` box) —
    /// ISO/IEC 13818-2 (ITU-T H.262) carried per ISO/IEC 14496-1 §7.2.6.6
    /// (ObjectTypeIndication 0x60–0x65, 0x61 = MPEG-2 Main Visual).
    ///
    /// The `esds` carries the ES/decoder descriptors (its DecoderSpecificInfo
    /// optionally the `sequence_header()` bytes); the coded picture geometry is
    /// decoded from the in-band `sequence_header()` (ISO/IEC 13818-2 §6.2.2.1).
    Mpeg2Video {
        /// The `esds` box (its body is re-embedded byte-identically).
        esds: EsdsBox,
        /// Coded width in pixels (from the `sequence_header()`).
        width: u16,
        /// Coded height in pixels (from the `sequence_header()`).
        height: u16,
    },
    /// MPEG-1/2 audio, Layers I/II/III (`mp4a` sample entry with an `esds` box)
    /// — ISO/IEC 11172-3 / ISO/IEC 13818-3, carried per ISO/IEC 14496-1
    /// §7.2.6.6 (ObjectTypeIndication 0x69 = MPEG-2 audio, 0x6B = MPEG-1 audio).
    MpegAudio {
        /// The `esds` box (OTI 0x69/0x6B; DecoderSpecificInfo usually empty).
        esds: EsdsBox,
        /// Audio layer (1/2/3), from the first frame header.
        layer: crate::mpeg_legacy::MpegAudioLayer,
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz.
        sample_rate: u32,
        /// Sample size in bits (typically 16).
        sample_size: u16,
    },
    /// DTS audio (`dtsc`/`dtsh`/`dtsl`/`dtse` sample entry with a `ddts` box) —
    /// ETSI TS 102 114 §E.2.
    ///
    /// `codec_fourcc` selects the sample-entry FourCC:
    /// `dtsc` (core only), `dtsh` (core + extension / multi-asset),
    /// `dtsl` (LBR only), or `dtse` (extension substream only).
    /// Use [`crate::dts::DTSC_FOURCC`] etc. for the named constants.
    Dts {
        /// The `ddts` DTSSpecificBox.
        config: DtsSpecificBox,
        /// Sample-entry FourCC: one of `dtsc`, `dtsh`, `dtsl`, `dtse`.
        codec_fourcc: [u8; 4],
        /// Channel count.
        channel_count: u16,
        /// Sampling rate in Hz (48000, 44100, or 32000 per §E.2.2.2).
        sample_rate: u32,
        /// Sample size in bits (always 16 per §E.2.2.2).
        sample_size: u16,
    },
    /// VP8 video (WebM-native; RFC 6386).
    ///
    /// VP8 has **no** out-of-band configuration box — the coded dimensions are
    /// carried in the key-frame header (RFC 6386 §9.1) and decoded from the first
    /// key frame at demux time (see `transmux/docs/codec/vp8-vorbis-webm.md`).
    /// Carried in the IR for `{WebM} → IR → {WebM}` / inspection; there is no
    /// ISOBMFF sample entry for VP8 in this crate (out of scope), so it does not
    /// participate in the fMP4 mux path.
    Vp8 {
        /// Coded width in pixels (key-frame header, masked `& 0x3FFF`).
        width: u16,
        /// Coded height in pixels (key-frame header, masked `& 0x3FFF`).
        height: u16,
    },
    /// Vorbis audio (WebM-native; Vorbis I specification, xiph.org).
    ///
    /// The three setup headers (Identification / Comment / Setup) are carried
    /// verbatim in `codec_private` (Xiph-laced, exactly as WebM `CodecPrivate`
    /// stores them — see `transmux/docs/codec/vp8-vorbis-webm.md`); `channels`
    /// and `sample_rate` are decoded from the Identification header (Vorbis I
    /// §4.2.2). Carried in the IR for `{WebM} → IR → {WebM}` / inspection; there
    /// is no ISOBMFF sample entry for Vorbis in this crate (out of scope), so it
    /// does not participate in the fMP4 mux path.
    Vorbis {
        /// The Xiph-laced 3-header `CodecPrivate`, verbatim.
        codec_private: Vec<u8>,
        /// Channel count (`audio_channels`, Vorbis I §4.2.2).
        channels: u16,
        /// Sampling rate in Hz (`audio_sample_rate`, Vorbis I §4.2.2).
        sample_rate: u32,
    },
    /// Subtitle track (media plane step 2d): a TTML/WebVTT ISOBMFF sample
    /// entry (`stpp`/`wvtt`, ISO/IEC 14496-30) demuxed by
    /// [`crate::media::Fmp4Demux`], carried losslessly rather than dropped.
    /// Samples are opaque payloads (TTML XML documents / WebVTT `vttc`/`vtte`
    /// boxes) — this crate never parses cue contents, only the sample
    /// entry's format tag.
    ///
    /// There is currently no re-mux path: reconstructing the sample entry
    /// (the TTML namespace/`stpp` config, or the WebVTT header block/`vttC`)
    /// needs more than the format tag alone, so every fMP4/CMAF mux entry
    /// point rejects a `Subtitle` track — naming it with
    /// [`Error::UnmuxableSubtitleTrack`](crate::error::Error::UnmuxableSubtitleTrack)
    /// (media plane step-2 fix wave 1, B1) — rather than silently dropping or
    /// mis-emitting it; a caller must pre-filter with
    /// [`Media::select_tracks_by`](crate::media::Media::select_tracks_by) —
    /// see `TODO(#753)` on `build_trak`.
    Subtitle {
        /// Which subtitle wire format this track carries.
        format: SubtitleFormat,
    },
    /// Opaque data track (issue #557/#576): a PMT-listed elementary stream
    /// whose `stream_type` is not a codec this crate decodes (DVB subtitles
    /// EN 300 743, teletext EN 300 472, SMPTE 2038 ANC, ID3/KLV metadata,
    /// SCTE-35, DSM-CC, private sections, and any other/unrecognised
    /// `stream_type`) — carried losslessly rather than dropped. `stream_type`
    /// per ISO/IEC 13818-1 Table 2-34; `descriptors` is the raw PMT ES_info
    /// descriptor loop so consumers can classify it (e.g. with dvb-si's
    /// parsers); `carriage` records whether the samples are PES payloads or
    /// whole PSI/private sections — see [`DataCarriage`].
    ///
    /// Carried in the IR for `{TS} → IR → {TS}` / inspection; there is no
    /// ISOBMFF sample entry for an opaque stream in this crate (out of
    /// scope), so it does not participate in the fMP4 mux path (mirrors
    /// [`CodecConfig::Vp8`] / [`CodecConfig::Vorbis`]) — every fMP4/CMAF mux
    /// entry point (media plane step-2 fix wave 1) rejects such a track,
    /// naming it, rather than silently omitting it (issue #576/#557); a
    /// caller mixing carriable and opaque tracks must pre-filter with
    /// [`Media::select_tracks_by`](crate::media::Media::select_tracks_by) —
    /// see [`CodecConfig::is_muxable_in_bmff`].
    Data {
        /// PMT `stream_type` (ISO/IEC 13818-1 Table 2-34).
        stream_type: u8,
        /// The raw PMT ES_info descriptor-loop bytes for this stream.
        descriptors: Vec<u8>,
        /// Whether this elementary stream carries PES packets or PSI/private
        /// sections (ISO/IEC 13818-1 §2.4.4.8) — determines how a demuxer
        /// reassembles it and how a muxer re-emits it.
        carriage: DataCarriage,
    },
}

impl CodecConfig {
    pub(crate) fn is_audio(&self) -> bool {
        matches!(
            self,
            CodecConfig::Aac { .. }
                | CodecConfig::Ac3 { .. }
                | CodecConfig::Eac3 { .. }
                | CodecConfig::Opus { .. }
                | CodecConfig::Flac { .. }
                | CodecConfig::Ac4 { .. }
                | CodecConfig::MpegH { .. }
                | CodecConfig::Dts { .. }
                | CodecConfig::MpegAudio { .. }
                | CodecConfig::Vorbis { .. }
        )
    }

    /// True for the opaque [`CodecConfig::Data`] variant (issue #557/#576): a
    /// PMT-carried elementary stream with no ISOBMFF sample entry in this
    /// crate. Every fMP4/CMAF mux entry point (init segment + every packager
    /// built on it) rejects such a track, naming it, rather than silently
    /// omitting it — mirrors how the TS mux path, unlike this one, *can*
    /// carry them verbatim. See [`CodecConfig::is_muxable_in_bmff`], which
    /// also covers [`CodecConfig::Subtitle`] (B1).
    pub(crate) fn is_opaque_data(&self) -> bool {
        matches!(self, CodecConfig::Data { .. })
    }

    /// True for [`CodecConfig::Subtitle`]: this crate has no re-mux sample
    /// entry for it yet (`TODO(#753)`, see that variant's doc comment).
    pub(crate) fn is_subtitle(&self) -> bool {
        matches!(self, CodecConfig::Subtitle { .. })
    }

    /// True when `self` can be placed into an ISOBMFF `trak` by this crate —
    /// i.e. neither the opaque [`CodecConfig::Data`] carriage nor
    /// [`CodecConfig::Subtitle`] (media plane step-2 fix wave 1, B1-B4: the
    /// single predicate every fMP4/CMAF mux entry point
    /// ([`build_init_segment`](crate::pipeline::build_init_segment) and
    /// every packager built on it — `CmafMux`, `ProgressiveMux`, `Segmenter`,
    /// `LlSegmenter`, `LlHlsSegmenter`) checks before building a `trak`:
    /// a track failing this predicate is rejected, naming the track, rather
    /// than silently dropped — a caller mixing carriable and non-carriable
    /// tracks must pre-filter first (e.g. with
    /// [`Media::select_tracks_by`](crate::media::Media::select_tracks_by)).
    ///
    /// Does NOT cover the WebM-native [`CodecConfig::Vp8`]/[`CodecConfig::Vorbis`]
    /// (genuinely unimplemented fMP4 carriage, not a filterable policy choice
    /// — those still fail with the generic
    /// [`Error::UnsupportedCodec`](crate::error::Error::UnsupportedCodec)
    /// regardless of filtering).
    ///
    /// `pub` (unlike the crate's other `CodecConfig` predicates): this is the
    /// caller-facing filter predicate the `Unmuxable*Track` errors point to,
    /// e.g. `media.select_tracks_by(|t| t.spec.config.is_muxable_in_bmff())`.
    pub fn is_muxable_in_bmff(&self) -> bool {
        !(self.is_opaque_data() || self.is_subtitle())
    }

    /// True if `self` is a video codec (issue #628) — codec-family-complete
    /// regardless of which output container actually carries it (e.g. the TS
    /// mux path does not carry every variant matched here; see
    /// `ts_mux::EsKind::from_config`). Mirrors [`CodecConfig::is_audio`]. Used
    /// to pick the anchor/segmentation track (`ts_hls::choose_anchor`,
    /// `Segmenter::new`).
    pub(crate) fn is_video(&self) -> bool {
        matches!(
            self,
            CodecConfig::Avc { .. }
                | CodecConfig::Hevc { .. }
                | CodecConfig::Vvc { .. }
                | CodecConfig::Av1 { .. }
                | CodecConfig::Vp9 { .. }
                | CodecConfig::Vp8 { .. }
                | CodecConfig::Mpeg2Video { .. }
        )
    }
}