feat(mux): emit HDR10 static metadata from HEVC SEI

Parse the two HDR10 HEVC SEI messages and emit the corresponding
Matroska Colour metadata, only when actually present in the bitstream
(SDR / no-SEI tracks omit it; nothing is fabricated).

Parse (Rec. ITU-T H.265 Annex D):
- Mastering Display Colour Volume SEI, payloadType 137 (D.2.28):
  display_primaries_x/y[3] (SEI order G,B,R), white_point_x/y
  (0.00002 units), max/min_display_mastering_luminance (0.0001 cd/m²).
- Content Light Level Info SEI, payloadType 144 (D.2.35):
  MaxCLL / MaxFALL (cd/m² integers).
HevcParser::scan_sei walks the sei_rbsp ff-extension payloadType/
payloadSize coding and de-emulates (00 00 03) before reading, reusing
the existing strip_emulation_prevention helper. Both SEI are required
before any metadata is surfaced; SEI NALs still pass through unchanged.

Carry: the measured Hdr10Metadata rides PictureInfo (the same per-coded-
picture seam FieldOrder uses), flowing through from_codec_frame onto
PesFrame.coding to the deferred-muxer activate path, where
apply_coding_to_track stamps it on the video track before the header is
written. Set only when both SEI were seen.

Emit (RFC 9559 / Matroska): new Colour children in ebml.rs
(MasteringMetadata 0x55D0, Primary R/G/B + WhitePoint chromaticity
0x55D1..0x55D8, Luminance max/min 0x55D9/0x55DA, MaxCLL 0x55BC,
MaxFALL 0x55BD). write_hdr10 converts chromaticity SEI int × 0.00002 →
Matroska float, luminance SEI int × 0.0001 → cd/m² float; MaxCLL/MaxFALL
are uints verbatim. SEI primary index 0/1/2 (G/B/R) mapped to the
Matroska R/G/B element layout. Emitted only when hdr10 is present.

Tests: SEI parse with exact raw values, requires-both-SEI, SDR omission,
and emulation-prevention stripping (hevc.rs); muxer emit with exact unit
scaling + SDR omission of MasteringMetadata/MaxCLL/MaxFALL (mkv.rs);
apply_coding_to_track HDR10 plumbing (mkvstream.rs).
This commit is contained in:
Matthew Jackson
2026-06-25 21:59:43 -07:00
parent 539b170f7e
commit dc1d05985b
6 changed files with 823 additions and 6 deletions
+57
View File
@@ -78,6 +78,39 @@ pub enum CodingDetail {
CodingTypeOnly,
}
/// HDR10 static metadata measured from a video bitstream (HEVC SEI). Carried on
/// [`PictureInfo`] as the per-stream colour-volume signalling: it only ever
/// reaches the muxer when BOTH SEI messages were actually present in the stream,
/// so an SDR / no-SEI track leaves it `None` and the muxer omits the elements
/// (never fabricated).
///
/// All values are stored in their RAW SEI integer units (NOT yet scaled to the
/// Matroska float domain); the muxer applies the H.265 → Matroska unit
/// conversion at emit time so the scaling lives in exactly one place.
///
/// Spec: Rec. ITU-T H.265 D.2.28 (Mastering Display Colour Volume,
/// payloadType 137) and D.2.35 (Content Light Level Info, payloadType 144).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Hdr10Metadata {
/// `display_primaries_x[c]` / `display_primaries_y[c]` for c = 0,1,2.
/// Per H.265 D.3.28 the SEI order is c=0 → Green, c=1 → Blue, c=2 → Red.
/// Stored here in that SAME SEI order; the muxer maps to Matroska's R/G/B
/// element layout. Units of 0.00002 (chromaticity).
pub display_primaries_x: [u16; 3],
pub display_primaries_y: [u16; 3],
/// `white_point_x` / `white_point_y` in units of 0.00002 (chromaticity).
pub white_point_x: u16,
pub white_point_y: u16,
/// `max_display_mastering_luminance` in units of 0.0001 cd/m².
pub max_display_mastering_luminance: u32,
/// `min_display_mastering_luminance` in units of 0.0001 cd/m².
pub min_display_mastering_luminance: u32,
/// `max_content_light_level` (MaxCLL) in cd/m² — already an integer.
pub max_content_light_level: u16,
/// `max_pic_average_light_level` (MaxFALL) in cd/m² — already an integer.
pub max_pic_average_light_level: u16,
}
/// Codec-agnostic per-picture coding carrier — the single per-frame record the
/// muxer reads through the accessors below. Raw codec signals live in
/// [`CodingDetail`]; consumers MUST use the accessors, never the inner fields.
@@ -89,6 +122,11 @@ pub struct PictureInfo {
/// Raw per-codec coding detail. Holds the bits the field/pulldown
/// accessors derive from.
detail: CodingDetail,
/// HDR10 static metadata measured from the bitstream (HEVC SEI), or `None`
/// when the stream carried no HDR10 SEI (SDR / not signalled). Per-stream,
/// but rides the per-picture carrier so it flows the same deferred-muxer
/// path the measured field order does. Never fabricated.
hdr10: Option<Hdr10Metadata>,
}
impl PictureInfo {
@@ -98,6 +136,7 @@ impl PictureInfo {
Self {
coding_type,
detail: CodingDetail::Mpeg2(m),
hdr10: None,
}
}
@@ -107,9 +146,27 @@ impl PictureInfo {
Self {
coding_type,
detail: CodingDetail::CodingTypeOnly,
hdr10: None,
}
}
/// Attach measured HDR10 static metadata (HEVC SEI) to this picture,
/// consuming and returning `self` for builder-style use. Only ever called
/// with `Some(..)` once both HDR10 SEI messages have been seen, so an SDR
/// track never carries fabricated colour-volume data.
pub fn with_hdr10(mut self, hdr10: Option<Hdr10Metadata>) -> Self {
self.hdr10 = hdr10;
self
}
/// Measured HDR10 static metadata for this picture's stream, or `None` when
/// the bitstream signalled no HDR10 SEI. Read at mux time to emit the
/// Matroska MasteringMetadata / MaxCLL / MaxFALL — omitted entirely when
/// `None`.
pub fn hdr10(&self) -> Option<Hdr10Metadata> {
self.hdr10
}
/// Agnostic coding type (I/P/B). The single signal for cue/keyframe marking
/// and B-frame display ordering.
pub fn coding_type(&self) -> CodingType {