[FFmpeg-devel] [PATCH] lavfi: add geq filter.

Stefano Sabatini stefasab at gmail.com
Thu Nov 1 20:20:13 CET 2012


On date Wednesday 2012-10-31 20:40:15 +0100, Clément Bœsch encoded:
> TODO: bump minor
> ---
> Another portage from MPlayer filters. It seems to work, but I still have some
> issues with ffplay when the filter is complex/heavy. Try for instance this one
> (stolen from a random guy on #mplayer):
> 
>     ffplay -f lavfi -i 'testsrc,geq=p(lt(X\,W/2)*X*(1+sin(N*PI/100))+not(lt(X\,W/2))*(W-X)*(1+sin(N*PI/100))\,lt(Y\,H/2)*Y*(1+sin(N*PI/100))+not(lt(Y\,H/2))*(H-Y)*(1+sin(N*PI/100)))'
> 
> mplayer seems to deal relatively well with slow filters contrary to ffplay, but
> I have no idea how to avoid the current frame drop (playing with the options
> didn't help).

-noframedrop may help

> 
> Except that, the filter seems to work fine.
> 
> BTW, I took the complex example from the git log (9e5e086b), but feel free
> to propose more fancy/useful equations for the documentation.
> 
> About FATE, given the amount of floating operations, I'm not really confident
> about adding such test.
> 
> Last thing: I'll drop mp=geq soon after this gets applied.
> ---
>  Changelog                |   1 +
>  LICENSE                  |   1 +
>  configure                |   1 +
>  doc/filters.texi         |  53 ++++++++++++
>  libavfilter/Makefile     |   1 +
>  libavfilter/allfilters.c |   1 +
>  libavfilter/vf_geq.c     | 213 +++++++++++++++++++++++++++++++++++++++++++++++
>  7 files changed, 271 insertions(+)
>  create mode 100644 libavfilter/vf_geq.c
> 
> diff --git a/Changelog b/Changelog
> index 8f6c90b..ccc2fdc 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -19,6 +19,7 @@ version <next>:
>  - ffescape tool
>  - metadata (info chunk) support in CAF muxer
>  - field filter ported from libmpcodecs
> +- geq filter ported from libmpcodecs
>  
>  
>  version 1.0:
> diff --git a/LICENSE b/LICENSE
> index f38fc16..a1204f4 100644
> --- a/LICENSE
> +++ b/LICENSE
> @@ -30,6 +30,7 @@ Specifically, the GPL parts of FFmpeg are
>      - vf_cropdetect.c
>      - vf_decimate.c
>      - vf_delogo.c
> +    - vf_geq.c
>      - vf_hqdn3d.c
>      - vf_hue.c
>      - vf_mp.c
> diff --git a/configure b/configure
> index dc1aa79..2c16f82 100755
> --- a/configure
> +++ b/configure
> @@ -1939,6 +1939,7 @@ frei0r_filter_deps="frei0r dlopen"
>  frei0r_filter_extralibs='$ldl'
>  frei0r_src_filter_deps="frei0r dlopen"
>  frei0r_src_filter_extralibs='$ldl'
> +geq_filter_deps="gpl"
>  hqdn3d_filter_deps="gpl"
>  hue_filter_deps="gpl"
>  movie_filter_deps="avcodec avformat"
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 786b2db..1cba190 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -2310,6 +2310,59 @@ frei0r=perspective:0.2/0.2:0.8/0.2
>  For more information see:
>  @url{http://frei0r.dyne.org}
>  
> + at section geq
> +
> +The filter takes one, two or three equations as parameter, separated by ':'.

> +The first equation is mandatory and applies to the luma plane. The two
> +following are respectively for chroma blue and chroma red planes. If one
> +equation is not specified, it is using the same as the previous one.

named options?

> +
> +The expressions can use the following variables:

... and functions.

> +
> + at table @option
> + at item X, Y
> +The coordinates of the current sample

nit: don't capitalize if it is not a complete sentence

> +
> + at item W, H
> +The width and height of the image
> +
> + at item SW, SH
> +Width and height scale depending on the currently filtered plane, e.g. 1,1 and
> +0.5,0.5 for YUV 4:2:0.

"scale" is not defined, I have no idea what they mean from this
description.

> +
> + at item p(x, y)

> +Return the value of the pixel at location (@var{x};@var{y}) of the current
> +plane.

nit: "(@var{x}, @var{y})" seems more mathematician-friendly, same below.

Also, what happens if the (x,y) value is not contained in the image?

> +
> + at item lum(x, y)
> +Return the value of the pixel at location (@var{x};@var{y}) of the luminance
> +plane.
> +
> + at item cb(x, y)
> +Return the value of the pixel at location (@var{x};@var{y}) of the
> +blue-difference chroma plane.
> +
> + at item cr(x, y)
> +Return the value of the pixel at location (@var{x};@var{y}) of the
> +red-difference chroma plane.

Do you think would be possible to extend the filter to take the RGB
values, and in general support the RGB colorspace? Alternatively we
may have geqyuv and geqrgb. Also supporting alpha would be cool.

> + at end table
> +
> +Some examples follow:
> +
> + at itemize
> + at item
> +Flip the image horizontally:
> + at example
> +geq=p(W-X\,Y)
> + at end example
> +
> + at item
> +Fancy enigmatic moving light:
> + at example
> +color=s=256x256,geq=random(1)/hypot(X-cos(N*0.07)*W/2-W/2\,Y-sin(N*0.09)*H/2-H/2)^2*1000000*sin(N*0.02):128:128
> + at end example
> + at end itemize
> +
>  @section gradfun
>  
>  Fix the banding artifacts that are sometimes introduced into nearly flat
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 0ab0633..dfd5200 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -105,6 +105,7 @@ OBJS-$(CONFIG_FORMAT_FILTER)                 += vf_format.o
>  OBJS-$(CONFIG_FRAMESTEP_FILTER)              += vf_framestep.o
>  OBJS-$(CONFIG_FPS_FILTER)                    += vf_fps.o
>  OBJS-$(CONFIG_FREI0R_FILTER)                 += vf_frei0r.o
> +OBJS-$(CONFIG_GEQ_FILTER)                    += vf_geq.o
>  OBJS-$(CONFIG_GRADFUN_FILTER)                += vf_gradfun.o
>  OBJS-$(CONFIG_HFLIP_FILTER)                  += vf_hflip.o
>  OBJS-$(CONFIG_HQDN3D_FILTER)                 += vf_hqdn3d.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index fef40e9..88e8d68 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -97,6 +97,7 @@ void avfilter_register_all(void)
>      REGISTER_FILTER (FPS,         fps,         vf);
>      REGISTER_FILTER (FRAMESTEP,   framestep,   vf);
>      REGISTER_FILTER (FREI0R,      frei0r,      vf);
> +    REGISTER_FILTER (GEQ,         geq,         vf);
>      REGISTER_FILTER (GRADFUN,     gradfun,     vf);
>      REGISTER_FILTER (HFLIP,       hflip,       vf);
>      REGISTER_FILTER (HQDN3D,      hqdn3d,      vf);
> diff --git a/libavfilter/vf_geq.c b/libavfilter/vf_geq.c
> new file mode 100644
> index 0000000..2080dfa
> --- /dev/null
> +++ b/libavfilter/vf_geq.c
> @@ -0,0 +1,213 @@
> +/*
> + * Copyright (C) 2006 Michael Niedermayer <michaelni at gmx.at>
> + * Copyright (C) 2012 Clément Bœsch <ubitux at gmail.com>
> + *
> + * This file is part of MPlayer.
> + *
> + * MPlayer is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * MPlayer 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 General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with MPlayer; if not, write to the Free Software Foundation, Inc.,
> + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> + */
> +
> +/**
> + * @file
> + * Generic equation change filter
> + * Originally written by Michael Niedermayer for the MPlayer project, and
> + * ported by Clément Bœsch for FFmpeg.
> + */
> +

> +#include "libavcodec/avcodec.h"

what's this good for?

> +#include "libavutil/eval.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/pixdesc.h"
> +#include "internal.h"
> +#include "video.h"
> +
> +typedef struct {
> +    AVExpr *e[3];               ///< expressions for each plane
> +    int framenum;               ///< frame counter
> +    AVFilterBufferRef *picref;  ///< current input buffer
> +    int hsub, vsub;
> +} GEQContext;
> +

> +static inline double getpix(void *priv, double x, double y, int plane)

I suggest to avoid the reference to the private context, and pass
either the picref or data+linesize to get a more generic function.

> +{
> +    int xi, yi;
> +    GEQContext *geq = priv;
> +    AVFilterBufferRef *picref = geq->picref;
> +    const uint8_t *src = picref->data[plane];
> +    const int linesize = picref->linesize[plane];
> +    const int w = picref->video->w >> (plane ? geq->hsub : 0);
> +    const int h = picref->video->h >> (plane ? geq->vsub : 0);
> +
> +    xi = x = FFMIN(FFMAX(x, 0), w - 1);
> +    yi = y = FFMIN(FFMAX(y, 0), h - 1);
> +
> +    x -= xi;
> +    y -= yi;
> +
> +    return (1-y)*((1-x)*src[xi +  yi    * linesize] + x*src[xi + 1 +  yi    * linesize])
> +          +   y *((1-x)*src[xi + (yi+1) * linesize] + x*src[xi + 1 + (yi+1) * linesize]);
> +}
> +

> +//TODO: cubic interpolate
> +//TODO: keep the last few frames

> +static double lum(void *priv, double x, double y) { return getpix(priv, x, y, 0); }
> +static double  cb(void *priv, double x, double y) { return getpix(priv, x, y, 1); }
> +static double  cr(void *priv, double x, double y) { return getpix(priv, x, y, 2); }
> +
> +static const char *const var_names[] = {   "X",   "Y",   "W",   "H",   "N",   "SW",   "SH",        NULL };
> +enum                                   { VAR_X, VAR_Y, VAR_W, VAR_H, VAR_N, VAR_SW, VAR_SH, VAR_VARS_NB };
> +
> +static av_cold int geq_init(AVFilterContext *ctx, const char *args)
> +{
> +    GEQContext *geq = ctx->priv;
> +    char *args1 = av_strdup(args);
> +    char *bufptr = NULL;
> +    char *arg = args1;
> +    const char *expr[3] = {0};
> +    int plane, ret = 0;
> +
> +    if (!args1) {
> +        ret = AVERROR(ENOMEM);
> +        goto end;
> +    }
> +
> +    for (plane = 0; plane < 3; plane++, arg = NULL) {
> +        static double (*p[])(void *, double, double) = { lum, cb, cr };
> +        static const char *const func2_names[]    = { "lum", "cb", "cr", "p", NULL };
> +        double (*func2[])(void *, double, double) = { lum, cb, cr, p[plane], NULL };
> +
> +        expr[plane] = av_strtok(arg, ":", &bufptr);
> +        if (!expr[plane]) {
> +            if (plane == 0) {
> +                av_log(ctx, AV_LOG_ERROR, "At least one expression must be specified\n");
> +                ret = AVERROR(EINVAL);
> +                goto end;
> +            }
> +            expr[plane] = expr[plane - 1];
> +        }
> +        ret = av_expr_parse(&geq->e[plane], expr[plane], var_names, NULL, NULL,
> +                            func2_names, func2, 0, ctx);
> +        if (ret < 0)
> +            goto end;
> +    }

av_opt_set_from_string() may help in many respects (more flexible
syntax, introspection, supported options from ffmpeg -h full, etc.

> +
> +end:
> +    av_free(args1);
> +    return ret;
> +}
> +
> +static int geq_query_formats(AVFilterContext *ctx)
> +{
> +    static const enum PixelFormat pix_fmts[] = {
> +        AV_PIX_FMT_YUV444P,  AV_PIX_FMT_YUV422P,  AV_PIX_FMT_YUV420P,
> +        AV_PIX_FMT_YUV411P,  AV_PIX_FMT_YUV410P,  AV_PIX_FMT_YUV440P,
> +        AV_PIX_FMT_YUVA420P,
> +        AV_PIX_FMT_NONE
> +    };
> +    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
> +    return 0;
> +}
> +
> +static int geq_config_props(AVFilterLink *inlink)
> +{
> +    GEQContext *geq = inlink->dst->priv;
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
> +
> +    geq->hsub = desc->log2_chroma_w;
> +    geq->vsub = desc->log2_chroma_h;
> +    return 0;
> +}
> +
> +static int geq_end_frame(AVFilterLink *inlink)
> +{
> +    int ret, plane;
> +    GEQContext *geq = inlink->dst->priv;
> +    AVFilterLink *outlink = inlink->dst->outputs[0];
> +    AVFilterBufferRef *outpicref = outlink->out_buf;

> +    double values[VAR_VARS_NB] = {
> +        [VAR_N] = geq->framenum++,
> +    };

shouldn't this be set in the context?

> +
> +    geq->picref = inlink->cur_buf;
> +
> +    for (plane = 0; plane < 3; plane++) {
> +        int x, y;
> +        uint8_t *dst = outpicref->data[plane];
> +        const int linesize = outpicref->linesize[plane];
> +        const int w = inlink->w >> (plane ? geq->hsub : 0);
> +        const int h = inlink->h >> (plane ? geq->vsub : 0);
> +
> +        values[VAR_W]  = w;
> +        values[VAR_H]  = h;
> +        values[VAR_SW] = w / (double)inlink->w;
> +        values[VAR_SH] = h / (double)inlink->h;
> +
> +        for (y = 0; y < h; y++) {
> +            values[VAR_Y] = y;
> +            for (x = 0; x < w; x++) {
> +                values[VAR_X] = x;
> +                dst[x] = av_expr_eval(geq->e[plane], values, geq);
> +            }
> +            dst += linesize;
> +        }
> +    }
> +
> +    if ((ret = ff_draw_slice(outlink, 0, outlink->h, 1)) < 0 ||
> +        (ret = ff_end_frame(outlink)) < 0)
> +        return ret;
> +    return 0;
> +}
> +
> +static av_cold void geq_uninit(AVFilterContext *ctx)
> +{
> +    int i;
> +    GEQContext *geq = ctx->priv;
> +
> +    for (i = 0; i < FF_ARRAY_ELEMS(geq->e); i++)
> +        av_expr_free(geq->e[i]);
> +}
> +
> +static int null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { return 0; }
> +

> +static const AVFilterPad avfilter_vf_geq_inputs[] = {

you can drop the avfilter_vf_ prefix, since it is local, same below

> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_VIDEO,
> +        .draw_slice   = null_draw_slice,
> +        .config_props = geq_config_props,
> +        .end_frame    = geq_end_frame,
> +        .min_perms    = AV_PERM_READ,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad avfilter_vf_geq_outputs[] = {
> +    {
> +        .name = "default",
> +        .type = AVMEDIA_TYPE_VIDEO,
> +    },
> +    { NULL }
> +};
> +
> +AVFilter avfilter_vf_geq = {
> +    .name          = "geq",

> +    .description   = NULL_IF_CONFIG_SMALL("Generic equation change filter"),

I'll never be tired of saying, description is a complete sentence, so
it should sound like "Apply generic equation to each pixel.".

[...]

Thanks for working on this :).
-- 
FFmpeg = Fascinating & Fantastic Muttering Practical Exuberant Gorilla


More information about the ffmpeg-devel mailing list