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