PR #22637 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22637 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22637.patch
As well as a few cosmetic commits backported from https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22540 >From 926c759958d04249eb643b02b9cc14e6552da913 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 27 Mar 2026 14:17:19 +0100 Subject: [PATCH 01/14] swscale/format: move SwsFormat sanitization to helper function Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 69 ++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index 3464a8af7c..af37ee09c8 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -305,6 +305,42 @@ int sws_isSupportedEndiannessConversion(enum AVPixelFormat pix_fmt) legacy_format_entries[pix_fmt].is_supported_endianness : 0; } +static void sanitize_fmt(SwsFormat *fmt, const AVPixFmtDescriptor *desc) +{ + if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL | AV_PIX_FMT_FLAG_BAYER)) { + /* RGB-like family */ + fmt->csp = AVCOL_SPC_RGB; + fmt->range = AVCOL_RANGE_JPEG; + } else if (desc->flags & AV_PIX_FMT_FLAG_XYZ) { + fmt->csp = AVCOL_SPC_UNSPECIFIED; + fmt->color = (SwsColor) { + .prim = AVCOL_PRI_BT709, /* swscale currently hard-codes this XYZ matrix */ + .trc = AVCOL_TRC_SMPTE428, + }; + } else if (desc->nb_components < 3) { + /* Grayscale formats */ + fmt->color.prim = AVCOL_PRI_UNSPECIFIED; + fmt->csp = AVCOL_SPC_UNSPECIFIED; + if (desc->flags & AV_PIX_FMT_FLAG_FLOAT) + fmt->range = AVCOL_RANGE_UNSPECIFIED; + else + fmt->range = AVCOL_RANGE_JPEG; // FIXME: this restriction should be lifted + } + + switch (av_pix_fmt_desc_get_id(desc)) { + case AV_PIX_FMT_YUVJ420P: + case AV_PIX_FMT_YUVJ411P: + case AV_PIX_FMT_YUVJ422P: + case AV_PIX_FMT_YUVJ444P: + case AV_PIX_FMT_YUVJ440P: + fmt->range = AVCOL_RANGE_JPEG; + break; + } + + if (!desc->log2_chroma_w && !desc->log2_chroma_h) + fmt->loc = AVCHROMA_LOC_UNSPECIFIED; +} + /** * This function also sanitizes and strips the input data, removing irrelevant * fields for certain formats. @@ -346,38 +382,7 @@ SwsFormat ff_fmt_from_frame(const AVFrame *frame, int field) av_assert1(fmt.height > 0); av_assert1(fmt.format != AV_PIX_FMT_NONE); av_assert0(desc); - if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL | AV_PIX_FMT_FLAG_BAYER)) { - /* RGB-like family */ - fmt.csp = AVCOL_SPC_RGB; - fmt.range = AVCOL_RANGE_JPEG; - } else if (desc->flags & AV_PIX_FMT_FLAG_XYZ) { - fmt.csp = AVCOL_SPC_UNSPECIFIED; - fmt.color = (SwsColor) { - .prim = AVCOL_PRI_BT709, /* swscale currently hard-codes this XYZ matrix */ - .trc = AVCOL_TRC_SMPTE428, - }; - } else if (desc->nb_components < 3) { - /* Grayscale formats */ - fmt.color.prim = AVCOL_PRI_UNSPECIFIED; - fmt.csp = AVCOL_SPC_UNSPECIFIED; - if (desc->flags & AV_PIX_FMT_FLAG_FLOAT) - fmt.range = AVCOL_RANGE_UNSPECIFIED; - else - fmt.range = AVCOL_RANGE_JPEG; // FIXME: this restriction should be lifted - } - - switch (frame->format) { - case AV_PIX_FMT_YUVJ420P: - case AV_PIX_FMT_YUVJ411P: - case AV_PIX_FMT_YUVJ422P: - case AV_PIX_FMT_YUVJ444P: - case AV_PIX_FMT_YUVJ440P: - fmt.range = AVCOL_RANGE_JPEG; - break; - } - - if (!desc->log2_chroma_w && !desc->log2_chroma_h) - fmt.loc = AVCHROMA_LOC_UNSPECIFIED; + sanitize_fmt(&fmt, desc); if (frame->flags & AV_FRAME_FLAG_INTERLACED) { fmt.height = (fmt.height + (field == FIELD_TOP)) >> 1; -- 2.52.0 >From 6d9d219a1b55124234d65d0b0837a0e60775e6a0 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 27 Mar 2026 14:19:09 +0100 Subject: [PATCH 02/14] swscale/format: add helper function to get "default" SwsFormat But still apply the sanitization/defaulting logic from ff_fmt_from_frame(). Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 10 ++++++++++ libswscale/format.h | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/libswscale/format.c b/libswscale/format.c index af37ee09c8..f7d420029e 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -479,6 +479,16 @@ skip_hdr10: return fmt; } +SwsFormat ff_fmt_from_pixfmt(enum AVPixelFormat pixfmt) +{ + SwsFormat fmt; + ff_fmt_clear(&fmt); + fmt.format = pixfmt; + fmt.desc = av_pix_fmt_desc_get(pixfmt); + sanitize_fmt(&fmt, fmt.desc); + return fmt; +} + static int infer_prim_ref(SwsColor *csp, const SwsColor *ref) { if (csp->prim != AVCOL_PRI_UNSPECIFIED) diff --git a/libswscale/format.h b/libswscale/format.h index d66851893f..ba5447da8b 100644 --- a/libswscale/format.h +++ b/libswscale/format.h @@ -106,6 +106,11 @@ static inline void ff_fmt_clear(SwsFormat *fmt) */ SwsFormat ff_fmt_from_frame(const AVFrame *frame, int field); +/** + * Subset of ff_fmt_from_frame() that sets default metadata for the format. + */ +SwsFormat ff_fmt_from_pixfmt(enum AVPixelFormat pixfmt); + static inline int ff_color_equal(const SwsColor *c1, const SwsColor *c2) { return c1->prim == c2->prim && -- 2.52.0 >From 82905de60061a3c17badbba7ce83e613677f11f0 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 27 Mar 2026 14:24:46 +0100 Subject: [PATCH 03/14] swscale/tests/sws_ops: simplify using ff_fmt_from_pixfmt() No need to allocate a dummy frame just for ff_fmt_from_frame(). Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/sws_ops.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index 504fc34f6b..d2803c6da9 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -27,17 +27,16 @@ #include <fcntl.h> #endif -static int run_test(SwsContext *const ctx, AVFrame *frame, - const AVPixFmtDescriptor *const src_desc, - const AVPixFmtDescriptor *const dst_desc) +static int run_test(SwsContext *const ctx, enum AVPixelFormat src_fmt, + enum AVPixelFormat dst_fmt) { - /* Reuse ff_fmt_from_frame() to ensure correctly sanitized metadata */ - frame->format = av_pix_fmt_desc_get_id(src_desc); - SwsFormat src = ff_fmt_from_frame(frame, 0); - frame->format = av_pix_fmt_desc_get_id(dst_desc); - SwsFormat dst = ff_fmt_from_frame(frame, 0); + SwsFormat src = ff_fmt_from_pixfmt(src_fmt); + SwsFormat dst = ff_fmt_from_pixfmt(dst_fmt); bool incomplete = ff_infer_colors(&src.color, &dst.color); + src.width = dst.width = 16; + src.height = dst.height = 16; + SwsOpList *ops = ff_sws_op_list_alloc(); if (!ops) return AVERROR(ENOMEM); @@ -124,10 +123,8 @@ bad_option: } SwsContext *ctx = sws_alloc_context(); - AVFrame *frame = av_frame_alloc(); - if (!ctx || !frame) + if (!ctx) goto fail; - frame->width = frame->height = 16; av_log_set_callback(log_stdout); for (const AVPixFmtDescriptor *src = NULL; (src = av_pix_fmt_desc_next(src));) { @@ -138,7 +135,7 @@ bad_option: enum AVPixelFormat dst_fmt = av_pix_fmt_desc_get_id(dst); if (dst_fmt < dst_fmt_min || dst_fmt > dst_fmt_max) continue; - int err = run_test(ctx, frame, src, dst); + int err = run_test(ctx, src_fmt, dst_fmt); if (err < 0) goto fail; } @@ -146,7 +143,6 @@ bad_option: ret = 0; fail: - av_frame_free(&frame); sws_free_context(&ctx); return ret; -- 2.52.0 >From 45ff7396a3d5f4a45706705d4bd93dd6b9a8901a Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 27 Mar 2026 14:38:07 +0100 Subject: [PATCH 04/14] swscale/ops: add helper function to enumerate over all op lists This moves the logic from tests/sws_ops into the library itself, where it can be reused by e.g. the aarch64 asmgen backend to iterate over all possible operation types it can expect to see. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ libswscale/ops.h | 26 +++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/libswscale/ops.c b/libswscale/ops.c index ece49e299f..08c1d64864 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -25,6 +25,7 @@ #include "libavutil/rational.h" #include "libavutil/refstruct.h" +#include "format.h" #include "ops.h" #include "ops_internal.h" @@ -877,3 +878,77 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, av_log(log, lev, " (X = unused, z = byteswapped, + = exact, 0 = zero)\n"); } + +static int enum_ops_fmt(SwsContext *ctx, void *opaque, + int (*cb)(SwsContext *ctx, void *opaque, SwsOpList *ops), + enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt) +{ + const SwsPixelType type = SWS_PIXEL_F32; + SwsOpList *ops = ff_sws_op_list_alloc(); + if (!ops) + return AVERROR(ENOMEM); + + ops->src = ff_fmt_from_pixfmt(src_fmt); + ops->dst = ff_fmt_from_pixfmt(dst_fmt); + ops->src.width = ops->dst.width = 16; + ops->src.height = ops->dst.height = 16; + + bool incomplete = ff_infer_colors(&ops->src.color, &ops->dst.color); + if (ff_sws_decode_pixfmt(ops, src_fmt) < 0 || + ff_sws_decode_colors(ctx, type, ops, &ops->src, &incomplete) < 0 || + ff_sws_encode_colors(ctx, type, ops, &ops->src, &ops->dst, &incomplete) < 0 || + ff_sws_encode_pixfmt(ops, dst_fmt) < 0) + goto fail; + + int ret = ff_sws_op_list_optimize(ops); + if (ret < 0) + return ret; + + ret = cb(ctx, opaque, ops); + if (ret < 0) + return ret; + +fail: + /* silently skip unsupported formats */ + ff_sws_op_list_free(&ops); + return 0; +} + +int ff_sws_enum_op_lists(SwsContext *ctx, void *opaque, + int (*cb)(SwsContext *ctx, void *opaque, SwsOpList *ops)) +{ + for (const AVPixFmtDescriptor *src = NULL; (src = av_pix_fmt_desc_next(src));) { + for (const AVPixFmtDescriptor *dst = NULL; (dst = av_pix_fmt_desc_next(dst));) { + const enum AVPixelFormat src_fmt = av_pix_fmt_desc_get_id(src); + const enum AVPixelFormat dst_fmt = av_pix_fmt_desc_get_id(dst); + int ret = enum_ops_fmt(ctx, opaque, cb, src_fmt, dst_fmt); + if (ret < 0) + return ret; + } + } + + return 0; +} + +struct EnumOpaque { + void *opaque; + int (*cb)(SwsContext *ctx, void *opaque, SwsOp *op); +}; + +static int enum_ops(SwsContext *ctx, void *opaque, SwsOpList *ops) +{ + struct EnumOpaque *priv = opaque; + for (int i = 0; i < ops->num_ops; i++) { + int ret = priv->cb(ctx, priv->opaque, &ops->ops[i]); + if (ret < 0) + return ret; + } + return 0; +} + +int ff_sws_enum_ops(SwsContext *ctx, void *opaque, + int (*cb)(SwsContext *ctx, void *opaque, SwsOp *op)) +{ + struct EnumOpaque priv = { opaque, cb }; + return ff_sws_enum_op_lists(ctx, &priv, enum_ops); +} diff --git a/libswscale/ops.h b/libswscale/ops.h index bc8e751f7b..39d3ffe526 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -319,4 +319,30 @@ enum SwsOpCompileFlags { int ff_sws_compile_pass(SwsGraph *graph, SwsOpList **ops, int flags, SwsPass *input, SwsPass **output); +/** + * Helper function to enumerate over all possible (optimized) operation lists, + * under the current set of options in `ctx`, and run the given callback on + * each list. + * + * @return 0 on success, the return value if cb() < 0, or a negative error code + * + * @note `ops` belongs to ff_sws_enum_ops_lists(), but may be mutated by `cb`. + * @see ff_sws_enum_ops() + */ +int ff_sws_enum_op_lists(SwsContext *ctx, void *opaque, + int (*cb)(SwsContext *ctx, void *opaque, SwsOpList *ops)); + +/** + * Helper function to enumerate over all possible operations, under the current + * set of options in `ctx`, and run the given callback on each operation. + * + * @return 0 on success, the return value if cb() < 0, or a negative error code + * + * @note May contain duplicates. `op` belongs to ff_sws_num_ops(), but may be + * mutated by `cb`. + * @see ff_sws_num_op_lists() + */ +int ff_sws_enum_ops(SwsContext *ctx, void *opaque, + int (*cb)(SwsContext *ctx, void *opaque, SwsOp *op)); + #endif -- 2.52.0 >From b7f49a4c3e71d798ad31af737a00e73743e49431 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 27 Mar 2026 14:39:40 +0100 Subject: [PATCH 05/14] swscale/tests/sws_ops: simplify using ff_sws_enum_op_lists() Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/sws_ops.c | 55 +++++++------------------------------- 1 file changed, 9 insertions(+), 46 deletions(-) diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index d2803c6da9..c4c1728ce1 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -27,46 +27,19 @@ #include <fcntl.h> #endif -static int run_test(SwsContext *const ctx, enum AVPixelFormat src_fmt, - enum AVPixelFormat dst_fmt) +static int print_ops(SwsContext *const ctx, void *opaque, SwsOpList *ops) { - SwsFormat src = ff_fmt_from_pixfmt(src_fmt); - SwsFormat dst = ff_fmt_from_pixfmt(dst_fmt); - bool incomplete = ff_infer_colors(&src.color, &dst.color); + av_log(opaque, AV_LOG_INFO, "%s -> %s:\n", + av_get_pix_fmt_name(ops->src.format), + av_get_pix_fmt_name(ops->dst.format)); - src.width = dst.width = 16; - src.height = dst.height = 16; - - SwsOpList *ops = ff_sws_op_list_alloc(); - if (!ops) - return AVERROR(ENOMEM); - ops->src = src; - ops->dst = dst; - - if (ff_sws_decode_pixfmt(ops, src.format) < 0) - goto fail; - if (ff_sws_decode_colors(ctx, SWS_PIXEL_F32, ops, &src, &incomplete) < 0) - goto fail; - if (ff_sws_encode_colors(ctx, SWS_PIXEL_F32, ops, &src, &dst, &incomplete) < 0) - goto fail; - if (ff_sws_encode_pixfmt(ops, dst.format) < 0) - goto fail; - - av_log(NULL, AV_LOG_INFO, "%s -> %s:\n", - av_get_pix_fmt_name(src.format), av_get_pix_fmt_name(dst.format)); - - ff_sws_op_list_optimize(ops); if (ff_sws_op_list_is_noop(ops)) - av_log(NULL, AV_LOG_INFO, " (no-op)\n"); + av_log(opaque, AV_LOG_INFO, " (no-op)\n"); else - ff_sws_op_list_print(NULL, AV_LOG_INFO, AV_LOG_INFO, ops); + ff_sws_op_list_print(opaque, AV_LOG_INFO, AV_LOG_INFO, ops); -fail: - /* silently skip unsupported formats */ - ff_sws_op_list_free(&ops); return 0; } - static void log_stdout(void *avcl, int level, const char *fmt, va_list vl) { if (level != AV_LOG_INFO) { @@ -127,19 +100,9 @@ bad_option: goto fail; av_log_set_callback(log_stdout); - for (const AVPixFmtDescriptor *src = NULL; (src = av_pix_fmt_desc_next(src));) { - enum AVPixelFormat src_fmt = av_pix_fmt_desc_get_id(src); - if (src_fmt < src_fmt_min || src_fmt > src_fmt_max) - continue; - for (const AVPixFmtDescriptor *dst = NULL; (dst = av_pix_fmt_desc_next(dst));) { - enum AVPixelFormat dst_fmt = av_pix_fmt_desc_get_id(dst); - if (dst_fmt < dst_fmt_min || dst_fmt > dst_fmt_max) - continue; - int err = run_test(ctx, src_fmt, dst_fmt); - if (err < 0) - goto fail; - } - } + ret = ff_sws_enum_op_lists(ctx, NULL, print_ops); + if (ret < 0) + goto fail; ret = 0; fail: -- 2.52.0 >From c3d03b2aa0c2c9280d2b4e17dd935bb2fa1a3fcb Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Tue, 24 Mar 2026 16:13:57 +0100 Subject: [PATCH 06/14] swscale/tests/sws_ops: add -v option to set log verbosity Sponsored-by: Sovereign Tech Fund Signed-off-by: Ramiro Polla <[email protected]> --- libswscale/tests/sws_ops.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index c4c1728ce1..4e91b73b3c 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -44,7 +44,7 @@ static void log_stdout(void *avcl, int level, const char *fmt, va_list vl) { if (level != AV_LOG_INFO) { av_log_default_callback(avcl, level, fmt, vl); - } else { + } else if (av_log_get_level() >= AV_LOG_INFO) { vfprintf(stdout, fmt, vl); } } @@ -71,6 +71,8 @@ int main(int argc, char **argv) " Only test the specified destination pixel format\n" " -src <pixfmt>\n" " Only test the specified source pixel format\n" + " -v <level>\n" + " Enable log verbosity at given level\n" ); return 0; } @@ -88,6 +90,8 @@ int main(int argc, char **argv) fprintf(stderr, "invalid pixel format %s\n", argv[i + 1]); goto error; } + } else if (!strcmp(argv[i], "-v")) { + av_log_set_level(atoi(argv[i + 1])); } else { bad_option: fprintf(stderr, "bad option or argument missing (%s) see -help\n", argv[i]); -- 2.52.0 >From 3552fd5d213757e89ebe02ce303bd15b9222cc81 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 18 Mar 2026 16:30:28 +0100 Subject: [PATCH 07/14] swscale/ops: correctly uninit all ops in ff_sws_op_list_remove_at() This only ever removed a single op, even with count > 1. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 08c1d64864..d2c7ec6fc9 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -588,9 +588,10 @@ const SwsOp *ff_sws_op_list_output(const SwsOpList *ops) void ff_sws_op_list_remove_at(SwsOpList *ops, int index, int count) { - const int end = ops->num_ops - count; av_assert2(index >= 0 && count >= 0 && index + count <= ops->num_ops); - op_uninit(&ops->ops[index]); + for (int i = 0; i < count; i++) + op_uninit(&ops->ops[index + i]); + const int end = ops->num_ops - count; for (int i = index; i < end; i++) ops->ops[i] = ops->ops[i + count]; ops->num_ops = end; -- 2.52.0 >From 92dfd73d12c566b67ed5d4e3cc07085709336c84 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 17 Mar 2026 01:21:37 +0100 Subject: [PATCH 08/14] swscale/ops: print exact constant on SWS_OP_SCALE More informative. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 2 +- tests/ref/fate/sws-ops-list | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index d2c7ec6fc9..11015f23cd 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -853,7 +853,7 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, PRINTQ(op->lin.m[3][0]), PRINTQ(op->lin.m[3][1]), PRINTQ(op->lin.m[3][2]), PRINTQ(op->lin.m[3][3]), PRINTQ(op->lin.m[3][4])); break; case SWS_OP_SCALE: - av_log(log, lev, "%-20s: * %s\n", name, PRINTQ(op->c.q)); + av_log(log, lev, "%-20s: * %d/%d\n", name, op->c.q.num, op->c.q.den); break; case SWS_OP_TYPE_NB: break; diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index c83105406f..4cfce95495 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -121d60ee0261ca5d9650a8d1e9e8a060 +c014aa1e7c12d3bb101951487c406148 -- 2.52.0 >From 2614284bd4155a278356bcbd41b17eca7b177c6f Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 18 Mar 2026 11:26:14 +0100 Subject: [PATCH 09/14] swscale/ops: use AVBPrint for assembling op descriptions This commit does not yet touch the PRINTQ macro, but it gets rid of at least one unnecessary hand-managed buffer. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 147 ++++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 71 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 11015f23cd..463b6bf0ae 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -20,6 +20,7 @@ #include "libavutil/avassert.h" #include "libavutil/avstring.h" +#include "libavutil/bprint.h" #include "libavutil/bswap.h" #include "libavutil/mem.h" #include "libavutil/rational.h" @@ -723,18 +724,6 @@ static char describe_comp_flags(SwsCompFlags flags) return '.'; } -static const char *describe_order(SwsSwizzleOp order, int planes, char buf[32]) -{ - if (order.mask == SWS_SWIZZLE(0, 1, 2, 3).mask) - return ""; - - av_strlcpy(buf, ", via {", 32); - for (int i = 0; i < planes; i++) - av_strlcatf(buf, 32, "%s%d", i ? ", " : "", order.in[i]); - av_strlcat(buf, "}", 32); - return buf; -} - static const char *print_q(const AVRational q, char buf[], int buf_len) { if (!q.den) { @@ -756,109 +745,125 @@ static const char *print_q(const AVRational q, char buf[], int buf_len) void ff_sws_op_list_print(void *log, int lev, int lev_extra, const SwsOpList *ops) { + AVBPrint bp; if (!ops->num_ops) { av_log(log, lev, " (empty)\n"); return; } + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + for (int i = 0; i < ops->num_ops; i++) { const SwsOp *op = &ops->ops[i]; const SwsOp *next = i + 1 < ops->num_ops ? &ops->ops[i + 1] : op; const char *name = ff_sws_op_type_name(op->op); - char buf[32]; - - av_log(log, lev, " [%3s %c%c%c%c -> %c%c%c%c] ", - ff_sws_pixel_type_name(op->type), - op->comps.unused[0] ? 'X' : '.', - op->comps.unused[1] ? 'X' : '.', - op->comps.unused[2] ? 'X' : '.', - op->comps.unused[3] ? 'X' : '.', - next->comps.unused[0] ? 'X' : describe_comp_flags(op->comps.flags[0]), - next->comps.unused[1] ? 'X' : describe_comp_flags(op->comps.flags[1]), - next->comps.unused[2] ? 'X' : describe_comp_flags(op->comps.flags[2]), - next->comps.unused[3] ? 'X' : describe_comp_flags(op->comps.flags[3])); + av_bprint_clear(&bp); + av_bprintf(&bp, " [%3s %c%c%c%c -> %c%c%c%c] ", + ff_sws_pixel_type_name(op->type), + op->comps.unused[0] ? 'X' : '.', + op->comps.unused[1] ? 'X' : '.', + op->comps.unused[2] ? 'X' : '.', + op->comps.unused[3] ? 'X' : '.', + next->comps.unused[0] ? 'X' : describe_comp_flags(op->comps.flags[0]), + next->comps.unused[1] ? 'X' : describe_comp_flags(op->comps.flags[1]), + next->comps.unused[2] ? 'X' : describe_comp_flags(op->comps.flags[2]), + next->comps.unused[3] ? 'X' : describe_comp_flags(op->comps.flags[3])); switch (op->op) { case SWS_OP_INVALID: case SWS_OP_SWAP_BYTES: - av_log(log, lev, "%s\n", name); + av_bprintf(&bp, "%s", name); break; case SWS_OP_READ: case SWS_OP_WRITE: - av_log(log, lev, "%-20s: %d elem(s) %s >> %d%s\n", name, - op->rw.elems, op->rw.packed ? "packed" : "planar", - op->rw.frac, - describe_order(op->op == SWS_OP_READ ? ops->order_src - : ops->order_dst, - op->rw.packed ? 1 : op->rw.elems, buf)); + av_bprintf(&bp, "%-20s: %d elem(s) %s >> %d", name, + op->rw.elems, op->rw.packed ? "packed" : "planar", + op->rw.frac); + SwsSwizzleOp order = op->op == SWS_OP_READ ? ops->order_src : ops->order_dst; + if (order.mask != SWS_SWIZZLE(0, 1, 2, 3).mask) { + const int planes = op->rw.packed ? 1 : op->rw.elems; + av_bprintf(&bp, ", via {"); + for (int i = 0; i < planes; i++) + av_bprintf(&bp, "%s%d", i ? ", " : "", order.in[i]); + av_bprintf(&bp, "}"); + } break; case SWS_OP_LSHIFT: - av_log(log, lev, "%-20s: << %u\n", name, op->c.u); + av_bprintf(&bp, "%-20s: << %u", name, op->c.u); break; case SWS_OP_RSHIFT: - av_log(log, lev, "%-20s: >> %u\n", name, op->c.u); + av_bprintf(&bp, "%-20s: >> %u", name, op->c.u); break; case SWS_OP_PACK: case SWS_OP_UNPACK: - av_log(log, lev, "%-20s: {%d %d %d %d}\n", name, - op->pack.pattern[0], op->pack.pattern[1], - op->pack.pattern[2], op->pack.pattern[3]); + av_bprintf(&bp, "%-20s: {%d %d %d %d}", name, + op->pack.pattern[0], op->pack.pattern[1], + op->pack.pattern[2], op->pack.pattern[3]); break; case SWS_OP_CLEAR: - av_log(log, lev, "%-20s: {%s %s %s %s}\n", name, - op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", - op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", - op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", - op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); + av_bprintf(&bp, "%-20s: {%s %s %s %s}", name, + op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", + op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", + op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", + op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); break; case SWS_OP_SWIZZLE: - av_log(log, lev, "%-20s: %d%d%d%d\n", name, - op->swizzle.x, op->swizzle.y, op->swizzle.z, op->swizzle.w); + av_bprintf(&bp, "%-20s: %d%d%d%d", name, + op->swizzle.x, op->swizzle.y, op->swizzle.z, op->swizzle.w); break; case SWS_OP_CONVERT: - av_log(log, lev, "%-20s: %s -> %s%s\n", name, - ff_sws_pixel_type_name(op->type), - ff_sws_pixel_type_name(op->convert.to), - op->convert.expand ? " (expand)" : ""); + av_bprintf(&bp, "%-20s: %s -> %s%s", name, + ff_sws_pixel_type_name(op->type), + ff_sws_pixel_type_name(op->convert.to), + op->convert.expand ? " (expand)" : ""); break; case SWS_OP_DITHER: - av_log(log, lev, "%-20s: %dx%d matrix + {%d %d %d %d}\n", name, - 1 << op->dither.size_log2, 1 << op->dither.size_log2, - op->dither.y_offset[0], op->dither.y_offset[1], - op->dither.y_offset[2], op->dither.y_offset[3]); + av_bprintf(&bp, "%-20s: %dx%d matrix + {%d %d %d %d}", name, + 1 << op->dither.size_log2, 1 << op->dither.size_log2, + op->dither.y_offset[0], op->dither.y_offset[1], + op->dither.y_offset[2], op->dither.y_offset[3]); break; case SWS_OP_MIN: - av_log(log, lev, "%-20s: x <= {%s %s %s %s}\n", name, - op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", - op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", - op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", - op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); + av_bprintf(&bp, "%-20s: x <= {%s %s %s %s}", name, + op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", + op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", + op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", + op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); break; case SWS_OP_MAX: - av_log(log, lev, "%-20s: {%s %s %s %s} <= x\n", name, - op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", - op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", - op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", - op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); + av_bprintf(&bp, "%-20s: {%s %s %s %s} <= x", name, + op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", + op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", + op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", + op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); break; case SWS_OP_LINEAR: - av_log(log, lev, "%-20s: %s [[%s %s %s %s %s] " - "[%s %s %s %s %s] " - "[%s %s %s %s %s] " - "[%s %s %s %s %s]]\n", - name, describe_lin_mask(op->lin.mask), - PRINTQ(op->lin.m[0][0]), PRINTQ(op->lin.m[0][1]), PRINTQ(op->lin.m[0][2]), PRINTQ(op->lin.m[0][3]), PRINTQ(op->lin.m[0][4]), - PRINTQ(op->lin.m[1][0]), PRINTQ(op->lin.m[1][1]), PRINTQ(op->lin.m[1][2]), PRINTQ(op->lin.m[1][3]), PRINTQ(op->lin.m[1][4]), - PRINTQ(op->lin.m[2][0]), PRINTQ(op->lin.m[2][1]), PRINTQ(op->lin.m[2][2]), PRINTQ(op->lin.m[2][3]), PRINTQ(op->lin.m[2][4]), - PRINTQ(op->lin.m[3][0]), PRINTQ(op->lin.m[3][1]), PRINTQ(op->lin.m[3][2]), PRINTQ(op->lin.m[3][3]), PRINTQ(op->lin.m[3][4])); + av_bprintf(&bp, "%-20s: %s [[%s %s %s %s %s] " + "[%s %s %s %s %s] " + "[%s %s %s %s %s] " + "[%s %s %s %s %s]]", + name, describe_lin_mask(op->lin.mask), + PRINTQ(op->lin.m[0][0]), PRINTQ(op->lin.m[0][1]), + PRINTQ(op->lin.m[0][2]), PRINTQ(op->lin.m[0][3]), + PRINTQ(op->lin.m[0][4]), PRINTQ(op->lin.m[1][0]), + PRINTQ(op->lin.m[1][1]), PRINTQ(op->lin.m[1][2]), + PRINTQ(op->lin.m[1][3]), PRINTQ(op->lin.m[1][4]), + PRINTQ(op->lin.m[2][0]), PRINTQ(op->lin.m[2][1]), + PRINTQ(op->lin.m[2][2]), PRINTQ(op->lin.m[2][3]), + PRINTQ(op->lin.m[2][4]), PRINTQ(op->lin.m[3][0]), + PRINTQ(op->lin.m[3][1]), PRINTQ(op->lin.m[3][2]), + PRINTQ(op->lin.m[3][3]), PRINTQ(op->lin.m[3][4])); break; case SWS_OP_SCALE: - av_log(log, lev, "%-20s: * %d/%d\n", name, op->c.q.num, op->c.q.den); + av_bprintf(&bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den); break; case SWS_OP_TYPE_NB: break; } + av_assert0(av_bprint_is_complete(&bp)); + av_log(log, lev, "%s\n", bp.str); + if (op->comps.min[0].den || op->comps.min[1].den || op->comps.min[2].den || op->comps.min[3].den || op->comps.max[0].den || op->comps.max[1].den || -- 2.52.0 >From a08f875f03c638d0a73169bb2bee9372e32fc1d2 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 18 Mar 2026 11:46:54 +0100 Subject: [PATCH 10/14] swscale/ops: remove , from comp min/max print-out for consistency Interferes with an upcoming simplification, otherwise. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 2 +- tests/ref/fate/sws-ops-list | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 463b6bf0ae..3c926db61a 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -869,7 +869,7 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, op->comps.max[0].den || op->comps.max[1].den || op->comps.max[2].den || op->comps.max[3].den) { - av_log(log, lev_extra, " min: {%s, %s, %s, %s}, max: {%s, %s, %s, %s}\n", + av_log(log, lev_extra, " min: {%s %s %s %s}, max: {%s %s %s %s}\n", next->comps.unused[0] ? "_" : PRINTQ(op->comps.min[0]), next->comps.unused[1] ? "_" : PRINTQ(op->comps.min[1]), next->comps.unused[2] ? "_" : PRINTQ(op->comps.min[2]), diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index 4cfce95495..28ca16f094 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -c014aa1e7c12d3bb101951487c406148 +ca5418b78a95b6b4eab07253f13ef731 -- 2.52.0 >From 4644eb81be0edc6dfd53f893df7e29557cdcb2e0 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 18 Mar 2026 11:55:24 +0100 Subject: [PATCH 11/14] swscale/ops: refactor PRINTQ() macro Instead of allocating a billion tiny temporary buffers, these helpers now directly append to an AVBPrint. I decided to explicitly control whether or not a value with denom 0 should be printed as "inf/nan" or as "_", because a lot of ops have the implicit semantic of "den == 0 -> ignored". At the same time, we don't want to obscure legitimate NAN/INF values when the do occur unintentionally. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 96 ++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 3c926db61a..bf9611a3ad 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -724,23 +724,36 @@ static char describe_comp_flags(SwsCompFlags flags) return '.'; } -static const char *print_q(const AVRational q, char buf[], int buf_len) +static void print_q(AVBPrint *bp, const AVRational q, bool ignore_den0) { - if (!q.den) { - return q.num > 0 ? "inf" : q.num < 0 ? "-inf" : "nan"; + if (!q.den && ignore_den0) { + av_bprintf(bp, "_"); + } else if (!q.den) { + av_bprintf(bp, "%s", q.num > 0 ? "inf" : q.num < 0 ? "-inf" : "nan"); } else if (q.den == 1) { - snprintf(buf, buf_len, "%d", q.num); - return buf; + av_bprintf(bp, "%d", q.num); } else if (abs(q.num) > 1000 || abs(q.den) > 1000) { - snprintf(buf, buf_len, "%f", av_q2d(q)); - return buf; + av_bprintf(bp, "%f", av_q2d(q)); } else { - snprintf(buf, buf_len, "%d/%d", q.num, q.den); - return buf; + av_bprintf(bp, "%d/%d", q.num, q.den); } } -#define PRINTQ(q) print_q(q, (char[32]){0}, sizeof(char[32])) +static void print_q4(AVBPrint *bp, const AVRational q4[4], bool ignore_den0, + const bool unused[4]) +{ + av_bprintf(bp, "{"); + for (int i = 0; i < 4; i++) { + if (i) + av_bprintf(bp, " "); + if (unused && unused[i]) { + av_bprintf(bp, "_"); + } else { + print_q(bp, q4[i], ignore_den0); + } + } + av_bprintf(bp, "}"); +} void ff_sws_op_list_print(void *log, int lev, int lev_extra, const SwsOpList *ops) @@ -801,11 +814,8 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, op->pack.pattern[2], op->pack.pattern[3]); break; case SWS_OP_CLEAR: - av_bprintf(&bp, "%-20s: {%s %s %s %s}", name, - op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", - op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", - op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", - op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); + av_bprintf(&bp, "%-20s: ", name); + print_q4(&bp, op->c.q4, true, NULL); break; case SWS_OP_SWIZZLE: av_bprintf(&bp, "%-20s: %d%d%d%d", name, @@ -824,35 +834,25 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, op->dither.y_offset[2], op->dither.y_offset[3]); break; case SWS_OP_MIN: - av_bprintf(&bp, "%-20s: x <= {%s %s %s %s}", name, - op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", - op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", - op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", - op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); + av_bprintf(&bp, "%-20s: x <= ", name); + print_q4(&bp, op->c.q4, true, NULL); break; case SWS_OP_MAX: - av_bprintf(&bp, "%-20s: {%s %s %s %s} <= x", name, - op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_", - op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_", - op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_", - op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_"); + av_bprintf(&bp, "%-20s: ", name); + print_q4(&bp, op->c.q4, true, NULL); + av_bprintf(&bp, " <= x"); break; case SWS_OP_LINEAR: - av_bprintf(&bp, "%-20s: %s [[%s %s %s %s %s] " - "[%s %s %s %s %s] " - "[%s %s %s %s %s] " - "[%s %s %s %s %s]]", - name, describe_lin_mask(op->lin.mask), - PRINTQ(op->lin.m[0][0]), PRINTQ(op->lin.m[0][1]), - PRINTQ(op->lin.m[0][2]), PRINTQ(op->lin.m[0][3]), - PRINTQ(op->lin.m[0][4]), PRINTQ(op->lin.m[1][0]), - PRINTQ(op->lin.m[1][1]), PRINTQ(op->lin.m[1][2]), - PRINTQ(op->lin.m[1][3]), PRINTQ(op->lin.m[1][4]), - PRINTQ(op->lin.m[2][0]), PRINTQ(op->lin.m[2][1]), - PRINTQ(op->lin.m[2][2]), PRINTQ(op->lin.m[2][3]), - PRINTQ(op->lin.m[2][4]), PRINTQ(op->lin.m[3][0]), - PRINTQ(op->lin.m[3][1]), PRINTQ(op->lin.m[3][2]), - PRINTQ(op->lin.m[3][3]), PRINTQ(op->lin.m[3][4])); + av_bprintf(&bp, "%-20s: %s [", name, describe_lin_mask(op->lin.mask)); + for (int i = 0; i < 4; i++) { + av_bprintf(&bp, "%s[", i ? " " : ""); + for (int j = 0; j < 5; j++) { + av_bprintf(&bp, j ? " " : ""); + print_q(&bp, op->lin.m[i][j], false); + } + av_bprintf(&bp, "]"); + } + av_bprintf(&bp, "]"); break; case SWS_OP_SCALE: av_bprintf(&bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den); @@ -869,15 +869,13 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, op->comps.max[0].den || op->comps.max[1].den || op->comps.max[2].den || op->comps.max[3].den) { - av_log(log, lev_extra, " min: {%s %s %s %s}, max: {%s %s %s %s}\n", - next->comps.unused[0] ? "_" : PRINTQ(op->comps.min[0]), - next->comps.unused[1] ? "_" : PRINTQ(op->comps.min[1]), - next->comps.unused[2] ? "_" : PRINTQ(op->comps.min[2]), - next->comps.unused[3] ? "_" : PRINTQ(op->comps.min[3]), - next->comps.unused[0] ? "_" : PRINTQ(op->comps.max[0]), - next->comps.unused[1] ? "_" : PRINTQ(op->comps.max[1]), - next->comps.unused[2] ? "_" : PRINTQ(op->comps.max[2]), - next->comps.unused[3] ? "_" : PRINTQ(op->comps.max[3])); + av_bprint_clear(&bp); + av_bprintf(&bp, " min: "); + print_q4(&bp, op->comps.min, false, next->comps.unused); + av_bprintf(&bp, ", max: "); + print_q4(&bp, op->comps.max, false, next->comps.unused); + av_assert0(av_bprint_is_complete(&bp)); + av_log(log, lev_extra, "%s\n", bp.str); } } -- 2.52.0 >From fa097028c937a11eddfb754d6307c75bad7ff36d Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 18 Mar 2026 12:00:08 +0100 Subject: [PATCH 12/14] swscale/ops: avoid printing values for ignored components Makes the list output a tiny bit tidier. This is cheap to support now thanks to the print_q4() helper. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 6 +++--- tests/ref/fate/sws-ops-list | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index bf9611a3ad..1d5acabffe 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -815,7 +815,7 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, break; case SWS_OP_CLEAR: av_bprintf(&bp, "%-20s: ", name); - print_q4(&bp, op->c.q4, true, NULL); + print_q4(&bp, op->c.q4, true, next->comps.unused); break; case SWS_OP_SWIZZLE: av_bprintf(&bp, "%-20s: %d%d%d%d", name, @@ -835,11 +835,11 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, break; case SWS_OP_MIN: av_bprintf(&bp, "%-20s: x <= ", name); - print_q4(&bp, op->c.q4, true, NULL); + print_q4(&bp, op->c.q4, true, next->comps.unused); break; case SWS_OP_MAX: av_bprintf(&bp, "%-20s: ", name); - print_q4(&bp, op->c.q4, true, NULL); + print_q4(&bp, op->c.q4, true, next->comps.unused); av_bprintf(&bp, " <= x"); break; case SWS_OP_LINEAR: diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index 28ca16f094..4385d809e7 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -ca5418b78a95b6b4eab07253f13ef731 +e5a6788fa43852d75544e7fb6ae7744d -- 2.52.0 >From a812af9acfbe9f10f48fa6925ddf1dda6d44b1f6 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 27 Mar 2026 15:22:39 +0100 Subject: [PATCH 13/14] swscale/ops: move op-formatting code to helper function Annoyingly, access to order_src/dst requires access to the SwsOpList, so we have to append that data after the fact. Maybe this is another incremental tick in favor of `SwsReadWriteOp` in the ever-present question in my head of whether the plane order should go there or into SwsOpList. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 148 +++++++++++++++++++++++++---------------------- libswscale/ops.h | 7 +++ 2 files changed, 85 insertions(+), 70 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 1d5acabffe..9f44fa95b5 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -755,6 +755,82 @@ static void print_q4(AVBPrint *bp, const AVRational q4[4], bool ignore_den0, av_bprintf(bp, "}"); } +void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const bool unused[4]) +{ + const char *name = ff_sws_op_type_name(op->op); + + switch (op->op) { + case SWS_OP_INVALID: + case SWS_OP_SWAP_BYTES: + av_bprintf(bp, "%s", name); + break; + case SWS_OP_READ: + case SWS_OP_WRITE: + av_bprintf(bp, "%-20s: %d elem(s) %s >> %d", name, + op->rw.elems, op->rw.packed ? "packed" : "planar", + op->rw.frac); + break; + case SWS_OP_LSHIFT: + av_bprintf(bp, "%-20s: << %u", name, op->c.u); + break; + case SWS_OP_RSHIFT: + av_bprintf(bp, "%-20s: >> %u", name, op->c.u); + break; + case SWS_OP_PACK: + case SWS_OP_UNPACK: + av_bprintf(bp, "%-20s: {%d %d %d %d}", name, + op->pack.pattern[0], op->pack.pattern[1], + op->pack.pattern[2], op->pack.pattern[3]); + break; + case SWS_OP_CLEAR: + av_bprintf(bp, "%-20s: ", name); + print_q4(bp, op->c.q4, true, unused); + break; + case SWS_OP_SWIZZLE: + av_bprintf(bp, "%-20s: %d%d%d%d", name, + op->swizzle.x, op->swizzle.y, op->swizzle.z, op->swizzle.w); + break; + case SWS_OP_CONVERT: + av_bprintf(bp, "%-20s: %s -> %s%s", name, + ff_sws_pixel_type_name(op->type), + ff_sws_pixel_type_name(op->convert.to), + op->convert.expand ? " (expand)" : ""); + break; + case SWS_OP_DITHER: + av_bprintf(bp, "%-20s: %dx%d matrix + {%d %d %d %d}", name, + 1 << op->dither.size_log2, 1 << op->dither.size_log2, + op->dither.y_offset[0], op->dither.y_offset[1], + op->dither.y_offset[2], op->dither.y_offset[3]); + break; + case SWS_OP_MIN: + av_bprintf(bp, "%-20s: x <= ", name); + print_q4(bp, op->c.q4, true, unused); + break; + case SWS_OP_MAX: + av_bprintf(bp, "%-20s: ", name); + print_q4(bp, op->c.q4, true, unused); + av_bprintf(bp, " <= x"); + break; + case SWS_OP_LINEAR: + av_bprintf(bp, "%-20s: %s [", name, describe_lin_mask(op->lin.mask)); + for (int i = 0; i < 4; i++) { + av_bprintf(bp, "%s[", i ? " " : ""); + for (int j = 0; j < 5; j++) { + av_bprintf(bp, j ? " " : ""); + print_q(bp, op->lin.m[i][j], false); + } + av_bprintf(bp, "]"); + } + av_bprintf(bp, "]"); + break; + case SWS_OP_SCALE: + av_bprintf(bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den); + break; + case SWS_OP_TYPE_NB: + break; + } +} + void ff_sws_op_list_print(void *log, int lev, int lev_extra, const SwsOpList *ops) { @@ -769,7 +845,6 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, for (int i = 0; i < ops->num_ops; i++) { const SwsOp *op = &ops->ops[i]; const SwsOp *next = i + 1 < ops->num_ops ? &ops->ops[i + 1] : op; - const char *name = ff_sws_op_type_name(op->op); av_bprint_clear(&bp); av_bprintf(&bp, " [%3s %c%c%c%c -> %c%c%c%c] ", ff_sws_pixel_type_name(op->type), @@ -782,16 +857,8 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, next->comps.unused[2] ? 'X' : describe_comp_flags(op->comps.flags[2]), next->comps.unused[3] ? 'X' : describe_comp_flags(op->comps.flags[3])); - switch (op->op) { - case SWS_OP_INVALID: - case SWS_OP_SWAP_BYTES: - av_bprintf(&bp, "%s", name); - break; - case SWS_OP_READ: - case SWS_OP_WRITE: - av_bprintf(&bp, "%-20s: %d elem(s) %s >> %d", name, - op->rw.elems, op->rw.packed ? "packed" : "planar", - op->rw.frac); + ff_sws_op_desc(&bp, op, next->comps.unused); + if (op->op == SWS_OP_READ || op->op == SWS_OP_WRITE) { SwsSwizzleOp order = op->op == SWS_OP_READ ? ops->order_src : ops->order_dst; if (order.mask != SWS_SWIZZLE(0, 1, 2, 3).mask) { const int planes = op->rw.packed ? 1 : op->rw.elems; @@ -800,65 +867,6 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, av_bprintf(&bp, "%s%d", i ? ", " : "", order.in[i]); av_bprintf(&bp, "}"); } - break; - case SWS_OP_LSHIFT: - av_bprintf(&bp, "%-20s: << %u", name, op->c.u); - break; - case SWS_OP_RSHIFT: - av_bprintf(&bp, "%-20s: >> %u", name, op->c.u); - break; - case SWS_OP_PACK: - case SWS_OP_UNPACK: - av_bprintf(&bp, "%-20s: {%d %d %d %d}", name, - op->pack.pattern[0], op->pack.pattern[1], - op->pack.pattern[2], op->pack.pattern[3]); - break; - case SWS_OP_CLEAR: - av_bprintf(&bp, "%-20s: ", name); - print_q4(&bp, op->c.q4, true, next->comps.unused); - break; - case SWS_OP_SWIZZLE: - av_bprintf(&bp, "%-20s: %d%d%d%d", name, - op->swizzle.x, op->swizzle.y, op->swizzle.z, op->swizzle.w); - break; - case SWS_OP_CONVERT: - av_bprintf(&bp, "%-20s: %s -> %s%s", name, - ff_sws_pixel_type_name(op->type), - ff_sws_pixel_type_name(op->convert.to), - op->convert.expand ? " (expand)" : ""); - break; - case SWS_OP_DITHER: - av_bprintf(&bp, "%-20s: %dx%d matrix + {%d %d %d %d}", name, - 1 << op->dither.size_log2, 1 << op->dither.size_log2, - op->dither.y_offset[0], op->dither.y_offset[1], - op->dither.y_offset[2], op->dither.y_offset[3]); - break; - case SWS_OP_MIN: - av_bprintf(&bp, "%-20s: x <= ", name); - print_q4(&bp, op->c.q4, true, next->comps.unused); - break; - case SWS_OP_MAX: - av_bprintf(&bp, "%-20s: ", name); - print_q4(&bp, op->c.q4, true, next->comps.unused); - av_bprintf(&bp, " <= x"); - break; - case SWS_OP_LINEAR: - av_bprintf(&bp, "%-20s: %s [", name, describe_lin_mask(op->lin.mask)); - for (int i = 0; i < 4; i++) { - av_bprintf(&bp, "%s[", i ? " " : ""); - for (int j = 0; j < 5; j++) { - av_bprintf(&bp, j ? " " : ""); - print_q(&bp, op->lin.m[i][j], false); - } - av_bprintf(&bp, "]"); - } - av_bprintf(&bp, "]"); - break; - case SWS_OP_SCALE: - av_bprintf(&bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den); - break; - case SWS_OP_TYPE_NB: - break; } av_assert0(av_bprint_is_complete(&bp)); diff --git a/libswscale/ops.h b/libswscale/ops.h index 39d3ffe526..53e460ad51 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -25,6 +25,8 @@ #include <stdbool.h> #include <stdalign.h> +#include <libavutil/bprint.h> + #include "graph.h" typedef enum SwsPixelType { @@ -208,6 +210,11 @@ typedef struct SwsOp { SwsComps comps; } SwsOp; +/** + * Describe an operation in human-readable form. + */ +void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const bool unused[4]); + /** * Frees any allocations associated with an SwsOp and sets it to {0}. */ -- 2.52.0 >From 92f36752a111578ad98170674da0cf608c79c4fa Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 16 Feb 2026 17:20:01 +0100 Subject: [PATCH 14/14] tests/sws_ops: add option for summarizing all operation patterns This can be used to either manually verify, or perhaps programmatically generate, the list of operation patterns that need to be supported by a backend to be feature-complete. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/sws_ops.c | 86 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index 4e91b73b3c..f2e82c3435 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -18,7 +18,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/mem.h" #include "libavutil/pixdesc.h" +#include "libavutil/tree.h" #include "libswscale/ops.h" #include "libswscale/format.h" @@ -40,6 +44,67 @@ static int print_ops(SwsContext *const ctx, void *opaque, SwsOpList *ops) return 0; } + +static int cmp_str(const void *a, const void *b) +{ + return strcmp(a, b); +} + +static const bool unused[4] = { false, false, false, false }; +static int register_op(SwsContext *ctx, void *opaque, SwsOp *op) +{ + struct AVTreeNode **root = opaque; + AVBPrint bp; + char *desc; + + /* Strip irrelevant constant data from some operations */ + switch (op->op) { + case SWS_OP_LINEAR: + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 5; j++) + op->lin.m[i][j] = (AVRational) { 0, 1 }; + } + break; + case SWS_OP_SCALE: + case SWS_OP_MIN: + case SWS_OP_MAX: + case SWS_OP_CLEAR: + for (int i = 0; i < 4; i++) + op->c.q4[i] = (AVRational) { 0, !!op->c.q4[i].den }; + break; + case SWS_OP_DITHER: + /* Strip arbitrary offset */ + for (int i = 0; i < 4; i++) + op->dither.y_offset[i] = op->dither.y_offset[i] >= 0 ? 0 : -1; + break; + } + + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); + ff_sws_op_desc(&bp, op, unused); + int ret = av_bprint_finalize(&bp, &desc); + if (ret < 0) + return ret; + + struct AVTreeNode *node = av_tree_node_alloc(); + if (!node) { + av_free(desc); + return AVERROR(ENOMEM); + } + + av_tree_insert(root, desc, cmp_str, &node); + if (node) { + av_free(node); + av_free(desc); + } + return ret; +} + +static int print_summary(void *opaque, void *key) +{ + av_log(opaque, AV_LOG_INFO, "%s\n", (const char *) key); + return 0; +} + static void log_stdout(void *avcl, int level, const char *fmt, va_list vl) { if (level != AV_LOG_INFO) { @@ -55,6 +120,7 @@ int main(int argc, char **argv) enum AVPixelFormat dst_fmt_min = 0; enum AVPixelFormat src_fmt_max = AV_PIX_FMT_NB - 1; enum AVPixelFormat dst_fmt_max = AV_PIX_FMT_NB - 1; + bool summarize = false; int ret = 1; #ifdef _WIN32 @@ -73,6 +139,8 @@ int main(int argc, char **argv) " Only test the specified source pixel format\n" " -v <level>\n" " Enable log verbosity at given level\n" + " -summarize <1 or 0>\n" + " Summarize operation types, instead of printing op lists\n" ); return 0; } @@ -92,6 +160,8 @@ int main(int argc, char **argv) } } else if (!strcmp(argv[i], "-v")) { av_log_set_level(atoi(argv[i + 1])); + } else if (!strcmp(argv[i], "-summarize")) { + summarize = atoi(argv[i + 1]); } else { bad_option: fprintf(stderr, "bad option or argument missing (%s) see -help\n", argv[i]); @@ -104,9 +174,19 @@ bad_option: goto fail; av_log_set_callback(log_stdout); - ret = ff_sws_enum_op_lists(ctx, NULL, print_ops); - if (ret < 0) - goto fail; + + if (summarize) { + struct AVTreeNode *root = NULL; + ret = ff_sws_enum_ops(ctx, &root, register_op); + if (ret < 0) + goto fail; + av_tree_enumerate(root, NULL, NULL, print_summary); + av_tree_destroy(root); + } else { + ret = ff_sws_enum_op_lists(ctx, NULL, print_ops); + if (ret < 0) + goto fail; + } ret = 0; fail: -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
