[FFmpeg-devel] [PATCH] Port mp=eq/eq2 to FFmpeg

Stefano Sabatini stefasab at gmail.com
Mon Jan 19 13:19:00 CET 2015


On date Monday 2015-01-19 04:04:45 +0530, Arwa Arif encoded:
> Attached the patch.

> From 79298b4f6d08abacb387dbd3f75fabe329d96772 Mon Sep 17 00:00:00 2001
> From: Arwa Arif <arwaarif1994 at gmail.com>
> Date: Mon, 19 Jan 2015 03:56:48 +0530
> Subject: [PATCH] Port mp=eq/eq2 to FFmpeg

Mention James Darnley in the log message. Something like: this code is
adapted from a port by James Darnley.

> 
> ---
>  configure                |    2 +
>  doc/filters.texi         |   68 +++++++++
>  libavfilter/Makefile     |    2 +
>  libavfilter/allfilters.c |    2 +
>  libavfilter/vf_eq.c      |  342 ++++++++++++++++++++++++++++++++++++++++++++++
>  libavfilter/vf_eq.h      |   63 +++++++++
>  libavfilter/x86/Makefile |    1 +
>  libavfilter/x86/vf_eq.c  |   94 +++++++++++++
>  8 files changed, 574 insertions(+)
>  create mode 100644 libavfilter/vf_eq.c
>  create mode 100644 libavfilter/vf_eq.h
>  create mode 100644 libavfilter/x86/vf_eq.c
> 
> diff --git a/configure b/configure
> index c73562b..a8042b2 100755
> --- a/configure
> +++ b/configure
> @@ -2579,6 +2579,8 @@ delogo_filter_deps="gpl"
>  deshake_filter_select="pixelutils"
>  drawtext_filter_deps="libfreetype"
>  ebur128_filter_deps="gpl"
> +eq_filter_deps="gpl"
> +eq2_filter_deps="gpl"
>  flite_filter_deps="libflite"
>  frei0r_filter_deps="frei0r dlopen"
>  frei0r_src_filter_deps="frei0r dlopen"
> diff --git a/doc/filters.texi b/doc/filters.texi
> index d7b2273..5eff0b5 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -4320,6 +4320,74 @@ edgedetect=mode=colormix:high=0
>  @end example
>  @end itemize
>  
> + at anchor{eq}
> + at section eq
> +Software equalizer with interactive controls just like the hardware equalizer,
> +for cards/drivers that do not support brightness and contrast controls in hardware.

Drop the redundant reference to software/hardware (this is obviously a
software equalizer). Something like:

Control brightness and contrast.

should be enough.

> +
> +Might also be useful with MEncoder, either for fixing poorly captured movies, or
> +for slightly reducing contrast to mask artifacts and get by with lower bitrates.

Drop the reference to MEncoder.

> +
> +The filter accepts the following options:
> +
> + at table @option
> +
> + at item brightness
> +Set the brightness value. It accepts a float value in range @code{-1.0} to
> + at code{1.0}. The default value is @code{0.0}.
> +
> + at item contrast
> +Set the contrast value. It accepts a float value in range @code{-1.0} to
> + at code{1.0}. The default value is @code{0.0}.
> + at end table
> +

> + at section eq2
> +Alternative software equalizer that uses lookup tables (very slow), allowing
> +gamma correction in addition to simple brightness and contrast adjustment.
> +

> +Note that it uses the same MMX optimized code as @ref{eq} if all gamma values

Note that it uses the same optimizied code ...

> +are 1.0. The parameters are given as floating point values.

I wonder if it still makes sense to get two distinct filters. Can you
show benhmarks?

> +
> +The filter accepts the following options:
> +
> + at table @option
> + at item brightness
> +Set the brightness value. It accepts a float value in range @code{-1.0} to
> + at code{1.0}. The default value is @code{0.0}.
> +
> + at item contrast
> +Set the contrast value. It accepts a float value in range @code{-2.0} to
> + at code{2.0}. The default value is @code{0.0}.
> +
> + at item gamma
> +Set the gamma value. It accepts a float value in range @code{0.1} to @code{10.0}.
> +The default value is @code{1.0}.
> +
> + at item gamma_b
> +Set the gamma value for blue component. It accepts a float value in range
> + at code{0.1} to @code{10.0}. The default value is @code{1.0}.
> +
> + at item gamma_g
> +Set the gamma value for green component. It accepts a float value in range
> + at code{0.1} to @code{10.0}. The default value is @code{1.0}.
> +
> + at item gamma_r
> +Set the gamma value for red component. It accepts a float value in range
> + at code{0.1} to @code{10.0}. The default value is @code{1.0}.
> +
> + at item saturation
> +Set the saturation value. It accepts a float value in range @code{0.0} to
> + at code{3.0}. The default value is @code{1.0}.
> +
> + at item weight
> +Can be used to reduce the effect of a high gamma value on bright image areas,
> +e.g. keep them from getting overamplified and just plain white. It accepts a
> +float value in range @code{0.0} to @code{1.0}.A value of @code{0.0} turns the
> +gamma correction all the way down while @code{1.0} leaves it at its full strength.
> +Default is @code{1.0}.
> +
> + at end table
> +
>  @section extractplanes
>  
>  Extract color channel components from input video stream into
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index e43d76d..8dab587 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -116,6 +116,8 @@ OBJS-$(CONFIG_DRAWGRID_FILTER)               += vf_drawbox.o
>  OBJS-$(CONFIG_DRAWTEXT_FILTER)               += vf_drawtext.o
>  OBJS-$(CONFIG_ELBG_FILTER)                   += vf_elbg.o
>  OBJS-$(CONFIG_EDGEDETECT_FILTER)             += vf_edgedetect.o
> +OBJS-$(CONFIG_EQ_FILTER)                     += vf_eq.o
> +OBJS-$(CONFIG_EQ2_FILTER)                    += vf_eq.o
>  OBJS-$(CONFIG_EXTRACTPLANES_FILTER)          += vf_extractplanes.o
>  OBJS-$(CONFIG_FADE_FILTER)                   += vf_fade.o
>  OBJS-$(CONFIG_FIELD_FILTER)                  += vf_field.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 381da4f..aa92449 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -132,6 +132,8 @@ void avfilter_register_all(void)
>      REGISTER_FILTER(DRAWTEXT,       drawtext,       vf);
>      REGISTER_FILTER(EDGEDETECT,     edgedetect,     vf);
>      REGISTER_FILTER(ELBG,           elbg,           vf);
> +    REGISTER_FILTER(EQ,             eq,             vf);
> +    REGISTER_FILTER(EQ2,            eq2,            vf);
>      REGISTER_FILTER(EXTRACTPLANES,  extractplanes,  vf);
>      REGISTER_FILTER(FADE,           fade,           vf);
>      REGISTER_FILTER(FIELD,          field,          vf);
> diff --git a/libavfilter/vf_eq.c b/libavfilter/vf_eq.c
> new file mode 100644
> index 0000000..c126c23
> --- /dev/null
> +++ b/libavfilter/vf_eq.c
> @@ -0,0 +1,342 @@
> +/*
> + * Original MPlayer filters by Richard Felker, Hampa Hug, Daniel Moreno,
> + * and Michael Niedermeyer.
> + *
> + * Copyright (c) 2014 James Darnley <james.darnley at gmail.com>
> + * Copyright (c) 2015 Arwa Arif <arwaarif1994 at gmail.com>
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg 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.
> + *
> + * FFmpeg 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 FFmpeg; if not, write to the Free Software Foundation, Inc.,
> + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> + */
> +
> +/**
> + * @file
> + * very simple video equalizer
> + */
> +
> +/* TODO:

> + * - copy plane pointers rather than data

uh? drop this comment unless it is clear to you what it means

> + * - support alpha channels

Add also an entry to add support to .process_command(). You can
implement it in a later patch.

> + */
> +
> +#include "libavfilter/internal.h"
> +#include "libavutil/common.h"
> +#include "libavutil/imgutils.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/pixdesc.h"
> +#include "vf_eq.h"
> +
> +AVFilter ff_vf_eq, ff_vf_eq2;
> +
> +#define IS_FILTER_EQ(x) ((x) == &ff_vf_eq)
> +
> +static void create_lut(EQ2Parameters *param)
> +{
> +    int i;
> +    double g = param->gamma;
> +

> +    if (g < 0.001 || g > 1000.0)
> +        g = 1.0;

is this check useful (considering that we limit the option value
through min/max)?

> +
> +    g = 1.0 / g;
> +
> +    for (i = 0; i < 256; i++) {
> +        double v = i / 255.0;
> +        v = param->contrast * (v - 0.5) + 0.5 + param->brightness;
> +
> +        if (v <= 0.0)
> +            param->lut[i] = 0;
> +        else {
> +            v = v * (1.0 - param->weight) + pow(v, g) * param->weight;
> +
> +            if (v >= 1.0)
> +                param->lut[i] = 255;
> +            else
> +                param->lut[i] = 256.0 * v;
> +        }
> +    }
> +
> +    param->lut_clean = 1;
> +}
> +
> +static void apply_lut(EQ2Parameters *param, uint8_t *dst, int dst_stride,
> +                      uint8_t *src, int src_stride, int w, int h)
> +{
> +    int x, y;
> +
> +    if (!param->lut_clean)
> +        create_lut(param);
> +
> +    for (y = 0; y < h; y++) {
> +        for (x = 0; x < w; x++) {
> +            dst[y*dst_stride+x] = param->lut[src[y*src_stride+x]];
> +        }
> +    }
> +}
> +
> +static void process_c(EQ2Parameters *param, uint8_t *dst, int dst_stride,
> +                      uint8_t *src, int src_stride, int w, int h)
> +{
> +    int x, y, pel;
> +
> +    for (y = 0; y < h; y++) {
> +        for (x = 0; x < w; x++) {
> +            pel = ((src[y * src_stride + x] * param->c) >> 16) + param->b;
> +
> +            if (pel & 768)
> +                pel = (-pel) >> 31;
> +
> +            dst[y * dst_stride + x] = pel;
> +        }
> +    }
> +}
> +
> +static void check_values(EQ2Parameters *param)
> +{
> +    if (param->contrast == 1.0 && param->brightness == 0.0 && param->gamma == 1.0)
> +        param->adjust = NULL;
> +    else if (param->gamma == 1.0)
> +        param->adjust = param->process;
> +    else
> +        param->adjust = apply_lut;
> +}
> +
> +static void set_contrast(EQ2Context *eq2)
> +{

> +    /* contrast already set as AVOpt */

drop these comments, they are useless and confusing

> +
> +    eq2->param[0].contrast = eq2->contrast;
> +    eq2->param[0].lut_clean = 0;
> +    check_values(&eq2->param[0]);
> +}
> +
> +static void set_brightness(EQ2Context *eq2)
> +{
> +    /* brightness already set as AVOpt */
> +
> +    eq2->param[0].brightness = eq2->brightness;
> +    eq2->param[0].lut_clean = 0;
> +    check_values(&eq2->param[0]);
> +}
> +
> +static void set_gamma(EQ2Context *eq2)
> +{
> +    int i;
> +    /* gamma already set as AVOpt */
> +
> +    eq2->param[0].gamma = eq2->gamma * eq2->gamma_g;
> +    eq2->param[1].gamma = sqrt(eq2->gamma_b / eq2->gamma_g);
> +    eq2->param[2].gamma = sqrt(eq2->gamma_r / eq2->gamma_g);
> +
> +    for (i = 0; i < 3; i++) {
> +        eq2->param[i].weight = eq2->weight;
> +        eq2->param[i].lut_clean = 0;
> +        check_values(&eq2->param[i]);
> +    }
> +}
> +
> +static void set_saturation(EQ2Context *eq2)
> +{
> +    int i;
> +    /* saturation already set as AVOpt */
> +
> +    for (i = 1; i < 3; i++) {
> +        eq2->param[i].contrast = eq2->saturation;
> +        eq2->param[i].lut_clean = 0;
> +        check_values(&eq2->param[i]);
> +    }

Is this really working with gray8 or crashing (like mp=eq does)?
You should store in the context the number of planes.

Also, please add a fate test.

> +}
> +
> +static int initialize(AVFilterContext *ctx)
> +{
> +    EQ2Context *eq2 = ctx->priv;
> +    int i;
> +
> +    set_gamma(eq2);
> +    set_contrast(eq2);
> +    set_brightness(eq2);
> +    set_saturation(eq2);
> +
> +    if (IS_FILTER_EQ(ctx->filter)) {
> +        eq2->param[0].contrast += 1.0;
> +        eq2->param[1].adjust = NULL;
> +        eq2->param[2].adjust = NULL;
> +    }
> +
> +    for (i = 0; i < 3; i++) {
> +        eq2->param[i].c = (eq2->param[i].contrast) * 65536.0;
> +        eq2->param[i].b = (eq2->param[i].brightness + 1.0) * 255.5 - 128.0 - (eq2->param[i].contrast) * 128.0;
> +    }
> +
> +    eq2->param->process = process_c;
> +
> +    if (ARCH_X86)
> +    {
> +        ff_eq_init_x86;
> +    }
> +
> +    return 0;
> +}
> +
> +static int query_formats(AVFilterContext *ctx)
> +{
> +    static const enum AVPixelFormat pixel_fmts_eq[] = {

> +        /* Only IMGFMT_CLPL is missing. */

what's this?

> +        AV_PIX_FMT_GRAY8,
> +        AV_PIX_FMT_NV12,
> +        AV_PIX_FMT_NV21,
> +        AV_PIX_FMT_YUV410P,
> +        AV_PIX_FMT_YUV411P,
> +        AV_PIX_FMT_YUV420P,
> +        AV_PIX_FMT_YUV422P,
> +        AV_PIX_FMT_YUV444P,
> +        AV_PIX_FMT_NONE
> +    };
> +
> +    static const enum AVPixelFormat pixel_fmts_eq2[] = {
> +        AV_PIX_FMT_GRAY8,
> +        AV_PIX_FMT_YUV410P,
> +        AV_PIX_FMT_YUV411P,
> +        AV_PIX_FMT_YUV420P,
> +        AV_PIX_FMT_YUV422P,
> +        AV_PIX_FMT_YUV444P,
> +        AV_PIX_FMT_NONE
> +    };
> +
> +    if (IS_FILTER_EQ(ctx->filter))
> +        ff_set_common_formats(ctx, ff_make_format_list(pixel_fmts_eq));
> +    else
> +        ff_set_common_formats(ctx, ff_make_format_list(pixel_fmts_eq2));
> +
> +    return 0;
> +}
> +

> +static int filter_frame(AVFilterLink *inlink, AVFrame *in)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    AVFilterLink *outlink = inlink->dst->outputs[0];
> +    EQ2Context *eq2 = ctx->priv;
> +    AVFrame *out;

> +    const AVPixFmtDescriptor *csp;

meaningful name please

> +    int i;
> +
> +    out = ff_get_video_buffer(outlink, inlink->w, inlink->h);
> +    if (!out)
> +        return AVERROR(ENOMEM);
> +
> +    av_frame_copy_props(out, in);
> +    csp = av_pix_fmt_desc_get(inlink->format);
> +
> +    for (i = 0; i < csp->nb_components; i++) {
> +        int w = inlink->w;
> +        int h = inlink->h;
> +
> +        if (i == 1 || i == 2) {
> +            w = FF_CEIL_RSHIFT(w, csp->log2_chroma_w);
> +            h = FF_CEIL_RSHIFT(h, csp->log2_chroma_h);
> +        }
> +
> +        if (eq2->param[i].adjust)
> +            eq2->param[i].adjust(&eq2->param[i], out->data[i], out->linesize[i],
> +                                 in->data[i], in->linesize[i], w, h);
> +        else
> +            av_image_copy_plane(out->data[i], out->linesize[i],
> +                                in->data[i], in->linesize[i], w, h);
> +    }
> +
> +    if (in != out) {
> +        if (in->data[3])
> +            av_image_copy_plane(out->data[3], out->linesize[3],
> +                                in ->data[3], in ->linesize[3],
> +                                inlink->w, inlink->h);
> +        av_frame_free(&in);
> +    }
> +
> +    return ff_filter_frame(outlink, out);
> +}
> +static const AVFilterPad eq_inputs[] = {
> +    {
> +        .name = "default",
> +        .type = AVMEDIA_TYPE_VIDEO,
> +        .filter_frame = filter_frame,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad eq_outputs[] = {
> +    {
> +        .name = "default",
> +        .type = AVMEDIA_TYPE_VIDEO,
> +    },
> +    { NULL }
> +};
> +
> +#define OFFSET(x) offsetof(EQ2Context, x)
> +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
> +
> +static const AVOption eq_options[] = {
> +    { "brightness", "set the brightness adjustment",
> +        OFFSET(brightness), AV_OPT_TYPE_DOUBLE, {.dbl = 0}, -1.0, 1.0, FLAGS },
> +    { "contrast",   "set the contrast adjustment",
> +        OFFSET(contrast),   AV_OPT_TYPE_DOUBLE, {.dbl = 0}, -1.0, 1.0, FLAGS },
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(eq);
> +
> +static const AVOption eq2_options[] = {
> +    { "brightness", "set the brightness adjustment",
> +        OFFSET(brightness), AV_OPT_TYPE_DOUBLE, {.dbl = 0.0}, -1.0, 1.0, FLAGS },
> +    { "contrast",   "set the contrast adjustment, negative values give a negative image",
> +        OFFSET(contrast),   AV_OPT_TYPE_DOUBLE, {.dbl = 1.0}, -2.0, 2.0, FLAGS },
> +    { "gamma",      "set the initial gamma value",
> +        OFFSET(gamma),      AV_OPT_TYPE_DOUBLE, {.dbl = 1.0},  0.1, 10.0, FLAGS },

> +    { "gamma_b",    "gamma value for the luma plane",
> +        OFFSET(gamma_b),    AV_OPT_TYPE_DOUBLE, {.dbl = 1.0},  0.1, 10.0, FLAGS },
> +    { "gamma_g",    "gamma value for the 1st chroma plane",
> +        OFFSET(gamma_g),    AV_OPT_TYPE_DOUBLE, {.dbl = 1.0},  0.1, 10.0, FLAGS },
> +    { "gamma_r",    "gamma value for the 2st chroma plane",
> +        OFFSET(gamma_r),    AV_OPT_TYPE_DOUBLE, {.dbl = 1.0},  0.1, 10.0, FLAGS },

gamma_{y,u,v} are probably better names.

[...]
-- 
FFmpeg = Furious Faithful Merciless Peaceless Elitarian Gadget


More information about the ffmpeg-devel mailing list