mux/mkv: spec-conformance fixes (field order, CICP, VobSub idx, SeekHead, AC-3)
- FieldOrder now derives from the bitstream's measured top_field_first (Some(true)→TFF, Some(false)→BFF) instead of hardcoding TFF for all interlaced content; falls back to TFF when unmeasured. Adds VideoStream::top_field_first; DVD/BD scan sets None with a precise TODO(spec) for parser→title plumbing. - CICP (matrix/transfer/primaries/range) now prefers measured CICP from the bitstream (VideoStream::measured_cicp) over the coarse ColorSpace enum, so the container stops assuming a colour space the stream may contradict. Enum remains the fallback. - VobSub S_VOBSUB CodecPrivate now emits a `size: WxH` line ahead of the palette per the .idx format so players place/scale subs correctly. - SeekHead: when zero cues are written, the CUES Seek entry is Voided instead of leaving a dangling pointer to the Cues offset (now Tags/EOF). - AC-3 Channels back-patch offset is captured from the writer instead of the hardcoded chan_elem_pos+2 (decoupled from the VINT width choice). - Hoisted inline CICP codes and the dvcC fourcc to named constants citing ITU-T H.273 / RFC 9559; fixed the stale FieldOrder comment. - DefaultDuration vs pulldown: precise TODO(spec) left (needs the same parser→title channel as top_field_first). Tests: BFF-from-measured-flag, measured-CICP-overrides-enum, VobSub size: line present/omitted, zero-cue SeekHead Void. precommit (1.86) green.
This commit is contained in:
@@ -148,6 +148,17 @@ impl Disc {
|
||||
// the CLI/UI render the localized descriptor. `label`
|
||||
// stays empty for disc video streams.
|
||||
label: String::new(),
|
||||
// TODO(spec): for 1080i HEVC/H.264/VC-1 titles, surface
|
||||
// the measured field order (H.264/HEVC pic_struct, VC-1
|
||||
// pulldown) from the codec parser instead of the TFF
|
||||
// fallback; needs the parser→title channel (see dvd.rs).
|
||||
top_field_first: None,
|
||||
// TODO(spec): prefer the HEVC/H.264 VUI colour_description
|
||||
// (measured CICP) over this MPLS playlist-nibble guess
|
||||
// once the parser surfaces it through the output title.
|
||||
// `None` keeps the enum fallback. (HDR MaxCLL/Mastering
|
||||
// metadata is a separate task and intentionally not here.)
|
||||
measured_cicp: None,
|
||||
})),
|
||||
2 | 5 => {
|
||||
// Guard: if coding_type is a subtitle codec (PGS 0x90/0x91),
|
||||
|
||||
+31
-2
@@ -49,7 +49,31 @@ impl Disc {
|
||||
}),
|
||||
secondary: false,
|
||||
label: String::new(),
|
||||
// TODO(spec): populate from the MPEG-2 picture coding extension
|
||||
// once the codec parser surfaces it. `Mpeg2Parser` already reads
|
||||
// `top_field_first` (mux/codec/mpeg2.rs `picture_nb_fields`) but
|
||||
// tracks are built from the IFO scan BEFORE any frame is parsed;
|
||||
// wiring it requires a CodecParser accessor surfaced through
|
||||
// PipelinedPesStream/DiscStream into the output title (mirroring
|
||||
// the existing `codec_private` handshake). Until then `None`
|
||||
// means the muxer falls back to TFF (correct for ~all DVDs).
|
||||
top_field_first: None,
|
||||
// TODO(spec): DVD MPEG-2 carries no VUI; the colour signalling is
|
||||
// the sequence_display_extension colour_description when present.
|
||||
// Surface it from `Mpeg2Parser` (same handshake as above) and set
|
||||
// this so a disc that states e.g. BT.601-625 colour overrides the
|
||||
// PAL/NTSC guess in `color_space`. `None` uses the enum fallback.
|
||||
measured_cicp: None,
|
||||
});
|
||||
// TODO(spec): DefaultDuration is derived from the declared IFO
|
||||
// frame_rate (25 / 29.97). A soft-telecined 23.976-in-29.97 DVD then
|
||||
// reports 29.97 fps instead of the true 23.976 film rate. The pulldown
|
||||
// cadence is detectable from the parser's per-picture `nb_fields`
|
||||
// (repeat_first_field) — when most frames are 3-field-then-2-field
|
||||
// 2:3 pulldown the film rate is frame_rate × 4/5. Emitting the film
|
||||
// DefaultDuration needs the parser to report the measured cadence
|
||||
// through the same parser→title channel as `top_field_first` above;
|
||||
// left as a follow-up to avoid a speculative rate change here.
|
||||
|
||||
// Map DvdAudioAttr to Stream::Audio. The PID is derived from the
|
||||
// stream's REAL on-wire private_stream_1 sub-stream id (assigned
|
||||
@@ -132,11 +156,16 @@ impl Disc {
|
||||
|
||||
let size_bytes: u64 = extents.iter().map(|e| e.sector_count as u64 * 2048).sum();
|
||||
|
||||
// Build pre-formatted palette codec_data for VobSub subtitle streams
|
||||
// Build pre-formatted VobSub `.idx` codec_data (size: + palette:
|
||||
// lines) for VobSub subtitle streams. The `size:` line carries
|
||||
// the coded video frame the subpicture was authored against
|
||||
// (720x480 NTSC / 720x576 PAL) so players place and scale the
|
||||
// bitmap correctly.
|
||||
let (vid_w, vid_h) = ts.video.resolution.pixels();
|
||||
let codec_data = dvd_title
|
||||
.palette
|
||||
.as_ref()
|
||||
.map(|pal| crate::mux::codec::dvdsub::format_palette(pal));
|
||||
.map(|pal| crate::mux::codec::dvdsub::format_palette(pal, vid_w, vid_h));
|
||||
|
||||
// Map DvdSubtitleAttr to Stream::Subtitle
|
||||
let subtitle_streams: Vec<Stream> = ts
|
||||
|
||||
@@ -196,6 +196,35 @@ pub struct VideoStream {
|
||||
pub secondary: bool,
|
||||
/// Extra label (e.g. "Dolby Vision EL")
|
||||
pub label: String,
|
||||
/// Field-display order MEASURED from the elementary stream's interlace
|
||||
/// signalling (MPEG-2 picture coding extension `top_field_first`, H.264/HEVC
|
||||
/// `pic_struct`), when available: `Some(true)` = top-field-first,
|
||||
/// `Some(false)` = bottom-field-first. `None` = not measured — the muxer
|
||||
/// falls back to TFF for interlaced content (the dominant DVD/HD case). The
|
||||
/// container FieldOrder must agree with the bitstream, so a measured BFF
|
||||
/// stream must NOT be stamped TFF. Ignored for progressive video.
|
||||
pub top_field_first: Option<bool>,
|
||||
/// CICP colour signalling (matrix, transfer, primaries, full_range) MEASURED
|
||||
/// from the bitstream — HEVC/H.264 VUI `colour_description` or MPEG-2
|
||||
/// `sequence_display_extension`. `Some(...)` takes precedence over the
|
||||
/// coarse `color_space` enum (a playlist nibble / PAL-NTSC guess); `None`
|
||||
/// means the bitstream did not state it, so the enum-derived triplet is used.
|
||||
/// Codes are ITU-T H.273 (CICP); `range` is 1 = limited/TV, 2 = full.
|
||||
pub measured_cicp: Option<MeasuredCicp>,
|
||||
}
|
||||
|
||||
/// Measured CICP colour signalling read directly from a video elementary stream
|
||||
/// (ITU-T H.273). Preferred over the coarse [`ColorSpace`] enum when present.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MeasuredCicp {
|
||||
/// MatrixCoefficients (ITU-T H.273 Table 4).
|
||||
pub matrix: u8,
|
||||
/// TransferCharacteristics (ITU-T H.273 Table 3).
|
||||
pub transfer: u8,
|
||||
/// ColourPrimaries (ITU-T H.273 Table 2).
|
||||
pub primaries: u8,
|
||||
/// Range: 1 = limited (studio/TV), 2 = full. Matroska Colour/Range values.
|
||||
pub range: u8,
|
||||
}
|
||||
|
||||
/// An audio stream.
|
||||
@@ -3776,6 +3805,8 @@ mod tests {
|
||||
display_aspect: None,
|
||||
secondary: false,
|
||||
label: String::new(),
|
||||
top_field_first: None,
|
||||
measured_cicp: None,
|
||||
})],
|
||||
chapters: Vec::new(),
|
||||
extents: Vec::new(),
|
||||
|
||||
Reference in New Issue
Block a user