mux: make B1 concealment decode-clean on every gap shape
Closes the three residual holes where a concealed/lost gap could still let a dangling-reference frame reach the muxer (degraded/undecryptable-disc path only; clean rips are byte-identical and untouched). Root cause: the discontinuity signal was reconstructed from the 4-bit continuity counter and applied per-PES, both of which are lossy. Three coordinated changes: 1. CC-INDEPENDENT marker. fill_null_ts_unit now tags its NULL packets with an adaptation-field discontinuity_indicator; the demuxer recognises a 0x1FFF packet carrying it as a concealed gap and forces a discontinuity on every tracked PID (the lost unit's PID is unknowable). This survives a loss that is an exact multiple of 16 packets (CC aliases to in-sequence — hole 3) and a loss at a PID's very start (no prior CC — hole 4); it also drops any open, potentially-truncated partial PES. 2. PUSI ATTRIBUTION. A gap landing on a PES boundary now flags the PES STARTING after it, not the one flushed at the boundary (hole 1) — stamping the pre-gap frame could arm-then-disarm the gate on a keyframe and admit the real post-gap inter frame. 3. PER-FRAME signal. codec::Frame gains `discontinuity`; each parser propagates it onto the first post-gap frame. MPEG-2 buffers whole GOPs asynchronously, so it associates the gap by ES OFFSET (like PTS/source), landing it on the exact post-gap picture mid-GOP (hole 2) — a per-PES flag stamped the previous picture. consume_ts (and the EOF flush drain) gate on frame.discontinuity. Tests: CC-independent marker with in-sequence CC + leading-loss; PUSI attribution flags the post-gap PES; MPEG-2 offset-mark stamps the post-gap picture through GOP reorder, not the previous one. Existing B1 gate + EOF tests still green (2270 lib tests).
This commit is contained in:
@@ -57,6 +57,7 @@ impl PgsParser {
|
||||
let (start_pts, data) = self.pending.take()?;
|
||||
let duration = end_pts_ns.saturating_sub(start_pts).max(0) as u64;
|
||||
Some(Frame {
|
||||
discontinuity: false,
|
||||
coding: None,
|
||||
source: None,
|
||||
pts_ns: start_pts,
|
||||
@@ -92,6 +93,7 @@ impl CodecParser for PgsParser {
|
||||
.take()
|
||||
.map(|(start_pts, data)| {
|
||||
vec![Frame {
|
||||
discontinuity: false,
|
||||
coding: None,
|
||||
source: None,
|
||||
pts_ns: start_pts,
|
||||
@@ -119,6 +121,7 @@ impl CodecParser for PgsParser {
|
||||
let frame = match pts {
|
||||
Some(end) => self.emit_pending(end),
|
||||
None => self.pending.take().map(|(start_pts, data)| Frame {
|
||||
discontinuity: false,
|
||||
coding: None,
|
||||
source: None,
|
||||
pts_ns: start_pts,
|
||||
@@ -142,6 +145,7 @@ impl CodecParser for PgsParser {
|
||||
// Flush any prior pending undurated and skip storing this one.
|
||||
None => {
|
||||
out.extend(self.pending.take().map(|(start_pts, data)| Frame {
|
||||
discontinuity: false,
|
||||
coding: None,
|
||||
source: None,
|
||||
pts_ns: start_pts,
|
||||
@@ -167,6 +171,7 @@ impl CodecParser for PgsParser {
|
||||
// (A missing PTS falls through to the drop path below: a
|
||||
// bitmap with no timing reference would land at 00:00:00.)
|
||||
out.push(Frame {
|
||||
discontinuity: false,
|
||||
coding: None,
|
||||
source: None,
|
||||
pts_ns: pts.unwrap_or(0),
|
||||
@@ -195,6 +200,7 @@ impl CodecParser for PgsParser {
|
||||
// the final on-screen subtitle (see the module doc).
|
||||
match self.pending.take() {
|
||||
Some((start_pts, data)) => vec![Frame {
|
||||
discontinuity: false,
|
||||
coding: None,
|
||||
source: None,
|
||||
pts_ns: start_pts,
|
||||
|
||||
Reference in New Issue
Block a user