Coverage Report

Created: 2026-01-26 07:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/video/sws_utils.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <assert.h>
19
20
#include <libswscale/swscale.h>
21
#include <libavcodec/avcodec.h>
22
#include <libavutil/bswap.h>
23
#include <libavutil/opt.h>
24
#include <libavutil/pixdesc.h>
25
#include <libplacebo/utils/libav.h>
26
27
#include "config.h"
28
29
#include "sws_utils.h"
30
31
#include "common/common.h"
32
#include "options/m_config.h"
33
#include "options/m_option.h"
34
#include "video/mp_image.h"
35
#include "video/img_format.h"
36
#include "fmt-conversion.h"
37
#include "csputils.h"
38
#include "common/msg.h"
39
#include "osdep/endian.h"
40
41
#if HAVE_ZIMG
42
#include "zimg.h"
43
#endif
44
45
//global sws_flags from the command line
46
struct sws_opts {
47
    int scaler;
48
    float lum_gblur;
49
    float chr_gblur;
50
    int chr_vshift;
51
    int chr_hshift;
52
    float chr_sharpen;
53
    float lum_sharpen;
54
    bool fast;
55
    bool bitexact;
56
    bool zimg;
57
};
58
59
#define OPT_BASE_STRUCT struct sws_opts
60
const struct m_sub_options sws_conf = {
61
    .opts = (const m_option_t[]) {
62
        {"scaler", OPT_CHOICE(scaler,
63
            {"fast-bilinear",   SWS_FAST_BILINEAR},
64
            {"bilinear",        SWS_BILINEAR},
65
            {"bicubic",         SWS_BICUBIC},
66
            {"x",               SWS_X},
67
            {"point",           SWS_POINT},
68
            {"area",            SWS_AREA},
69
            {"bicublin",        SWS_BICUBLIN},
70
            {"gauss",           SWS_GAUSS},
71
            {"sinc",            SWS_SINC},
72
            {"lanczos",         SWS_LANCZOS},
73
            {"spline",          SWS_SPLINE})},
74
        {"lgb", OPT_FLOAT(lum_gblur), M_RANGE(0, 100.0)},
75
        {"cgb", OPT_FLOAT(chr_gblur), M_RANGE(0, 100.0)},
76
        {"cvs", OPT_INT(chr_vshift), M_RANGE(-100, 100)},
77
        {"chs", OPT_INT(chr_hshift), M_RANGE(-100, 100)},
78
        {"ls", OPT_FLOAT(lum_sharpen), M_RANGE(-100.0, 100.0)},
79
        {"cs", OPT_FLOAT(chr_sharpen), M_RANGE(-100.0, 100.0)},
80
        {"fast", OPT_BOOL(fast)},
81
        {"bitexact", OPT_BOOL(bitexact)},
82
        {"allow-zimg", OPT_BOOL(zimg)},
83
        {0}
84
    },
85
    .size = sizeof(struct sws_opts),
86
    .defaults = &(const struct sws_opts){
87
        .scaler = SWS_LANCZOS,
88
        .zimg = true,
89
    },
90
};
91
92
// Highest quality, but also slowest.
93
static const int mp_sws_hq_flags = SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP |
94
                                   SWS_ACCURATE_RND;
95
96
// Fast, lossy.
97
const int mp_sws_fast_flags = SWS_BILINEAR;
98
99
// Set ctx parameters to global command line flags.
100
static void mp_sws_update_from_cmdline(struct mp_sws_context *ctx)
101
14.3k
{
102
14.3k
    m_config_cache_update(ctx->opts_cache);
103
14.3k
    struct sws_opts *opts = ctx->opts_cache->opts;
104
105
14.3k
    sws_freeFilter(ctx->src_filter);
106
14.3k
    ctx->src_filter = sws_getDefaultFilter(opts->lum_gblur, opts->chr_gblur,
107
14.3k
                                           opts->lum_sharpen, opts->chr_sharpen,
108
14.3k
                                           opts->chr_hshift, opts->chr_vshift, 0);
109
14.3k
    ctx->force_reload = true;
110
111
14.3k
    ctx->flags = SWS_PRINT_INFO;
112
14.3k
    ctx->flags |= opts->scaler;
113
14.3k
    if (!opts->fast)
114
14.3k
        ctx->flags |= mp_sws_hq_flags;
115
14.3k
    if (opts->bitexact)
116
0
        ctx->flags |= SWS_BITEXACT;
117
118
14.3k
    ctx->allow_zimg = opts->zimg;
119
14.3k
}
120
121
bool mp_sws_supported_format(int imgfmt)
122
15.0k
{
123
15.0k
    enum AVPixelFormat av_format = imgfmt2pixfmt(imgfmt);
124
125
15.0k
    return av_format != AV_PIX_FMT_NONE && sws_isSupportedInput(av_format)
126
6.21k
        && sws_isSupportedOutput(av_format);
127
15.0k
}
128
129
#if HAVE_ZIMG
130
static bool allow_zimg(struct mp_sws_context *ctx)
131
{
132
    return ctx->force_scaler == MP_SWS_ZIMG ||
133
           (ctx->force_scaler == MP_SWS_AUTO && ctx->allow_zimg);
134
}
135
#endif
136
137
static bool allow_sws(struct mp_sws_context *ctx)
138
13.8k
{
139
13.8k
    return ctx->force_scaler == MP_SWS_SWS || ctx->force_scaler == MP_SWS_AUTO;
140
13.8k
}
141
142
bool mp_sws_supports_formats(struct mp_sws_context *ctx,
143
                             int imgfmt_out, int imgfmt_in)
144
6.99k
{
145
#if HAVE_ZIMG
146
    if (allow_zimg(ctx)) {
147
        if (mp_zimg_supports_in_format(imgfmt_in) &&
148
            mp_zimg_supports_out_format(imgfmt_out))
149
            return true;
150
    }
151
#endif
152
153
6.99k
    return allow_sws(ctx) &&
154
6.99k
           sws_isSupportedInput(imgfmt2pixfmt(imgfmt_in)) &&
155
6.98k
           sws_isSupportedOutput(imgfmt2pixfmt(imgfmt_out));
156
6.99k
}
157
158
static int pl_csp_to_sws_colorspace(enum pl_color_system csp)
159
13.7k
{
160
    // The SWS_CS_* macros are just convenience redefinitions of the
161
    // AVCOL_SPC_* macros, inside swscale.h.
162
13.7k
    return pl_system_to_av(csp);
163
13.7k
}
164
165
static bool cache_valid(struct mp_sws_context *ctx)
166
19.2k
{
167
19.2k
    struct mp_sws_context *old = ctx->cached;
168
19.2k
    if (ctx->force_reload)
169
6.89k
        return false;
170
12.3k
    return mp_image_params_equal(&ctx->src, &old->src) &&
171
12.3k
           mp_image_params_equal(&ctx->dst, &old->dst) &&
172
12.3k
           ctx->flags == old->flags &&
173
12.3k
           ctx->allow_zimg == old->allow_zimg &&
174
12.3k
           ctx->force_scaler == old->force_scaler &&
175
12.3k
           (!ctx->opts_cache || !m_config_cache_update(ctx->opts_cache));
176
19.2k
}
177
178
static void free_mp_sws(void *p)
179
7.41k
{
180
7.41k
    struct mp_sws_context *ctx = p;
181
7.41k
    sws_freeContext(ctx->sws);
182
7.41k
    sws_freeFilter(ctx->src_filter);
183
7.41k
    sws_freeFilter(ctx->dst_filter);
184
7.41k
    TA_FREEP(&ctx->aligned_src);
185
7.41k
    TA_FREEP(&ctx->aligned_dst);
186
7.41k
}
187
188
// You're supposed to set your scaling parameters on the returned context.
189
// Free the context with talloc_free().
190
struct mp_sws_context *mp_sws_alloc(void *talloc_ctx)
191
7.41k
{
192
7.41k
    struct mp_sws_context *ctx = talloc_ptrtype(talloc_ctx, ctx);
193
7.41k
    *ctx = (struct mp_sws_context) {
194
7.41k
        .log = mp_null_log,
195
7.41k
        .flags = SWS_BILINEAR,
196
7.41k
        .force_reload = true,
197
7.41k
        .params = {SWS_PARAM_DEFAULT, SWS_PARAM_DEFAULT},
198
7.41k
        .cached = talloc_zero(ctx, struct mp_sws_context),
199
7.41k
    };
200
7.41k
    talloc_set_destructor(ctx, free_mp_sws);
201
202
#if HAVE_ZIMG
203
    ctx->zimg = mp_zimg_alloc();
204
    talloc_steal(ctx, ctx->zimg);
205
#endif
206
207
7.41k
    return ctx;
208
7.41k
}
209
210
// Enable auto-update of parameters from command line. Don't try to set custom
211
// options (other than possibly .src/.dst), because they might be overwritten
212
// if the user changes any options.
213
void mp_sws_enable_cmdline_opts(struct mp_sws_context *ctx, struct mpv_global *g)
214
7.41k
{
215
    // Should only ever be NULL for tests.
216
7.41k
    if (!g)
217
0
        return;
218
7.41k
    if (ctx->opts_cache)
219
0
        return;
220
221
7.41k
    ctx->opts_cache = m_config_cache_alloc(ctx, g, &sws_conf);
222
7.41k
    ctx->force_reload = true;
223
7.41k
    mp_sws_update_from_cmdline(ctx);
224
225
#if HAVE_ZIMG
226
    mp_zimg_enable_cmdline_opts(ctx->zimg, g);
227
#endif
228
7.41k
}
229
230
// Reinitialize (if needed) - return error code.
231
// Optional, but possibly useful to avoid having to handle mp_sws_scale errors.
232
int mp_sws_reinit(struct mp_sws_context *ctx)
233
19.2k
{
234
19.2k
    struct mp_image_params src = ctx->src;
235
19.2k
    struct mp_image_params dst = ctx->dst;
236
237
19.2k
    if (cache_valid(ctx))
238
12.3k
        return 0;
239
240
6.89k
    if (ctx->opts_cache)
241
6.89k
        mp_sws_update_from_cmdline(ctx);
242
243
6.89k
    sws_freeContext(ctx->sws);
244
6.89k
    ctx->sws = NULL;
245
6.89k
    ctx->zimg_ok = false;
246
6.89k
    TA_FREEP(&ctx->aligned_src);
247
6.89k
    TA_FREEP(&ctx->aligned_dst);
248
249
#if HAVE_ZIMG
250
    if (allow_zimg(ctx)) {
251
        ctx->zimg->log = ctx->log;
252
        ctx->zimg->src = src;
253
        ctx->zimg->dst = dst;
254
        if (ctx->zimg_opts)
255
            ctx->zimg->opts = *ctx->zimg_opts;
256
        if (mp_zimg_config(ctx->zimg)) {
257
            ctx->zimg_ok = true;
258
            MP_VERBOSE(ctx, "Using zimg.\n");
259
            goto success;
260
        }
261
        MP_WARN(ctx, "Not using zimg, falling back to swscale.\n");
262
    }
263
#endif
264
265
6.89k
    if (!allow_sws(ctx)) {
266
0
        MP_ERR(ctx, "No scaler.\n");
267
0
        return -1;
268
0
    }
269
270
6.89k
    ctx->sws = sws_alloc_context();
271
6.89k
    if (!ctx->sws)
272
0
        return -1;
273
274
6.89k
    mp_image_params_guess_csp(&src); // sanitize colorspace/colorlevels
275
6.89k
    mp_image_params_guess_csp(&dst);
276
277
6.89k
    enum AVPixelFormat s_fmt = imgfmt2pixfmt(src.imgfmt);
278
6.89k
    if (s_fmt == AV_PIX_FMT_NONE || sws_isSupportedInput(s_fmt) < 1) {
279
0
        MP_ERR(ctx, "Input image format %s not supported by libswscale.\n",
280
0
               mp_imgfmt_to_name(src.imgfmt));
281
0
        return -1;
282
0
    }
283
284
6.89k
    enum AVPixelFormat d_fmt = imgfmt2pixfmt(dst.imgfmt);
285
6.89k
    if (d_fmt == AV_PIX_FMT_NONE || sws_isSupportedOutput(d_fmt) < 1) {
286
0
        MP_ERR(ctx, "Output image format %s not supported by libswscale.\n",
287
0
               mp_imgfmt_to_name(dst.imgfmt));
288
0
        return -1;
289
0
    }
290
291
6.89k
    int s_csp = pl_csp_to_sws_colorspace(src.repr.sys);
292
6.89k
    int s_range = src.repr.levels == PL_COLOR_LEVELS_FULL;
293
294
6.89k
    int d_csp = pl_csp_to_sws_colorspace(src.repr.sys);
295
6.89k
    int d_range = dst.repr.levels == PL_COLOR_LEVELS_FULL;
296
297
6.89k
    av_opt_set_int(ctx->sws, "sws_flags", ctx->flags, 0);
298
299
6.89k
    av_opt_set_int(ctx->sws, "srcw", src.w, 0);
300
6.89k
    av_opt_set_int(ctx->sws, "srch", src.h, 0);
301
6.89k
    av_opt_set_int(ctx->sws, "src_format", s_fmt, 0);
302
303
6.89k
    av_opt_set_int(ctx->sws, "dstw", dst.w, 0);
304
6.89k
    av_opt_set_int(ctx->sws, "dsth", dst.h, 0);
305
6.89k
    av_opt_set_int(ctx->sws, "dst_format", d_fmt, 0);
306
307
6.89k
    av_opt_set_double(ctx->sws, "param0", ctx->params[0], 0);
308
6.89k
    av_opt_set_double(ctx->sws, "param1", ctx->params[1], 0);
309
310
6.89k
    int cr_src = pl_chroma_to_av(src.chroma_location);
311
6.89k
    int cr_dst = pl_chroma_to_av(dst.chroma_location);
312
6.89k
    int cr_xpos, cr_ypos;
313
6.89k
    if (av_chroma_location_enum_to_pos(&cr_xpos, &cr_ypos, cr_src) >= 0) {
314
6.89k
        av_opt_set_int(ctx->sws, "src_h_chr_pos", cr_xpos, 0);
315
6.89k
        av_opt_set_int(ctx->sws, "src_v_chr_pos", cr_ypos, 0);
316
6.89k
    }
317
6.89k
    if (av_chroma_location_enum_to_pos(&cr_xpos, &cr_ypos, cr_dst) >= 0) {
318
6.89k
        av_opt_set_int(ctx->sws, "dst_h_chr_pos", cr_xpos, 0);
319
6.89k
        av_opt_set_int(ctx->sws, "dst_v_chr_pos", cr_ypos, 0);
320
6.89k
    }
321
322
    // This can fail even with normal operation, e.g. if a conversion path
323
    // simply does not support these settings.
324
6.89k
    int r =
325
6.89k
        sws_setColorspaceDetails(ctx->sws, sws_getCoefficients(s_csp), s_range,
326
6.89k
                                 sws_getCoefficients(d_csp), d_range,
327
6.89k
                                 0, 1 << 16, 1 << 16);
328
6.89k
    ctx->supports_csp = r >= 0;
329
330
6.89k
    if (sws_init_context(ctx->sws, ctx->src_filter, ctx->dst_filter) < 0)
331
3
        return -1;
332
333
#if HAVE_ZIMG
334
success:
335
#endif
336
337
6.89k
    ctx->force_reload = false;
338
6.89k
    *ctx->cached = *ctx;
339
6.89k
    return 1;
340
6.89k
}
341
342
static struct mp_image *check_alignment(struct mp_log *log,
343
                                        struct mp_image **alloc,
344
                                        struct mp_image *img)
345
37.9k
{
346
    // It's completely unclear which alignment libswscale wants (for performance)
347
    // or requires (for avoiding crashes and memory corruption).
348
    // Is it av_cpu_max_align()? Is it the hardcoded AVFrame "default" of 32
349
    // in get_video_buffer()? Is it whatever avcodec_align_dimensions2()
350
    // determines? It's like you can't win if you try to prevent libswscale from
351
    // corrupting memory...
352
    // So use 32, a value that has been experimentally determined to be safe,
353
    // and which in most cases is not larger than decoder output. It is smaller
354
    // or equal to what most image allocators in mpv/ffmpeg use.
355
37.9k
    size_t align = 32;
356
37.9k
    mp_assert(align <= MP_IMAGE_BYTE_ALIGN); // or mp_image_alloc will not cut it
357
358
37.9k
    bool is_aligned = true;
359
123k
    for (int p = 0; p < img->num_planes; p++) {
360
85.1k
        is_aligned &= MP_IS_ALIGNED((uintptr_t)img->planes[p], align);
361
85.1k
        is_aligned &= MP_IS_ALIGNED(labs(img->stride[p]), align);
362
85.1k
    }
363
364
37.9k
    if (is_aligned)
365
33.8k
        return img;
366
367
4.09k
    if (!*alloc) {
368
1.41k
        mp_verbose(log, "unaligned libswscale parameter; using slow copy.\n");
369
1.41k
        *alloc = mp_image_alloc(img->imgfmt, img->w, img->h);
370
1.41k
        if (!*alloc)
371
0
            return NULL;
372
1.41k
    }
373
374
4.09k
    mp_image_copy_attributes(*alloc, img);
375
4.09k
    return *alloc;
376
4.09k
}
377
378
// Scale from src to dst - if src/dst have different parameters from previous
379
// calls, the context is reinitialized. Return error code. (It can fail if
380
// reinitialization was necessary, and swscale returned an error.)
381
int mp_sws_scale(struct mp_sws_context *ctx, struct mp_image *dst,
382
                 struct mp_image *src)
383
18.9k
{
384
18.9k
    ctx->src = src->params;
385
18.9k
    ctx->dst = dst->params;
386
387
18.9k
    int r = mp_sws_reinit(ctx);
388
18.9k
    if (r < 0) {
389
3
        MP_ERR(ctx, "libswscale initialization failed.\n");
390
3
        return r;
391
3
    }
392
393
#if HAVE_ZIMG
394
    if (ctx->zimg_ok)
395
        return mp_zimg_convert(ctx->zimg, dst, src) ? 0 : -1;
396
#endif
397
398
18.9k
    if (src->params.repr.sys == PL_COLOR_SYSTEM_XYZ && dst->params.repr.sys != PL_COLOR_SYSTEM_XYZ) {
399
        // swsscale has hardcoded gamma 2.2 internally and 2.6 for XYZ
400
445
        dst->params.color.transfer = PL_COLOR_TRC_GAMMA22;
401
        // and sRGB primaries...
402
445
        dst->params.color.primaries = PL_COLOR_PRIM_BT_709;
403
        // it doesn't adjust white point though, but it is not worth to support
404
        // this case. It would require custom prim with equal energy white point
405
        // and sRGB primaries.
406
445
    }
407
408
18.9k
    struct mp_image *a_src = check_alignment(ctx->log, &ctx->aligned_src, src);
409
18.9k
    struct mp_image *a_dst = check_alignment(ctx->log, &ctx->aligned_dst, dst);
410
18.9k
    if (!a_src || !a_dst) {
411
0
        MP_ERR(ctx, "image allocation failed.\n");
412
0
        return -1;
413
0
    }
414
415
18.9k
    if (a_src != src)
416
4.09k
        mp_image_copy(a_src, src);
417
418
18.9k
    sws_scale(ctx->sws, (const uint8_t *const *) a_src->planes, a_src->stride,
419
18.9k
              0, a_src->h, a_dst->planes, a_dst->stride);
420
421
18.9k
    if (a_dst != dst)
422
0
        mp_image_copy(dst, a_dst);
423
424
18.9k
    return 0;
425
18.9k
}
426
427
int mp_image_sw_blur_scale(struct mp_image *dst, struct mp_image *src,
428
                           float gblur)
429
0
{
430
0
    struct mp_sws_context *ctx = mp_sws_alloc(NULL);
431
0
    ctx->flags = SWS_LANCZOS | mp_sws_hq_flags;
432
0
    ctx->src_filter = sws_getDefaultFilter(gblur, gblur, 0, 0, 0, 0, 0);
433
0
    ctx->force_reload = true;
434
0
    int res = mp_sws_scale(ctx, dst, src);
435
0
    talloc_free(ctx);
436
0
    return res;
437
0
}
438
439
static const int endian_swaps[][2] = {
440
#if BYTE_ORDER == LITTLE_ENDIAN
441
#if defined(AV_PIX_FMT_YA16) && defined(AV_PIX_FMT_RGBA64)
442
    {AV_PIX_FMT_YA16BE,     AV_PIX_FMT_YA16LE},
443
    {AV_PIX_FMT_RGBA64BE,   AV_PIX_FMT_RGBA64LE},
444
    {AV_PIX_FMT_GRAY16BE,   AV_PIX_FMT_GRAY16LE},
445
    {AV_PIX_FMT_RGB48BE,    AV_PIX_FMT_RGB48LE},
446
#endif
447
#endif
448
    {AV_PIX_FMT_NONE,       AV_PIX_FMT_NONE}
449
};
450
451
// Swap _some_ non-native endian formats to native. We do this specifically
452
// for pixel formats used by PNG, to avoid going through libswscale, which
453
// might reduce the effective bit depth in some cases.
454
struct mp_image *mp_img_swap_to_native(struct mp_image *img)
455
381k
{
456
381k
    int avfmt = imgfmt2pixfmt(img->imgfmt);
457
381k
    int to = AV_PIX_FMT_NONE;
458
1.90M
    for (int n = 0; endian_swaps[n][0] != AV_PIX_FMT_NONE; n++) {
459
1.52M
        if (endian_swaps[n][0] == avfmt)
460
4
            to = endian_swaps[n][1];
461
1.52M
    }
462
381k
    if (to == AV_PIX_FMT_NONE || !mp_image_make_writeable(img))
463
381k
        return img;
464
4
    int elems = img->fmt.bpp[0] / 8 / 2 * img->w;
465
100
    for (int y = 0; y < img->h; y++) {
466
96
        uint16_t *p = (uint16_t *)(img->planes[0] + y * img->stride[0]);
467
3.82k
        for (int i = 0; i < elems; i++)
468
3.72k
            p[i] = av_be2ne16(p[i]);
469
96
    }
470
4
    mp_image_setfmt(img, pixfmt2imgfmt(to));
471
4
    return img;
472
381k
}
473
474
// vim: ts=4 sw=4 et tw=80