Skip to content

2.4.2 - pgssub: close the predecessor cue on a missing palette

Choose a tag to compare

@superuser404notfound superuser404notfound released this 10 Aug 07:32
· 32 commits to main since this release

Replaces the pgssubdec patch this build has carried since 2.1.1. Same defect, a different repair, and the new one covers more cases while dropping a regression the old one carried. Still four local patches, still FFmpeg n8.1.2.

Changed

  • A damaged PGS display set now closes its predecessor instead of being dropped. PGS carries no end time: a cue is closed by the start of its successor. When a display set references a palette the decoder does not have, stock pgssubdec drops the whole set, so the successor that would have closed the previous cue disappears with it and the previous subtitle stays on screen past its authored end (AetherEngine #142). Outside AV_EF_EXPLODE the decoder now returns the empty subtitle at that branch instead: the pts is already set, no rectangle is allocated yet, and the result is the clearing form the object_count == 0 path a few lines above returns. With strict error recognition the set is still rejected, unchanged.

Why the old patch had to go

2.1.1 repaired the same symptom by keeping the palette and object caches across composition state 3 (Epoch Continue), so a bare PCS + WDS + END set could render from retained state.

That approach is not safe. The caches are fixed arrays bounded by a count, not by an id namespace: parse_object_segment() rejects a previously unseen object id once objects.count reaches MAX_EPOCH_OBJECTS (64). Retained pre-connection objects therefore occupy the slots a self-contained connection display set needs, and a conformant set conveying a new object id was rejected with Too many objects in epoch although stock FFmpeg renders it. The finding came out of the upstream submission of the patch (FFmpeg PR 23851) and is confirmed; the upstream PR now carries the recovery shipped here instead.

The new patch is also not scoped to Epoch Continue. Any display set that lost its palette, truncated or partially received, closes its predecessor at the right time now.

Verification

PGSMissingPaletteTests (new) drives the shipped libavcodec through avcodec_decode_subtitle2 with synthetic display sets and pins all four outcomes: the clearing cue and its pts, the unscoped recovery, the unchanged AV_EF_EXPLODE rejection, and a bare Normal set still rendering from retained state. The same change measured with ffprobe on FFmpeg master 6bbc22dc09:

before                          after
[SUBTITLE]                      [SUBTITLE]
pts_time=1.000000               pts_time=1.000000
num_rects=1                     num_rects=1
[/SUBTITLE]                     [/SUBTITLE]
Invalid palette id 0            Invalid palette id 0
                                [SUBTITLE]
                                pts_time=3.000000
                                num_rects=0
                                [/SUBTITLE]

The diagnostic stays; the stream is still damaged. What changes is that the cue starting at 1s ends at 3s instead of remaining on screen.

Note for consumers

A damaged Epoch Continue set no longer re-renders the previous bitmap from retained state. The predecessor still ends at its authored time, but the damaged set itself shows nothing until the next intact display set arrives, which is what it actually conveys. A consumer that decodes PGS must treat a zero-rectangle subtitle as a clear event for this to be visible as an end time; AetherEngine's EmbeddedSubtitleDecoder already does.

Built against FFmpeg n8.1.2 with Xcode 26. Only Libavcodec changed.