[FFmpeg-devel] [PATCH] avfilter: add acrusher filter

Paul B Mahol onemda at gmail.com
Thu Aug 11 16:07:44 EEST 2016


On 8/11/16, Michael Niedermayer <michael at niedermayer.cc> wrote:
> On Wed, Aug 10, 2016 at 06:23:32PM +0200, Paul B Mahol wrote:
>> Hi,
>>
>> patch attached.
>
>>  doc/filters.texi          |   49 +++++++
>>  libavfilter/Makefile      |    1
>>  libavfilter/af_acrusher.c |  291
>> ++++++++++++++++++++++++++++++++++++++++++++++
>>  libavfilter/allfilters.c  |    1
>>  4 files changed, 342 insertions(+)
>> edb554dcdc55f1658d3570acf9eb1ab8b7699a88
>> 0001-avfilter-add-acrusher-filter.patch
>> From 0d50ac2d704ebc719ab2557c1024ed4b58505a28 Mon Sep 17 00:00:00 2001
>> From: Paul B Mahol <onemda at gmail.com>
>> Date: Wed, 10 Aug 2016 16:11:37 +0200
>> Subject: [PATCH] avfilter: add acrusher filter
>>
>> ---
>>  doc/filters.texi          |  49 ++++++++
>>  libavfilter/Makefile      |   1 +
>>  libavfilter/af_acrusher.c | 291
>> ++++++++++++++++++++++++++++++++++++++++++++++
>>  libavfilter/allfilters.c  |   1 +
>>  4 files changed, 342 insertions(+)
>>  create mode 100644 libavfilter/af_acrusher.c
>>
>> diff --git a/doc/filters.texi b/doc/filters.texi
>> index 9dab959..562fff5 100644
>> --- a/doc/filters.texi
>> +++ b/doc/filters.texi
>> @@ -441,6 +441,55 @@ ffmpeg -i first.flac -i second.flac -filter_complex
>> acrossfade=d=10:o=0:c1=exp:c
>>  @end example
>>  @end itemize
>>
>> + at section acrusher
>> +
>> +Reduce audio bit resolution.
>> +
>> +This filter is bit crusher with enhanced funcionality. A bit crusher
>> +is used to audibly reduce number of bits an audio signal is sampled
>> +with. This doesn't change the bit depth at all, it just produces the
>> +effect. Material reduced in bit depth sounds more harsh and "digital".
>> +This filter is able to even round to continous values instead of
>> discrete
>> +bit depths.
>> +Additionally it has a D/C offset which results in different crushing of
>> +the lower and the upper half of the signal.
>> +An Anti-Aliasing setting is able to produce "softer" crushing sounds.
>> +
>> +Another feature of this filter is the logarithmic mode.
>> +This setting switches from linear distances between bits to logarithmic
>> ones.
>> +The result is a much more "natural" sounding crusher which doesn't gate
>> low
>> +signals for example. The human ear has a logarithmic perception, too
>> +so this kind of crushing is much more pleasant.
>> +Logarithmic crushing is also able to get anti-aliased.
>> +
>> +The filter accepts the following options:
>> +
>> + at table @option
>> + at item level_in
>> +Set level in.
>> +
>> + at item level_out
>> +Set level out.
>> +
>> + at item bits
>> +Set bit reduction.
>> +
>> + at item mix
>> +Set mixing ammount.
>> +
>> + at item mode
>> +Can be linear: @code{lin} or logarithmic: @code{log}.
>> +
>> + at item dc
>> +Set DC.
>> +
>> + at item aa
>> +Set anti-aliasing.
>> +
>> + at item samples
>> +Set sample reduction.
>> + at end table
>> +
>>  @section adelay
>>
>>  Delay one or more audio channels.
>> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
>> index cd62fd5..0d94f84 100644
>> --- a/libavfilter/Makefile
>> +++ b/libavfilter/Makefile
>> @@ -30,6 +30,7 @@ OBJS-$(HAVE_THREADS)                         +=
>> pthread.o
>>  OBJS-$(CONFIG_ABENCH_FILTER)                 += f_bench.o
>>  OBJS-$(CONFIG_ACOMPRESSOR_FILTER)            += af_sidechaincompress.o
>>  OBJS-$(CONFIG_ACROSSFADE_FILTER)             += af_afade.o
>> +OBJS-$(CONFIG_ACRUSHER_FILTER)               += af_acrusher.o
>>  OBJS-$(CONFIG_ADELAY_FILTER)                 += af_adelay.o
>>  OBJS-$(CONFIG_AECHO_FILTER)                  += af_aecho.o
>>  OBJS-$(CONFIG_AEMPHASIS_FILTER)              += af_aemphasis.o
>> diff --git a/libavfilter/af_acrusher.c b/libavfilter/af_acrusher.c
>> new file mode 100644
>> index 0000000..339cbf2
>> --- /dev/null
>> +++ b/libavfilter/af_acrusher.c
>> @@ -0,0 +1,291 @@
>> +/*
>> + * Copyright (c) Markus Schmidt and Christian Holschuh
>> + *
>> + * This file is part of FFmpeg.
>> + *
>> + * FFmpeg is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License as published by the Free Software Foundation; either
>> + * version 2.1 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
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser 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
>> + */
>> +
>> +#include "libavutil/opt.h"
>> +#include "avfilter.h"
>> +#include "internal.h"
>> +#include "audio.h"
>> +
>> +typedef struct SRContext {
>> +    double target;
>> +    double real;
>> +    double samples;
>> +    double last;
>> +} SRContext;
>> +
>> +typedef struct ACrusherContext {
>> +    const AVClass *class;
>> +
>> +    double level_in;
>> +    double level_out;
>> +    double bits;
>> +    double mix;
>> +    int mode;
>> +    double dc;
>> +    double aa;
>> +    double samples;
>> +
>> +    double sqr;
>> +    double aa1;
>> +    double coeff;
>> +    int    round;
>> +
>> +    SRContext *sr;
>> +} ACrusherContext;
>> +
>> +#define OFFSET(x) offsetof(ACrusherContext, x)
>> +#define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
>> +
>> +static const AVOption acrusher_options[] = {
>> +    { "level_in", "set level in",         OFFSET(level_in),
>> AV_OPT_TYPE_DOUBLE, {.dbl=1},  0.015625, 64, A },
>> +    { "level_out","set level out",        OFFSET(level_out),
>> AV_OPT_TYPE_DOUBLE, {.dbl=1},  0.015625, 64, A },
>> +    { "bits",     "set bit reduction",    OFFSET(bits),
>> AV_OPT_TYPE_DOUBLE, {.dbl=8},  1,        64, A },
>> +    { "mix",      "set mix",              OFFSET(mix),
>> AV_OPT_TYPE_DOUBLE, {.dbl=.5}, 0,         1, A },
>> +    { "mode",     "set mode",             OFFSET(mode),
>> AV_OPT_TYPE_INT,    {.i64=0},  0,         1, A, "mode" },
>> +    {   "lin",    "linear",               0,
>> AV_OPT_TYPE_CONST,  {.i64=0},  0,         0, A, "mode" },
>> +    {   "log",    "logarithmic",          0,
>> AV_OPT_TYPE_CONST,  {.i64=1},  0,         0, A, "mode" },
>> +    { "dc",       "set DC",               OFFSET(dc),
>> AV_OPT_TYPE_DOUBLE, {.dbl=1},.25,         4, A },
>> +    { "aa",       "set anti-aliasing",    OFFSET(aa),
>> AV_OPT_TYPE_DOUBLE, {.dbl=.5}, 0,         1, A },
>> +    { "samples",  "set sample reduction", OFFSET(samples),
>> AV_OPT_TYPE_DOUBLE, {.dbl=1},  1,       250, A },
>> +    { NULL }
>> +};
>> +
>> +AVFILTER_DEFINE_CLASS(acrusher);
>> +
>> +static double samplereduction(ACrusherContext *s, SRContext *sr, double
>> in)
>> +{
>> +    sr->samples++;
>> +    if (sr->samples >= s->round) {
>> +        sr->target += s->samples;
>> +        sr->real += s->round;
>> +        if (sr->target + s->samples >= sr->real + 1) {
>> +            sr->last = in;
>> +            sr->target = 0;
>> +            sr->real   = 0;
>> +        }
>> +        sr->samples = 0;
>> +    }
>> +    return sr->last;
>> +}
>> +
>
>> +static double add_dc(double s, double dc)
>> +{
>> +    return s > 0 ? s * dc : s / dc;
>> +}
>> +
>> +static double remove_dc(double s, double dc)
>> +{
>> +    return s > 0 ? s / dc : s * dc;
>> +}
>
> the division can be avoided as dc is a constant, multiplication by
> the inverse can be used ...
>
>
>> +
>> +static double bitreduction(ACrusherContext *s, double in)
>> +{
>> +    const double sqr = s->sqr;
>> +    const double coeff = s->coeff;
>> +    const double aa = s->aa;
>> +    const double aa1 = s->aa1;
>> +    double y, k;
>> +
>> +    // add dc
>> +    in = add_dc(in, s->dc);
>> +
>> +    // main rounding calculation depending on mode
>> +
>> +    // the idea for anti-aliasing:
>> +    // you need a function f which brings you to the scale, where you
>> want to round
>> +    // and the function f_b (with f(f_b)=id) which brings you back to
>> your original scale.
>> +    //
>> +    // then you can use the logic below in the following way:
>> +    // y = f(in) and k = roundf(y)
>> +    // if (y > k + aa1)
>> +    //      k = f_b(k) + ( f_b(k+1) - f_b(k) ) *0.5 * (sin(x - PI/2) +
>> 1)
>> +    // if (y < k + aa1)
>> +    //      k = f_b(k) - ( f_b(k+1) - f_b(k) ) *0.5 * (sin(x - PI/2) +
>> 1)
>> +    //
>> +    // whereas x = (fabs(f(in) - k) - aa1) * PI / aa
>> +    // for both cases.
>> +
>> +    switch (s->mode) {
>> +    case 0:
>> +    default:
>> +        // linear
>> +        y = in * coeff;
>> +        k = roundf(y);
>> +        if (k - aa1 <= y && y <= k + aa1) {
>> +            k /= coeff;
>> +        } else if (y > k + aa1) {
>> +            k = k / coeff + ((k + 1) / coeff - k / coeff) * 0.5 *
>> (sin(M_PI * (fabs(y - k) - aa1) / aa - M_PI_2) + 1);
>> +        } else {
>> +            k = k / coeff - (k / coeff - (k - 1) / coeff) * 0.5 *
>> (sin(M_PI * (fabs(y - k) - aa1) / aa - M_PI_2) + 1);
>> +        }
>> +        break;
>> +    case 1:
>> +        // logarithmic
>> +        y = sqr * log(fabs(in)) + sqr * sqr;
>> +        k = roundf(y);
>> +        if(!in) {
>> +            k = 0;
>> +        } else if (k - aa1 <= y && y <= k + aa1) {
>> +            k = in / fabs(in) * exp(k / sqr - sqr);
>> +        } else if (y > k + aa1) {
>> +            k = in / fabs(in) * (exp(k / sqr - sqr) + (exp((k + 1) / sqr
>> - sqr) -
>> +                exp(k / sqr - sqr)) * 0.5 * (sin(M_PI * (fabs(y - k) -
>> aa1) / aa - M_PI_2) + 1));
>> +        } else {
>> +            k = in / fabs(in) * (exp(k / sqr - sqr) - (exp(k / sqr - sqr)
>> -
>> +                exp((k - 1) / sqr - sqr)) * 0.5 * (sin(M_PI * (fabs(y -
>> k) - aa1) / aa - M_PI_2) + 1));
>
> 0.5 * (sin(M_PI * (fabs(y - k) - aa1) / aa - M_PI_2) + 1)
> occurs 4 times, maybe it could be factored into its own inline function
>
> in / fabs(in) should be some sign() macro or function
>
> exp(k / sqr - sqr) is used multiple times
>
> k = Y * (exp(k / S - S) - (exp(k / S - S) - exp((k - 1) / S - S)) * X);
> can be simplified to
> k = Y * exp(k / S - S)(1 - (1 - exp( - 1 / S)) * X);
>
> an S being constant IIUC so some of this can be calculated outside
> the loop
>
>
> [...]
>> +static int config_input(AVFilterLink *inlink)
>> +{
>> +    AVFilterContext *ctx = inlink->dst;
>> +    ACrusherContext *s = ctx->priv;
>> +
>
>> +    s->coeff = pow(2., s->bits) - 1;
>
> exp2()
>

Changed what I could and applied.


More information about the ffmpeg-devel mailing list