PR #25158 opened by Baptiste Coudurier (bcoudurier) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25158 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25158.patch
With slice threading, the MJPEG encoder writes a restart marker after each macroblock row. The row of the marker was derived from the macroblock position: the previous row when called at the start of a new row (`mb_x == 0`), the current row at the end of a slice (`mb_x == mb_width - 1`). With a single macroblock column (width <= 16) both cases have `mb_x == 0`, so the end of a slice wrote the marker of the previous row (RST7 for the first slice) and an extra marker after the last row. libjpeg rejects these files with "Corrupt JPEG data: found marker 0xd7 instead of RST0". The row of the last coded macroblock is now passed explicitly from `write_slice_end()` to `ff_mjpeg_encode_stuffing()`. Tested with 300 encodes (4:2:0/4:2:2/4:4:4, widths 8-48, heights 16-136, 2/3/4/8 slices), checking the RST sequence (RST0..RST7 cycling, `mb_height - 1` markers) and decoding with `djpeg`: 96 broken before, all of them one macroblock wide, none after. FATE passes for all mpegvideo based encoders (849 tests), no reference changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) From 46bd529110fbada2d4f9a159b6f661905bd8fdf6 Mon Sep 17 00:00:00 2001 From: Baptiste Coudurier <[email protected]> Date: Sat, 17 Oct 2026 11:17:16 -0700 Subject: [PATCH] avcodec/mjpegenc: fix restart markers for pictures one macroblock wide The row of the restart marker was derived from the macroblock position, as the previous row when called at the start of a new row (mb_x == 0) and the current row at the end of a slice (mb_x == mb_width - 1). With a single macroblock column both cases have mb_x == 0, so the end of a slice wrote the marker of the previous row, and a marker after the last row. Pass the row of the last coded macroblock explicitly. Co-Authored-By: Claude Opus 5.5 <[email protected]> --- libavcodec/mjpegenc.c | 8 ++++---- libavcodec/mjpegenc.h | 2 +- libavcodec/mpegvideo_enc.c | 11 +++++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/libavcodec/mjpegenc.c b/libavcodec/mjpegenc.c index 4305f11774..4bdeef8b66 100644 --- a/libavcodec/mjpegenc.c +++ b/libavcodec/mjpegenc.c @@ -233,13 +233,13 @@ static void mjpeg_build_optimal_huffman(MJpegContext *m) * Header + values + stuffing. * * @param s The MPVEncContext. + * @param last_mb_y Row of the last coded macroblock. * @return int Error code, 0 if successful. */ -int ff_mjpeg_encode_stuffing(MPVEncContext *const s) +int ff_mjpeg_encode_stuffing(MPVEncContext *const s, int last_mb_y) { MJpegContext *const m = s->mjpeg_ctx; PutBitContext *pbc = &s->pb; - int mb_y = s->c.mb_y - !s->c.mb_x; int ret; #if CONFIG_MJPEG_ENCODER @@ -273,8 +273,8 @@ int ff_mjpeg_encode_stuffing(MPVEncContext *const s) ff_mjpeg_escape_FF(pbc, s->esc_pos); - if (s->c.slice_context_count > 1 && mb_y < s->c.mb_height - 1) - put_marker(pbc, RST0 + (mb_y&7)); + if (s->c.slice_context_count > 1 && last_mb_y < s->c.mb_height - 1) + put_marker(pbc, RST0 + (last_mb_y & 7)); s->esc_pos = put_bytes_count(pbc, 0); fail: diff --git a/libavcodec/mjpegenc.h b/libavcodec/mjpegenc.h index 865f13f50e..0db364e648 100644 --- a/libavcodec/mjpegenc.h +++ b/libavcodec/mjpegenc.h @@ -97,6 +97,6 @@ static inline void put_marker(PutBitContext *p, enum JpegMarker code) typedef struct MPVEncContext MPVEncContext; -int ff_mjpeg_encode_stuffing(MPVEncContext *s); +int ff_mjpeg_encode_stuffing(MPVEncContext *s, int last_mb_y); #endif /* AVCODEC_MJPEGENC_H */ diff --git a/libavcodec/mpegvideo_enc.c b/libavcodec/mpegvideo_enc.c index 08d7aa59b7..71f981b04e 100644 --- a/libavcodec/mpegvideo_enc.c +++ b/libavcodec/mpegvideo_enc.c @@ -2866,7 +2866,10 @@ static int mb_var_thread(AVCodecContext *c, void *arg){ return 0; } -static void write_slice_end(MPVEncContext *const s) +/** + * @param last_mb_y row of the last coded macroblock + */ +static void write_slice_end(MPVEncContext *const s, int last_mb_y) { if (CONFIG_MPEG4_ENCODER && s->c.codec_id == AV_CODEC_ID_MPEG4) { if (s->partitioned_frame) @@ -2875,7 +2878,7 @@ static void write_slice_end(MPVEncContext *const s) ff_mpeg4_stuffing(&s->pb); } else if ((CONFIG_MJPEG_ENCODER || CONFIG_AMV_ENCODER) && s->c.out_format == FMT_MJPEG) { - ff_mjpeg_encode_stuffing(s); + ff_mjpeg_encode_stuffing(s, last_mb_y); } else if (CONFIG_SPEEDHQ_ENCODER && s->c.out_format == FMT_SPEEDHQ) { ff_speedhq_end_slice(s); } @@ -3086,7 +3089,7 @@ static int encode_thread(AVCodecContext *c, void *arg){ if(is_gob_start){ if (s->c.start_mb_y != mb_y || mb_x != 0) { - write_slice_end(s); + write_slice_end(s, mb_y - !mb_x); if (CONFIG_MPEG4_ENCODER && s->c.codec_id == AV_CODEC_ID_MPEG4 && s->partitioned_frame) ff_mpeg4_init_partitions(s); @@ -3590,7 +3593,7 @@ static int encode_thread(AVCodecContext *c, void *arg){ ff_msmpeg4_encode_ext_header(s); #endif - write_slice_end(s); + write_slice_end(s, s->c.mb_y); return 0; } -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
