[FFmpeg-devel] [PATCH] avfilter: add 30 band audio equalizer

Paul B Mahol onemda at gmail.com
Sun Dec 20 16:00:46 CET 2015


Signed-off-by: Paul B Mahol <onemda at gmail.com>
---
 libavfilter/Makefile              |   1 +
 libavfilter/af_aequalizer30band.c | 358 ++++++++++++++++++++++++++++++++++++++
 libavfilter/allfilters.c          |   1 +
 3 files changed, 360 insertions(+)
 create mode 100644 libavfilter/af_aequalizer30band.c

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index dea012a..dc48b30 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -29,6 +29,7 @@ OBJS-$(CONFIG_ACROSSFADE_FILTER)             += af_afade.o
 OBJS-$(CONFIG_ADELAY_FILTER)                 += af_adelay.o
 OBJS-$(CONFIG_AECHO_FILTER)                  += af_aecho.o
 OBJS-$(CONFIG_AEMPHASIS_FILTER)              += af_aemphasis.o
+OBJS-$(CONFIG_AEQUALIZER30BAND_FILTER)       += af_aequalizer30band.o
 OBJS-$(CONFIG_AEVAL_FILTER)                  += aeval.o
 OBJS-$(CONFIG_AFADE_FILTER)                  += af_afade.o
 OBJS-$(CONFIG_AFORMAT_FILTER)                += af_aformat.o
diff --git a/libavfilter/af_aequalizer30band.c b/libavfilter/af_aequalizer30band.c
new file mode 100644
index 0000000..a7869f7
--- /dev/null
+++ b/libavfilter/af_aequalizer30band.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2001-2010 Krzysztof Foltman, Markus Schmidt, Thor Harald Johansen and others
+ *
+ * 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"
+
+#define CENTER_FREQUENCY_HZ  1000
+#define LOWEST_FREQUENCY_HZ    20
+#define HIGHEST_FREQUENCY_HZ 2000
+
+typedef struct FoSection {
+    double b0, b1, b2, b3, b4;
+    double a0, a1, a2, a3, a4;
+
+    double num[4];
+    double denum[4];
+} FoSection;
+
+typedef struct EqualizatorFilter {
+    FoSection section[2];
+} EqualizatorFilter;
+
+typedef struct AudioFrequency {
+    double min;
+    double center;
+    double max;
+} AudioFrequency;
+
+typedef struct AudioEqualizer30BandContext {
+    const AVClass *class;
+    double gain[30];
+    AudioFrequency freq[30];
+    EqualizatorFilter *filter;
+} AudioEqualizer30BandContext;
+
+#define OFFSET(x) offsetof(AudioEqualizer30BandContext, x)
+#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption aequalizer30band_options[] = {
+    { "b1",  "set gain for 1. band",  OFFSET(gain[0]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b2",  "set gain for 2. band",  OFFSET(gain[1]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b3",  "set gain for 3. band",  OFFSET(gain[2]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b4",  "set gain for 4. band",  OFFSET(gain[3]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b5",  "set gain for 5. band",  OFFSET(gain[4]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b6",  "set gain for 6. band",  OFFSET(gain[5]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b7",  "set gain for 7. band",  OFFSET(gain[6]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b8",  "set gain for 8. band",  OFFSET(gain[7]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b9",  "set gain for 9. band",  OFFSET(gain[8]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b10", "set gain for 10. band", OFFSET(gain[9]),  AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b11", "set gain for 11. band", OFFSET(gain[10]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b12", "set gain for 12. band", OFFSET(gain[11]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b13", "set gain for 13. band", OFFSET(gain[12]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b14", "set gain for 14. band", OFFSET(gain[13]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b15", "set gain for 15. band", OFFSET(gain[14]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b16", "set gain for 16. band", OFFSET(gain[15]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b17", "set gain for 17. band", OFFSET(gain[16]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b18", "set gain for 18. band", OFFSET(gain[17]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b19", "set gain for 19. band", OFFSET(gain[18]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b20", "set gain for 20. band", OFFSET(gain[19]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b21", "set gain for 21. band", OFFSET(gain[20]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b22", "set gain for 22. band", OFFSET(gain[21]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b23", "set gain for 23. band", OFFSET(gain[22]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b24", "set gain for 24. band", OFFSET(gain[23]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b25", "set gain for 25. band", OFFSET(gain[24]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b26", "set gain for 26. band", OFFSET(gain[25]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b27", "set gain for 27. band", OFFSET(gain[26]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b28", "set gain for 28. band", OFFSET(gain[27]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b29", "set gain for 29. band", OFFSET(gain[28]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { "b30", "set gain for 30. band", OFFSET(gain[29]), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -32, 32, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(aequalizer30band);
+
+static int query_formats(AVFilterContext *ctx)
+{
+    AVFilterFormats *formats;
+    AVFilterChannelLayouts *layouts;
+    static const enum AVSampleFormat sample_fmts[] = {
+        AV_SAMPLE_FMT_DBLP,
+        AV_SAMPLE_FMT_NONE
+    };
+    int ret;
+
+    layouts = ff_all_channel_counts();
+    if (!layouts)
+        return AVERROR(ENOMEM);
+    ret = ff_set_common_channel_layouts(ctx, layouts);
+    if (ret < 0)
+        return ret;
+
+    formats = ff_make_format_list(sample_fmts);
+    if (!formats)
+        return AVERROR(ENOMEM);
+
+    ret = ff_set_common_formats(ctx, formats);
+    if (ret < 0)
+        return ret;
+
+    formats = ff_all_samplerates();
+    if (!formats)
+        return AVERROR(ENOMEM);
+    return ff_set_common_samplerates(ctx, formats);
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    AudioEqualizer30BandContext *s = ctx->priv;
+
+    av_freep(&s->filter);
+}
+
+static void butterworth_fo_section(FoSection *S, double beta, double s, double g, double g0,
+	                           double D, double c0)
+{
+    S->b0 = (g*g*beta*beta + 2*g*g0*s*beta + g0*g0)/D;
+    S->b1 = -4*c0*(g0*g0 + g*g0*s*beta)/D;
+    S->b2 = 2*(g0*g0*(1 + 2*c0*c0) - g*g*beta*beta)/D;
+    S->b3 = -4*c0*(g0*g0 - g*g0*s*beta)/D;
+    S->b4 = (g*g*beta*beta - 2*g*g0*s*beta + g0*g0)/D;
+
+    S->a0 = 1;
+    S->a1 = -4*c0*(1 + s*beta)/D;
+    S->a2 = 2*(1 + 2*c0*c0 - beta*beta)/D;
+    S->a3 = -4*c0*(1 - s*beta)/D;
+    S->a4 = (beta*beta - 2*s*beta + 1)/D;
+}
+
+static void butterworth_bp_filter(EqualizatorFilter *f, int N, double w0, double wb, double G,
+                                  double Gb, double G0)
+{
+    double g, c0, g0, beta;
+    double epsilon;
+    int r =  N % 2;
+    int L = (N - r) / 2;
+    int i;
+
+    if (G == 0 && G0 == 0) {
+        f->section[0].a0 = 1;
+        f->section[0].b0 = 1;
+        f->section[1].a0 = 1;
+        f->section[1].b0 = 1;
+        return;
+    }
+
+    G  = pow(10,  G/20);
+    Gb = pow(10, Gb/20);
+    G0 = pow(10, G0/20);
+
+    epsilon = sqrt((G * G - Gb * Gb) / (Gb * Gb - G0 * G0));
+    g  = pow(G,  1.0 / (double)N);
+    g0 = pow(G0, 1.0 / (double)N);
+    beta = pow(epsilon, -1.0/(double)N) * tan(wb / 2.0);
+
+    c0 = cos(w0);
+    if (w0 == 0)
+        c0 = 1;
+    if (w0 == M_PI/2)
+        c0 = 0;
+    if (w0 == M_PI)
+        c0 =- 1;
+
+    for (i = 1; i <= L; i++) {
+        double ui = (2.0 * i - 1) / N;
+        double si = sin(M_PI * ui / 2.0);
+        double Di = beta * beta + 2 * si * beta + 1;
+
+        butterworth_fo_section(&f->section[i - 1], beta, si, g, g0, Di, c0);
+    }
+}
+
+static double compute_bw_gain_db(double gain)
+{
+    double bw_gain = 0;
+
+    if (gain <= -6)
+        bw_gain = gain + 3;
+    else if(gain > -6 && gain < 6)
+        bw_gain = gain * 0.5;
+    else if(gain >= 6)
+        bw_gain = gain - 3;
+
+    return bw_gain;
+}
+
+static inline double hz_2_rad(double x, double fs)
+{
+    return 2 * M_PI * x / fs;
+}
+
+static void equalizer_channel(EqualizatorFilter *f, double gain,
+                              double sample_rate, double f0, double fb)
+{
+    double wb = hz_2_rad(fb, sample_rate);
+    double w0 = hz_2_rad(f0, sample_rate);
+    double bw_gain = compute_bw_gain_db(gain);
+
+    butterworth_bp_filter(f, 4, w0, wb, gain, bw_gain, 0);
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AudioEqualizer30BandContext *s = ctx->priv;
+    double f0, lowest_center_freq = CENTER_FREQUENCY_HZ;
+    int b, c;
+
+    while (lowest_center_freq > LOWEST_FREQUENCY_HZ)
+        lowest_center_freq /= exp2(1./3.);
+
+    if (lowest_center_freq < LOWEST_FREQUENCY_HZ)
+        lowest_center_freq *= exp2(1./3.);
+
+    f0 = lowest_center_freq;
+    for (b = 0; b < 30; b++) {
+        s->freq[b].min = f0 / exp2(1./6.);
+        s->freq[b].center = f0;
+        s->freq[b].max = f0 * exp2(1./6.);
+        f0 *= exp2(1./3.);
+    }
+
+    s->filter = av_calloc(inlink->channels, 30 * sizeof(*s->filter));
+    if (!s->filter)
+        return AVERROR(ENOMEM);
+
+    for (c = 0; c < inlink->channels; c++) {
+        for (b = 0; b < 30; b++) {
+            equalizer_channel(&s->filter[c * 30 + b], s->gain[b],
+                              inlink->sample_rate, s->freq[b].center,
+                              s->freq[b].max - s->freq[b].min);
+        }
+    }
+    return 0;
+}
+
+static inline double section_process(FoSection *S, double in)
+{
+    double out;
+
+    out = S->b0 * in;
+    out+= S->b1 * S->num[0] - S->denum[0] * S->a1;
+    out+= S->b2 * S->num[1] - S->denum[1] * S->a2;
+    out+= S->b3 * S->num[2] - S->denum[2] * S->a3;
+    out+= S->b4 * S->num[3] - S->denum[3] * S->a4;
+
+    S->num[3] = S->num[2];
+    S->num[2] = S->num[1];
+    S->num[1] = S->num[0];
+    S->num[0] = in;
+
+    S->denum[3] = S->denum[2];
+    S->denum[2] = S->denum[1];
+    S->denum[1] = S->denum[0];
+    S->denum[0] = out;
+
+    return out;
+}
+
+static double butterworth_process(FoSection *s1, FoSection *s2, double in)
+{
+    double p0 = in, p1;
+
+    p1 = section_process(s1, p0);
+    p1 = section_process(s2, p1);
+
+    return p1;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AudioEqualizer30BandContext *s = ctx->priv;
+    AVFilterLink *outlink = ctx->outputs[0];
+    AVFrame *out;
+    const double *src;
+    double *dst;
+    int b, c, n;
+
+    if (av_frame_is_writable(in)) {
+        out = in;
+    } else {
+        out = ff_get_audio_buffer(inlink, in->nb_samples);
+        if (!out) {
+            av_frame_free(&in);
+            return AVERROR(ENOMEM);
+        }
+        av_frame_copy_props(out, in);
+    }
+
+
+    for (c = 0; c < inlink->channels; c++) {
+        src = (const double *)in->extended_data[c];
+        dst = (double *)out->extended_data[c];
+
+        for (n = 0; n < in->nb_samples; n++) {
+            double sample = src[n];
+            for (b = 0; b < 30; b++) {
+                sample = butterworth_process(&s->filter[30 * c + b].section[0],
+                                             &s->filter[30 * c + b].section[1],
+                                             sample);
+            }
+            dst[n] = sample;
+        }
+    }
+
+    if (in != out)
+        av_frame_free(&in);
+
+    return ff_filter_frame(outlink, out);
+}
+
+static const AVFilterPad inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_AUDIO,
+        .config_props = config_input,
+        .filter_frame = filter_frame,
+    },
+    { NULL }
+};
+
+static const AVFilterPad outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_AUDIO,
+    },
+    { NULL }
+};
+
+AVFilter ff_af_aequalizer30band = {
+    .name          = "aequalizer30band",
+    .description   = NULL_IF_CONFIG_SMALL("Apply audio equalizer with 30 bands."),
+    .priv_size     = sizeof(AudioEqualizer30BandContext),
+    .priv_class    = &aequalizer30band_class,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .inputs        = inputs,
+    .outputs       = outputs,
+};
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 131e067..12f9f45 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -50,6 +50,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER(ADELAY,         adelay,         af);
     REGISTER_FILTER(AECHO,          aecho,          af);
     REGISTER_FILTER(AEMPHASIS,      aemphasis,      af);
+    REGISTER_FILTER(AEQUALIZER30BAND, aequalizer30band, af);
     REGISTER_FILTER(AEVAL,          aeval,          af);
     REGISTER_FILTER(AFADE,          afade,          af);
     REGISTER_FILTER(AFORMAT,        aformat,        af);
-- 
1.9.1



More information about the ffmpeg-devel mailing list