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]
