PR #22961 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22961 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22961.patch
I intentionally don't yet add support for 32-bit formats to swscale/ops.c as that would be a larger change and I want to wait for the conclusion of https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22876 for it first. Mostly, I need comments on the new public API added by this series. I also suspect that the aarch64 code in swscale probably needs further changes, but CI should catch those. >From 0dc375558fdedacdaaee38a5e6878990f2f33415 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 11:17:28 +0200 Subject: [PATCH 01/11] avutil/int128: add header for 128-bit integers These can be implemented efficiently on most modern 64-bit compilers. Fallback is only needed for 32-bit, which we do using the existing generic "integer.h" header (currently also hard-coded as 128 bits). Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libavutil/int128.h | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 libavutil/int128.h diff --git a/libavutil/int128.h b/libavutil/int128.h new file mode 100644 index 0000000000..294f571047 --- /dev/null +++ b/libavutil/int128.h @@ -0,0 +1,86 @@ +/* + * 128-bit integers + * Copyright (c) 2026 Niklas Haas + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * 128-bit integers, falling back to integer.h if necessary + * @author Niklas Haas + */ + +#ifndef AVUTIL_INT128_H +#define AVUTIL_INT128_H + +#include <stdint.h> +#include <limits.h> + +#include "integer.h" + +#if defined(__SIZEOF_INT128__) && __SIZEOF_INT128__ >= 16 +#define AV_INT128_NATIVE 1 +typedef unsigned __int128 av_uint128; +typedef __int128 av_int128; +#elif defined(BITINT_MAXWIDTH) && BITINT_MAXWIDTH >= 128 +#define AV_INT128_NATIVE 1 +typedef unsigned _BitInt(128) av_uint128; +typedef _BitInt(128) av_int128; +#elif AV_INTEGER_SIZE >= 8 +#define AV_INT128_NATIVE 0 +typedef AVInteger av_uint128; +typedef AVInteger av_int128; +#else +#error "128-bit integer type not available" +#endif + +#if AV_INT128_NATIVE +# define av_add128(a, b) ((a) + (b)) +# define av_sub128(a, b) ((a) - (b)) +# define av_mul128(a, b) ((a) * (b)) +# define av_div128(a, b) ((a) / (b)) +# define av_cmp128(a, b) ((a) < (b) ? -1 : (a) > (b) ? 1 : 0) +# define av_min128(a, b) ((a) > (b) ? (b) : (a)) +# define av_max128(a, b) ((a) > (b) ? (a) : (b)) +# define av_eq128(a, b) ((a) == (b)) +# define av_mod128(a, b) ((a) % (b)) +# define av_shr128(a, b) ((a) >> (b)) +# define av_to128u(a) ((av_uint128) (a)) +# define av_to128i(a) ((av_int128) (a)) +# define av_from128i(a) ((int64_t) (a)) +# define av_from128u(a) ((uint64_t) (a)) +# define av_test128(a) (!!(a)) +#else +# define av_add128(a, b) av_add_i(a, b) +# define av_sub128(a, b) av_sub_i(a, b) +# define av_mul128(a, b) av_mul_i(a, b) +# define av_div128(a, b) av_div_i(a, b) +# define av_cmp128(a, b) av_cmp_i(a, b) +# define av_min128(a, b) (av_cmp_i(a, b) > 0 ? (b) : (a)) +# define av_max128(a, b) (av_cmp_i(a, b) > 0 ? (a) : (b)) +# define av_eq128(a, b) (av_cmp_i(a, b) == 0) +# define av_mod128(a, b) av_mod_i(NULL, a, b) +# define av_shr128(a, b) av_shr_i(a, b) +# define av_to128u(a) av_int2i((int64_t) a) +# define av_to128i(a) av_int2i(a) +# define av_from128i(a) av_i2int(a) +# define av_from128u(a) ((uint64_t) av_i2int(a))) +# define av_test128(a) (!av_eq128(a, av_to128u(0))) +#endif + +#endif /* AVUTIL_INT128_H */ -- 2.52.0 >From 45bb8ed9506494bc78fbe33cd524f51b7fabbe0e Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 27 Apr 2026 17:51:10 +0200 Subject: [PATCH 02/11] avutil/tests/rational: add explicit unit tests for edge cases The existing tests don't really stress test the edge-case behavior when adding or multiplying values that over/underflow. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libavutil/tests/rational.c | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/libavutil/tests/rational.c b/libavutil/tests/rational.c index ff75896de2..2c9ca947fc 100644 --- a/libavutil/tests/rational.c +++ b/libavutil/tests/rational.c @@ -53,6 +53,59 @@ int main(void) } } + /* Check overflow behavior and edge cases */ + static const AVRational unit_mul_q[][3] = { + {{INT_MAX, 2}, { 2, 1}, { INT_MAX, 1}}, + {{INT_MAX, 2}, {-2, 1}, {-INT_MAX, 1}}, + {{INT_MAX, 2}, { 0, 1}, {0, 1}}, + {{INT_MIN, 2}, { 2, 1}, {-INT_MAX, 1}}, /* not INT_MIN */ + {{INT_MIN, 2}, {-2, 1}, { INT_MAX, 1}}, + {{INT_MIN, 2}, { 0, 1}, {0, 1}}, + {{INT_MAX >> 8, 1}, {INT_MAX >> 8, 1}, {INT_MAX, 1}}, + {{1, INT_MAX >> 8}, {1, INT_MAX >> 8}, {0, 1}}, + {{1, 1}, {0, 0}, {0, 0}}, + {{0, 1}, {0, 0}, {0, 0}}, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(unit_mul_q); i++) { + for (int c = 0; c < 2; c++) { /* test commutativity */ + AVRational a = unit_mul_q[i][c ? 1 : 0]; + AVRational b = unit_mul_q[i][c ? 0 : 1]; + AVRational c = unit_mul_q[i][2]; + AVRational r = av_mul_q(a, b); + if (r.num != c.num || r.den != c.den) { + av_log(NULL, AV_LOG_ERROR, "%d/%d * %d/%d = %d/%d, expected %d/%d\n", + a.num, a.den, b.num, b.den, r.num, r.den, c.num, c.den); + } + } + } + + static const AVRational unit_add_q[][3] = { + {{INT_MAX, 1}, { 2, 2}, { INT_MAX, 1}}, + {{INT_MAX, 1}, {-2, 2}, { INT_MAX - 1, 1}}, + {{INT_MAX, 1}, { 0, 2}, { INT_MAX, 1}}, + {{INT_MIN, 1}, { 2, 2}, {-INT_MAX, 1}}, + {{INT_MIN, 1}, {-2, 2}, {-INT_MAX, 1}}, + {{INT_MIN, 1}, { 0, 2}, {-INT_MAX, 1}}, + {{INT_MAX - 10, 1}, {20, 1}, { INT_MAX, 1}}, + {{2, INT_MAX}, {2, INT_MAX}, {4, INT_MAX}}, + {{1, 1}, {0, 0}, {0, 0}}, + {{0, 1}, {0, 0}, {0, 0}}, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(unit_add_q); i++) { + for (int c = 0; c < 2; c++) { /* test commutativity */ + AVRational a = unit_add_q[i][c ? 1 : 0]; + AVRational b = unit_add_q[i][c ? 0 : 1]; + AVRational c = unit_add_q[i][2]; + AVRational r = av_add_q(a, b); + if (r.num != c.num || r.den != c.den) { + av_log(NULL, AV_LOG_ERROR, "%d/%d + %d/%d = %d/%d, expected %d/%d\n", + a.num, a.den, b.num, b.den, r.num, r.den, c.num, c.den); + } + } + } + for (i = 0; i < FF_ARRAY_ELEMS(numlist); i++) { int64_t a = numlist[i]; -- 2.52.0 >From b7a03856c654fceb813015832d800e50ef415171 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Jan 2026 00:55:00 +0100 Subject: [PATCH 03/11] avutil/rational64: add 64-bit rational type This is needed by swscale, to represent intermediate values for 32-bit formats, which can exceed the value range of int32_t (especially for intermediate products). I copied the math almost 1:1 from rational.c, but adapted to use the 128-bit integer wrappers defined by int128.h. I added a generous amount of tests in any case. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- doc/APIchanges | 3 + libavutil/Makefile | 3 + libavutil/rational64.c | 139 +++++++++++++++++++++++++++++++++++ libavutil/rational64.h | 147 +++++++++++++++++++++++++++++++++++++ libavutil/tests/rational.c | 123 +++++++++++++++++++++++++++++++ libavutil/version.h | 2 +- 6 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 libavutil/rational64.c create mode 100644 libavutil/rational64.h diff --git a/doc/APIchanges b/doc/APIchanges index f1cbbb41b8..17efb937f5 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28 API changes, most recent first: +2026-04-28 - xxxxxxxxxx - lavu 60.31.100 - rational64.h + Add AVRational64 and related API. + 2026-04-14 - 7faa6ee2aa - lavc 62.30.100 - packet.h Add AV_PKT_DATA_DYNAMIC_HDR_SMPTE_2094_APP5 side data type. diff --git a/libavutil/Makefile b/libavutil/Makefile index 38e08a3d5d..e7a53a938e 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -78,6 +78,7 @@ HEADERS = adler32.h \ random_seed.h \ rc4.h \ rational.h \ + rational64.h \ refstruct.h \ replaygain.h \ ripemd.h \ @@ -170,6 +171,7 @@ OBJS = adler32.o \ pixelutils.o \ random_seed.o \ rational.o \ + rational64.o \ refstruct.o \ reverse.o \ rc4.o \ @@ -298,6 +300,7 @@ TESTPROGS = adler32 \ pixfmt_best \ random_seed \ rational \ + rational64 \ rc4 \ ripemd \ sha \ diff --git a/libavutil/rational64.c b/libavutil/rational64.c new file mode 100644 index 0000000000..d2f47530b3 --- /dev/null +++ b/libavutil/rational64.c @@ -0,0 +1,139 @@ +/* + * 64-bit rational numbers + * Copyright (c) 2025 Niklas Haas + * Copyright (c) 2003 Michael Niedermayer <[email protected]> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * 64-bit rational numbers + * @author Niklas Haas + */ + +#include <limits.h> + +#include "common.h" +#include "int128.h" +#include "rational64.h" + +static av_int128 gcd128(av_int128 a, av_int128 b) +{ + while (av_test128(b)) { + av_int128 tmp = b; + b = av_mod128(a, b); + a = tmp; + } + return a; +} + +static AVRational64 reduce64(av_int128 num, av_int128 den) +{ + const av_int128 zero = av_to128i(0); + const av_int128 max = av_to128i(INT64_MAX); + const int num_sign = av_cmp128(num, zero) < 0; + const int den_sign = av_cmp128(den, zero) < 0; + if (num_sign) + num = av_sub128(zero, num); + if (den_sign) + den = av_sub128(zero, den); + + const av_int128 gcd = gcd128(num, den); + if (av_test128(gcd)) { + num = av_div128(num, gcd); + den = av_div128(den, gcd); + } + + av_uint128 a0n = av_to128u(0), a0d = av_to128u(1); + av_uint128 a1n = av_to128u(1), a1d = av_to128u(0); + if (av_cmp128(num, max) <= 0 && av_cmp128(den, max) <= 0) { + a1n = num; + a1d = den; + goto done; + } + + while (av_test128(den)) { + av_int128 x = av_div128(num, den); + av_int128 next_den = av_sub128(num, av_mul128(den, x)); + av_uint128 a2n = av_add128(av_mul128(x, a1n), a0n); + av_uint128 a2d = av_add128(av_mul128(x, a1d), a0d); + + if (av_cmp128(a2n, max) > 0 || av_cmp128(a2d, max) > 0) { + if (av_test128(a1n)) + x = av_div128(av_sub128(max, a0n), a1n); + if (av_test128(a1d)) { + av_uint128 tmp = av_div128(av_sub128(max, a0d), a1d); + x = av_min128(x, tmp); + } + + av_uint128 x1d = av_mul128(x, a1d); + av_uint128 a = av_mul128(den, av_add128(av_add128(x1d, x1d), a0d)); + av_uint128 b = av_mul128(num, a1d); + if (av_cmp128(a, b) > 0) { + a1n = av_add128(av_mul128(x, a1n), a0n); + a1d = av_add128(x1d, a0d); + } + break; + } + + a0n = a1n; + a0d = a1d; + a1n = a2n; + a1d = a2d; + num = den; + den = next_den; + } + +done:; + AVRational64 res = { av_from128i(a1n), av_from128i(a1d) }; + if (num_sign ^ den_sign) + res.num = -res.num; + return res; +} + +int av_cmp_q64(AVRational64 a, AVRational64 b) +{ + const __int128 tmp = a.num * (__int128) b.den - b.num * (__int128) a.den; + + if (tmp) return (int) ((tmp ^ a.den ^ b.den) >> 127)|1; + else if (b.den && a.den) return 0; + else if (a.num && b.num) return (a.num >> 63) - (b.num >> 63); + else return INT_MIN; +} + +AVRational64 av_mul_q64(AVRational64 b, AVRational64 c) +{ + return reduce64(av_mul128(av_to128i(b.num), av_to128i(c.num)), + av_mul128(av_to128i(b.den), av_to128i(c.den))); +} + +AVRational64 av_div_q64(AVRational64 b, AVRational64 c) +{ + return av_mul_q64(b, av_inv_q64(c)); +} + +AVRational64 av_add_q64(AVRational64 b, AVRational64 c) { + return reduce64(av_add128(av_mul128(av_to128i(b.num), av_to128i(c.den)), + av_mul128(av_to128i(c.num), av_to128i(b.den))), + av_mul128(av_to128i(b.den), av_to128i(c.den))); +} + +AVRational64 av_sub_q64(AVRational64 b, AVRational64 c) +{ + return av_add_q64(b, (AVRational64) { -c.num, c.den }); +} diff --git a/libavutil/rational64.h b/libavutil/rational64.h new file mode 100644 index 0000000000..6480a176e3 --- /dev/null +++ b/libavutil/rational64.h @@ -0,0 +1,147 @@ +/* + * 64-bit rational numbers + * Copyright (c) 2025 Niklas Haas + * Copyright (c) 2003 Michael Niedermayer <[email protected]> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_math_rational + * 64-bit extension of AVRational. + * @author Niklas Haas + */ + +#ifndef AVUTIL_RATIONAL64_H +#define AVUTIL_RATIONAL64_H + +#include <stdint.h> +#include <limits.h> +#include "attributes.h" + +/** + * @defgroup lavu_math_rational AVRational64 + * @ingroup lavu_math + * 64-bit extension of AVRational + * + * Offers a 64-bit extended version of AVRational. This is less efficient (and + * may revolve around emulated 128-bit multiplications internally), but allows + * to represent a much larger range of rational numbers without overflow. + * + * @{ + */ + +/** + * 64-bit Rational number (pair of numerator and denominator). + */ +typedef struct AVRational64 { + int64_t num; ///< Numerator + int64_t den; ///< Denominator +} AVRational64; + +/** + * Create an AVRational64. + * + * Useful for compilers that do not support compound literals. + * + * @note The return value is not reduced. + */ +static inline AVRational64 av_make_q64(int64_t num, int64_t den) +{ + AVRational64 r = { num, den }; + return r; +} + +/** + * Compare two 64-bit rationals. + * + * @param a First rational + * @param b Second rational + * + * @return One of the following values: + * - 0 if `a == b` + * - 1 if `a > b` + * - -1 if `a < b` + * - `INT_MIN` if one of the values is of the form `0 / 0` + */ +int av_cmp_q64(AVRational64 a, AVRational64 b); + +/** + * Convert an AVRational64 to a `double`. + * @param a AVRational64 to convert + * @return `a` in floating-point form + * @see av_d2q() + */ +static inline double av_q2d_64(AVRational64 a){ + return a.num / (double) a.den; +} + +/** + * Multiply two 64-bit rationals. + * @param b First multiplicant + * @param c Second multiplicant + * @return b*c + */ +AVRational64 av_mul_q64(AVRational64 b, AVRational64 c) av_const; + +/** + * Divide one 64-bit rational by another. + * @param b Dividend + * @param c Divisor + * @return b/c + */ +AVRational64 av_div_q64(AVRational64 b, AVRational64 c) av_const; + +/** + * Add two 64-bit rationals. + * @param b First addend + * @param c Second addend + * @return b+c + */ +AVRational64 av_add_q64(AVRational64 b, AVRational64 c) av_const; + +/** + * Subtract one 64-bit rational from another. + * @param b Minuend + * @param c Subtrahend + * @return b-c + */ +AVRational64 av_sub_q64(AVRational64 b, AVRational64 c) av_const; + +/** + * Invert a 64-bit rational. + * @param q value + * @return 1 / q + */ +static av_always_inline AVRational64 av_inv_q64(AVRational64 q) +{ + AVRational64 r = { q.den, q.num }; + return r; +} + +/** + * Return the best rational so that a and b are multiple of it. + * If the resulting denominator is larger than max_den, return def. + */ +AVRational64 av_gcd_q64(AVRational64 a, AVRational64 b, int max_den, AVRational64 def); + +/** + * @} + */ + +#endif /* AVUTIL_RATIONAL64_H */ diff --git a/libavutil/tests/rational.c b/libavutil/tests/rational.c index 2c9ca947fc..a0258288ff 100644 --- a/libavutil/tests/rational.c +++ b/libavutil/tests/rational.c @@ -20,12 +20,14 @@ */ #include "libavutil/rational.c" +#include "libavutil/rational64.c" #include "libavutil/integer.h" #include "libavutil/intfloat.h" int main(void) { AVRational a,b,r; + AVRational64 a64,b64,r64; int i,j,k; static const int64_t numlist[] = { INT64_MIN, INT64_MIN+1, INT64_MAX, INT32_MIN, INT32_MAX, 1,0,-1, @@ -53,6 +55,69 @@ int main(void) } } + for (a64.num = -2; a64.num <= 2; a64.num++) { + for (a64.den = -2; a64.den <= 2; a64.den++) { + for (b64.num = -2; b64.num <= 2; b64.num++) { + for (b64.den = -2; b64.den <= 2; b64.den++) { + const double adbl = av_q2d_64(a64); + const double bdbl = av_q2d_64(b64); + const int c = av_cmp_q64(a64,b64); + const int d = adbl == bdbl ? 0 : + adbl > bdbl ? 1 : + adbl < bdbl ? -1 : INT_MIN; + + if (c != d) + av_log(NULL, AV_LOG_ERROR, "%lld/%lld %lld/%lld, %d != %d\n", + (long long) a64.num, (long long) a64.den, + (long long) b64.num, (long long) b64.den, c,d); + + // Check arithmetic result + if (a64.den && b64.den) { + double rdbl; + + r64 = av_add_q64(a64, b64); + rdbl = av_q2d_64(r64); + if (rdbl != adbl + bdbl) { + av_log(NULL, AV_LOG_ERROR, "%f + %f = %f != %f\n", + adbl, bdbl, rdbl, adbl + bdbl); + } + + r64 = av_mul_q64(a64, b64); + rdbl = av_q2d_64(r64); + if (rdbl != adbl * bdbl) { + av_log(NULL, AV_LOG_ERROR, "%f * %f = %f != %f\n", + adbl, bdbl, rdbl, adbl * bdbl); + } + } + + // Check addition round-trip + r64 = av_sub_q64(av_add_q64(a64, b64), b64); + if (b64.den && (r64.num*a64.den != a64.num*r64.den || + !r64.num != !a64.num || + !r64.den != !a64.den)) + { + av_log(NULL, AV_LOG_ERROR, "%lld/%lld != %lld/%lld\n", + (long long) a64.num, (long long) a64.den, + (long long) r64.num, (long long) r64.den); + } + + if (b64.num) { + // Check multiplication round-trip + r64 = av_div_q64(av_mul_q64(a64, b64), b64); + if (b64.den && (r64.num*a64.den != a64.num*r64.den || + !r64.num != !a64.num || + !r64.den != !a64.den)) + { + av_log(NULL, AV_LOG_ERROR, "%lld/%lld != %lld/%lld\n", + (long long) a64.num, (long long) a64.den, + (long long) r64.num, (long long) r64.den); + } + } + } + } + } + } + /* Check overflow behavior and edge cases */ static const AVRational unit_mul_q[][3] = { {{INT_MAX, 2}, { 2, 1}, { INT_MAX, 1}}, @@ -106,6 +171,64 @@ int main(void) } } + static const AVRational64 unit_mul_q64[][3] = { + {{INT64_MAX, 2}, { 2, 1}, { INT64_MAX, 1}}, + {{INT64_MAX, 2}, {-2, 1}, {-INT64_MAX, 1}}, + {{INT64_MAX, 2}, { 0, 1}, {0, 1}}, + {{INT64_MIN, 2}, { 2, 1}, {-INT64_MAX, 1}}, /* not INT64_MIN */ + {{INT64_MIN, 2}, {-2, 1}, { INT64_MAX, 1}}, + {{INT64_MIN, 2}, { 0, 1}, {0, 1}}, + {{INT64_MAX >> 8, 1}, {INT64_MAX >> 8, 1}, {INT64_MAX, 1}}, + {{1, INT64_MAX >> 8}, {1, INT64_MAX >> 8}, {0, 1}}, + {{1, 1}, {0, 0}, {0, 0}}, + {{0, 1}, {0, 0}, {0, 0}}, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(unit_mul_q64); i++) { + for (int c = 0; c < 2; c++) { /* test commutativity */ + AVRational64 a = unit_mul_q64[i][c ? 1 : 0]; + AVRational64 b = unit_mul_q64[i][c ? 0 : 1]; + AVRational64 c = unit_mul_q64[i][2]; + AVRational64 r = av_mul_q64(a, b); + if (r.num != c.num || r.den != c.den) { + av_log(NULL, AV_LOG_ERROR, "%lld/%lld * %lld/%lld = %lld/%lld, expected %lld/%lld\n", + (long long) a.num, (long long) a.den, + (long long) b.num, (long long) b.den, + (long long) r.num, (long long) r.den, + (long long) c.num, (long long) c.den); + } + } + } + + static const AVRational64 unit_add_q64[][3] = { + {{INT64_MAX, 1}, { 2, 2}, { INT64_MAX, 1}}, + {{INT64_MAX, 1}, {-2, 2}, { INT64_MAX - 1, 1}}, + {{INT64_MAX, 1}, { 0, 2}, { INT64_MAX, 1}}, + {{INT64_MIN, 1}, { 2, 2}, {-INT64_MAX, 1}}, + {{INT64_MIN, 1}, {-2, 2}, {-INT64_MAX, 1}}, + {{INT64_MIN, 1}, { 0, 2}, {-INT64_MAX, 1}}, + {{INT64_MAX - 10, 1}, {20, 1}, { INT64_MAX, 1}}, + {{2, INT64_MAX}, {2, INT64_MAX}, {4, INT64_MAX}}, + {{1, 1}, {0, 0}, {0, 0}}, + {{0, 1}, {0, 0}, {0, 0}}, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(unit_add_q64); i++) { + for (int c = 0; c < 2; c++) { /* test commutativity */ + AVRational64 a = unit_add_q64[i][c ? 1 : 0]; + AVRational64 b = unit_add_q64[i][c ? 0 : 1]; + AVRational64 c = unit_add_q64[i][2]; + AVRational64 r = av_add_q64(a, b); + if (r.num != c.num || r.den != c.den) { + av_log(NULL, AV_LOG_ERROR, "%lld/%lld + %lld/%lld = %lld/%lld, expected %lld/%lld\n", + (long long) a.num, (long long) a.den, + (long long) b.num, (long long) b.den, + (long long) r.num, (long long) r.den, + (long long) c.num, (long long) c.den); + } + } + } + for (i = 0; i < FF_ARRAY_ELEMS(numlist); i++) { int64_t a = numlist[i]; diff --git a/libavutil/version.h b/libavutil/version.h index 40a7d8e300..07c47da803 100644 --- a/libavutil/version.h +++ b/libavutil/version.h @@ -79,7 +79,7 @@ */ #define LIBAVUTIL_VERSION_MAJOR 60 -#define LIBAVUTIL_VERSION_MINOR 30 +#define LIBAVUTIL_VERSION_MINOR 31 #define LIBAVUTIL_VERSION_MICRO 100 #define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ -- 2.52.0 >From 1916d2c26c8532ab858a9cdf882c42ca1c5562f1 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 11:55:37 +0200 Subject: [PATCH 04/11] swscale/ops: don't re-define Q() macro It's already in ops_internal.h Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 1 - libswscale/ops_chain.c | 2 -- 2 files changed, 3 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index ad85f46cfa..6ea77c2bbc 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -32,7 +32,6 @@ #include "libavutil/hwcontext.h" #endif -#define Q(N) ((AVRational) { N, 1 }) #define Q0 Q(0) #define Q1 Q(1) diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index ff301d4a50..0bbfa4ae4c 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -24,8 +24,6 @@ #include "ops_chain.h" -#define Q(N) ((AVRational) { N, 1 }) - SwsOpChain *ff_sws_op_chain_alloc(void) { return av_mallocz(sizeof(SwsOpChain)); -- 2.52.0 >From 5f74e0e5c08ab9e09808ab76f52f20e2494356b8 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 12:19:06 +0200 Subject: [PATCH 05/11] swscale/format: avoid Q0/Q1 macros These come with some challenging ambiguity in the following patch to switch from AVRational to AVRational64, so best just avoid them and have individual usage sites define them locally with the correct type. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 66 ++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index 6ea77c2bbc..75d1ff0324 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -32,9 +32,6 @@ #include "libavutil/hwcontext.h" #endif -#define Q0 Q(0) -#define Q1 Q(1) - #define RET(x) \ do { \ int _ret = (x); \ @@ -932,12 +929,12 @@ static SwsClearOp fmt_clear(enum AVPixelFormat fmt) SwsClearOp c = {0}; if (!has_chroma) { c.mask |= SWS_COMP(1) | SWS_COMP(2); - c.value[1] = c.value[2] = Q0; + c.value[1] = c.value[2] = Q(0); } if (!has_alpha) { c.mask |= SWS_COMP(3); - c.value[3] = Q0; + c.value[3] = Q(0); } return c; @@ -981,7 +978,7 @@ int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) for (int c = 0; c < desc->nb_components; c++) { const int bits = desc->comp[c].depth + shift.amount; const int idx = swizzle.in[is_ya ? 3 * c : c]; - comps->min[idx] = Q0; + comps->min[idx] = Q(0); if (bits < 32) /* FIXME: AVRational is limited to INT_MAX */ comps->max[idx] = Q((1ULL << bits) - 1); } @@ -1065,7 +1062,7 @@ int ff_sws_encode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) .op = SWS_OP_CLEAR, .type = pixel_type, .clear.mask = SWS_COMP(3), - .clear.value[3] = Q0, + .clear.value[3] = Q(0), })); } @@ -1113,11 +1110,14 @@ static inline AVRational av_neg_q(AVRational x) static SwsLinearOp fmt_encode_range(const SwsFormat *fmt, bool *incomplete) { + const AVRational q0 = Q(0); + const AVRational q1 = Q(1); + SwsLinearOp c = { .m = { - { Q1, Q0, Q0, Q0, Q0 }, - { Q0, Q1, Q0, Q0, Q0 }, - { Q0, Q0, Q1, Q0, Q0 }, - { Q0, Q0, Q0, Q1, Q0 }, + { q1, q0, q0, q0, q0 }, + { q0, q1, q0, q0, q0 }, + { q0, q0, q1, q0, q0 }, + { q0, q0, q0, q1, q0 }, }}; const int depth0 = fmt->desc->comp[0].depth; @@ -1186,7 +1186,7 @@ static SwsLinearOp fmt_decode_range(const SwsFormat *fmt, bool *incomplete) /* Explicitly initialize alpha for sanity */ if (!(fmt->desc->flags & AV_PIX_FMT_FLAG_ALPHA)) - c.m[3][4] = Q1; + c.m[3][4] = Q(1); c.mask = ff_sws_linear_mask(c); return c; @@ -1202,7 +1202,7 @@ static AVRational *generate_bayer_matrix(const int size_log2) return NULL; /* Start with a 1x1 matrix */ - m[0] = Q0; + m[0] = Q(0); /* Generate three copies of the current, appropriately scaled and offset */ for (int sz = 1; sz < size; sz <<= 1) { @@ -1347,11 +1347,13 @@ linear_mat3(const AVRational m00, const AVRational m01, const AVRational m02, const AVRational m10, const AVRational m11, const AVRational m12, const AVRational m20, const AVRational m21, const AVRational m22) { + const AVRational q0 = Q(0); + const AVRational q1 = Q(1); SwsLinearOp c = {{ - { m00, m01, m02, Q0, Q0 }, - { m10, m11, m12, Q0, Q0 }, - { m20, m21, m22, Q0, Q0 }, - { Q0, Q0, Q0, Q1, Q0 }, + { m00, m01, m02, q0, q0 }, + { m10, m11, m12, q0, q0 }, + { m20, m21, m22, q0, q0 }, + { q0, q0, q0, q1, q0 }, }}; c.mask = ff_sws_linear_mask(c); @@ -1366,6 +1368,10 @@ int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type, if (!pixel_type) return AVERROR(ENOTSUP); + const AVRational q0 = Q(0); + const AVRational q1 = Q(1); + const AVRational q2 = Q(2); + RET(ff_sws_op_list_append(ops, &(SwsOp) { .op = SWS_OP_CONVERT, .type = pixel_type, @@ -1393,10 +1399,10 @@ int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type, case AVCOL_SPC_BT709: case AVCOL_SPC_SMPTE240M: case AVCOL_SPC_BT2020_NCL: { - AVRational crg = av_sub_q(Q0, av_div_q(c->cr, c->cg)); - AVRational cbg = av_sub_q(Q0, av_div_q(c->cb, c->cg)); - AVRational m02 = av_mul_q(Q(2), av_sub_q(Q1, c->cr)); - AVRational m21 = av_mul_q(Q(2), av_sub_q(Q1, c->cb)); + AVRational crg = av_sub_q(q0, av_div_q(c->cr, c->cg)); + AVRational cbg = av_sub_q(q0, av_div_q(c->cb, c->cg)); + AVRational m02 = av_mul_q(q2, av_sub_q(q1, c->cr)); + AVRational m21 = av_mul_q(q2, av_sub_q(q1, c->cb)); AVRational m11 = av_mul_q(cbg, m21); AVRational m12 = av_mul_q(crg, m02); @@ -1404,9 +1410,9 @@ int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type, .type = type, .op = SWS_OP_LINEAR, .lin = linear_mat3( - Q1, Q0, m02, - Q1, m11, m12, - Q1, m21, Q0 + q1, q0, m02, + q1, m11, m12, + q1, m21, q0 ), }); } @@ -1416,9 +1422,9 @@ int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type, .type = type, .op = SWS_OP_LINEAR, .lin = linear_mat3( - Q1, Q(-1), Q( 1), - Q1, Q( 1), Q( 0), - Q1, Q(-1), Q(-1) + q1, av_make_q(-1, 1), av_make_q( 1, 1), + q1, av_make_q( 1, 1), av_make_q( 0, 1), + q1, av_make_q(-1, 1), av_make_q(-1, 1) ), }); @@ -1465,8 +1471,8 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, case AVCOL_SPC_BT709: case AVCOL_SPC_SMPTE240M: case AVCOL_SPC_BT2020_NCL: { - AVRational cb1 = av_sub_q(c->cb, Q1); - AVRational cr1 = av_sub_q(c->cr, Q1); + AVRational cb1 = av_sub_q(c->cb, av_make_q(1, 1)); + AVRational cr1 = av_sub_q(c->cr, av_make_q(1, 1)); AVRational m20 = av_make_q(1,2); AVRational m10 = av_mul_q(m20, av_div_q(c->cr, cb1)); AVRational m11 = av_mul_q(m20, av_div_q(c->cg, cb1)); @@ -1532,7 +1538,7 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, RET(ff_sws_op_list_append(ops, &(SwsOp) { .op = SWS_OP_MAX, .type = type, - .clamp = {{ Q0, Q0, Q0, Q0 }}, + .clamp = {{ Q(0), Q(0), Q(0), Q(0) }}, })); RET(ff_sws_op_list_append(ops, &(SwsOp) { -- 2.52.0 >From 1015b4bb9db38ed8ddbb5846d8fd4b0766c41749 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 13:04:53 +0200 Subject: [PATCH 06/11] swscale/format: factor out intmax_q() and make more robust This formulation is generally preferred as it avoids the risk of (1 << depth) overflowing when depth is 32/64/etc. I also wanted to have these in a common place to make the upcoming changes easier. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index 75d1ff0324..e70c3d58b0 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -946,6 +946,12 @@ static SwsClearOp fmt_clear(enum AVPixelFormat fmt) # define NATIVE_ENDIAN_FLAG 0 #endif +static inline AVRational intmax_q(int bits) +{ + av_assert1(bits >= 0 && bits < 32); + return Q(UINT32_MAX >> (32 - bits)); +} + int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) { const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt); @@ -980,7 +986,7 @@ int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) const int idx = swizzle.in[is_ya ? 3 * c : c]; comps->min[idx] = Q(0); if (bits < 32) /* FIXME: AVRational is limited to INT_MAX */ - comps->max[idx] = Q((1ULL << bits) - 1); + comps->max[idx] = intmax_q(bits); } } @@ -1128,19 +1134,18 @@ static SwsLinearOp fmt_encode_range(const SwsFormat *fmt, bool *incomplete) if (fmt->desc->flags & AV_PIX_FMT_FLAG_FLOAT) return c; /* floats are directly output as-is */ - av_assert0(depth0 < 32 && depth1 < 32 && depth2 < 32 && depth3 < 32); if (fmt->csp == AVCOL_SPC_RGB || (fmt->desc->flags & AV_PIX_FMT_FLAG_XYZ)) { - c.m[0][0] = Q((1 << depth0) - 1); - c.m[1][1] = Q((1 << depth1) - 1); - c.m[2][2] = Q((1 << depth2) - 1); + c.m[0][0] = intmax_q(depth0); + c.m[1][1] = intmax_q(depth1); + c.m[2][2] = intmax_q(depth2); } else if (fmt->range == AVCOL_RANGE_JPEG) { /* Full range YUV */ - c.m[0][0] = Q((1 << depth0) - 1); + c.m[0][0] = intmax_q(depth0); if (fmt->desc->nb_components >= 3) { /* This follows the ITU-R convention, which is slightly different * from the JFIF convention. */ - c.m[1][1] = Q((1 << depth1) - 1); - c.m[2][2] = Q((1 << depth2) - 1); + c.m[1][1] = intmax_q(depth1); + c.m[2][2] = intmax_q(depth2); c.m[1][4] = Q(1 << (depth1 - 1)); c.m[2][4] = Q(1 << (depth2 - 1)); } @@ -1160,7 +1165,7 @@ static SwsLinearOp fmt_encode_range(const SwsFormat *fmt, bool *incomplete) if (fmt->desc->flags & AV_PIX_FMT_FLAG_ALPHA) { const bool is_ya = fmt->desc->nb_components == 2; - c.m[3][3] = Q((1 << (is_ya ? depth1 : depth3)) - 1); + c.m[3][3] = intmax_q(is_ya ? depth1 : depth3); } if (fmt->format == AV_PIX_FMT_MONOWHITE) { @@ -1531,7 +1536,7 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, for (int i = 0; i < dst->desc->nb_components; i++) { /* Clamp to legal pixel range */ const int idx = i * (is_ya ? 3 : 1); - range.limit[idx] = Q((1 << dst->desc->comp[i].depth) - 1); + range.limit[idx] = intmax_q(dst->desc->comp[i].depth); } RET(fmt_dither(ctx, ops, type, src, dst)); -- 2.52.0 >From 13964cca208cf75404f6f8b555b88a2cae469fb7 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 12:06:26 +0200 Subject: [PATCH 07/11] swscale/ops_optimizer: omit overflow check on SWS_OP_SCALE 1. This is currently impossible to trigger 2. We're about to switch to AVRational64, eliminating this concern 3. The AVRational64 API intentionally doesn't expose av_reduce64() Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 8e0ceafa0b..d7c8b9b488 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -701,15 +701,11 @@ retry: goto retry; } - /* Merge consecutive scaling operations (that don't overflow) */ + /* Merge consecutive scaling operations */ if (next->op == SWS_OP_SCALE) { - int64_t p = op->scale.factor.num * (int64_t) next->scale.factor.num; - int64_t q = op->scale.factor.den * (int64_t) next->scale.factor.den; - if (FFABS(p) <= INT_MAX && FFABS(q) <= INT_MAX) { - av_reduce(&op->scale.factor.num, &op->scale.factor.den, p, q, INT_MAX); - ff_sws_op_list_remove_at(ops, n + 1, 1); - goto retry; - } + op->scale.factor = av_mul_q(op->scale.factor, next->scale.factor); + ff_sws_op_list_remove_at(ops, n + 1, 1); + goto retry; } /* Scaling by exact power of two */ -- 2.52.0 >From 4de2e18c955509fcbf910b392b6e5086d70f6896 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 12:29:13 +0200 Subject: [PATCH 08/11] swscale/ops: switch from AVRational to AVRational64 This has two immediate consequences: 1. Fixes overflow in the range tracker for some 32-bit packed formats: rgb24 -> v30xbe: [ u8 +++X] SWS_OP_READ : 3 elem(s) packed >> 0 min: {0 0 0 _}, max: {255 255 255 _} [ u8 +++X] SWS_OP_CONVERT : u8 -> f32 min: {0 0 0 _}, max: {255 255 255 _} [f32 ...X] SWS_OP_LINEAR : matrix3+off3 [...] min: {64 64 64 _}, max: {940 960 960 _} [f32 ...X] SWS_OP_DITHER : 16x16 matrix + {0 3 2 -1} min: {64.001953 64.001953 64.001953 _}, max: {940.998047 960.998047 960.998047 _} [f32 +++X] SWS_OP_CONVERT : f32 -> u32 min: {64 64 64 _}, max: {940 960 960 _} [u32 +++X] SWS_OP_SWIZZLE : 2013 min: {64 64 64 _}, max: {960 940 960 _} [u32 ++++] SWS_OP_CLEAR : {_ _ _ 1} min: {64 64 64 1}, max: {960 940 960 1} [u32 +XXX] SWS_OP_PACK : {10 10 10 2} - min: {268697857 _ _ _}, max: {-264581375 _ _ _} + min: {268697857 _ _ _}, max: {4030385921 _ _ _} [u32 zXXX] SWS_OP_SWAP_BYTES - min: {268697857 _ _ _}, max: {-264581375 _ _ _} + min: {268697857 _ _ _}, max: {4030385921 _ _ _} 2. Slightly increases the accuracy of intermediate values for some linear ops: yuv444p10be -> rgb48be: [u16 zzzX] SWS_OP_READ : 3 elem(s) planar >> 0 min: {0 0 0 _}, max: {1023 1023 1023 _} [u16 +++X] SWS_OP_SWAP_BYTES min: {0 0 0 _}, max: {1023 1023 1023 _} [u16 +++X] SWS_OP_CONVERT : u16 -> f32 min: {0 0 0 _}, max: {1023 1023 1023 _} [f32 ...X] SWS_OP_LINEAR : matrix3+off3 [...] 46.813777] [0 0 0 1 0]] - min: {-57290.842348 -44341.337325 -71146.813777 _}, max: {124144.718860 111375.162457 137973.627845 _} + min: {-57290.842348 -44341.337326 -71146.813777 _}, max: {124144.718860 111375.162457 137973.627845 _} [f32 ...X] SWS_OP_MAX : {0 0 0 _} <= x min: {0 0 0 _}, max: {124144.718860 111375.162457 137973.627845 _} [f32 ...X] SWS_OP_MIN : x <= {65535 65535 65535 _} min: {0 0 0 _}, max: {65535 65535 65535 _} [f32 +++X] SWS_OP_CONVERT : f32 -> u16 min: {0 0 0 _}, max: {65535 65535 65535 _} [u16 zzzX] SWS_OP_SWAP_BYTES min: {0 0 0 _}, max: {65535 65535 65535 _} [u16 XXXX] SWS_OP_WRITE : 3 elem(s) packed >> 0 (X = unused, z = byteswapped, + = exact, 0 = zero) Importantly, none of the changes affect the actual operation list, just the range tracking metadata. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/aarch64/ops_impl_conv.c | 4 +- libswscale/format.c | 80 +++++++++++++------------- libswscale/ops.c | 90 +++++++++++++++--------------- libswscale/ops.h | 21 +++---- libswscale/ops_chain.c | 10 ++-- libswscale/ops_chain.h | 2 +- libswscale/ops_internal.h | 4 +- libswscale/ops_optimizer.c | 26 ++++----- libswscale/ops_tmpl_float.c | 2 +- libswscale/tests/sws_ops.c | 8 +-- libswscale/vulkan/ops.c | 10 ++-- libswscale/x86/ops.c | 14 ++--- tests/checkasm/sw_ops.c | 38 ++++++------- tests/ref/fate/sws-ops-list | 2 +- 14 files changed, 156 insertions(+), 155 deletions(-) diff --git a/libswscale/aarch64/ops_impl_conv.c b/libswscale/aarch64/ops_impl_conv.c index 48504dc671..229c9bab97 100644 --- a/libswscale/aarch64/ops_impl_conv.c +++ b/libswscale/aarch64/ops_impl_conv.c @@ -207,9 +207,9 @@ static int convert_to_aarch64_impl(SwsContext *ctx, const SwsOpList *ops, int n, MASK_SET(out->mask, i, 1); for (int j = 0; j < 5; j++) { int jj = linear_index_from_sws_op(j); - if (!av_cmp_q(op->lin.m[i][j], av_make_q(1, 1))) + if (!av_cmp_q64(op->lin.m[i][j], av_make_q64(1, 1))) LINEAR_MASK_SET(out->linear.mask, i, jj, LINEAR_MASK_1); - else if (av_cmp_q(op->lin.m[i][j], av_make_q(0, 1))) + else if (av_cmp_q64(op->lin.m[i][j], av_make_q64(0, 1))) LINEAR_MASK_SET(out->linear.mask, i, jj, LINEAR_MASK_X); } } diff --git a/libswscale/format.c b/libswscale/format.c index e70c3d58b0..fdb6c91d47 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -946,10 +946,10 @@ static SwsClearOp fmt_clear(enum AVPixelFormat fmt) # define NATIVE_ENDIAN_FLAG 0 #endif -static inline AVRational intmax_q(int bits) +static inline AVRational64 intmax_q64(int bits) { - av_assert1(bits >= 0 && bits < 32); - return Q(UINT32_MAX >> (32 - bits)); + av_assert1(bits >= 0 && bits < 64); + return Q(UINT64_MAX >> (64 - bits)); } int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) @@ -986,7 +986,7 @@ int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) const int idx = swizzle.in[is_ya ? 3 * c : c]; comps->min[idx] = Q(0); if (bits < 32) /* FIXME: AVRational is limited to INT_MAX */ - comps->max[idx] = intmax_q(bits); + comps->max[idx] = intmax_q64(bits); } } @@ -1109,15 +1109,15 @@ int ff_sws_encode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) return 0; } -static inline AVRational av_neg_q(AVRational x) +static inline AVRational64 av_neg_q64(AVRational64 x) { - return (AVRational) { -x.num, x.den }; + return (AVRational64) { -x.num, x.den }; } static SwsLinearOp fmt_encode_range(const SwsFormat *fmt, bool *incomplete) { - const AVRational q0 = Q(0); - const AVRational q1 = Q(1); + const AVRational64 q0 = Q(0); + const AVRational64 q1 = Q(1); SwsLinearOp c = { .m = { { q1, q0, q0, q0, q0 }, @@ -1135,17 +1135,17 @@ static SwsLinearOp fmt_encode_range(const SwsFormat *fmt, bool *incomplete) return c; /* floats are directly output as-is */ if (fmt->csp == AVCOL_SPC_RGB || (fmt->desc->flags & AV_PIX_FMT_FLAG_XYZ)) { - c.m[0][0] = intmax_q(depth0); - c.m[1][1] = intmax_q(depth1); - c.m[2][2] = intmax_q(depth2); + c.m[0][0] = intmax_q64(depth0); + c.m[1][1] = intmax_q64(depth1); + c.m[2][2] = intmax_q64(depth2); } else if (fmt->range == AVCOL_RANGE_JPEG) { /* Full range YUV */ - c.m[0][0] = intmax_q(depth0); + c.m[0][0] = intmax_q64(depth0); if (fmt->desc->nb_components >= 3) { /* This follows the ITU-R convention, which is slightly different * from the JFIF convention. */ - c.m[1][1] = intmax_q(depth1); - c.m[2][2] = intmax_q(depth2); + c.m[1][1] = intmax_q64(depth1); + c.m[2][2] = intmax_q64(depth2); c.m[1][4] = Q(1 << (depth1 - 1)); c.m[2][4] = Q(1 << (depth2 - 1)); } @@ -1165,13 +1165,13 @@ static SwsLinearOp fmt_encode_range(const SwsFormat *fmt, bool *incomplete) if (fmt->desc->flags & AV_PIX_FMT_FLAG_ALPHA) { const bool is_ya = fmt->desc->nb_components == 2; - c.m[3][3] = intmax_q(is_ya ? depth1 : depth3); + c.m[3][3] = intmax_q64(is_ya ? depth1 : depth3); } if (fmt->format == AV_PIX_FMT_MONOWHITE) { /* This format is inverted, 0 = white, 1 = black */ - c.m[0][4] = av_add_q(c.m[0][4], c.m[0][0]); - c.m[0][0] = av_neg_q(c.m[0][0]); + c.m[0][4] = av_add_q64(c.m[0][4], c.m[0][0]); + c.m[0][0] = av_neg_q64(c.m[0][0]); } c.mask = ff_sws_linear_mask(c); @@ -1185,8 +1185,8 @@ static SwsLinearOp fmt_decode_range(const SwsFormat *fmt, bool *incomplete) /* Invert main diagonal + offset: x = s * y + k ==> y = (x - k) / s */ for (int i = 0; i < 4; i++) { av_assert1(c.m[i][i].num); - c.m[i][i] = av_inv_q(c.m[i][i]); - c.m[i][4] = av_mul_q(c.m[i][4], av_neg_q(c.m[i][i])); + c.m[i][i] = av_inv_q64(c.m[i][i]); + c.m[i][4] = av_mul_q64(c.m[i][4], av_neg_q64(c.m[i][i])); } /* Explicitly initialize alpha for sanity */ @@ -1197,11 +1197,11 @@ static SwsLinearOp fmt_decode_range(const SwsFormat *fmt, bool *incomplete) return c; } -static AVRational *generate_bayer_matrix(const int size_log2) +static AVRational64 *generate_bayer_matrix(const int size_log2) { const int size = 1 << size_log2; const int num_entries = size * size; - AVRational *m = av_refstruct_allocz(sizeof(*m) * num_entries); + AVRational64 *m = av_refstruct_allocz(sizeof(*m) * num_entries); av_assert1(size_log2 < 16); if (!m) return NULL; @@ -1214,10 +1214,10 @@ static AVRational *generate_bayer_matrix(const int size_log2) const int den = 4 * sz * sz; for (int y = 0; y < sz; y++) { for (int x = 0; x < sz; x++) { - const AVRational cur = m[y * size + x]; - m[(y + sz) * size + x + sz] = av_add_q(cur, av_make_q(1, den)); - m[(y ) * size + x + sz] = av_add_q(cur, av_make_q(2, den)); - m[(y + sz) * size + x ] = av_add_q(cur, av_make_q(3, den)); + const AVRational64 cur = m[y * size + x]; + m[(y + sz) * size + x + sz] = av_add_q64(cur, av_make_q64(1, den)); + m[(y ) * size + x + sz] = av_add_q64(cur, av_make_q64(2, den)); + m[(y + sz) * size + x ] = av_add_q64(cur, av_make_q64(3, den)); } } } @@ -1233,7 +1233,7 @@ static AVRational *generate_bayer_matrix(const int size_log2) * To make the average value equal to 1/2 = N/(2N), add a bias of 1/(2N). */ for (int i = 0; i < num_entries; i++) - m[i] = av_add_q(m[i], av_make_q(1, 2 * num_entries)); + m[i] = av_add_q64(m[i], av_make_q64(1, 2 * num_entries)); return m; } @@ -1272,10 +1272,10 @@ static int fmt_dither(SwsContext *ctx, SwsOpList *ops, case SWS_DITHER_NONE: if (ctx->flags & SWS_ACCURATE_RND) { /* Add constant 0.5 for correct rounding */ - AVRational *bias = av_refstruct_allocz(sizeof(*bias)); + AVRational64 *bias = av_refstruct_allocz(sizeof(*bias)); if (!bias) return AVERROR(ENOMEM); - *bias = (AVRational) {1, 2}; + *bias = (AVRational64) {1, 2}; return ff_sws_op_list_append(ops, &(SwsOp) { .op = SWS_OP_DITHER, .type = type, @@ -1300,9 +1300,9 @@ static int fmt_dither(SwsContext *ctx, SwsOpList *ops, const int size = 1 << dither.size_log2; dither.min = dither.max = dither.matrix[0]; for (int i = 1; i < size * size; i++) { - if (av_cmp_q(dither.min, dither.matrix[i]) > 0) + if (av_cmp_q64(dither.min, dither.matrix[i]) > 0) dither.min = dither.matrix[i]; - if (av_cmp_q(dither.matrix[i], dither.max) > 0) + if (av_cmp_q64(dither.matrix[i], dither.max) > 0) dither.max = dither.matrix[i]; } @@ -1347,18 +1347,18 @@ static int fmt_dither(SwsContext *ctx, SwsOpList *ops, return AVERROR(EINVAL); } +#define Q64(x) av_make_q64((x).num, (x).den) + static inline SwsLinearOp linear_mat3(const AVRational m00, const AVRational m01, const AVRational m02, const AVRational m10, const AVRational m11, const AVRational m12, const AVRational m20, const AVRational m21, const AVRational m22) { - const AVRational q0 = Q(0); - const AVRational q1 = Q(1); SwsLinearOp c = {{ - { m00, m01, m02, q0, q0 }, - { m10, m11, m12, q0, q0 }, - { m20, m21, m22, q0, q0 }, - { q0, q0, q0, q1, q0 }, + { Q64(m00), Q64(m01), Q64(m02), Q(0), Q(0) }, + { Q64(m10), Q64(m11), Q64(m12), Q(0), Q(0) }, + { Q64(m20), Q64(m21), Q64(m22), Q(0), Q(0) }, + { Q(0), Q(0), Q(0), Q(1), Q(0) }, }}; c.mask = ff_sws_linear_mask(c); @@ -1373,9 +1373,9 @@ int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type, if (!pixel_type) return AVERROR(ENOTSUP); - const AVRational q0 = Q(0); - const AVRational q1 = Q(1); - const AVRational q2 = Q(2); + const AVRational q0 = av_make_q(0, 1); + const AVRational q1 = av_make_q(1, 1); + const AVRational q2 = av_make_q(2, 1); RET(ff_sws_op_list_append(ops, &(SwsOp) { .op = SWS_OP_CONVERT, @@ -1536,7 +1536,7 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, for (int i = 0; i < dst->desc->nb_components; i++) { /* Clamp to legal pixel range */ const int idx = i * (is_ya ? 3 : 1); - range.limit[idx] = intmax_q(dst->desc->comp[i].depth); + range.limit[idx] = intmax_q64(dst->desc->comp[i].depth); } RET(fmt_dither(ctx, ops, type, src, dst)); diff --git a/libswscale/ops.c b/libswscale/ops.c index 0dec54c49c..c694ed0c34 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -133,7 +133,7 @@ const char *ff_sws_op_type_name(SwsOpType op) return "ERR"; } -SwsCompMask ff_sws_comp_mask_q4(const AVRational q[4]) +SwsCompMask ff_sws_comp_mask_q4(const AVRational64 q[4]) { SwsCompMask mask = 0; for (int i = 0; i < 4; i++) { @@ -166,17 +166,17 @@ SwsCompMask ff_sws_comp_mask_needed(const SwsOp *op) } /* biased towards `a` */ -static AVRational av_min_q(AVRational a, AVRational b) +static AVRational64 av_min_q64(AVRational64 a, AVRational64 b) { - return av_cmp_q(a, b) == 1 ? b : a; + return av_cmp_q64(a, b) == 1 ? b : a; } -static AVRational av_max_q(AVRational a, AVRational b) +static AVRational64 av_max_q64(AVRational64 a, AVRational64 b) { - return av_cmp_q(a, b) == -1 ? b : a; + return av_cmp_q64(a, b) == -1 ? b : a; } -void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) +void ff_sws_apply_op_q(const SwsOp *op, AVRational64 x[4]) { uint64_t mask[4]; int shift[4]; @@ -223,9 +223,9 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) return; case SWS_OP_LSHIFT: { av_assert1(ff_sws_pixel_type_is_int(op->type)); - AVRational mult = Q(1 << op->shift.amount); + AVRational64 mult = Q(1 << op->shift.amount); for (int i = 0; i < 4; i++) - x[i] = x[i].den ? av_mul_q(x[i], mult) : x[i]; + x[i] = x[i].den ? av_mul_q64(x[i], mult) : x[i]; return; } case SWS_OP_RSHIFT: { @@ -235,18 +235,18 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) return; } case SWS_OP_SWIZZLE: { - const AVRational orig[4] = { x[0], x[1], x[2], x[3] }; + const AVRational64 orig[4] = { x[0], x[1], x[2], x[3] }; for (int i = 0; i < 4; i++) x[i] = orig[op->swizzle.in[i]]; return; } case SWS_OP_CONVERT: if (ff_sws_pixel_type_is_int(op->convert.to)) { - const AVRational scale = ff_sws_pixel_expand(op->type, op->convert.to); + const AVRational64 scale = ff_sws_pixel_expand(op->type, op->convert.to); for (int i = 0; i < 4; i++) { x[i] = x[i].den ? Q(x[i].num / x[i].den) : x[i]; if (op->convert.expand) - x[i] = av_mul_q(x[i], scale); + x[i] = av_mul_q64(x[i], scale); } } return; @@ -254,31 +254,31 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) av_assert1(!ff_sws_pixel_type_is_int(op->type)); for (int i = 0; i < 4; i++) { if (op->dither.y_offset[i] >= 0 && x[i].den) - x[i] = av_add_q(x[i], av_make_q(1, 2)); + x[i] = av_add_q64(x[i], av_make_q64(1, 2)); } return; case SWS_OP_MIN: for (int i = 0; i < 4; i++) - x[i] = av_min_q(x[i], op->clamp.limit[i]); + x[i] = av_min_q64(x[i], op->clamp.limit[i]); return; case SWS_OP_MAX: for (int i = 0; i < 4; i++) - x[i] = av_max_q(x[i], op->clamp.limit[i]); + x[i] = av_max_q64(x[i], op->clamp.limit[i]); return; case SWS_OP_LINEAR: { av_assert1(!ff_sws_pixel_type_is_int(op->type)); - const AVRational orig[4] = { x[0], x[1], x[2], x[3] }; + const AVRational64 orig[4] = { x[0], x[1], x[2], x[3] }; for (int i = 0; i < 4; i++) { - AVRational sum = op->lin.m[i][4]; + AVRational64 sum = op->lin.m[i][4]; for (int j = 0; j < 4; j++) - sum = av_add_q(sum, av_mul_q(orig[j], op->lin.m[i][j])); + sum = av_add_q64(sum, av_mul_q64(orig[j], op->lin.m[i][j])); x[i] = sum; } return; } case SWS_OP_SCALE: for (int i = 0; i < 4; i++) - x[i] = x[i].den ? av_mul_q(x[i], op->scale.factor) : x[i]; + x[i] = x[i].den ? av_mul_q64(x[i], op->scale.factor) : x[i]; return; case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: @@ -310,7 +310,7 @@ static void propagate_flags(SwsOp *op, const SwsComps *prev) } /* Clear undefined values in dst with src */ -static void clear_undefined_values(AVRational dst[4], const AVRational src[4]) +static void clear_undefined_values(AVRational64 dst[4], const AVRational64 src[4]) { for (int i = 0; i < 4; i++) { if (dst[i].den == 0) @@ -321,18 +321,18 @@ static void clear_undefined_values(AVRational dst[4], const AVRational src[4]) static void apply_filter_weights(SwsComps *comps, const SwsComps *prev, const SwsFilterWeights *weights) { - const AVRational posw = { weights->sum_positive, SWS_FILTER_SCALE }; - const AVRational negw = { weights->sum_negative, SWS_FILTER_SCALE }; + const AVRational64 posw = { weights->sum_positive, SWS_FILTER_SCALE }; + const AVRational64 negw = { weights->sum_negative, SWS_FILTER_SCALE }; for (int i = 0; i < 4; i++) { comps->flags[i] = prev->flags[i]; /* Only point sampling preserves exactness */ if (weights->filter_size != 1) comps->flags[i] &= ~SWS_COMP_EXACT; /* Update min/max assuming extremes */ - comps->min[i] = av_add_q(av_mul_q(prev->min[i], posw), - av_mul_q(prev->max[i], negw)); - comps->max[i] = av_add_q(av_mul_q(prev->min[i], negw), - av_mul_q(prev->max[i], posw)); + comps->min[i] = av_add_q64(av_mul_q64(prev->min[i], posw), + av_mul_q64(prev->max[i], negw)); + comps->max[i] = av_add_q64(av_mul_q64(prev->min[i], negw), + av_mul_q64(prev->max[i], posw)); } } @@ -417,8 +417,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) continue; /* Strip zero flag because of the nonzero dithering offset */ op->comps.flags[i] = prev.flags[i] & ~SWS_COMP_ZERO; - op->comps.min[i] = av_add_q(op->comps.min[i], op->dither.min); - op->comps.max[i] = av_add_q(op->comps.max[i], op->dither.max); + op->comps.min[i] = av_add_q64(op->comps.min[i], op->dither.min); + op->comps.max[i] = av_add_q64(op->comps.max[i], op->dither.max); } break; case SWS_OP_UNPACK: @@ -471,27 +471,27 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_LINEAR: for (int i = 0; i < 4; i++) { SwsCompFlags flags = SWS_COMP_IDENTITY; - AVRational min = Q(0), max = Q(0); + AVRational64 min = Q(0), max = Q(0); for (int j = 0; j < 4; j++) { - const AVRational k = op->lin.m[i][j]; - AVRational mink = av_mul_q(prev.min[j], k); - AVRational maxk = av_mul_q(prev.max[j], k); + const AVRational64 k = op->lin.m[i][j]; + AVRational64 mink = av_mul_q64(prev.min[j], k); + AVRational64 maxk = av_mul_q64(prev.max[j], k); if (k.num) { flags = merge_comp_flags(flags, prev.flags[j]); if (k.den != 1) /* fractional coefficient */ flags &= ~SWS_COMP_EXACT; if (k.num < 0) - FFSWAP(AVRational, mink, maxk); - min = av_add_q(min, mink); - max = av_add_q(max, maxk); + FFSWAP(AVRational64, mink, maxk); + min = av_add_q64(min, mink); + max = av_add_q64(max, maxk); } } if (op->lin.m[i][4].num) { /* nonzero offset */ flags &= ~SWS_COMP_ZERO; if (op->lin.m[i][4].den != 1) /* fractional offset */ flags &= ~SWS_COMP_EXACT; - min = av_add_q(min, op->lin.m[i][4]); - max = av_add_q(max, op->lin.m[i][4]); + min = av_add_q64(min, op->lin.m[i][4]); + max = av_add_q64(max, op->lin.m[i][4]); } op->comps.flags[i] = flags; op->comps.min[i] = min; @@ -504,7 +504,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) if (op->scale.factor.den != 1) /* fractional scale */ op->comps.flags[i] &= ~SWS_COMP_EXACT; if (op->scale.factor.num < 0) - FFSWAP(AVRational, op->comps.min[i], op->comps.max[i]); + FFSWAP(AVRational64, op->comps.min[i], op->comps.max[i]); } break; case SWS_OP_FILTER_H: @@ -760,7 +760,7 @@ uint32_t ff_sws_linear_mask(const SwsLinearOp c) uint32_t mask = 0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 5; j++) { - if (av_cmp_q(c.m[i][j], Q(i == j))) + if (av_cmp_q64(c.m[i][j], Q(i == j))) mask |= SWS_MASK(i, j); } } @@ -822,20 +822,20 @@ static char describe_comp_flags(SwsCompFlags flags) return '.'; } -static void print_q(AVBPrint *bp, const AVRational q) +static void print_q(AVBPrint *bp, const AVRational64 q) { if (!q.den) { av_bprintf(bp, "%s", q.num > 0 ? "inf" : q.num < 0 ? "-inf" : "nan"); } else if (q.den == 1) { - av_bprintf(bp, "%d", q.num); + av_bprintf(bp, "%"PRId64, q.num); } else if (abs(q.num) > 1000 || abs(q.den) > 1000) { - av_bprintf(bp, "%f", av_q2d(q)); + av_bprintf(bp, "%f", av_q2d_64(q)); } else { - av_bprintf(bp, "%d/%d", q.num, q.den); + av_bprintf(bp, "%"PRId64"/%"PRId64, q.num, q.den); } } -static void print_q4(AVBPrint *bp, const AVRational q4[4], SwsCompMask mask) +static void print_q4(AVBPrint *bp, const AVRational64 q4[4], SwsCompMask mask) { av_bprintf(bp, "{"); for (int i = 0; i < 4; i++) { @@ -926,9 +926,9 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) av_bprintf(bp, "]"); break; case SWS_OP_SCALE: - av_bprintf(bp, "%-20s: * %d", name, op->scale.factor.num); + av_bprintf(bp, "%-20s: * %"PRId64, name, op->scale.factor.num); if (op->scale.factor.den != 1) - av_bprintf(bp, "/%d", op->scale.factor.den); + av_bprintf(bp, "/%"PRId64, op->scale.factor.den); break; case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: { diff --git a/libswscale/ops.h b/libswscale/ops.h index b23f500695..5872cef4ae 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -26,6 +26,7 @@ #include <stdalign.h> #include "libavutil/bprint.h" +#include "libavutil/rational64.h" #include "graph.h" #include "filters.h" @@ -97,7 +98,7 @@ enum { }; /* Compute SwsCompMask from values with denominator != 0 */ -SwsCompMask ff_sws_comp_mask_q4(const AVRational q[4]); +SwsCompMask ff_sws_comp_mask_q4(const AVRational64 q[4]); typedef enum SwsCompFlags { SWS_COMP_GARBAGE = 1 << 0, /* contents are undefined / garbage data */ @@ -111,7 +112,7 @@ typedef struct SwsComps { /* Keeps track of the known possible value range, or {0, 0} for undefined * or (unknown range) floating point inputs */ - AVRational min[4], max[4]; + AVRational64 min[4], max[4]; } SwsComps; typedef struct SwsReadWriteOp { @@ -167,7 +168,7 @@ typedef struct SwsShiftOp { typedef struct SwsClearOp { SwsCompMask mask; /* mask of components to clear */ - AVRational value[4]; /* value to set */ + AVRational64 value[4]; /* value to set */ } SwsClearOp; typedef struct SwsConvertOp { @@ -176,16 +177,16 @@ typedef struct SwsConvertOp { } SwsConvertOp; typedef struct SwsClampOp { - AVRational limit[4]; /* per-component min/max value */ + AVRational64 limit[4]; /* per-component min/max value */ } SwsClampOp; typedef struct SwsScaleOp { - AVRational factor; /* scalar multiplication factor */ + AVRational64 factor; /* scalar multiplication factor */ } SwsScaleOp; typedef struct SwsDitherOp { - AVRational *matrix; /* tightly packed dither matrix (refstruct) */ - AVRational min, max; /* minimum/maximum value in `matrix` */ + AVRational64 *matrix; /* tightly packed dither matrix (refstruct) */ + AVRational64 min, max; /* minimum/maximum value in `matrix` */ int size_log2; /* size (in bits) of the dither matrix */ int8_t y_offset[4]; /* row offset for each component, or -1 for ignored */ } SwsDitherOp; @@ -203,7 +204,7 @@ typedef struct SwsLinearOp { * example the common subset of {A, E, G, J, M, O} can be implemented with * just three fused multiply-add operations. */ - AVRational m[4][5]; + AVRational64 m[4][5]; uint32_t mask; /* m[i][j] <-> 1 << (5 * i + j) */ } SwsLinearOp; @@ -278,9 +279,9 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op); void ff_sws_op_uninit(SwsOp *op); /** - * Apply an operation to an AVRational. No-op for read/write operations. + * Apply an operation to an AVRational64. No-op for read/write operations. */ -void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]); +void ff_sws_apply_op_q(const SwsOp *op, AVRational64 x[4]); /** * Helper struct for representing a list of operations. diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index 0bbfa4ae4c..01260a897d 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -134,7 +134,7 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) continue; else if (!entry->clear.value[i].den) continue; /* Any clear value supported */ - else if (av_cmp_q(op->clear.value[i], entry->clear.value[i])) + else if (av_cmp_q64(op->clear.value[i], entry->clear.value[i])) return 0; } return score; @@ -164,7 +164,7 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) return 0; return score; case SWS_OP_SCALE: - return av_cmp_q(op->scale.factor, entry->scale) ? 0 : score; + return av_cmp_q64(op->scale.factor, entry->scale) ? 0 : score; case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: return score; @@ -248,7 +248,7 @@ int ff_sws_setup_shift(const SwsImplParams *params, SwsImplResult *out) int ff_sws_setup_scale(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; - const AVRational factor = op->scale.factor; + const AVRational64 factor = op->scale.factor; switch (op->type) { case SWS_PIXEL_U8: out->priv.u8[0] = q2pixel(uint8_t, factor); break; case SWS_PIXEL_U16: out->priv.u16[0] = q2pixel(uint16_t, factor); break; @@ -264,7 +264,7 @@ int ff_sws_setup_clamp(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; for (int i = 0; i < 4; i++) { - const AVRational limit = op->clamp.limit[i]; + const AVRational64 limit = op->clamp.limit[i]; switch (op->type) { case SWS_PIXEL_U8: out->priv.u8[i] = q2pixel(uint8_t, limit); break; case SWS_PIXEL_U16: out->priv.u16[i] = q2pixel(uint16_t, limit); break; @@ -281,7 +281,7 @@ int ff_sws_setup_clear(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; for (int i = 0; i < 4; i++) { - const AVRational value = op->clear.value[i]; + const AVRational64 value = op->clear.value[i]; if (!value.den) continue; switch (op->type) { diff --git a/libswscale/ops_chain.h b/libswscale/ops_chain.h index 9fe2c06613..233f9b3c4b 100644 --- a/libswscale/ops_chain.h +++ b/libswscale/ops_chain.h @@ -131,7 +131,7 @@ typedef struct SwsOpEntry { SwsClearOp clear; uint32_t linear_mask; /* subset of SwsLinearOp */ int dither_size; /* subset of SwsDitherOp */ - AVRational scale; /* scale factor for SWS_OP_SCALE */ + AVRational64 scale; /* scale factor for SWS_OP_SCALE */ }; /* Kernel implementation */ diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h index 91509ce67d..d276e79bb7 100644 --- a/libswscale/ops_internal.h +++ b/libswscale/ops_internal.h @@ -26,9 +26,9 @@ #include "ops.h" #include "ops_dispatch.h" -#define Q(N) ((AVRational) { N, 1 }) +#define Q(N) ((AVRational64) { N, 1 }) -static inline AVRational ff_sws_pixel_expand(SwsPixelType from, SwsPixelType to) +static inline AVRational64 ff_sws_pixel_expand(SwsPixelType from, SwsPixelType to) { const int src = ff_sws_pixel_type_size(from); const int dst = ff_sws_pixel_type_size(to); diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index d7c8b9b488..b17444d373 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -134,7 +134,7 @@ static bool op_commute_swizzle(SwsOp *op, SwsOp *next) if (!SWS_OP_NEEDED(op, i)) continue; const int j = op->swizzle.in[i]; - if (seen[j] && av_cmp_q(next->clamp.limit[j], c.limit[i])) + if (seen[j] && av_cmp_q64(next->clamp.limit[j], c.limit[i])) return false; next->clamp.limit[j] = c.limit[i]; seen[j] = true; @@ -228,7 +228,7 @@ static int exact_log2(const int x) return (1 << p) == x ? p : 0; } -static int exact_log2_q(const AVRational x) +static int exact_log2_q64(const AVRational64 x) { if (x.den == 1) return exact_log2(x.num); @@ -252,11 +252,11 @@ static bool extract_scalar(const SwsLinearOp *c, SwsComps comps, SwsComps prev, return false; for (int i = 0; i < 4; i++) { - const AVRational s = c->m[i][i]; + const AVRational64 s = c->m[i][i]; if ((prev.flags[i] & SWS_COMP_ZERO) || (comps.flags[i] & SWS_COMP_GARBAGE)) continue; - if (scale.factor.den && av_cmp_q(s, scale.factor)) + if (scale.factor.den && av_cmp_q64(s, scale.factor)) return false; scale.factor = s; } @@ -555,8 +555,8 @@ retry: if (next->op == SWS_OP_SCALE && !op->convert.expand && ff_sws_pixel_type_is_int(op->type) && ff_sws_pixel_type_is_int(op->convert.to) && - !av_cmp_q(next->scale.factor, - ff_sws_pixel_expand(op->type, op->convert.to))) + !av_cmp_q64(next->scale.factor, + ff_sws_pixel_expand(op->type, op->convert.to))) { op->convert.expand = true; ff_sws_op_list_remove_at(ops, n + 1, 1); @@ -568,7 +568,7 @@ retry: for (int i = 0; i < 4; i++) { if (!SWS_OP_NEEDED(op, i) || !op->clamp.limit[i].den) continue; - if (av_cmp_q(op->clamp.limit[i], prev->comps.max[i]) < 0) + if (av_cmp_q64(op->clamp.limit[i], prev->comps.max[i]) < 0) noop = false; } @@ -582,7 +582,7 @@ retry: for (int i = 0; i < 4; i++) { if (!SWS_OP_NEEDED(op, i) || !op->clamp.limit[i].den) continue; - if (av_cmp_q(prev->comps.min[i], op->clamp.limit[i]) < 0) + if (av_cmp_q64(prev->comps.min[i], op->clamp.limit[i]) < 0) noop = false; } @@ -627,11 +627,11 @@ retry: const SwsLinearOp m2 = next->lin; for (int i = 0; i < 4; i++) { for (int j = 0; j < 5; j++) { - AVRational sum = Q(0); + AVRational64 sum = Q(0); for (int k = 0; k < 4; k++) - sum = av_add_q(sum, av_mul_q(m2.m[i][k], m1.m[k][j])); + sum = av_add_q64(sum, av_mul_q64(m2.m[i][k], m1.m[k][j])); if (j == 4) /* m1.m[4][j] == 1 */ - sum = av_add_q(sum, m2.m[i][4]); + sum = av_add_q64(sum, m2.m[i][4]); op->lin.m[i][j] = sum; } } @@ -693,7 +693,7 @@ retry: } case SWS_OP_SCALE: { - const int factor2 = exact_log2_q(op->scale.factor); + const int factor2 = exact_log2_q64(op->scale.factor); /* No-op scaling */ if (op->scale.factor.num == 1 && op->scale.factor.den == 1) { @@ -703,7 +703,7 @@ retry: /* Merge consecutive scaling operations */ if (next->op == SWS_OP_SCALE) { - op->scale.factor = av_mul_q(op->scale.factor, next->scale.factor); + op->scale.factor = av_mul_q64(op->scale.factor, next->scale.factor); ff_sws_op_list_remove_at(ops, n + 1, 1); goto retry; } diff --git a/libswscale/ops_tmpl_float.c b/libswscale/ops_tmpl_float.c index d2a196873e..66828b9f4e 100644 --- a/libswscale/ops_tmpl_float.c +++ b/libswscale/ops_tmpl_float.c @@ -47,7 +47,7 @@ DECL_SETUP(setup_dither, params, out) const int size = 1 << op->dither.size_log2; if (size == 1) { /* We special case this value */ - av_assert1(!av_cmp_q(op->dither.matrix[0], av_make_q(1, 2))); + av_assert1(!av_cmp_q64(op->dither.matrix[0], av_make_q64(1, 2))); out->priv.ptr = NULL; return 0; } diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index 1256c73ec6..eabdc717df 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -61,20 +61,20 @@ static int register_op(SwsContext *ctx, void *opaque, SwsOp *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 }; + op->lin.m[i][j] = (AVRational64) { 0, 1 }; } break; case SWS_OP_SCALE: - op->scale.factor = (AVRational) { 0, 1 }; + op->scale.factor = (AVRational64) { 0, 1 }; break; case SWS_OP_MIN: case SWS_OP_MAX: for (int i = 0; i < 4; i++) - op->clamp.limit[i] = (AVRational) { 0, 1 }; + op->clamp.limit[i] = (AVRational64) { 0, 1 }; break; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) - op->clear.value[i] = (AVRational) { 0, SWS_COMP_TEST(op->clear.mask, i) }; + op->clear.value[i] = (AVRational64) { 0, SWS_COMP_TEST(op->clear.mask, i) }; break; case SWS_OP_DITHER: /* Strip arbitrary offset */ diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c index cf128b00c1..5d56b513fb 100644 --- a/libswscale/vulkan/ops.c +++ b/libswscale/vulkan/ops.c @@ -192,7 +192,7 @@ static int create_dither_bufs(FFVulkanOpsCtx *s, VulkanPriv *p, SwsOpList *ops) for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { - const AVRational r = op->dither.matrix[i*size + j]; + const AVRational64 r = op->dither.matrix[i*size + j]; dither_data[i*size + j] = r.num/(float)r.den; } } @@ -389,7 +389,7 @@ static void define_shader_consts(SwsOpList *ops, SPICtx *spi, SPIRVIDs *id) switch (op->op) { case SWS_OP_CONVERT: if (ff_sws_pixel_type_is_int(op->convert.to) && op->convert.expand) { - AVRational m = ff_sws_pixel_expand(op->type, op->convert.to); + AVRational64 m = ff_sws_pixel_expand(op->type, op->convert.to); int tmp = spi_OpConstantUInt(spi, id->u32_type, m.num); tmp = spi_OpConstantComposite(spi, id->u32vec4_type, tmp, tmp, tmp, tmp); @@ -400,7 +400,7 @@ static void define_shader_consts(SwsOpList *ops, SPICtx *spi, SPIRVIDs *id) for (int i = 0; i < 4; i++) { if (!SWS_COMP_TEST(op->clear.mask, i)) continue; - AVRational cv = op->clear.value[i]; + AVRational64 cv = op->clear.value[i]; if (op->type == SWS_PIXEL_F32) { float q = (float)cv.num/cv.den; id->const_ids[id->nb_const_ids++] = @@ -440,7 +440,7 @@ static void define_shader_consts(SwsOpList *ops, SPICtx *spi, SPIRVIDs *id) case SWS_OP_MAX: for (int i = 0; i < 4; i++) { int tmp; - AVRational cl = op->clamp.limit[i]; + AVRational64 cl = op->clamp.limit[i]; if (!op->clamp.limit[i].den) { continue; } else if (op->type == SWS_PIXEL_F32) { @@ -1067,7 +1067,7 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, break; case SWS_OP_CONVERT: if (ff_sws_pixel_type_is_int(cur_type) && op->convert.expand) { - const AVRational sc = ff_sws_pixel_expand(op->type, op->convert.to); + const AVRational64 sc = ff_sws_pixel_expand(op->type, op->convert.to); av_bprintf(&shd->src, " %s = %s((%s*%i)/%i);\n", type_name, type_v, ff_sws_pixel_type_name(op->type), sc.num, sc.den); diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index 4ae5a14d27..66f08f96ec 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -213,7 +213,7 @@ static int setup_dither(const SwsImplParams *params, SwsImplResult *out) const SwsOp *op = params->op; /* 1x1 matrix / single constant */ if (!op->dither.size_log2) { - const AVRational k = op->dither.matrix[0]; + const AVRational64 k = op->dither.matrix[0]; out->priv.f32[0] = (float) k.num / k.den; return 0; } @@ -301,14 +301,14 @@ static bool check_filter_fma(const SwsImplParams *params) return false; /* Check if maximum/minimum partial sum fits losslessly inside float */ - AVRational max_range = { 1 << 24, 1 }; - AVRational min_range = { -(1 << 24), 1 }; - const AVRational scale = Q(SWS_FILTER_SCALE); + AVRational64 max_range = { 1 << 24, 1 }; + AVRational64 min_range = { -(1 << 24), 1 }; + const AVRational64 scale = Q(SWS_FILTER_SCALE); for (int i = 0; i < op->rw.elems; i++) { - const AVRational min = av_mul_q(op->comps.min[i], scale); - const AVRational max = av_mul_q(op->comps.max[i], scale); - if (av_cmp_q(min, min_range) < 0 || av_cmp_q(max_range, max) < 0) + const AVRational64 min = av_mul_q64(op->comps.min[i], scale); + const AVRational64 max = av_mul_q64(op->comps.max[i], scale); + if (av_cmp_q64(min, min_range) < 0 || av_cmp_q64(max_range, max) < 0) return false; } diff --git a/tests/checkasm/sw_ops.c b/tests/checkasm/sw_ops.c index 82415574f4..086e044792 100644 --- a/tests/checkasm/sw_ops.c +++ b/tests/checkasm/sw_ops.c @@ -103,12 +103,12 @@ static void fill8(uint8_t *line, int num, unsigned range) } } -static void set_range(AVRational *rangeq, unsigned range, unsigned range_def) +static void set_range(AVRational64 *rangeq, unsigned range, unsigned range_def) { if (!range) range = range_def; - if (range && range <= INT_MAX) - *rangeq = (AVRational) { range, 1 }; + if (range) + *rangeq = (AVRational64) { range, 1 }; } static void check_compiled(const char *name, const SwsOpBackend *backend, @@ -265,20 +265,20 @@ static void check_ops(const char *name, const unsigned ranges[NB_PLANES], switch (read_op->type) { case U8: set_range(&oplist.comps_src.max[p], ranges[p], UINT8_MAX); - oplist.comps_src.min[p] = (AVRational) { 0, 1 }; + oplist.comps_src.min[p] = (AVRational64) { 0, 1 }; break; case U16: set_range(&oplist.comps_src.max[p], ranges[p], UINT16_MAX); - oplist.comps_src.min[p] = (AVRational) { 0, 1 }; + oplist.comps_src.min[p] = (AVRational64) { 0, 1 }; break; case U32: set_range(&oplist.comps_src.max[p], ranges[p], UINT32_MAX); - oplist.comps_src.min[p] = (AVRational) { 0, 1 }; + oplist.comps_src.min[p] = (AVRational64) { 0, 1 }; break; case F32: if (ranges[p] && ranges[p] <= INT_MAX) { - oplist.comps_src.max[p] = (AVRational) { ranges[p], 1 }; - oplist.comps_src.min[p] = (AVRational) { 0, 1 }; + oplist.comps_src.max[p] = (AVRational64) { ranges[p], 1 }; + oplist.comps_src.min[p] = (AVRational64) { 0, 1 }; } break; } @@ -515,15 +515,15 @@ static void check_pack_unpack(void) } } -static AVRational rndq(SwsPixelType t) +static AVRational64 rndq(SwsPixelType t) { const unsigned num = rnd(); if (ff_sws_pixel_type_is_int(t)) { const unsigned mask = UINT_MAX >> (32 - ff_sws_pixel_type_size(t) * 8); - return (AVRational) { num & mask, 1 }; + return (AVRational64) { num & mask, 1 }; } else { const unsigned den = rnd(); - return (AVRational) { num, den ? den : 1 }; + return (AVRational64) { num, den ? den : 1 }; } } @@ -535,12 +535,12 @@ static void check_clear(void) /* TODO: AVRational can't fit 32 bit constants */ if (bits < 32) { - const AVRational chroma = (AVRational) { 1 << (bits - 1), 1}; - const AVRational alpha = (AVRational) { (1 << bits) - 1, 1}; - const AVRational zero = (AVRational) { 0, 1}; - const AVRational none = {0}; + const AVRational64 chroma = (AVRational64) { 1 << (bits - 1), 1}; + const AVRational64 alpha = (AVRational64) { (1 << bits) - 1, 1}; + const AVRational64 zero = (AVRational64) { 0, 1}; + const AVRational64 none = {0}; - const AVRational patterns[][4] = { + const AVRational64 patterns[][4] = { /* Zero only */ { none, none, none, zero }, { zero, none, none, none }, @@ -704,14 +704,14 @@ static void check_dither(void) for (int size_log2 = 0; size_log2 <= 8; size_log2++) { const int size = 1 << size_log2; const int mask = size - 1; - AVRational *matrix = av_refstruct_allocz(size * size * sizeof(*matrix)); + AVRational64 *matrix = av_refstruct_allocz(size * size * sizeof(*matrix)); if (!matrix) { fail(); return; } if (size == 1) { - matrix[0] = (AVRational) { 1, 2 }; + matrix[0] = (AVRational64) { 1, 2 }; } else { for (int i = 0; i < size * size; i++) matrix[i] = rndq(t); @@ -781,7 +781,7 @@ static void check_linear(void) if (mask & SWS_MASK(i, j)) { lin.m[i][j] = rndq(t); } else { - lin.m[i][j] = (AVRational) { i == j, 1 }; + lin.m[i][j] = (AVRational64) { i == j, 1 }; } } } diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index 0dc23800a6..6bb9e75389 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -374319dfd2b74cb5b69dac68b627fa9b +acb7f4030452bd2c0893bd6da14e6eed -- 2.52.0 >From dbd39db3455706dafc877933a24732ccfbef9789 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 12:51:01 +0200 Subject: [PATCH 09/11] swscale/ops_optimizer: simplify 32-bit overflow check No longer needed with AVRational64. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index b17444d373..b5c2fc3612 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -70,10 +70,7 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next) for (int i = 0; i < 4; i++) { if (!SWS_COMP_TEST(op->clear.mask, i)) continue; - uint32_t v = av_bswap32(op->clear.value[i].num); - if (v > INT_MAX) - return false; /* can't represent as AVRational anymore */ - tmp.value[i] = Q(v); + tmp.value[i] = Q(av_bswap32(op->clear.value[i].den)); } op->clear = tmp; return true; -- 2.52.0 >From be320d1411cb2868864203a5f89f794d40f5f938 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 13:14:51 +0200 Subject: [PATCH 10/11] tests/checkasm/sw_ops: eliminate unneeded overflow check Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- tests/checkasm/sw_ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/checkasm/sw_ops.c b/tests/checkasm/sw_ops.c index 086e044792..c1a12d676d 100644 --- a/tests/checkasm/sw_ops.c +++ b/tests/checkasm/sw_ops.c @@ -276,7 +276,7 @@ static void check_ops(const char *name, const unsigned ranges[NB_PLANES], oplist.comps_src.min[p] = (AVRational64) { 0, 1 }; break; case F32: - if (ranges[p] && ranges[p] <= INT_MAX) { + if (ranges[p]) { oplist.comps_src.max[p] = (AVRational64) { ranges[p], 1 }; oplist.comps_src.min[p] = (AVRational64) { 0, 1 }; } -- 2.52.0 >From e07bf91ea767182eb6dee5ca94bb5cffa85c1206 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 28 Apr 2026 13:21:57 +0200 Subject: [PATCH 11/11] tests/checkasm/sw_ops: also test 32-bit clears Now that these can be represented. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- tests/checkasm/sw_ops.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/checkasm/sw_ops.c b/tests/checkasm/sw_ops.c index c1a12d676d..949d37d80d 100644 --- a/tests/checkasm/sw_ops.c +++ b/tests/checkasm/sw_ops.c @@ -533,10 +533,9 @@ static void check_clear(void) const char *type = ff_sws_pixel_type_name(t); const int bits = ff_sws_pixel_type_size(t) * 8; - /* TODO: AVRational can't fit 32 bit constants */ - if (bits < 32) { - const AVRational64 chroma = (AVRational64) { 1 << (bits - 1), 1}; - const AVRational64 alpha = (AVRational64) { (1 << bits) - 1, 1}; + if (ff_sws_pixel_type_is_int(t)) { + const AVRational64 chroma = (AVRational64) { 1ULL << (bits - 1), 1}; + const AVRational64 alpha = (AVRational64) { (1ULL << bits) - 1, 1}; const AVRational64 zero = (AVRational64) { 0, 1}; const AVRational64 none = {0}; @@ -570,7 +569,7 @@ static void check_clear(void) .clear = clear, }); } - } else if (!ff_sws_pixel_type_is_int(t)) { + } else { /* Floating point YUV doesn't exist, only alpha needs to be cleared */ CHECK(FMT("clear_alpha_%s", type), 4, 4, t, t, { .op = SWS_OP_CLEAR, -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
