[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