6.1.4 - A/53 caption timing
A timing fix on the in-picture closed-caption path. Anyone rendering A/53 captions from a broadcast source wants this one.
Fixed
-
In-picture A/53 closed captions are timed against the source again, not against the playlist. Every A/53 caption was displaced by the current playlist shift, and because that shift is recomputed on each producer restart, the displacement changed after every seek. Reported by edde746, who traced the value flow from source and measured the shift magnitude without a caption-bearing clip on hand (#259).
The axis, not the time base. A/53 extraction rides the segment producer's per-packet finalize step, alongside the HDR10+ scan, which is the right place for it: decode order, repaired DTS, and the packet is still in the source time base because the rescale to the muxer's happens on the next line. What that spot no longer has is the source axis. The pump rebases every packet onto the output timeline well above it (
packet.pointee.pts -= activeShift), so the timestamps handed to the caption tap were on the item axis while everything downstream reads them as source PTS: the decoded cues feedsubtitleCues, and a host renders those againstcurrentTime, which folds that same shift back in.Both sibling feeds were already correct, which is why nothing pointed at this. A demuxable
eia_608/c608caption track is handed to the tap at the top of the read loop, before the rebase, and the software path reads its triplets out ofAV_FRAME_DATA_A53_CCside data, where no shift exists at all.NativeSubtitleCueStorestates the same contract from the other side: it holds cues on the source axis and subtracts the shift when a window is queried.The magnitude is the shift,
firstActualVideoDts - desiredFirstVideoTfdt. On a clip with B-frames it is two frames at head of stream (83 ms at 24 fps); a restart that lands off its planned keyframe has been measured in seconds; and a broadcast MPEG-TS source whose first DTS sits far from zero, the case this path exists for, carries it from the first packet.foldingShiftBackfolds it back at the observation site, mirroringsegmentIndex(forSourcePts:), which already does exactly that before comparing against the source-axis segment boundaries. NOPTS and an unresolved shift pass through untouched.The regression witness needed a caption-bearing source, and no encoder emits A/53 from a synthetic one, so
Scripts/fetch-fixtures.shnow builds it:user_data_registered_itu_t_t35SEI injected into a synthetic H.264 clip, acc_dataon every access unit, and a complete pop-on sequence every 2 s held inside a single access unit so the caption is atomic there and survives B-frame reordering. FFmpeg's own CEA-608 decoder reads the captions back out of it.
Upgrading
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.1.4")No API change and no source change for consumers. Hosts that render subtitleCues against sourceTime, which is the documented contract, get the corrected timing with no work.