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}