[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