Skip to main content

container/
lib.rs

1pub mod aac_asc;
2pub mod ac3_sync;
3pub(crate) mod annexb;
4pub mod avi;
5pub mod cmaf;
6pub mod demux;
7pub mod hls;
8pub mod mp4_sanitize;
9pub mod mux;
10pub mod nal_mux;
11pub mod streaming;
12pub mod ts;
13
14/// Parameters required to bolt an audio track onto `Av1Mp4Muxer`.
15///
16/// Four codec families are supported:
17/// - **AAC-LC** (Squad-18, task #63 v1): mono or stereo, sample_rate as the
18///   mdhd timescale per ISO/IEC 14496-14 standard practice, and the
19///   AudioSpecificConfig surfaced verbatim from the demuxer (see
20///   `demux::AudioTrack::asc`) so HE-AAC / xHE-AAC signalling bits survive
21///   the passthrough intact. Sample entry: `mp4a` + `esds`.
22/// - **Opus** (Squad-23): mono or stereo, sample_rate is the source's
23///   `InputSampleRate` (typically 48000), `mdhd` timescale is pinned at
24///   48000 per RFC 7845 §3 (Opus internally always operates at 48 kHz).
25///   Sample entry: `Opus` (4cc per RFC 7845 §4.4 — capital O) + `dOps`
26///   (Opus-Specific Box per §4.5). The `OpusHead` body bytes are carried
27///   in `codec_private` and emitted verbatim inside `dOps`.
28/// - **AC-3** / Dolby Digital (Squad-26): up to 5.1 channels, sample_rate
29///   from the source's syncframe (32 / 44.1 / 48 kHz). Sample entry:
30///   `ac-3` + `dac3` (ETSI TS 102 366 §F.4 / Annex F). The 3-byte `dac3`
31///   body is carried in `codec_private` and emitted verbatim.
32/// - **E-AC-3** / Dolby Digital Plus (Squad-26): up to 5.1 channels in v1
33///   scope (single independent substream). Sample entry: `ec-3` + `dec3`
34///   (ETSI TS 102 366 §F.6). The `dec3` body is carried in `codec_private`
35///   and emitted verbatim.
36///
37/// Discriminator: `codec` field. `"aac"` → AAC path; `"opus"` → Opus path;
38/// `"ac3"` → AC-3 path; `"eac3"` → E-AC-3 path. Anything else is rejected
39/// at `with_audio()` time.
40#[derive(Debug, Clone)]
41pub struct AudioInfo {
42    /// Human-readable codec tag. Muxer accepts `"aac"` (case-insensitive)
43    /// and `"opus"` (case-insensitive). Anything else is rejected with a
44    /// clear error — this is intentional (no stubs).
45    pub codec: String,
46    /// Audio sample rate in Hz. For AAC: typically 44100 / 48000; doubles as
47    /// the `mdhd` timescale. For Opus: the source's `InputSampleRate`
48    /// (informational; the mdhd timescale is pinned to 48000 per RFC 7845
49    /// regardless of this value).
50    pub sample_rate: u32,
51    /// Channel count. Both codecs support 1 (mono) and 2 (stereo) only;
52    /// the muxer bails on other values.
53    pub channels: u16,
54    /// Audio timescale in ticks per second. AAC: equals `sample_rate`.
55    /// Opus: caller should pass 48000 (RFC 7845); the muxer additionally
56    /// validates this for the Opus path.
57    pub timescale: u32,
58    /// AudioSpecificConfig bytes verbatim from the demuxer (AAC only).
59    /// Embedded into the `esds` box's DecoderSpecificInfo (tag 0x05)
60    /// payload. Empty for non-AAC codecs.
61    pub asc_bytes: Vec<u8>,
62    /// Codec-private body bytes (Opus / AC-3 / E-AC-3). For Opus this MUST
63    /// be the RFC 7845 §5.1 `OpusHead` payload (the same bytes a WebM/MKV
64    /// `CodecPrivate` element would carry; see RFC 7845 §5.2 for the
65    /// MKV mapping). Emitted verbatim as the body of the `dOps` box
66    /// inside the `Opus` sample entry. For AC-3 this carries the 3-byte
67    /// `dac3` body (ETSI TS 102 366 §F.4); for E-AC-3 the variable-size
68    /// `dec3` body (§F.6). Empty for AAC.
69    ///
70    /// Layout (RFC 7845 §5.1, 19 bytes minimum for ChannelMappingFamily=0
71    /// with the 8-byte 'OpusHead' magic prefix; the magic is NOT carried
72    /// in `dOps` — only the post-magic body, which is 11 bytes minimum):
73    ///   - `Version` u8 = 1 (in OpusHead; mapped to 0 in dOps per §4.5)
74    ///   - `OutputChannelCount` u8
75    ///   - `PreSkip` u16 LE  (in OpusHead; converted to BE for dOps per §4.5)
76    ///   - `InputSampleRate` u32 LE  (LE in OpusHead, BE in dOps)
77    ///   - `OutputGain` i16 LE  (LE in OpusHead, BE in dOps)
78    ///   - `ChannelMappingFamily` u8
79    ///   - (if family != 0: 1 + 1 + N additional bytes)
80    ///
81    /// The byte-order conversion between OpusHead (Ogg LE convention) and
82    /// dOps (ISOBMFF BE convention) is handled by `build_dops` in mux.rs.
83    /// Callers should pass the OpusHead bytes (LE numeric fields) — that's
84    /// the form the MKV / WebM demuxer surfaces directly out of CodecPrivate.
85    pub codec_private: Vec<u8>,
86}
87
88impl AudioInfo {
89    /// Convenience constructor for the AAC-LC path. Mirrors Squad-18's
90    /// original API surface so existing AAC call sites stay terse.
91    pub fn aac_lc(sample_rate: u32, channels: u16, asc_bytes: Vec<u8>) -> Self {
92        Self {
93            codec: "aac".into(),
94            sample_rate,
95            channels,
96            timescale: sample_rate,
97            asc_bytes,
98            codec_private: Vec::new(),
99        }
100    }
101
102    /// Convenience constructor for the Opus path. Pins timescale to 48000
103    /// per RFC 7845 §3 — Opus is internally always 48 kHz so the mdhd
104    /// timescale, not the source's nominal `InputSampleRate`, is what
105    /// drives sample-duration math on every player.
106    pub fn opus(input_sample_rate: u32, channels: u16, codec_private: Vec<u8>) -> Self {
107        Self {
108            codec: "opus".into(),
109            sample_rate: input_sample_rate,
110            channels,
111            timescale: 48_000,
112            asc_bytes: Vec::new(),
113            codec_private,
114        }
115    }
116
117    /// Convenience constructor for the AC-3 (Dolby Digital) passthrough
118    /// path (Squad-26). `codec_private` carries the 3-byte `dac3` body
119    /// payload (ETSI TS 102 366 §F.4) the muxer writes verbatim into the
120    /// `dac3` box. mdhd timescale = sample_rate (48000 / 44100 / 32000) —
121    /// AC-3 doesn't have Opus's "internally fixed at 48 kHz" rule.
122    pub fn ac3(sample_rate: u32, channels: u16, dac3_body: Vec<u8>) -> Self {
123        Self {
124            codec: "ac3".into(),
125            sample_rate,
126            channels,
127            timescale: sample_rate,
128            asc_bytes: Vec::new(),
129            codec_private: dac3_body,
130        }
131    }
132
133    /// Convenience constructor for the E-AC-3 (Dolby Digital Plus) passthrough
134    /// path (Squad-26). `codec_private` carries the `dec3` body payload
135    /// (ETSI TS 102 366 §F.6) — variable size based on substream count;
136    /// minimum ~5 bytes for the single-independent-substream case.
137    pub fn eac3(sample_rate: u32, channels: u16, dec3_body: Vec<u8>) -> Self {
138        Self {
139            codec: "eac3".into(),
140            sample_rate,
141            channels,
142            timescale: sample_rate,
143            asc_bytes: Vec::new(),
144            codec_private: dec3_body,
145        }
146    }
147}
148
149/// Extended MKV colour/mastering metadata parsed from `Segment → Tracks →
150/// TrackEntry → Video → Colour` and its nested `MasteringMetadata`. The
151/// core H.273-equivalent fields (matrix / primaries / transfer /
152/// full-range) round-trip through `StreamInfo.color_metadata` on
153/// `DemuxResult`; this struct exists to carry the rest (bits_per_channel,
154/// chroma siting / subsampling offsets, MaxCLL/MaxFALL, SMPTE-2086
155/// mastering chromaticities) without requiring a breaking extension of
156/// the shared `StreamInfo` type in the `codec` crate.
157///
158/// Populated by `demux::probe_mkv_color_info` for callers that need it
159/// (mux HDR signalling, future SEI passthrough). Returns `None` for
160/// non-MKV containers and for MKVs with no `Colour` element.
161#[derive(Debug, Clone, Default, PartialEq)]
162pub struct MkvColorInfo {
163    /// MatroskaElement 0x55B2 — decoded bits per channel (e.g. 10 for
164    /// HDR10 sources).
165    pub bits_per_channel: Option<u8>,
166    /// MatroskaElement 0x55B3 — Cb/Cr horizontal subsampling ratio.
167    pub chroma_subsampling_horz: Option<u8>,
168    /// MatroskaElement 0x55B4 — Cb/Cr vertical subsampling ratio.
169    pub chroma_subsampling_vert: Option<u8>,
170    /// MatroskaElement 0x55B7 — horizontal chroma siting (0=unspecified,
171    /// 1=left-collocated, 2=half).
172    pub chroma_siting_horz: Option<u8>,
173    /// MatroskaElement 0x55B8 — vertical chroma siting.
174    pub chroma_siting_vert: Option<u8>,
175    /// MatroskaElement 0x55BC — MaxCLL in cd/m².
176    pub max_cll: Option<u32>,
177    /// MatroskaElement 0x55BD — MaxFALL in cd/m².
178    pub max_fall: Option<u32>,
179    /// MatroskaElement 0x55D0 nested — SMPTE ST 2086 mastering display
180    /// primaries + luminance. Emitted when any sub-element is present.
181    pub mastering: Option<MkvMasteringMetadata>,
182}
183
184/// SMPTE ST 2086 mastering display metadata, carried verbatim from the
185/// MKV `MasteringMetadata` sub-element. Used by HDR10 mux and by future
186/// SEI-passthrough paths to preserve the creator-intended display gamut
187/// and min/max luminance.
188#[derive(Debug, Clone, Default, PartialEq)]
189pub struct MkvMasteringMetadata {
190    pub primary_r_chromaticity_x: Option<f64>,
191    pub primary_r_chromaticity_y: Option<f64>,
192    pub primary_g_chromaticity_x: Option<f64>,
193    pub primary_g_chromaticity_y: Option<f64>,
194    pub primary_b_chromaticity_x: Option<f64>,
195    pub primary_b_chromaticity_y: Option<f64>,
196    pub white_point_chromaticity_x: Option<f64>,
197    pub white_point_chromaticity_y: Option<f64>,
198    pub luminance_max: Option<f64>,
199    pub luminance_min: Option<f64>,
200}