Commit f9110f2a49 for ffmpeg
commit f9110f2a491ffb9b8de8306199707b1292633b93
Author: Kacper Michajłow <kasper93@gmail.com>
Date: Tue Sep 8 05:51:42 2026 +0200
avcodec/h264dec: decode late SEI before finishing frame threading setup
SEI NAL units following the last VCL NAL unit start a new access unit,
but some files keep them in the same packet as the preceding picture,
e.g. YouTube encodes carry an unregistered user data SEI after the slice
data. With frame threading such SEI was skipped and a "Late SEI is not
implemented" warning was printed for every affected packet, because the
SEI context is copied to the next frame thread in
ff_h264_update_thread_context() and must not be modified after
ff_thread_finish_setup(). Single threaded decoding decoded the SEI and
kept its persistent state, like the alternative transfer characteristics
or the x264 build, for the following pictures.
Decode such SEI right before finishing the setup instead. The current
picture is already set up at this point, so the SEI does not apply to
it, matching the single threaded behavior.
Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/21348
Fixes: https://github.com/mpv-player/mpv/issues/18104
Signed-off-by: Kacper Michajłow <kasper93@gmail.com>
diff --git a/libavcodec/h264dec.c b/libavcodec/h264dec.c
index 9bb5999005..597bdae548 100644
--- a/libavcodec/h264dec.c
+++ b/libavcodec/h264dec.c
@@ -603,6 +603,39 @@ static int h264_attach_partitions(const H264Context *h, H264SliceContext *sl,
return idx;
}
+static int h264_decode_sei(H264Context *h, H2645NAL *nal)
+{
+ AVCodecContext *const avctx = h->avctx;
+ int ret;
+
+ ret = ff_h264_sei_decode(&h->sei, &nal->gb, &h->ps, avctx);
+ h->has_recovery_point = h->has_recovery_point || h->sei.recovery_point.recovery_frame_cnt != -1;
+ if (avctx->debug & FF_DEBUG_GREEN_MD)
+ debug_green_metadata(&h->sei.green_metadata, avctx);
+ if (ret < 0 && (avctx->err_recognition & AV_EF_EXPLODE))
+ return ret;
+
+ return 0;
+}
+
+/* Decode the SEI NAL units following the slice at idx. */
+static int h264_decode_late_sei(H264Context *h, int idx)
+{
+ for (int i = idx + 1; i < h->pkt.nb_nals; i++) {
+ H2645NAL *nal = &h->pkt.nals[i];
+ int ret;
+
+ if (nal->type != H264_NAL_SEI)
+ continue;
+
+ ret = h264_decode_sei(h, nal);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
static int decode_nal_units(H264Context *h, AVBufferRef *buf_ref,
const uint8_t *buf, int buf_size)
{
@@ -707,6 +740,9 @@ static int decode_nal_units(H264Context *h, AVBufferRef *buf_ref,
if (h->current_slice == 1) {
if (avctx->active_thread_type & FF_THREAD_FRAME &&
i >= nals_needed && !h->setup_finished && h->cur_pic_ptr) {
+ ret = h264_decode_late_sei(h, i);
+ if (ret < 0)
+ goto end;
ff_thread_finish_setup(avctx);
h->setup_finished = 1;
}
@@ -737,15 +773,11 @@ static int decode_nal_units(H264Context *h, AVBufferRef *buf_ref,
nal->type == H264_NAL_DPB ? 'B' : 'C');
break;
case H264_NAL_SEI:
- if (h->setup_finished) {
- avpriv_request_sample(avctx, "Late SEI");
+ /* already decoded by h264_decode_late_sei() */
+ if (h->setup_finished)
break;
- }
- ret = ff_h264_sei_decode(&h->sei, &nal->gb, &h->ps, avctx);
- h->has_recovery_point = h->has_recovery_point || h->sei.recovery_point.recovery_frame_cnt != -1;
- if (avctx->debug & FF_DEBUG_GREEN_MD)
- debug_green_metadata(&h->sei.green_metadata, h->avctx);
- if (ret < 0 && (h->avctx->err_recognition & AV_EF_EXPLODE))
+ ret = h264_decode_sei(h, nal);
+ if (ret < 0)
goto end;
break;
case H264_NAL_SPS: {