summaryrefslogtreecommitdiffstats
path: root/video/sws_utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'video/sws_utils.c')
-rw-r--r--video/sws_utils.c227
1 files changed, 163 insertions, 64 deletions
diff --git a/video/sws_utils.c b/video/sws_utils.c
index 55faadb1ee..a07bb55424 100644
--- a/video/sws_utils.c
+++ b/video/sws_utils.c
@@ -21,6 +21,10 @@
#include <libavcodec/avcodec.h>
#include <libavutil/bswap.h>
#include <libavutil/opt.h>
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
+#include <libavutil/pixdesc.h>
+#endif
+#include <libplacebo/utils/libav.h>
#include "config.h"
@@ -49,41 +53,41 @@ struct sws_opts {
int chr_hshift;
float chr_sharpen;
float lum_sharpen;
- int fast;
- int bitexact;
- int zimg;
+ bool fast;
+ bool bitexact;
+ bool zimg;
};
#define OPT_BASE_STRUCT struct sws_opts
const struct m_sub_options sws_conf = {
.opts = (const m_option_t[]) {
- OPT_CHOICE("scaler", scaler, 0,
- ({"fast-bilinear", SWS_FAST_BILINEAR},
- {"bilinear", SWS_BILINEAR},
- {"bicubic", SWS_BICUBIC},
- {"x", SWS_X},
- {"point", SWS_POINT},
- {"area", SWS_AREA},
- {"bicublin", SWS_BICUBLIN},
- {"gauss", SWS_GAUSS},
- {"sinc", SWS_SINC},
- {"lanczos", SWS_LANCZOS},
- {"spline", SWS_SPLINE})),
- OPT_FLOATRANGE("lgb", lum_gblur, 0, 0, 100.0),
- OPT_FLOATRANGE("cgb", chr_gblur, 0, 0, 100.0),
- OPT_INT("cvs", chr_vshift, 0),
- OPT_INT("chs", chr_hshift, 0),
- OPT_FLOATRANGE("ls", lum_sharpen, 0, -100.0, 100.0),
- OPT_FLOATRANGE("cs", chr_sharpen, 0, -100.0, 100.0),
- OPT_FLAG("fast", fast, 0),
- OPT_FLAG("bitexact", bitexact, 0),
- OPT_FLAG("allow-zimg", zimg, 0),
+ {"scaler", OPT_CHOICE(scaler,
+ {"fast-bilinear", SWS_FAST_BILINEAR},
+ {"bilinear", SWS_BILINEAR},
+ {"bicubic", SWS_BICUBIC},
+ {"x", SWS_X},
+ {"point", SWS_POINT},
+ {"area", SWS_AREA},
+ {"bicublin", SWS_BICUBLIN},
+ {"gauss", SWS_GAUSS},
+ {"sinc", SWS_SINC},
+ {"lanczos", SWS_LANCZOS},
+ {"spline", SWS_SPLINE})},
+ {"lgb", OPT_FLOAT(lum_gblur), M_RANGE(0, 100.0)},
+ {"cgb", OPT_FLOAT(chr_gblur), M_RANGE(0, 100.0)},
+ {"cvs", OPT_INT(chr_vshift)},
+ {"chs", OPT_INT(chr_hshift)},
+ {"ls", OPT_FLOAT(lum_sharpen), M_RANGE(-100.0, 100.0)},
+ {"cs", OPT_FLOAT(chr_sharpen), M_RANGE(-100.0, 100.0)},
+ {"fast", OPT_BOOL(fast)},
+ {"bitexact", OPT_BOOL(bitexact)},
+ {"allow-zimg", OPT_BOOL(zimg)},
{0}
},
.size = sizeof(struct sws_opts),
.defaults = &(const struct sws_opts){
.scaler = SWS_LANCZOS,
- .zimg = 1,
+ .zimg = true,
},
};
@@ -124,26 +128,40 @@ bool mp_sws_supported_format(int imgfmt)
&& sws_isSupportedOutput(av_format);
}
+#if HAVE_ZIMG
+static bool allow_zimg(struct mp_sws_context *ctx)
+{
+ return ctx->force_scaler == MP_SWS_ZIMG ||
+ (ctx->force_scaler == MP_SWS_AUTO && ctx->allow_zimg);
+}
+#endif
+
+static bool allow_sws(struct mp_sws_context *ctx)
+{
+ return ctx->force_scaler == MP_SWS_SWS || ctx->force_scaler == MP_SWS_AUTO;
+}
+
bool mp_sws_supports_formats(struct mp_sws_context *ctx,
int imgfmt_out, int imgfmt_in)
{
#if HAVE_ZIMG
- if (ctx->allow_zimg) {
+ if (allow_zimg(ctx)) {
if (mp_zimg_supports_in_format(imgfmt_in) &&
mp_zimg_supports_out_format(imgfmt_out))
return true;
}
#endif
- return sws_isSupportedInput(imgfmt2pixfmt(imgfmt_in)) &&
+ return allow_sws(ctx) &&
+ sws_isSupportedInput(imgfmt2pixfmt(imgfmt_in)) &&
sws_isSupportedOutput(imgfmt2pixfmt(imgfmt_out));
}
-static int mp_csp_to_sws_colorspace(enum mp_csp csp)
+static int pl_csp_to_sws_colorspace(enum pl_color_system csp)
{
// The SWS_CS_* macros are just convenience redefinitions of the
// AVCOL_SPC_* macros, inside swscale.h.
- return mp_csp_to_avcol_spc(csp);
+ return pl_system_to_av(csp);
}
static bool cache_valid(struct mp_sws_context *ctx)
@@ -154,10 +172,8 @@ static bool cache_valid(struct mp_sws_context *ctx)
return mp_image_params_equal(&ctx->src, &old->src) &&
mp_image_params_equal(&ctx->dst, &old->dst) &&
ctx->flags == old->flags &&
- ctx->brightness == old->brightness &&
- ctx->contrast == old->contrast &&
- ctx->saturation == old->saturation &&
ctx->allow_zimg == old->allow_zimg &&
+ ctx->force_scaler == old->force_scaler &&
(!ctx->opts_cache || !m_config_cache_update(ctx->opts_cache));
}
@@ -167,6 +183,8 @@ static void free_mp_sws(void *p)
sws_freeContext(ctx->sws);
sws_freeFilter(ctx->src_filter);
sws_freeFilter(ctx->dst_filter);
+ TA_FREEP(&ctx->aligned_src);
+ TA_FREEP(&ctx->aligned_dst);
}
// You're supposed to set your scaling parameters on the returned context.
@@ -177,8 +195,6 @@ struct mp_sws_context *mp_sws_alloc(void *talloc_ctx)
*ctx = (struct mp_sws_context) {
.log = mp_null_log,
.flags = SWS_BILINEAR,
- .contrast = 1 << 16, // 1.0 in 16.16 fixed point
- .saturation = 1 << 16,
.force_reload = true,
.params = {SWS_PARAM_DEFAULT, SWS_PARAM_DEFAULT},
.cached = talloc_zero(ctx, struct mp_sws_context),
@@ -198,6 +214,9 @@ struct mp_sws_context *mp_sws_alloc(void *talloc_ctx)
// if the user changes any options.
void mp_sws_enable_cmdline_opts(struct mp_sws_context *ctx, struct mpv_global *g)
{
+ // Should only ever be NULL for tests.
+ if (!g)
+ return;
if (ctx->opts_cache)
return;
@@ -214,12 +233,8 @@ void mp_sws_enable_cmdline_opts(struct mp_sws_context *ctx, struct mpv_global *g
// Optional, but possibly useful to avoid having to handle mp_sws_scale errors.
int mp_sws_reinit(struct mp_sws_context *ctx)
{
- struct mp_image_params *src = &ctx->src;
- struct mp_image_params *dst = &ctx->dst;
-
- // Neutralize unsupported or ignored parameters.
- src->p_w = dst->p_w = 0;
- src->p_h = dst->p_h = 0;
+ struct mp_image_params src = ctx->src;
+ struct mp_image_params dst = ctx->dst;
if (cache_valid(ctx))
return 0;
@@ -230,12 +245,16 @@ int mp_sws_reinit(struct mp_sws_context *ctx)
sws_freeContext(ctx->sws);
ctx->sws = NULL;
ctx->zimg_ok = false;
+ TA_FREEP(&ctx->aligned_src);
+ TA_FREEP(&ctx->aligned_dst);
#if HAVE_ZIMG
- if (ctx->allow_zimg) {
+ if (allow_zimg(ctx)) {
ctx->zimg->log = ctx->log;
- ctx->zimg->src = *src;
- ctx->zimg->dst = *dst;
+ ctx->zimg->src = src;
+ ctx->zimg->dst = dst;
+ if (ctx->zimg_opts)
+ ctx->zimg->opts = *ctx->zimg_opts;
if (mp_zimg_config(ctx->zimg)) {
ctx->zimg_ok = true;
MP_VERBOSE(ctx, "Using zimg.\n");
@@ -245,49 +264,64 @@ int mp_sws_reinit(struct mp_sws_context *ctx)
}
#endif
+ if (!allow_sws(ctx)) {
+ MP_ERR(ctx, "No scaler.\n");
+ return -1;
+ }
+
ctx->sws = sws_alloc_context();
if (!ctx->sws)
return -1;
- mp_image_params_guess_csp(src); // sanitize colorspace/colorlevels
- mp_image_params_guess_csp(dst);
+ mp_image_params_guess_csp(&src); // sanitize colorspace/colorlevels
+ mp_image_params_guess_csp(&dst);
- enum AVPixelFormat s_fmt = imgfmt2pixfmt(src->imgfmt);
+ enum AVPixelFormat s_fmt = imgfmt2pixfmt(src.imgfmt);
if (s_fmt == AV_PIX_FMT_NONE || sws_isSupportedInput(s_fmt) < 1) {
MP_ERR(ctx, "Input image format %s not supported by libswscale.\n",
- mp_imgfmt_to_name(src->imgfmt));
+ mp_imgfmt_to_name(src.imgfmt));
return -1;
}
- enum AVPixelFormat d_fmt = imgfmt2pixfmt(dst->imgfmt);
+ enum AVPixelFormat d_fmt = imgfmt2pixfmt(dst.imgfmt);
if (d_fmt == AV_PIX_FMT_NONE || sws_isSupportedOutput(d_fmt) < 1) {
MP_ERR(ctx, "Output image format %s not supported by libswscale.\n",
- mp_imgfmt_to_name(dst->imgfmt));
+ mp_imgfmt_to_name(dst.imgfmt));
return -1;
}
- int s_csp = mp_csp_to_sws_colorspace(src->color.space);
- int s_range = src->color.levels == MP_CSP_LEVELS_PC;
+ int s_csp = pl_csp_to_sws_colorspace(src.repr.sys);
+ int s_range = src.repr.levels == PL_COLOR_LEVELS_FULL;
- int d_csp = mp_csp_to_sws_colorspace(dst->color.space);
- int d_range = dst->color.levels == MP_CSP_LEVELS_PC;
+ int d_csp = pl_csp_to_sws_colorspace(src.repr.sys);
+ int d_range = dst.repr.levels == PL_COLOR_LEVELS_FULL;
av_opt_set_int(ctx->sws, "sws_flags", ctx->flags, 0);
- av_opt_set_int(ctx->sws, "srcw", src->w, 0);
- av_opt_set_int(ctx->sws, "srch", src->h, 0);
+ av_opt_set_int(ctx->sws, "srcw", src.w, 0);
+ av_opt_set_int(ctx->sws, "srch", src.h, 0);
av_opt_set_int(ctx->sws, "src_format", s_fmt, 0);
- av_opt_set_int(ctx->sws, "dstw", dst->w, 0);
- av_opt_set_int(ctx->sws, "dsth", dst->h, 0);
+ av_opt_set_int(ctx->sws, "dstw", dst.w, 0);
+ av_opt_set_int(ctx->sws, "dsth", dst.h, 0);
av_opt_set_int(ctx->sws, "dst_format", d_fmt, 0);
av_opt_set_double(ctx->sws, "param0", ctx->params[0], 0);
av_opt_set_double(ctx->sws, "param1", ctx->params[1], 0);
- int cr_src = mp_chroma_location_to_av(src->chroma_location);
- int cr_dst = mp_chroma_location_to_av(dst->chroma_location);
+ int cr_src = pl_chroma_to_av(src.chroma_location);
+ int cr_dst = pl_chroma_to_av(dst.chroma_location);
int cr_xpos, cr_ypos;
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
+ if (av_chroma_location_enum_to_pos(&cr_xpos, &cr_ypos, cr_src) >= 0) {
+ av_opt_set_int(ctx->sws, "src_h_chr_pos", cr_xpos, 0);
+ av_opt_set_int(ctx->sws, "src_v_chr_pos", cr_ypos, 0);
+ }
+ if (av_chroma_location_enum_to_pos(&cr_xpos, &cr_ypos, cr_dst) >= 0) {
+ av_opt_set_int(ctx->sws, "dst_h_chr_pos", cr_xpos, 0);
+ av_opt_set_int(ctx->sws, "dst_v_chr_pos", cr_ypos, 0);
+ }
+#else
if (avcodec_enum_to_chroma_pos(&cr_xpos, &cr_ypos, cr_src) >= 0) {
av_opt_set_int(ctx->sws, "src_h_chr_pos", cr_xpos, 0);
av_opt_set_int(ctx->sws, "src_v_chr_pos", cr_ypos, 0);
@@ -296,24 +330,64 @@ int mp_sws_reinit(struct mp_sws_context *ctx)
av_opt_set_int(ctx->sws, "dst_h_chr_pos", cr_xpos, 0);
av_opt_set_int(ctx->sws, "dst_v_chr_pos", cr_ypos, 0);
}
+#endif
// This can fail even with normal operation, e.g. if a conversion path
// simply does not support these settings.
int r =
sws_setColorspaceDetails(ctx->sws, sws_getCoefficients(s_csp), s_range,
sws_getCoefficients(d_csp), d_range,
- ctx->brightness, ctx->contrast, ctx->saturation);
+ 0, 1 << 16, 1 << 16);
ctx->supports_csp = r >= 0;
if (sws_init_context(ctx->sws, ctx->src_filter, ctx->dst_filter) < 0)
return -1;
+#if HAVE_ZIMG
success:
+#endif
+
ctx->force_reload = false;
*ctx->cached = *ctx;
return 1;
}
+static struct mp_image *check_alignment(struct mp_log *log,
+ struct mp_image **alloc,
+ struct mp_image *img)
+{
+ // It's completely unclear which alignment libswscale wants (for performance)
+ // or requires (for avoiding crashes and memory corruption).
+ // Is it av_cpu_max_align()? Is it the hardcoded AVFrame "default" of 32
+ // in get_video_buffer()? Is it whatever avcodec_align_dimensions2()
+ // determines? It's like you can't win if you try to prevent libswscale from
+ // corrupting memory...
+ // So use 32, a value that has been experimentally determined to be safe,
+ // and which in most cases is not larger than decoder output. It is smaller
+ // or equal to what most image allocators in mpv/ffmpeg use.
+ size_t align = 32;
+ assert(align <= MP_IMAGE_BYTE_ALIGN); // or mp_image_alloc will not cut it
+
+ bool is_aligned = true;
+ for (int p = 0; p < img->num_planes; p++) {
+ is_aligned &= MP_IS_ALIGNED((uintptr_t)img->planes[p], align);
+ is_aligned &= MP_IS_ALIGNED(labs(img->stride[p]), align);
+ }
+
+ if (is_aligned)
+ return img;
+
+ if (!*alloc) {
+ mp_verbose(log, "unaligned libswscale parameter; using slow copy.\n");
+ *alloc = mp_image_alloc(img->imgfmt, img->w, img->h);
+ if (!*alloc)
+ return NULL;
+ }
+
+ mp_image_copy_attributes(*alloc, img);
+ return *alloc;
+}
+
// Scale from src to dst - if src/dst have different parameters from previous
// calls, the context is reinitialized. Return error code. (It can fail if
// reinitialization was necessary, and swscale returned an error.)
@@ -334,8 +408,32 @@ int mp_sws_scale(struct mp_sws_context *ctx, struct mp_image *dst,
return mp_zimg_convert(ctx->zimg, dst, src) ? 0 : -1;
#endif
- sws_scale(ctx->sws, (const uint8_t *const *) src->planes, src->stride,
- 0, src->h, dst->planes, dst->stride);
+ if (src->params.repr.sys == PL_COLOR_SYSTEM_XYZ && dst->params.repr.sys != PL_COLOR_SYSTEM_XYZ) {
+ // swsscale has hardcoded gamma 2.2 internally and 2.6 for XYZ
+ dst->params.color.transfer = PL_COLOR_TRC_GAMMA22;
+ // and sRGB primaries...
+ dst->params.color.primaries = PL_COLOR_PRIM_BT_709;
+ // it doesn't adjust white point though, but it is not worth to support
+ // this case. It would require custom prim with equal energy white point
+ // and sRGB primaries.
+ }
+
+ struct mp_image *a_src = check_alignment(ctx->log, &ctx->aligned_src, src);
+ struct mp_image *a_dst = check_alignment(ctx->log, &ctx->aligned_dst, dst);
+ if (!a_src || !a_dst) {
+ MP_ERR(ctx, "image allocation failed.\n");
+ return -1;
+ }
+
+ if (a_src != src)
+ mp_image_copy(a_src, src);
+
+ sws_scale(ctx->sws, (const uint8_t *const *) a_src->planes, a_src->stride,
+ 0, a_src->h, a_dst->planes, a_dst->stride);
+
+ if (a_dst != dst)
+ mp_image_copy(dst, a_dst);
+
return 0;
}
@@ -378,14 +476,15 @@ static const int endian_swaps[][2] = {
// might reduce the effective bit depth in some cases.
struct mp_image *mp_img_swap_to_native(struct mp_image *img)
{
+ int avfmt = imgfmt2pixfmt(img->imgfmt);
int to = AV_PIX_FMT_NONE;
for (int n = 0; endian_swaps[n][0] != AV_PIX_FMT_NONE; n++) {
- if (endian_swaps[n][0] == img->fmt.avformat)
+ if (endian_swaps[n][0] == avfmt)
to = endian_swaps[n][1];
}
if (to == AV_PIX_FMT_NONE || !mp_image_make_writeable(img))
return img;
- int elems = img->fmt.bytes[0] / 2 * img->w;
+ int elems = img->fmt.bpp[0] / 8 / 2 * img->w;
for (int y = 0; y < img->h; y++) {
uint16_t *p = (uint16_t *)(img->planes[0] + y * img->stride[0]);
for (int i = 0; i < elems; i++)