PR #24808 opened by SATVIKsynopsis URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24808 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24808.patch
# Summary of changes Fixes #23910. The MJPEG encoder always used a fixed DC quantizer of 8 so the DC entry in the quantization table could never go below 8. so because of that, quality 100 (an all ones table) wasn't reachable,even with -q:v 1 and an all 8 intra matrix. So, this adds an `intra_dc_precision` option (0-3, default 0) same as the one the MPEG-2 encoder already has. So, at 3 DC is quantized with 1 instead of 8. With the test image from the issue (-q:v 1 -qmin 1, all 8 matrix),MSE against the source goes from 0.462 to 0.334. ImageMagick at quality 100 gives 0.278. Default output is unchanged and the existing mjpeg/amv FATE tests still pass. I also added a FATE test for the new option with slice threading. Open to a different approach if preferred like taking the DC value from the custom matrix instead. >From 29e64935e2fb579d037a0868098c8f5fa435f1f5 Mon Sep 17 00:00:00 2001 From: Satvik Upadhyaya <[email protected]> Date: Wed, 30 Sep 2026 15:24:03 +0530 Subject: [PATCH 1/2] avcodec/mjpegenc: add intra_dc_precision option Fixes #23910 --- doc/encoders.texi | 11 +++++++++++ libavcodec/mjpegenc.c | 5 +++-- libavcodec/mpegvideo_enc.c | 8 +++++--- tests/fate/vcodec.mak | 3 ++- tests/ref/vsynth/vsynth1-mjpeg-dcprec | 4 ++++ tests/ref/vsynth/vsynth2-mjpeg-dcprec | 4 ++++ tests/ref/vsynth/vsynth3-mjpeg-dcprec | 4 ++++ 7 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 tests/ref/vsynth/vsynth1-mjpeg-dcprec create mode 100644 tests/ref/vsynth/vsynth2-mjpeg-dcprec create mode 100644 tests/ref/vsynth/vsynth3-mjpeg-dcprec diff --git a/doc/encoders.texi b/doc/encoders.texi index a3ce15fcff..dba2dec79c 100644 --- a/doc/encoders.texi +++ b/doc/encoders.texi @@ -1266,6 +1266,17 @@ Use the default huffman tables. This is the default strategy. Compute and use optimal huffman tables. @end table + +@item intra_dc_precision @var{integer} +Set the precision of the DC coefficient, minus 8. Valid range is 0 to 3. +Higher values quantize DC more finely: the DC entry of the written +quantization table is 8 >> @var{intra_dc_precision}, so 3 gives a DC +quantizer of 1. Default is 0, which keeps the previous fixed DC +quantizer of 8. + +Combined with @code{-q:v 1} and an @code{intra_matrix} of all 8s, +a value of 3 produces a quantization table of all ones. + @end table @anchor{wavpackenc} diff --git a/libavcodec/mjpegenc.c b/libavcodec/mjpegenc.c index b9ebe3d769..adfbb64e97 100644 --- a/libavcodec/mjpegenc.c +++ b/libavcodec/mjpegenc.c @@ -279,7 +279,7 @@ int ff_mjpeg_encode_stuffing(MPVEncContext *const s) fail: for (int i = 0; i < 3; i++) - s->last_dc[i] = 128; + s->last_dc[i] = 128 << s->c.intra_dc_precision; return ret; } @@ -644,11 +644,12 @@ static int amv_encode_picture(AVCodecContext *avctx, AVPacket *pkt, #define OFFSET(x) offsetof(MJPEGEncContext, mjpeg.x) #define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM static const AVOption options[] = { -#define AMV_OPTIONS_OFFSET 4 +#define AMV_OPTIONS_OFFSET 5 { "huffman", "Huffman table strategy", OFFSET(huffman), AV_OPT_TYPE_INT, { .i64 = HUFFMAN_TABLE_OPTIMAL }, 0, NB_HUFFMAN_TABLE_OPTION - 1, VE, .unit = "huffman" }, { "default", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = HUFFMAN_TABLE_DEFAULT }, INT_MIN, INT_MAX, VE, .unit = "huffman" }, { "optimal", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = HUFFMAN_TABLE_OPTIMAL }, INT_MIN, INT_MAX, VE, .unit = "huffman" }, { "force_duplicated_matrix", "Always write luma and chroma matrix for mjpeg, useful for rtp streaming.", OFFSET(force_duplicated_matrix), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, VE }, +{ "intra_dc_precision", "Precision of the DC coefficient - 8", FF_MPV_OFFSET(c.intra_dc_precision), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 3, VE }, FF_MPV_COMMON_OPTS { NULL}, }; diff --git a/libavcodec/mpegvideo_enc.c b/libavcodec/mpegvideo_enc.c index 2bbbe01b82..06ec772d79 100644 --- a/libavcodec/mpegvideo_enc.c +++ b/libavcodec/mpegvideo_enc.c @@ -858,6 +858,10 @@ av_cold int ff_mpv_encode_init(AVCodecContext *avctx) m->intra_only = 1; /* force intra only for jpeg */ avctx->delay = 0; s->c.low_delay = 1; + if (avctx->codec_id == AV_CODEC_ID_MJPEG) { + s->c.y_dc_scale_table = + s->c.c_dc_scale_table = ff_mpeg12_dc_scale_table[s->c.intra_dc_precision]; + } break; #endif case AV_CODEC_ID_SPEEDHQ: @@ -3860,9 +3864,7 @@ static int encode_picture(MPVMainEncContext *const m, const AVPacket *pkt) s->c.chroma_intra_matrix[j] = av_clip_uint8((chroma_matrix[i] * s->c.qscale) >> 3); s->c. intra_matrix[j] = av_clip_uint8(( luma_matrix[i] * s->c.qscale) >> 3); } - s->c.y_dc_scale_table = - s->c.c_dc_scale_table = ff_mpeg12_dc_scale_table[0]; - s->c.chroma_intra_matrix[0] = s->c.intra_matrix[0] = 8; + s->c.chroma_intra_matrix[0] = s->c.intra_matrix[0] = 8 >> s->c.intra_dc_precision; } else { static const uint8_t y[32] = {13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13}; static const uint8_t c[32] = {14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14}; diff --git a/tests/fate/vcodec.mak b/tests/fate/vcodec.mak index b24eebc61c..5003289d06 100644 --- a/tests/fate/vcodec.mak +++ b/tests/fate/vcodec.mak @@ -245,13 +245,14 @@ fate-vsynth3-magicyuv: ENCOPTS = -pix_fmt yuv444p -pred median \ fate-vsynth_lena-magicyuv: ENCOPTS = -slices 3 -pix_fmt gray -pred left fate-vsynth%-magicyuv: DECOPTS = -sws_flags neighbor+bitexact -FATE_VCODEC_SCALE-$(call ENCDEC, MJPEG, AVI) += mjpeg mjpeg-422 mjpeg-444 mjpeg-trell mjpeg-huffman mjpeg-trell-huffman +FATE_VCODEC_SCALE-$(call ENCDEC, MJPEG, AVI) += mjpeg mjpeg-422 mjpeg-444 mjpeg-trell mjpeg-huffman mjpeg-trell-huffman mjpeg-dcprec fate-vsynth%-mjpeg: ENCOPTS = -qscale 9 -pix_fmt yuvj420p -huffman default -threads 5 -thread_type slice fate-vsynth%-mjpeg-422: ENCOPTS = -qscale 9 -pix_fmt yuvj422p fate-vsynth%-mjpeg-444: ENCOPTS = -qscale 9 -pix_fmt yuvj444p fate-vsynth%-mjpeg-trell: ENCOPTS = -qscale 9 -pix_fmt yuvj420p -trellis 1 -huffman default fate-vsynth%-mjpeg-huffman: ENCOPTS = -qscale 9 -pix_fmt yuvj420p -huffman optimal fate-vsynth%-mjpeg-trell-huffman: ENCOPTS = -qscale 9 -pix_fmt yuvj420p -trellis 1 -huffman optimal +fate-vsynth%-mjpeg-dcprec: ENCOPTS = -qscale 9 -pix_fmt yuvj420p -intra_dc_precision 3 -huffman default -threads 5 -thread_type slice FATE_VCODEC-$(call ENCDEC, MPEG1VIDEO, MPEG1VIDEO MPEGVIDEO) += mpeg1 mpeg1b fate-vsynth%-mpeg1: FMT = mpeg1video diff --git a/tests/ref/vsynth/vsynth1-mjpeg-dcprec b/tests/ref/vsynth/vsynth1-mjpeg-dcprec new file mode 100644 index 0000000000..6abbb8f563 --- /dev/null +++ b/tests/ref/vsynth/vsynth1-mjpeg-dcprec @@ -0,0 +1,4 @@ +72e1112ae1d98a3b906a53b7fcf71bee *tests/data/fate/vsynth1-mjpeg-dcprec.avi +1599208 tests/data/fate/vsynth1-mjpeg-dcprec.avi +17e84ca2a6876daee3d086e960fe2ea8 *tests/data/fate/vsynth1-mjpeg-dcprec.out.rawvideo +stddev: 7.87 PSNR: 30.21 MAXDIFF: 62 bytes: 7603200/ 7603200 diff --git a/tests/ref/vsynth/vsynth2-mjpeg-dcprec b/tests/ref/vsynth/vsynth2-mjpeg-dcprec new file mode 100644 index 0000000000..2175fa451c --- /dev/null +++ b/tests/ref/vsynth/vsynth2-mjpeg-dcprec @@ -0,0 +1,4 @@ +ba9b7af5c2946269e4ccf5a6ca22d2b9 *tests/data/fate/vsynth2-mjpeg-dcprec.avi +910622 tests/data/fate/vsynth2-mjpeg-dcprec.avi +e05824ca581ae95bdd544ba0d5bc97dc *tests/data/fate/vsynth2-mjpeg-dcprec.out.rawvideo +stddev: 4.86 PSNR: 34.38 MAXDIFF: 55 bytes: 7603200/ 7603200 diff --git a/tests/ref/vsynth/vsynth3-mjpeg-dcprec b/tests/ref/vsynth/vsynth3-mjpeg-dcprec new file mode 100644 index 0000000000..8deced67e2 --- /dev/null +++ b/tests/ref/vsynth/vsynth3-mjpeg-dcprec @@ -0,0 +1,4 @@ +72e395ca9005268c746de2d2c7ac4b66 *tests/data/fate/vsynth3-mjpeg-dcprec.avi +67142 tests/data/fate/vsynth3-mjpeg-dcprec.avi +b6c7b383aa0686e1e3d81ab1b875ecd8 *tests/data/fate/vsynth3-mjpeg-dcprec.out.rawvideo +stddev: 8.60 PSNR: 29.43 MAXDIFF: 58 bytes: 86700/ 86700 -- 2.52.0 >From 7774b5964603da2fa3488ae3ea01865a026001d2 Mon Sep 17 00:00:00 2001 From: Satvik Upadhyaya <[email protected]> Date: Wed, 30 Sep 2026 15:42:16 +0530 Subject: [PATCH 2/2] minor changes --- doc/encoders.texi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/encoders.texi b/doc/encoders.texi index dba2dec79c..e9de2cee85 100644 --- a/doc/encoders.texi +++ b/doc/encoders.texi @@ -1268,10 +1268,10 @@ Compute and use optimal huffman tables. @end table @item intra_dc_precision @var{integer} -Set the precision of the DC coefficient, minus 8. Valid range is 0 to 3. -Higher values quantize DC more finely: the DC entry of the written -quantization table is 8 >> @var{intra_dc_precision}, so 3 gives a DC -quantizer of 1. Default is 0, which keeps the previous fixed DC +Set the precision of the DC coefficient minus 8. Valid range is 0 to 3. +Higher values quantize DC more finely as the DC entry of the written +quantization table is 8 >> @var{intra_dc_precision} so 3 gives a DC +quantizer of 1. Default is 0 which keeps the previous fixed DC quantizer of 8. Combined with @code{-q:v 1} and an @code{intra_matrix} of all 8s, -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
