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 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}