PR #23859 opened by zsiec
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23859
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23859.patch

# avcodec/jpegxs_parser: use Lcod for constant-size frames

## Summary

JPEG XS slice data may contain bytes matching codestream markers. The parser 
currently treats EOC immediately followed by SOC as a frame boundary. A valid 
codestream containing `FF11 FF10` in slice padding is therefore split into two 
invalid packets.

This change reads the PIH `Lcod` value when it is nonzero and consumes exactly 
the declared codestream length. It verifies the terminal EOC when enough input 
is buffered and retains the existing marker scan for variable-size codestreams 
where `Lcod` is zero.

## Observed behavior

The affected frame is 1,042,708 bytes. The current parser emits packets of 
602,156 and 440,552 bytes. The first packet fails decoding as truncated, and 
the second begins without CAP and PIH markers.

Direct SVT-JPEG-XS decoding succeeds when the frame is delivered intact.

## Regression

The proposed FATE sample is a valid 16,384-byte 256x256 JPEG XS codestream 
generated with FFmpeg and SVT-JPEG-XS. A false `FF11 FF10` sequence was 
inserted into zero-valued slice padding, and the resulting codestream still 
decodes successfully with SVT-JPEG-XS.

Sample path: `jxs/lcod-false-markers.jxs`

SHA-256: `addb3cdfa058777d26d7a63e4df77eed6f9ac349689279cdcb8da125221ea3ae`

The FATE test concatenates the sample with itself and requires two 16,384-byte 
packets. Current master emits four packets of 16,378 and 6 bytes.

## Validation

- `make fate-jxs-concat-demux fate-jxs-lcod-demux`
- `make fate`
- One-byte input reads through `jpegxs_pipe`
- Captured 1,042,708-byte production frame
- GNU `tools/patcheck`; remaining output is limited to Makefile and 
variable-name false positives plus the optional changelog notice

## Related work

- FFmpeg issue `#21997`
- FFmpeg PR `#21210`

PR `#21210` handles false EOC candidates that are not followed by SOC. This 
change covers the remaining case where slice data contains the complete EOC/SOC 
pair.



>From 77654e14e6bcf0f350280649174ded7eb43fa7d9 Mon Sep 17 00:00:00 2001
From: Thomas Symborski <[email protected]>
Date: Mon, 20 Jul 2026 01:15:22 -0400
Subject: [PATCH] avcodec/jpegxs_parser: use Lcod for constant-size frames

JPEG XS slice data may contain byte sequences matching EOC followed by
SOC. The marker scanner consequently splits valid constant-size
codestreams.

Use the nonzero PIH Lcod value to frame constant-size codestreams and
validate the terminal EOC when it is available. Retain marker scanning
for variable-size codestreams.

Add a FATE regression containing false EOC and SOC markers in slice
padding.

Related: #21997
Signed-off-by: Thomas Symborski <[email protected]>
---
 libavcodec/jpegxs_parser.c    | 68 +++++++++++++++++++++++++++++++++--
 tests/fate/demux.mak          |  3 ++
 tests/ref/fate/jxs-lcod-demux |  7 ++++
 3 files changed, 76 insertions(+), 2 deletions(-)
 create mode 100644 tests/ref/fate/jxs-lcod-demux

diff --git a/libavcodec/jpegxs_parser.c b/libavcodec/jpegxs_parser.c
index a9750b0a02..d41bb41b4e 100644
--- a/libavcodec/jpegxs_parser.c
+++ b/libavcodec/jpegxs_parser.c
@@ -16,6 +16,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include "libavutil/intreadwrite.h"
 #include "libavutil/mem.h"
 
 #include "bytestream.h"
@@ -24,6 +25,62 @@
 #include "parser.h"
 #include "parser_internal.h"
 
+static int jpegxs_read_bytes(ParseContext *pc, const uint8_t *buf, int 
buf_size,
+                             int offset, uint8_t *dst, int size)
+{
+    int buffered, current_offset;
+
+    if (offset < 0 || size < 0 ||
+        (int64_t)offset + size > (int64_t)pc->index + buf_size)
+        return 0;
+
+    buffered = FFMIN(size, FFMAX(pc->index - offset, 0));
+    if (buffered)
+        memcpy(dst, pc->buffer + offset, buffered);
+
+    current_offset = FFMAX(offset - pc->index, 0);
+    if (buffered < size)
+        memcpy(dst + buffered, buf + current_offset, size - buffered);
+
+    return 1;
+}
+
+static int jpegxs_declared_frame_size(ParseContext *pc, const uint8_t *buf,
+                                      int buf_size)
+{
+    uint8_t prefix[6], pih[8], eoc[2];
+    uint32_t frame_size;
+    int cap_size, pih_offset, pih_size;
+
+    if (!jpegxs_read_bytes(pc, buf, buf_size, 0, prefix, sizeof(prefix)) ||
+        AV_RB16(prefix) != JPEGXS_MARKER_SOC ||
+        AV_RB16(prefix + 2) != JPEGXS_MARKER_CAP)
+        return 0;
+
+    cap_size = AV_RB16(prefix + 4);
+    if (cap_size < 2)
+        return 0;
+
+    pih_offset = 4 + cap_size;
+    if (!jpegxs_read_bytes(pc, buf, buf_size, pih_offset, pih, sizeof(pih)) ||
+        AV_RB16(pih) != JPEGXS_MARKER_PIH)
+        return 0;
+
+    pih_size = AV_RB16(pih + 2);
+    frame_size = AV_RB32(pih + 4);
+    if (pih_size < 26 || !frame_size || frame_size > INT_MAX ||
+        frame_size < pih_offset + pih_size + 2 ||
+        pc->index > frame_size)
+        return 0;
+
+    if ((int64_t)pc->index + buf_size >= frame_size &&
+        (!jpegxs_read_bytes(pc, buf, buf_size, frame_size - 2, eoc, 
sizeof(eoc)) ||
+         AV_RB16(eoc) != JPEGXS_MARKER_EOC))
+        return 0;
+
+    return frame_size;
+}
+
 typedef struct JPEGXSParseContext {
     ParseContext pc;
 
@@ -218,9 +275,16 @@ static int jpegxsvideo_parse(AVCodecParserContext *s,
 {
     JPEGXSParseContext *jpegxs = s->priv_data;
     ParseContext *pc = &jpegxs->pc;
-    int next;
+    int frame_size, next;
 
-    next = jpegxs_find_frame_end(jpegxs, buf, buf_size);
+    frame_size = jpegxs_declared_frame_size(pc, buf, buf_size);
+    if (frame_size > 0) {
+        next = frame_size - pc->index;
+        if (next > buf_size)
+            next = END_NOT_FOUND;
+    } else {
+        next = jpegxs_find_frame_end(jpegxs, buf, buf_size);
+    }
 
     if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {
         *poutbuf = NULL;
diff --git a/tests/fate/demux.mak b/tests/fate/demux.mak
index 4cdc1a583f..de38ba7e3c 100644
--- a/tests/fate/demux.mak
+++ b/tests/fate/demux.mak
@@ -189,6 +189,9 @@ fate-ts-timed-id3-hls-demux: CMD = ffprobe_demux 
$(TARGET_PATH)/tests/data/id3.m
 FATE_SAMPLES_DEMUX-$(call PARSERDEM, JPEGXS, IMAGE_JPEGXS_PIPE, 
CONCAT_PROTOCOL) += fate-jxs-concat-demux
 fate-jxs-concat-demux: CMD = framecrc "-i 
concat:$(TARGET_SAMPLES)/jxs/lena.jxs|$(TARGET_SAMPLES)/jxs/lena.jxs -c:v copy"
 
+FATE_SAMPLES_DEMUX-$(call PARSERDEM, JPEGXS, IMAGE_JPEGXS_PIPE, 
CONCAT_PROTOCOL) += fate-jxs-lcod-demux
+fate-jxs-lcod-demux: CMD = framecrc "-i 
concat:$(TARGET_SAMPLES)/jxs/lcod-false-markers.jxs|$(TARGET_SAMPLES)/jxs/lcod-false-markers.jxs
 -c:v copy"
+
 FATE_SAMPLES_DEMUX += $(FATE_SAMPLES_DEMUX-yes)
 FATE_SAMPLES_FFMPEG += $(FATE_SAMPLES_DEMUX)
 FATE_FFPROBE_DEMUX   += $(FATE_FFPROBE_DEMUX-yes)
diff --git a/tests/ref/fate/jxs-lcod-demux b/tests/ref/fate/jxs-lcod-demux
new file mode 100644
index 0000000000..98b256ab11
--- /dev/null
+++ b/tests/ref/fate/jxs-lcod-demux
@@ -0,0 +1,7 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: jpegxs
+#dimensions 0: 256x256
+#sar 0: 0/1
+0,          0,          0,        1,    16384, 0x708c3e9c
+0,          1,          1,        1,    16384, 0x708c3e9c
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to