PR #21233 opened by Zhao Zhili (quink)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21233
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21233.patch

Some tools like v4l2-ctl dump data without skip padding. If the
padding size is not an integer multiple of the number of pixel
bytes, we cannot handle it by using a larger width, e.g.,, for
RGB24 image with 32 bytes padding in each line.


>From ac104802ad391f752ccf908c8f7bd9032acbdc03 Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Thu, 18 Dec 2025 16:23:11 +0800
Subject: [PATCH 1/2] avformat/rawvideodec: use AV_OPT_TYPE_PIXEL_FMT instead
 of parse from string manually

---
 libavformat/rawvideodec.c | 18 ++++--------------
 1 file changed, 4 insertions(+), 14 deletions(-)

diff --git a/libavformat/rawvideodec.c b/libavformat/rawvideodec.c
index 0d0b8876e6..1fde1124a5 100644
--- a/libavformat/rawvideodec.c
+++ b/libavformat/rawvideodec.c
@@ -32,7 +32,7 @@
 typedef struct RawVideoDemuxerContext {
     const AVClass *class;     /**< Class for private options. */
     int width, height;        /**< Integers describing video size, set by a 
private option. */
-    char *pixel_format;       /**< Set by a private option. */
+    enum AVPixelFormat pix_fmt;
     AVRational framerate;     /**< AVRational describing framerate, set by a 
private option. */
 } RawVideoDemuxerContext;
 
@@ -42,7 +42,7 @@ typedef struct RawVideoDemuxerContext {
 static int rawvideo_read_header(AVFormatContext *ctx)
 {
     RawVideoDemuxerContext *s = ctx->priv_data;
-    enum AVPixelFormat pix_fmt;
+    enum AVPixelFormat pix_fmt = s->pix_fmt;
     AVStream *st;
     int packet_size;
     int ret;
@@ -54,16 +54,6 @@ static int rawvideo_read_header(AVFormatContext *ctx)
     st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
 
     st->codecpar->codec_id = ffifmt(ctx->iformat)->raw_codec_id;
-
-    if ((ffifmt(ctx->iformat)->raw_codec_id != AV_CODEC_ID_V210) &&
-        (ffifmt(ctx->iformat)->raw_codec_id != AV_CODEC_ID_V210X)) {
-        if ((pix_fmt = av_get_pix_fmt(s->pixel_format)) == AV_PIX_FMT_NONE) {
-            av_log(ctx, AV_LOG_ERROR, "No such pixel format: %s.\n",
-                    s->pixel_format);
-            return AVERROR(EINVAL);
-        }
-    }
-
     avpriv_set_pts_info(st, 64, s->framerate.den, s->framerate.num);
 
     ret = av_image_check_size(s->width, s->height, 0, ctx);
@@ -92,7 +82,7 @@ static int rawvideo_read_header(AVFormatContext *ctx)
             st->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO;
         } else {
             av_log(ctx, AV_LOG_ERROR, "unsupported format: %s for 
bitpacked.\n",
-                    s->pixel_format);
+                    desc->name);
             return AVERROR(EINVAL);
         }
         st->codecpar->codec_tag = tag;
@@ -137,7 +127,7 @@ static int rawvideo_read_packet(AVFormatContext *s, 
AVPacket *pkt)
 #define DEC AV_OPT_FLAG_DECODING_PARAM
 static const AVOption rawvideo_options[] = {
     /* pixel_format is not used by the v210 demuxers. */
-    { "pixel_format", "set pixel format", OFFSET(pixel_format), 
AV_OPT_TYPE_STRING, {.str = "yuv420p"}, 0, 0, DEC },
+    { "pixel_format", "set pixel format", OFFSET(pix_fmt), 
AV_OPT_TYPE_PIXEL_FMT, {.i64 = AV_PIX_FMT_YUV420P}, AV_PIX_FMT_YUV420P, 
INT_MAX, DEC },
     { "video_size", "set frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, 
{.str = NULL}, 0, 0, DEC },
     { "framerate", "set frame rate", OFFSET(framerate), 
AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, DEC },
     { NULL },
-- 
2.49.1


>From 1320bc39f2001bebffcd6ac4fb1bc7c73d00f9b9 Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Thu, 18 Dec 2025 20:33:19 +0800
Subject: [PATCH 2/2] avformat/rawvideodec: add stride option to skip line
 padding

Some tools like v4l2-ctl dump data without skip padding. If the
padding size is not an integer multiple of the number of pixel
bytes, we cannot handle it by using a larger width, e.g.,, for
RGB24 image with 32 bytes padding in each line.
---
 libavformat/rawvideodec.c         | 84 ++++++++++++++++++++++++++++---
 libavformat/version.h             |  2 +-
 tests/fate/demux.mak              |  6 +++
 tests/ref/fate/rawvideo-rgb-demux |  7 +++
 tests/ref/fate/rawvideo-yuv-demux |  7 +++
 5 files changed, 99 insertions(+), 7 deletions(-)
 create mode 100644 tests/ref/fate/rawvideo-rgb-demux
 create mode 100644 tests/ref/fate/rawvideo-yuv-demux

diff --git a/libavformat/rawvideodec.c b/libavformat/rawvideodec.c
index 1fde1124a5..62f1f8aff9 100644
--- a/libavformat/rawvideodec.c
+++ b/libavformat/rawvideodec.c
@@ -21,6 +21,8 @@
 
 #include "config_components.h"
 
+#include <stdbool.h>
+
 #include "libavutil/imgutils.h"
 #include "libavutil/parseutils.h"
 #include "libavutil/pixdesc.h"
@@ -34,6 +36,16 @@ typedef struct RawVideoDemuxerContext {
     int width, height;        /**< Integers describing video size, set by a 
private option. */
     enum AVPixelFormat pix_fmt;
     AVRational framerate;     /**< AVRational describing framerate, set by a 
private option. */
+
+    bool has_padding;
+    /* We could derive linesize[1 to N] from linesize[0] for multiplane 
formats,
+     * but having users explicitly specify linesize for each plane can reduce
+     * unexpected results and support more use cases.
+     */
+    int *linesize;
+    unsigned nb_linesize;
+    // linesize without padding
+    int raw_bytes[4];
 } RawVideoDemuxerContext;
 
 // v210 frame width is padded to multiples of 48
@@ -63,6 +75,31 @@ static int rawvideo_read_header(AVFormatContext *ctx)
     st->codecpar->width  = s->width;
     st->codecpar->height = s->height;
 
+    if (s->nb_linesize) {
+        int n = av_pix_fmt_count_planes(pix_fmt);
+        if (s->nb_linesize != n) {
+            av_log(ctx, AV_LOG_ERROR, "Invalid number of stride %u, "
+                   "pixel format has %d plane\n",
+                   s->nb_linesize, n);
+            return AVERROR(EINVAL);
+        }
+
+        ret = av_image_fill_linesizes(s->raw_bytes, pix_fmt, s->width);
+        if (ret < 0)
+            return ret;
+
+        for (int i = 0; i < n; i++) {
+            if (s->linesize[i] < s->raw_bytes[i]) {
+                av_log(ctx, AV_LOG_ERROR, "Invalid stride %u of plane %d, "
+                       "minimum required size is %d for width %d\n",
+                       s->linesize[i], i, s->raw_bytes[i], s->width);
+                return AVERROR(EINVAL);
+            }
+            if (s->linesize[i] > s->raw_bytes[i])
+                s->has_padding = true;
+        }
+    }
+
     if (ffifmt(ctx->iformat)->raw_codec_id == AV_CODEC_ID_BITPACKED) {
         unsigned int pgroup; /* size of the pixel group in bytes */
         unsigned int xinc;
@@ -110,24 +147,59 @@ static int rawvideo_read_header(AVFormatContext *ctx)
 }
 
 
-static int rawvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
+static int rawvideo_read_packet(AVFormatContext *ctx, AVPacket *pkt)
 {
     int ret;
+    RawVideoDemuxerContext *s = ctx->priv_data;
 
-    ret = av_get_packet(s->pb, pkt, s->packet_size);
-    pkt->pts = pkt->dts = pkt->pos / s->packet_size;
+    if (!s->has_padding) {
+        ret = av_get_packet(ctx->pb, pkt, ctx->packet_size);
+        if (ret < 0)
+            return ret;
+        pkt->pts = pkt->dts = pkt->pos / ctx->packet_size;
 
-    pkt->stream_index = 0;
+        return 0;
+    }
+
+    ret = av_new_packet(pkt, ctx->packet_size);
     if (ret < 0)
         return ret;
+
+    pkt->pos = avio_tell(ctx->pb);
+    if (pkt->pos < 0)
+        return pkt->pos;
+
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(s->pix_fmt);
+    uint8_t *p = pkt->data;
+    for (int i = 0; i < s->nb_linesize; i++) {
+        int shift = (i == 1 || i == 2) ? desc->log2_chroma_h : 0;
+        int h = (s->height + (1 << shift) - 1) >> shift;
+        int skip_bytes = s->linesize[i] - s->raw_bytes[i];
+
+        for (int j = 0; j < h; j++) {
+            ret = avio_read(ctx->pb, p, s->raw_bytes[i]);
+            if (ret != s->raw_bytes[i])
+                return ret < 0 ? ret : AVERROR_INVALIDDATA;
+
+            p += s->raw_bytes[i];
+            avio_skip(ctx->pb, skip_bytes);
+        }
+    }
+
+    pkt->pts = pkt->dts = pkt->pos / (p - pkt->data);
+
     return 0;
 }
 
 #define OFFSET(x) offsetof(RawVideoDemuxerContext, x)
 #define DEC AV_OPT_FLAG_DECODING_PARAM
 static const AVOption rawvideo_options[] = {
+    // Only supported by rawvideo demuxer
+    {"stride", "frame line size in bytes", OFFSET(linesize), AV_OPT_TYPE_INT | 
AV_OPT_TYPE_FLAG_ARRAY, {.arr = NULL}, 0, INT_MAX, DEC },
+#define BITPACKED_OPTION_OFFSET 1   // skip stride option
     /* pixel_format is not used by the v210 demuxers. */
     { "pixel_format", "set pixel format", OFFSET(pix_fmt), 
AV_OPT_TYPE_PIXEL_FMT, {.i64 = AV_PIX_FMT_YUV420P}, AV_PIX_FMT_YUV420P, 
INT_MAX, DEC },
+#define V210_OPTION_OFFSET 2       // skip stride and pixel_format option
     { "video_size", "set frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, 
{.str = NULL}, 0, 0, DEC },
     { "framerate", "set frame rate", OFFSET(framerate), 
AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, DEC },
     { NULL },
@@ -155,7 +227,7 @@ const FFInputFormat ff_rawvideo_demuxer = {
 static const AVClass bitpacked_demuxer_class = {
     .class_name = "bitpacked demuxer",
     .item_name  = av_default_item_name,
-    .option     = rawvideo_options,
+    .option     = &rawvideo_options[BITPACKED_OPTION_OFFSET],
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
@@ -176,7 +248,7 @@ const FFInputFormat ff_bitpacked_demuxer = {
 static const AVClass v210_demuxer_class = {
     .class_name = "v210(x) demuxer",
     .item_name  = av_default_item_name,
-    .option     = rawvideo_options + 1,
+    .option     = &rawvideo_options[V210_OPTION_OFFSET],
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
diff --git a/libavformat/version.h b/libavformat/version.h
index 35d796d318..392211f6b0 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -32,7 +32,7 @@
 #include "version_major.h"
 
 #define LIBAVFORMAT_VERSION_MINOR   8
-#define LIBAVFORMAT_VERSION_MICRO 100
+#define LIBAVFORMAT_VERSION_MICRO 101
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
                                                LIBAVFORMAT_VERSION_MINOR, \
diff --git a/tests/fate/demux.mak b/tests/fate/demux.mak
index ad1046a453..e904647410 100644
--- a/tests/fate/demux.mak
+++ b/tests/fate/demux.mak
@@ -136,6 +136,12 @@ fate-pva-demux: CMD = framecrc -idct simple -i 
$(TARGET_SAMPLES)/pva/PVA_test-pa
 FATE_SAMPLES_DEMUX-$(call CRC, QCP) += fate-qcp-demux
 fate-qcp-demux: CMD = crc -i $(TARGET_SAMPLES)/qcp/0036580847.QCP -c:a copy
 
+FATE_SAMPLES_DEMUX-$(call FRAMECRC, RAWVIDEO, RAWVIDEO) += 
fate-rawvideo-rgb-demux
+fate-rawvideo-rgb-demux: CMD = framecrc -f rawvideo -pixel_format rgb24 
-video_size 160x500 -stride 512 -i $(TARGET_SAMPLES)/hevc/two_first_slice.mp4
+
+FATE_SAMPLES_DEMUX-$(call FRAMECRC, RAWVIDEO, RAWVIDEO) += 
fate-rawvideo-yuv-demux
+fate-rawvideo-yuv-demux: CMD = framecrc -f rawvideo -pixel_format yuv420p 
-video_size 250x500 -stride 256,128,128 -i 
$(TARGET_SAMPLES)/hevc/two_first_slice.mp4
+
 FATE_SAMPLES_DEMUX-$(call FRAMECRC, R3D, JPEG2000 PCM_S32BE) += 
fate-redcode-demux
 fate-redcode-demux: CMD = framecrc -i $(TARGET_SAMPLES)/r3d/4MB-sample.r3d 
-c:v copy -c:a copy
 
diff --git a/tests/ref/fate/rawvideo-rgb-demux 
b/tests/ref/fate/rawvideo-rgb-demux
new file mode 100644
index 0000000000..5231b84a59
--- /dev/null
+++ b/tests/ref/fate/rawvideo-rgb-demux
@@ -0,0 +1,7 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 160x500
+#sar 0: 0/1
+0,          0,          0,        1,   240000, 0x184c16a4
+0,          1,          1,        1,   240000, 0xb28b58b2
diff --git a/tests/ref/fate/rawvideo-yuv-demux 
b/tests/ref/fate/rawvideo-yuv-demux
new file mode 100644
index 0000000000..bba00718d1
--- /dev/null
+++ b/tests/ref/fate/rawvideo-yuv-demux
@@ -0,0 +1,7 @@
+#tb 0: 1/25
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 250x500
+#sar 0: 0/1
+0,          0,          0,        1,   187500, 0x16e17c12
+0,          1,          1,        1,   187500, 0xd36f0de2
-- 
2.49.1

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

Reply via email to