PR #25705 opened by Baptiste Coudurier (bcoudurier) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25705 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25705.patch
Add the opt-in `seq_end` MPEG-2 encoder option to append a sequence-end code to the final picture packet. Use both existing picture queues to detect the final picture during draining so B-frame reordering does not cause missing or repeated end codes. Keep the option in the encoder context, document it, and reject `seq_end=1` with `+low_delay`, which would require a separate trailer packet. Add FATE regressions for a partial B-frame chain and queued B-frames. Validation on current upstream master: - Both new FATE regressions pass. - 240 combinations of frame counts, B-frame counts/strategies, and VBR/CBR: exactly one trailing end code and the expected decoded frame count. - With VBR, enabled output is byte-identical to disabled output plus the four-byte sequence-end code. - Low-delay option validation passes. >From f5927391c502d4670bd197e59eddadee8110ebcd Mon Sep 17 00:00:00 2001 From: Baptiste Coudurier <[email protected]> Date: Sat, 17 Oct 2026 11:18:11 -0700 Subject: [PATCH] avcodec/mpeg12enc: add optional sequence end code Add the seq_end option to append a sequence end code to the final MPEG-2 picture packet. Detect the final picture from both existing picture queues so B-frame draining emits exactly one end code. Keep the option in the encoder context and reject low-delay mode, which would require a separate trailer packet. Document the option and add FATE coverage for partial and queued B-frame chains. Signed-off-by: Baptiste Coudurier <[email protected]> --- doc/encoders.texi | 4 ++++ libavcodec/mpeg12enc.c | 6 ++++++ libavcodec/mpegvideo_enc.c | 11 +++++++++++ libavcodec/mpegvideoenc.h | 2 ++ tests/fate/ffmpeg.mak | 5 +++++ tests/ref/fate/mpeg2-seq-end-bframes | 1 + tests/ref/fate/mpeg2-seq-end-short | 1 + 7 files changed, 30 insertions(+) create mode 100644 tests/ref/fate/mpeg2-seq-end-bframes create mode 100644 tests/ref/fate/mpeg2-seq-end-short diff --git a/doc/encoders.texi b/doc/encoders.texi index d3487b40a5c..4ec09ac38d5 100644 --- a/doc/encoders.texi +++ b/doc/encoders.texi @@ -3781,6 +3781,10 @@ Select the mpeg2 level to encode: @item low @end table +@item seq_end @var{boolean} +Write a sequence end code after the last picture. Disabled by default. +Cannot be combined with @option{flags +low_delay}. + @item seq_disp_ext @var{integer} Specifies if the encoder should write a sequence_display_extension to the output. diff --git a/libavcodec/mpeg12enc.c b/libavcodec/mpeg12enc.c index ab2df9d1f9b..b30a348112b 100644 --- a/libavcodec/mpeg12enc.c +++ b/libavcodec/mpeg12enc.c @@ -1056,6 +1056,11 @@ static av_cold int encode_init(AVCodecContext *avctx) int ret; int max_size = avctx->codec_id == AV_CODEC_ID_MPEG2VIDEO ? 16383 : 4095; + if (m->seq_end && (avctx->flags & AV_CODEC_FLAG_LOW_DELAY)) { + av_log(avctx, AV_LOG_ERROR, "Sequence end code is not supported with low delay\n"); + return AVERROR(EINVAL); + } + if (avctx->width > max_size || avctx->height > max_size) { av_log(avctx, AV_LOG_ERROR, "%s does not support resolutions above %dx%d\n", CONFIG_SMALL ? avctx->codec->name : avctx->codec->long_name, @@ -1268,6 +1273,7 @@ static const AVOption mpeg2_options[] = { FF_MPV_OFFSET(c.intra_vlc_format), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, { "non_linear_quant", "Use nonlinear quantizer.", FF_MPV_OFFSET(c.q_scale_type), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, { "alternate_scan", "Enable alternate scantable.", FF_MPV_OFFSET(c.alternate_scan), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, + { "seq_end", "Write sequence end code.", FF_MPV_MAIN_OFFSET(seq_end), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, { "a53cc", "Use A53 Closed Captions (if available)", OFFSET(a53_cc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, VE }, { "seq_disp_ext", "Write sequence_display_extension blocks.", OFFSET(seq_disp_ext), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 1, VE, .unit = "seq_disp_ext" }, { "auto", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, VE, .unit = "seq_disp_ext" }, diff --git a/libavcodec/mpegvideo_enc.c b/libavcodec/mpegvideo_enc.c index c9da0326164..aa4caec3233 100644 --- a/libavcodec/mpegvideo_enc.c +++ b/libavcodec/mpegvideo_enc.c @@ -1918,6 +1918,7 @@ int ff_mpv_encode_picture(AVCodecContext *avctx, AVPacket *pkt, MPVMainEncContext *const m = avctx->priv_data; MPVEncContext *const s = &m->s; int stuffing_count, ret; + int seq_end = !pic_arg && m->seq_end; int context_count = s->c.slice_context_count; ff_mpv_unref_picture(&s->c.cur_pic); @@ -1934,6 +1935,10 @@ int ff_mpv_encode_picture(AVCodecContext *avctx, AVPacket *pkt, if (ret < 0) return ret; + /* Both queues must be empty after selecting the last picture. */ + for (int i = 1; seq_end && i <= MPVENC_MAX_B_FRAMES; i++) + seq_end = !m->input_picture[i] && !m->reordered_input_picture[i]; + /* output? */ if (s->new_pic->data[0]) { int growing_buffer = context_count == 1 && !s->data_partitioning; @@ -2058,6 +2063,12 @@ vbv_retry: m->frame_bits = put_bits_count(&s->pb); } + if (seq_end) { + put_bits32(&s->pb, 0x1B7); // sequence_end_code + flush_put_bits(&s->pb); + m->frame_bits = put_bits_count(&s->pb); + } + /* update MPEG-1/2 vbv_delay for CBR */ if (avctx->rc_max_rate && avctx->rc_min_rate == avctx->rc_max_rate && diff --git a/libavcodec/mpegvideoenc.h b/libavcodec/mpegvideoenc.h index 3943a284e8c..c4275d2b016 100644 --- a/libavcodec/mpegvideoenc.h +++ b/libavcodec/mpegvideoenc.h @@ -209,6 +209,8 @@ typedef struct MPVMainEncContext { int input_picture_number; ///< used to set pic->display_picture_number int coded_picture_number; ///< used to set pic->coded_picture_number + int seq_end; ///< write sequence end code after the last picture + MPVPicture *input_picture[MPVENC_MAX_B_FRAMES + 1]; ///< next pictures in display order MPVPicture *reordered_input_picture[MPVENC_MAX_B_FRAMES + 1]; ///< next pictures in coded order diff --git a/tests/fate/ffmpeg.mak b/tests/fate/ffmpeg.mak index c2bb00b67c6..25c2e5909f4 100644 --- a/tests/fate/ffmpeg.mak +++ b/tests/fate/ffmpeg.mak @@ -469,6 +469,11 @@ fate-ffmpeg-loopback-decoding: CMD = transcode \ "-map 0:v:0 -c:v mpeg2video -f null - -flags +bitexact -idct simple -threads $$threads -dec 0:0 -filter_complex '[0:v][dec:0]hstack[stack]' -map '[stack]' -c:v ffv1" "" FATE_FFMPEG-$(call ENCDEC2, MPEG2VIDEO, FFV1, NUT, HSTACK_FILTER PIPE_PROTOCOL FRAMECRC_MUXER) += fate-ffmpeg-loopback-decoding +# Test sequence end with a partial B-frame chain and with queued B-frames. +FATE_FFMPEG-$(call ENCMUX, MPEG2VIDEO, MPEG2VIDEO, TESTSRC2_FILTER FILE_PROTOCOL) += fate-mpeg2-seq-end-short fate-mpeg2-seq-end-bframes +fate-mpeg2-seq-end-short: CMD = md5 -filter_complex testsrc2=s=64x64:r=25:d=0.08 -c:v mpeg2video -flags +bitexact -dct fastint -q:v 2 -bf 2 -seq_end 1 -f mpeg2video +fate-mpeg2-seq-end-bframes: CMD = md5 -filter_complex testsrc2=s=64x64:r=25:d=0.12 -c:v mpeg2video -flags +bitexact -dct fastint -q:v 2 -bf 1 -seq_end 1 -f mpeg2video + # test matching by stream disposition fate-ffmpeg-spec-disposition: CMD = framecrc -i $(TARGET_SAMPLES)/mpegts/pmtchange.ts -map '0:disp:visual_impaired+descriptions:1' -c copy FATE_SAMPLES_FFMPEG-$(call FRAMECRC, MPEGTS,,) += fate-ffmpeg-spec-disposition diff --git a/tests/ref/fate/mpeg2-seq-end-bframes b/tests/ref/fate/mpeg2-seq-end-bframes new file mode 100644 index 00000000000..76323f5145a --- /dev/null +++ b/tests/ref/fate/mpeg2-seq-end-bframes @@ -0,0 +1 @@ +79f0eefaeaf99d10de679f2b7d640398 diff --git a/tests/ref/fate/mpeg2-seq-end-short b/tests/ref/fate/mpeg2-seq-end-short new file mode 100644 index 00000000000..c88f08f5648 --- /dev/null +++ b/tests/ref/fate/mpeg2-seq-end-short @@ -0,0 +1 @@ +0df4fa71ba94fa23a72e853133febfd7 -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
