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]

Reply via email to