diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index ac87cb26fc..350f1b7182 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -112,15 +112,55 @@ With filters that support it, you can access parameters by their name. Available mpv-only filters are: ``format=fmt=:colormatrix=:...`` - Restricts the color space for the next filter without doing any conversion. - Use together with the scale filter for a real conversion. + Applies video parameter overrides, with optional conversion. By default, + this .. note:: - For a list of available formats, see ``format=fmt=help``. + For a list of available formats, use ``--vf=format=fmt=help``. ```` - Format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change). + Image format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change). + + This filter always performs conversion to the given format. + + ```` + Force conversion of color parameters (default: no). + + If this is disabled (the default), the only conversion that is possibly + performed is format conversion if ```` is set. All other parameters + (like ````) are forced without conversion. This mode is + typically useful when files have been incorrectly tagged. + + If this is enabled, libswscale or zimg is used if any of the parameters + mismatch. zimg is used of the input/output image formats are supported + by mpv's zimg wrapper, and if ``--sws-allow-zimg=yes`` is used. Both + libraries may not support all kinds of conversions. This typically + results in silent incorrect conversion. zimg has in many cases a better + chance of performing the conversion correctly. + + In both cases, the color parameters are set on the output stage of the + image format conversion (if ``fmt`` was set). The difference is that + with ``convert=no``, the color parameters are not passed on to the + converter. + + If input and output video parameters are the same, conversion is always + skipped. + + .. admonition:: Examples + + ``mpv test.mkv --vf=format:colormatrix=ycgco`` + Results in incorrect colors (if test.mkv was tagged correctly). + + ``mpv test.mkv --vf=format:colormatrix=ycgco:convert=yes --sws-allow-zimg`` + Results in true conversion to ``ycgco``, assuming the renderer + supports it (``--vo=gpu`` normally does). You can add ``--vo=xv`` + to force a VO which definitely does not support it, which should + show incorrect colors as confirmation. + + Using ``--sws-allow-zimg=no`` (or disabling zimg at build time) + will use libswscale, which cannot perform this conversion as + of this writing. ```` Controls the YUV to RGB color space conversion when playing video. There diff --git a/filters/f_autoconvert.c b/filters/f_autoconvert.c index 065092ac64..20c083de54 100644 --- a/filters/f_autoconvert.c +++ b/filters/f_autoconvert.c @@ -27,6 +27,8 @@ struct priv { int *imgfmts; int *subfmts; int num_imgfmts; + struct mp_image_params imgparams; + bool imgparams_set; // Enable special conversion for the final stage before the VO. bool vo_convert; @@ -73,6 +75,7 @@ void mp_autoconvert_clear(struct mp_autoconvert *c) struct priv *p = c->f->priv; p->num_imgfmts = 0; + p->imgparams_set = false; p->num_afmts = 0; p->num_srates = 0; p->chmaps = (struct mp_chmap_sel){0}; @@ -93,6 +96,23 @@ void mp_autoconvert_add_imgfmt(struct mp_autoconvert *c, int imgfmt, int subfmt) p->force_update = true; } +void mp_autoconvert_set_target_image_params(struct mp_autoconvert *c, + struct mp_image_params *par) +{ + struct priv *p = c->f->priv; + + if (p->imgparams_set && mp_image_params_equal(&p->imgparams, par) && + p->num_imgfmts == 1 && p->imgfmts[0] == par->imgfmt && + p->subfmts[0] == par->hw_subfmt) + return; + + p->imgparams = *par; + p->imgparams_set = true; + + p->num_imgfmts = 0; + mp_autoconvert_add_imgfmt(c, par->imgfmt, par->hw_subfmt); +} + void mp_autoconvert_add_all_sw_imgfmts(struct mp_autoconvert *c) { for (int n = IMGFMT_START; n < IMGFMT_END; n++) { @@ -185,8 +205,13 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log, bool samesubffmt = img->params.hw_subfmt == p->subfmts[n]; if (samefmt && !samesubffmt) different_subfmt = true; - if (samefmt && (samesubffmt || !p->subfmts[n])) + if (samefmt && (samesubffmt || !p->subfmts[n])) { + if (p->imgparams_set) { + if (!mp_image_params_equal(&p->imgparams, &img->params)) + break; + } return true; + } } struct mp_stream_info *info = mp_filter_find_stream_info(f); @@ -200,6 +225,8 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log, // 2: sw->hw upload struct mp_filter *filters[3] = {0}; bool need_sws = true; + bool force_sws_params = false; + struct mp_image_params imgpar = img->params; int *fmts = p->imgfmts; int num_fmts = p->num_imgfmts; @@ -259,9 +286,21 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log, if (hwd) { filters[0] = hwd->f; src_fmt = res_fmt; + // Downloading from hw will obviously change the parameters. We + // stupidly don't know the result parameters, but if it's + // sufficiently sane, it will only do the following. + imgpar.imgfmt = src_fmt; + imgpar.hw_subfmt = 0; + // Try to compensate for in-sane cases. + mp_image_params_guess_csp(&imgpar); } } + if (p->imgparams_set) { + force_sws_params |= !mp_image_params_equal(&imgpar, &p->imgparams); + need_sws |= force_sws_params; + } + if (need_sws) { // Create a new conversion filter. struct mp_sws_filter *sws = mp_sws_filter_create(conv); @@ -277,11 +316,13 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log, goto fail; } - if (out == src_fmt) { + if (out == src_fmt && !force_sws_params) { // Can happen if hwupload goes to same format. talloc_free(sws->f); } else { sws->out_format = out; + sws->out_params = p->imgparams; + sws->use_out_params = force_sws_params; mp_info(log, "Converting %s -> %s\n", mp_imgfmt_to_name(src_fmt), mp_imgfmt_to_name(sws->out_format)); filters[1] = sws->f; diff --git a/filters/f_autoconvert.h b/filters/f_autoconvert.h index e8c5a44134..ae08fd4e51 100644 --- a/filters/f_autoconvert.h +++ b/filters/f_autoconvert.h @@ -3,6 +3,7 @@ #include "filter.h" struct mp_image; +struct mp_image_params; // A filter which automatically creates and uses a conversion filter based on // the filter settings, or passes through data unchanged if no conversion is @@ -22,6 +23,14 @@ struct mp_autoconvert { // (to free this, free the filter itself, mp_autoconvert.f) struct mp_autoconvert *mp_autoconvert_create(struct mp_filter *parent); +// Require that output frames have the following params set. +// This implicitly clears the image format list, and calls +// mp_autoconvert_add_imgfmt() with the values in *p. +// Idempotent on subsequent calls (no reinit forced if parameters don't change). +// Mixing this with other format-altering calls has undefined effects. +void mp_autoconvert_set_target_image_params(struct mp_autoconvert *c, + struct mp_image_params *p); + // Add the imgfmt as allowed video image format, and error on non-video frames. // Each call adds to the list of allowed formats. Before the first call, all // formats are allowed (even non-video). diff --git a/filters/f_swscale.c b/filters/f_swscale.c index 07198b1377..1ff25ab909 100644 --- a/filters/f_swscale.c +++ b/filters/f_swscale.c @@ -88,9 +88,18 @@ static void process(struct mp_filter *f) } struct mp_image *src = frame.data; - int dstfmt = s->out_format ? s->out_format : src->imgfmt; - struct mp_image *dst = mp_image_pool_get(s->pool, dstfmt, src->w, src->h); + int dstfmt = s->out_format ? s->out_format : src->imgfmt; + int w = src->w; + int h = src->h; + + if (s->use_out_params) { + w = s->out_params.w; + h = s->out_params.h; + dstfmt = s->out_params.imgfmt; + } + + struct mp_image *dst = mp_image_pool_get(s->pool, dstfmt, w, h); if (!dst) goto error; @@ -102,6 +111,8 @@ static void process(struct mp_filter *f) { dst->params.color.levels = MP_CSP_LEVELS_TV; } + if (s->use_out_params) + dst->params = s->out_params; mp_image_params_guess_csp(&dst->params); bool ok = mp_sws_scale(s->sws, dst, src) >= 0; diff --git a/filters/f_swscale.h b/filters/f_swscale.h index fd5aa11f5e..6e26aef1ac 100644 --- a/filters/f_swscale.h +++ b/filters/f_swscale.h @@ -2,10 +2,15 @@ #include +#include "video/mp_image.h" + struct mp_sws_filter { struct mp_filter *f; // Desired output imgfmt. If 0, uses the input format. int out_format; + // If set, force all image params; ignores out_format. + bool use_out_params; + struct mp_image_params out_params; // private state struct mp_sws_context *sws; struct mp_image_pool *pool; diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c index 1700f002ab..942fa27a52 100644 --- a/video/filter/vf_format.c +++ b/video/filter/vf_format.c @@ -36,12 +36,11 @@ struct priv { struct vf_format_opts *opts; - struct mp_pin *in_pin; + struct mp_autoconvert *conv; }; struct vf_format_opts { int fmt; - int outfmt; int colormatrix; int colorlevels; int primaries; @@ -53,34 +52,11 @@ struct vf_format_opts { int rotate; int dw, dh; double dar; + int convert; }; -static void vf_format_process(struct mp_filter *f) +static void set_params(struct vf_format_opts *p, struct mp_image_params *out) { - struct priv *priv = f->priv; - struct vf_format_opts *p = priv->opts; - - if (!mp_pin_can_transfer_data(f->ppins[1], priv->in_pin)) - return; - - struct mp_frame frame = mp_pin_out_read(priv->in_pin); - - if (mp_frame_is_signaling(frame)) { - mp_pin_in_write(f->ppins[1], frame); - return; - } - if (frame.type != MP_FRAME_VIDEO) { - MP_ERR(f, "unsupported frame type\n"); - mp_frame_unref(&frame); - mp_filter_internal_mark_failed(f); - return; - } - - struct mp_image *img = frame.data; - struct mp_image_params *out = &img->params; - - if (p->outfmt) - out->imgfmt = p->outfmt; if (p->colormatrix) out->color.space = p->colormatrix; if (p->colorlevels) @@ -118,11 +94,53 @@ static void vf_format_process(struct mp_filter *f) if (p->dar > 0) dsize = av_d2q(p->dar, INT_MAX); mp_image_params_set_dsize(out, dsize.num, dsize.den); +} - // Make sure the user-overrides are consistent (no RGB csp for YUV, etc.). - mp_image_params_guess_csp(out); +static void vf_format_process(struct mp_filter *f) +{ + struct priv *priv = f->priv; - mp_pin_in_write(f->ppins[1], frame); + if (mp_pin_can_transfer_data(priv->conv->f->pins[0], f->ppins[0])) { + struct mp_frame frame = mp_pin_out_read(f->ppins[0]); + + if (priv->opts->convert && frame.type == MP_FRAME_VIDEO) { + struct mp_image *img = frame.data; + struct mp_image_params par = img->params; + int outfmt = priv->opts->fmt; + + // If we convert from RGB to YUV, default to limited range. + if (mp_imgfmt_get_forced_csp(img->imgfmt) == MP_CSP_RGB && + outfmt && mp_imgfmt_get_forced_csp(outfmt) == MP_CSP_AUTO) + { + par.color.levels = MP_CSP_LEVELS_TV; + } + + set_params(priv->opts, &par); + + if (par.imgfmt != outfmt) { + par.imgfmt = outfmt; + par.hw_subfmt = 0; + } + mp_image_params_guess_csp(&par); + + mp_autoconvert_set_target_image_params(priv->conv, &par); + } + + mp_pin_in_write(priv->conv->f->pins[0], frame); + } + + if (mp_pin_can_transfer_data(f->ppins[1], priv->conv->f->pins[1])) { + struct mp_frame frame = mp_pin_out_read(priv->conv->f->pins[1]); + + if (!priv->opts->convert && frame.type == MP_FRAME_VIDEO) { + struct mp_image *img = frame.data; + + set_params(priv->opts, &img->params); + mp_image_params_guess_csp(&img->params); + } + + mp_pin_in_write(f->ppins[1], frame); + } } static const struct mp_filter_info vf_format_filter = { @@ -145,17 +163,14 @@ static struct mp_filter *vf_format_create(struct mp_filter *parent, void *option mp_filter_add_pin(f, MP_PIN_IN, "in"); mp_filter_add_pin(f, MP_PIN_OUT, "out"); - struct mp_autoconvert *conv = mp_autoconvert_create(f); - if (!conv) { + priv->conv = mp_autoconvert_create(f); + if (!priv->conv) { talloc_free(f); return NULL; } if (priv->opts->fmt) - mp_autoconvert_add_imgfmt(conv, priv->opts->fmt, 0); - - priv->in_pin = conv->f->pins[1]; - mp_pin_connect(conv->f->pins[0], f->ppins[0]); + mp_autoconvert_add_imgfmt(priv->conv, priv->opts->fmt, 0); return f; } @@ -175,6 +190,7 @@ static const m_option_t vf_opts_fields[] = { OPT_INT("dw", dw, 0), OPT_INT("dh", dh, 0), OPT_DOUBLE("dar", dar, 0), + OPT_FLAG("convert", convert, 0), OPT_REMOVED("outputlevels", "use the --video-output-levels global option"), OPT_REMOVED("peak", "use sig-peak instead (changed value scale!)"), {0}