mux: fix keyframe signalling for BlockGroup frames

Inside a Matroska BlockGroup the SimpleBlock 0x80 keyframe bit is
reserved and is always written as 0; keyframe-ness is carried only by
the presence or absence of a ReferenceBlock child. Both halves of the
round-trip got this wrong:

  - the reader skipped past ReferenceBlock and read the reserved bit,
    so every BlockGroup frame came back as a non-keyframe;
  - write_block_group discarded its `keyframe` argument and never
    emitted a ReferenceBlock, on the assumption that only intra frames
    (PGS subtitles) reached that path.

The MPEG-2 parser stamps a per-frame duration on I, P and B pictures
alike, so all MPEG-2 video is written as a BlockGroup. That makes the
assumption false and left no video frame looking like a keyframe on
read-back. Downstream, mkv:// -> m2ts:// dropped every video frame (the
TS muxer discards non-key video until the first keyframe) while still
reporting success, and mkv:// -> mkv:// and the stdio round-trip failed
E6008, because the MKV muxer opens a cluster only on a track-0 video
keyframe and so wrote nothing at all. HEVC was unaffected: it carries no
per-frame duration, so it takes the SimpleBlock path where the flag bit
is authoritative.

Verified on a real CSS DVD: 841 keyframes out of 11440 video packets
survive a re-mux, matching the I-picture count in the source bitstream.

Also stop running non-NAL video through the Annex-B converter. MPEG-2
and VC-1 elementary streams are already start-code framed, so
length-prefix conversion corrupts them. TsMuxer takes a per-track
nal_video flag, defaulting to the previous behaviour, which M2tsStream
sets from each video stream's codec.
This commit is contained in:
Matthew Jackson
2026-07-29 15:29:48 -07:00
parent eedd27e352
commit c812a32f3d
4 changed files with 395 additions and 38 deletions
+11 -1
View File
@@ -8,7 +8,7 @@
//! write-only.
use super::meta;
use crate::disc::{DiscTitle, Stream as DiscStream};
use crate::disc::{Codec, DiscTitle, Stream as DiscStream};
use std::io::{self, Write};
/// BD transport stream write sink with embedded FMKV metadata
@@ -40,6 +40,16 @@ impl M2tsStream {
.collect();
let boxed: Box<dyn Write + Send> = Box::new(writer);
let mut muxer = super::tsmux::TsMuxer::new(boxed, &pids);
// Only HEVC/H.264 arrive length-prefixed (MKV/PES NALU convention);
// MPEG-2 and VC-1 are already start-code ES and must NOT go through
// Annex-B conversion (see `TsMuxer::set_nal_video`) or the frame is
// silently mangled while the mux still reports success.
for (i, s) in title.streams.iter().enumerate() {
if let DiscStream::Video(v) = s {
let is_nal = matches!(v.codec, Codec::Hevc | Codec::H264);
muxer.set_nal_video(i, is_nal)?;
}
}
for (i, cp) in title.codec_privates.iter().enumerate() {
// codec_privates is parallel to streams/pids; ignore any
// trailing entries that exceed the track count rather than