filters: extend vf_format so that it can convert color parameters

Form some reason (and because of my fault), vf_format converts image
formats, but nothing else. For example, setting the "colormatrix"
sub-parameter would not convert it to the new value, but instead
overwrite the metadata (basically "reinterpreting" the image data
without changing it).

Make the historical mistake worse, and go all the way and extend it such
that it can perform a conversion. For compatibility reasons, this needs
to be requested explicitly. (Maybe this would deserve a separate filter
to begin with, but things are messed up anyway. Feel free to suggest an
elegant and simple solution.)

This demonstrates how zimg can properly perform some conversions which
swscale cannot (see examples added to vf.rst).

Stupidly this requires 2 code paths, one for conversion, and one for
overriding the parameters.

Due to the filter bullshit (what was I thinking), this requires quite
some acrobatics that would not be necessary without these abstractions.
On the other hand, it'd definitely be more of a mess without it. Oh
whatever.
This commit is contained in:
wm4 2019-10-21 01:29:48 +02:00
parent 8a4e9d5c18
commit 5dba244c22
6 changed files with 166 additions and 44 deletions

View File

@ -112,15 +112,55 @@ With filters that support it, you can access parameters by their name.
Available mpv-only filters are:
``format=fmt=<value>:colormatrix=<value>:...``
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``.
``<fmt>``
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.
``<convert=yes|no>``
Force conversion of color parameters (default: no).
If this is disabled (the default), the only conversion that is possibly
performed is format conversion if ``<fmt>`` is set. All other parameters
(like ``<colormatrix>``) 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.
``<colormatrix>``
Controls the YUV to RGB color space conversion when playing video. There

View File

@ -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;

View File

@ -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).

View File

@ -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;

View File

@ -2,10 +2,15 @@
#include <stdbool.h>
#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;

View File

@ -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}