[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