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:
Matthew Jackson
2026-06-25 18:40:30 -07:00
parent 8e0797eab0
commit 43fb97f71f
17 changed files with 514 additions and 54 deletions
+31 -2
View File
@@ -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