PR #25303 opened by Marti2203
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25303
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25303.patch
The ILV=1 (line interleaved) path of `ff_jpegls_decode_picture()` always calls
`ls_decode_line()` with 8 bits per sample. The ILV=0 path picks 8 or 16 bits
from `s->bits`. JPEG-LS with more than 8 bits is only accepted for a single
component, and for one component line interleaving is the same as ILV=0. Such a
scan is accepted, but it is decoded as 8-bit samples into the 16-bit frame.
Only half of each line is written and the picture is wrong. 8-bit
single-component scans with ILV=1 already decode the same as with ILV=0.
`repro.sh` encodes testsrc2 as 8-bit and 16-bit gray with our jpegls encoder,
sets the ILV byte of the SOS header to 1, and compares the decoded MD5s:
```
./repro.sh /path/to/ffmpeg
```
Before (c50d6fca):
```
gray: source MD5=cd06272df6d816052b7c0f27a97b92aa
gray: ILV=0 MD5=cd06272df6d816052b7c0f27a97b92aa
gray: ILV=1 MD5=cd06272df6d816052b7c0f27a97b92aa
gray16le: source MD5=508405c5ba22f58bca3b73e82e6887e8
gray16le: ILV=0 MD5=508405c5ba22f58bca3b73e82e6887e8
gray16le: ILV=1 MD5=784699b6b5149fd02a2c6db76ff00356
```
After:
```
gray16le: ILV=1 MD5=508405c5ba22f58bca3b73e82e6887e8
```
(the 8-bit lines are unchanged)
The fix uses the 16-bit line decoder when there are more than 8 bits, as the
ILV=0 path does. Because only single-component scans can have more than 8 bits,
the per-component byte offsets used by the 8-bit path aren't needed there. The
second commit adds `fate-jpegls-ilv1-16bit`. It generates the file with the
encoder, patches the ILV byte with `run_with_patched_temp`, and checks the
decoded CRC (it equals the source's). The test fails on c50d6fca and passes
with the fix.
Testing: `fate-jpegls` (including the sample-based `ilv0-rst`/`ilv1-rst`),
`fate-vsynth{1,2,3}-jpegls` and `fate-source` pass.
This issue was found by an LLM-Enhanced Static Analysis Engine and verified
manually.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
From 250f99770494ca870b288c3a702a88fe843f73c6 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 21 Oct 2026 18:26:38 +0800
Subject: [PATCH 1/2] avcodec/jpeglsdec: decode line interleaved scans with
more than 8 bits
The ILV=1 (line interleaved) path always called ls_decode_line() with
8 bits per sample, while the ILV=0 path selects 8 or 16 bits from the
frame. JPEG-LS with more than 8 bits is only accepted for a single
component, for which line interleaving is equivalent to ILV=0, but such
a scan was decoded as 8-bit samples into the 16-bit frame: only half of
each line was written and the output was wrong, e.g. a 16-bit gray image
from our encoder with the ILV byte set to 1 decodes to a different
picture than with ILV=0.
Use the 16-bit line decoder for more than 8 bits, as for ILV=0.
Signed-off-by: Martin Mirchev <[email protected]>
---
libavcodec/jpeglsdec.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/libavcodec/jpeglsdec.c b/libavcodec/jpeglsdec.c
index 7633bcceb68..0ab73eb3e42 100644
--- a/libavcodec/jpeglsdec.c
+++ b/libavcodec/jpeglsdec.c
@@ -480,11 +480,18 @@ int ff_jpegls_decode_picture(MJpegDecodeContext *s)
last = zero;
}
for (j = 0; j < stride; j++) {
- ret = ls_decode_line(state, s, last + j, cur + j,
- Rc[j], width, stride, j, 8);
+ if (s->bits <= 8) {
+ ret = ls_decode_line(state, s, last + j, cur + j,
+ Rc[j], width, stride, j, 8);
+ Rc[j] = last[j];
+ } else {
+ /* only single component scans can have more than 8 bits */
+ ret = ls_decode_line(state, s, last, cur,
+ Rc[j], width, stride, j, 16);
+ Rc[j] = *((uint16_t *)last);
+ }
if (ret < 0)
break;
- Rc[j] = last[j];
}
if (ret < 0)
break;
--
2.54.0
From 1ae9af603bb1257f1892849f797da403b396e9ac Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 21 Oct 2026 18:26:38 +0800
Subject: [PATCH 2/2] fate/image: add a test for 16-bit JPEG-LS with line
interleaving
Encode a 16-bit gray image with the jpegls encoder, set the ILV byte of
the SOS header to 1 and check that it decodes to the source picture.
Signed-off-by: Martin Mirchev <[email protected]>
---
tests/fate/image.mak | 13 +++++++++++++
tests/ref/fate/jpegls-ilv1-16bit | 6 ++++++
2 files changed, 19 insertions(+)
create mode 100644 tests/ref/fate/jpegls-ilv1-16bit
diff --git a/tests/fate/image.mak b/tests/fate/image.mak
index c78baa8e086..e7914aa85cc 100644
--- a/tests/fate/image.mak
+++ b/tests/fate/image.mak
@@ -394,6 +394,19 @@ FATE_JPEGLS-$(call DEMDEC, IMAGE2, JPEGLS) +=
$(FATE_JPEGLS)
FATE_IMAGE_FRAMECRC += $(FATE_JPEGLS-yes)
fate-jpegls: $(FATE_JPEGLS-yes)
+# A single-component scan with line interleaving (ILV=1) decodes like ILV=0.
+# Encode a 16-bit gray image and set the ILV byte of the SOS header (offset 23)
+# to 1; the output must match the source.
+JPEGLS_ILV1_GEN = $(FFMPEG) -nostdin -v error -f lavfi -i
testsrc2=s=64x64,format=gray16le \
+ -frames:v 1 -c:v jpegls -flags +bitexact -fflags +bitexact -f image2 -y
+JPEGLS_ILV1_DEC = $(FFMPEG) -nostdin -v error -flags +bitexact -fflags
+bitexact \
+ -f framecrc - -i
+FATE_JPEGLS_ILV1-$(call ENCDEC, JPEGLS, IMAGE2, LAVFI_INDEV TESTSRC2_FILTER
FORMAT_FILTER \
+ WRAPPED_AVFRAME_DECODER FRAMECRC_MUXER PIPE_PROTOCOL) +=
fate-jpegls-ilv1-16bit
+fate-jpegls-ilv1-16bit: CMD = run_with_patched_temp "$(JPEGLS_ILV1_GEN)"
"$(JPEGLS_ILV1_DEC)" jls 23 "\\0001"
+FATE_FFMPEG += $(FATE_JPEGLS_ILV1-yes)
+fate-jpegls: $(FATE_JPEGLS_ILV1-yes)
+
FATE_IMAGE_FRAMECRC-$(call DEMDEC, IMAGE2, QDRAW) += fate-pict
fate-pict: CMD = framecrc -i $(TARGET_SAMPLES)/quickdraw/TRU256.PCT -pix_fmt
rgb24
diff --git a/tests/ref/fate/jpegls-ilv1-16bit b/tests/ref/fate/jpegls-ilv1-16bit
new file mode 100644
index 00000000000..ca238f13f32
--- /dev/null
+++ b/tests/ref/fate/jpegls-ilv1-16bit
@@ -0,0 +1,6 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 64x64
+#sar 0: 0/1
+0, 0, 0, 1, 8192, 0xb5879ab5
--
2.54.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]