[FFmpeg-devel] [PATCH] lavfi: add mbfequalizer filter.

Nicolas George george at nsup.org
Mon Dec 23 13:17:20 EET 2019


TODO changelog and version bump

Signed-off-by: Nicolas George <george at nsup.org>
---
 doc/filters.texi              |  48 +++
 libavfilter/Makefile          |   1 +
 libavfilter/af_mbfequalizer.c | 567 ++++++++++++++++++++++++++++++++++
 libavfilter/allfilters.c      |   1 +
 4 files changed, 617 insertions(+)
 create mode 100644 libavfilter/af_mbfequalizer.c


I think I'll rewrite the bands parser to allow per-channel options and make
the structure of the code clearer. But I can already send the whole patch
for your consideration.


diff --git a/doc/filters.texi b/doc/filters.texi
index 8c5d3a5760..c6917fad38 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -4383,6 +4383,54 @@ lv2=p=http\\\\://www.openavproductions.com/artyfx#bitta:c=crush=0.3
 @end example
 @end itemize
 
+ at section mbfequalizer
+Multiband fast equalizer.
+
+As an equalizer, it allows to selectively alter the volume of the different
+frequencies in the audio signal. This particular equalizer does not allow a
+very sharp separation of the frequencies, but that gives it other
+advantages. It is rather fast. It does not add latency. It supports all
+sample formats, and for integer formats it uses bit-exact integer
+arithmetic.
+
+It accepts the following parameters:
+
+ at table @option
+ at item defs
+Definition of bands and gain. The syntax is:
+"gain/freq/gain[/freq/gain...][|gain/freq/gain...]"
+where @var{freq} is the cut frequency between bands and @var{gain} the
+associated gain.
+Gain and bands for several channels can be defined by separating them with
+the pipe "|" character; if only one definition is given, it is used for all
+channels; otherwise, the number of definitions must match the number of
+input channels.
+Frequencies should be in ascending order. Gain greater than 1 or negative
+may result in overflows and ugly noise or other strange results.
+
+ at item global_gain
+Gain applied to all channels and all bands. Can be greater than 1 without
+risk; in case of overflow, soft clipping is applied.
+
+ at item keep_all_bands
+By default, bands beyond @var{sample_rate}/4 are discarded. If this option
+is enabled, they are kept; it can result in strange effects.
+ at end table
+
+A Gnuplot formula mapping the frequency to the gain is printed at verbose
+log level. It can be used to check the result, proably with
+"set logscale x; db(x) = 20*log(x)/log(10); plot [20:2000] db(...)".
+
+ at subsection Examples
+
+Assuming 3.1 input, reduce high frequencies on left and right, keep a narrow
+band on center and dampen bass on LFE. All gains were lowered by 5dB, then
+raised again using global gain, and the -2.8dB for the middle band was found
+by looking at the graph.
+ at example
+mbfequalizer=-5dB/7040/-15dB|-5dB/7040/-15dB|0/220/-2.8dB/880/0|-10dB/110/-5dB:global_gain=+5dB
+ at end example
+
 @section mcompand
 Multiband Compress or expand the audio's dynamic range.
 
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 37d4eee858..f5690a50db 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -124,6 +124,7 @@ OBJS-$(CONFIG_LOWPASS_FILTER)                += af_biquads.o
 OBJS-$(CONFIG_LOWSHELF_FILTER)               += af_biquads.o
 OBJS-$(CONFIG_LV2_FILTER)                    += af_lv2.o
 OBJS-$(CONFIG_MCOMPAND_FILTER)               += af_mcompand.o
+OBJS-$(CONFIG_MBFEQUALIZER_FILTER)           += af_mbfequalizer.o
 OBJS-$(CONFIG_PAN_FILTER)                    += af_pan.o
 OBJS-$(CONFIG_REPLAYGAIN_FILTER)             += af_replaygain.o
 OBJS-$(CONFIG_RESAMPLE_FILTER)               += af_resample.o
diff --git a/libavfilter/af_mbfequalizer.c b/libavfilter/af_mbfequalizer.c
new file mode 100644
index 0000000000..aadd3cb3da
--- /dev/null
+++ b/libavfilter/af_mbfequalizer.c
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) 2019 Nicolas George <george at nsup.org>
+ *
+ * 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
+ */
+
+/*
+ * Low-pass-high-pass equalizer using a simple sliding weighed average.
+ *
+ * Input signal: u(t)
+ * Low freqs:    a(t)
+ * High freqs:   b(t)
+ *
+ * a(t) = (1-k) a(t-1) + k u(t)
+ * b(t) = u(t) - a(t)
+ *
+ * F = sample frequency; f = input frequency; ω = 2πf/F
+ * For u(t) = exp(iωt),
+ * a(t) =                 k/(1-exp(-iω)(1-k)) exp(iwt)
+ * b(t) = (1-k)(1-exp(-iω))/(1-exp(-iω)(1-k)) exp(iwt)
+ * hi/low = (1-k)(1-exp(-iω)) / k
+ * R(k,w) = (1-k)*(1-exp(-I*w)) / k
+ *
+ * Cut when |hi/low| = 1, i.e. |1-exp(iω)| = k/(1-k)
+ * Triangle 0, 1, exp(iω) has sides 1, 1, k/(1-k)
+ * w = 2 asin(1/2 k/(1-k))
+ * k/(1-k) = 2 sin(ω/2)
+ * k = 2 sin(ω/2) / (1 + 2 sin(ω/2))
+ *
+ * Gnuplot script: response for cut frequency 110 Hz and 440 Hz.
+ * I = {0,1}
+ * F = 48000
+ * w(f) = 2*pi*f/F
+ * db(x) = 20*log(x)/log(10)
+ * set samples 1000
+ * set grid
+ * set logscale x 2
+ * k(f) = 2*sin(w(f)/2) / (1+2*sin(w(f)/2))
+ * d(f, x) = abs(exp(I*w(x))-(1-k(f)))
+ * al(f, x) = k(f) / d(f, x)
+ * ah(f, x) = abs((1-k(f))*(1-exp(I*w(x)))) / d(f, x)
+ * plot [20:16000] [-20:0] db(al(110, x)), db(ah(110, x)), db(al(880, x)), db(ah(880, x))
+ *
+ * Approximation without sin():
+ * ω - ω³/6  ≤  sin(ω)   ≤ ω - ω³/6  + ω⁵/120
+ * ω - ω³/24 ≤ 2sin(ω/2) ≤ ω - ω³/24 + ω⁵/1920
+ * Accurate enough with 0 ≤ ω ≤ π/2.
+ *
+ * Clip to [-A;A] with soft decay on [A(1-k); +∞[:
+ * s(x) = (Ak)² / (x - A(1-2k))
+ */
+
+/*
+
+aevalsrc=sin(13.75*4.35*exp(0.7*t)):d=10
+arealtime,asplit=3[a][b][c];
+[a]showwaves=s=1764x128[ap];
+[b]mbfequalizer=1:0,showwaves=s=1764x128[bp];
+[c]mbfequalizer=0:1,showwaves=s=1764x128[cp];
+[ap][bp][cp]vstack=3
+
+*/
+
+#include "libavutil/avassert.h"
+#include "libavutil/bprint.h"
+#include "libavutil/eval.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "audio.h"
+#include "internal.h"
+
+typedef struct EqBand {
+    int k; /* k(2πf) */
+    int g; /* gain of the high band */
+} EqBand;
+
+typedef struct EqContext EqContext;
+struct EqContext {
+    const AVClass *class;
+    char *defs;
+    EqBand *bands;
+    void *vals;
+    void (*process_samples)(EqContext *s, unsigned channels, unsigned samples,
+                            AVFrame *frame, AVFrame *frame_out);
+    double global_gain_opt;
+    unsigned channels;
+    int global_gain;
+    int keep_all_bands;
+};
+
+#define FPOINT 15
+#define SOFTCLIP_SHIFT 2 /* k = 1/(1<<SHIFT) */
+
+#if 1&&HAVE_FAST_64BIT
+
+static inline int fpmul_int(int a, int b)
+{
+    return ((int64_t)a * b) >> FPOINT;
+}
+
+#else
+
+static inline int fpmul_int(int a, int b)
+{
+#define MASK ((1 << FPOINT) - 1)
+    return (((a >> FPOINT) * (b >> FPOINT)) << FPOINT)
+         + (a >> FPOINT) * (b & MASK)
+         + (b >> FPOINT) * (a & MASK)
+         + (((a & MASK) * (b & MASK)) >> FPOINT);
+#undef MASK
+}
+
+#endif
+
+static int64_t fpmul_int64_t(int a, int64_t b)
+{
+    uint64_t a0 = a & 0xFFFFFFFF;
+    uint64_t b0 = b & 0xFFFFFFFF;
+    /*int64_t  a1 = a >> 32;*/ /* a1 = 0 */
+    int64_t  b1 = b >> 32;
+    uint64_t r0 = a0 * b0;
+    int64_t r1a = (int64_t)a0 * b1;
+    /*int64_t r1b = (int64_t)b0 * a1;*/
+    int64_t r2  = 0 /*a1 * b1*/;
+    r2 += (r1a >> 32) /*+ (r1b >> 32)*/;
+    r1a &= 0xFFFFFFFF;
+    /*r1b &= 0xFFFFFFFF;*/
+    r2 += (r1a /*+ r1b*/ + (r0 >> 32)) >> 32;
+    r0 += (r1a /*+ r1b*/) << 32;
+    return (int64_t)(r0 >> FPOINT) + (r2 << (64 - FPOINT));
+}
+
+static inline float fpmul_float(int a, float b)
+{
+    return a * b / (1 << FPOINT);
+}
+
+static inline double fpmul_double(int a, double b)
+{
+    return a * b / (1 << FPOINT);
+}
+
+static int approx_factor(int f, int sf)
+{
+    /* 31089 / 151 =~ 2*Pi*32768 / 1000 */
+    int w = av_rescale(f, 31089, 151 * sf);
+    int s = w - fpmul_int(w, fpmul_int(w, w)) / 24;
+    int r = (s << FPOINT) / ((1 << FPOINT) + s);
+    return r;
+}
+
+static void reorder_bands(EqBand *bands, size_t nb_bands, unsigned sample_rate)
+{
+    size_t i, j;
+
+    if (nb_bands == 1)
+        return;
+    /* bands arrive in ascending order with kf = gain below,
+       and k = 0 on the final band;
+       we want them in descending order with kf = gain above;
+       therefore, reverse the order of k except the final 0,
+       and reverse the order of kf including the final 0 */
+    for (i = 0, j = nb_bands - 2; i < j; i++, j--)
+        FFSWAP(int, bands[i].k, bands[j].k);
+    for (i = 0, j = nb_bands - 1; i < j; i++, j--)
+        FFSWAP(int, bands[i].g, bands[j].g);
+    for (i = 0; i < nb_bands - 1; i++)
+        bands[i].k = approx_factor(bands[i].k, sample_rate);
+}
+
+static void gain_to_bprint(AVBPrint *bp, unsigned rate, EqBand *bands)
+{
+    double k = bands->k / (double)(1 << FPOINT);
+    double g = bands->g / (double)(1 << FPOINT);
+
+    if (bands->k == 0) {
+        av_bprintf(bp, "%.5f", g);
+        return;
+    }
+    av_bprintf(bp, "(%.5f*%.5f*(1-exp(-{0,1}*2*pi*x/%d))+%.5f*", g, 1 - k, rate, k);
+    gain_to_bprint(bp, rate, bands + 1);
+    av_bprintf(bp, ")");
+    av_bprintf(bp, "/(1-exp(-{0,1}*2*pi*x/%d)*%.5f)", rate, 1 - k);
+}
+
+static void print_gain(AVFilterContext *ctx, unsigned channel, EqBand *bands)
+{
+    EqContext *s = ctx->priv;
+    AVBPrint bp;
+    unsigned rate = ctx->outputs[0]->sample_rate;
+
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    if (s->global_gain != 1 << FPOINT)
+        av_bprintf(&bp, "%.5f*", s->global_gain / (double)(1 << FPOINT));
+    av_bprintf(&bp, "abs(");
+    gain_to_bprint(&bp, rate, bands);
+    av_bprintf(&bp, ")");
+    if (av_bprint_is_complete(&bp))
+        av_log(ctx, AV_LOG_VERBOSE, "Channel %d: gain: %s\n", channel, bp.str);
+    else
+        av_log(ctx, AV_LOG_WARNING, "Not enough memory to build gain formula; no consequence on output.\n");
+    av_bprint_finalize(&bp, NULL);
+}
+
+static inline int unpack_sample_uint8_t(uint8_t s)
+{
+    return ((int)s - 0x80) << FPOINT;
+}
+
+static inline int unpack_sample_int16_t(int16_t s)
+{
+    return ((int)s) << FPOINT;
+}
+
+static inline int64_t unpack_sample_int32_t(int32_t s)
+{
+    return ((int64_t)s) << FPOINT;
+}
+
+static inline float unpack_sample_float(float s)
+{
+    return s;
+}
+
+static inline double unpack_sample_double(double s)
+{
+    return s;
+}
+
+#define SOFTCLIP_PLUS(A, D, x) (A - D * D / ((x) - (A - 2 * D)))
+#define SOFTCLIP_INT(A, D, x) ((x) < -A + D ? -SOFTCLIP_PLUS(A, D, -(x)) : \
+                               (x) > +A - D ? +SOFTCLIP_PLUS(A, D, +(x)) : (x))
+#define SOFTCLIP(A, x) SOFTCLIP_INT(A, (A / 8), x)
+
+static inline uint8_t pack_sample_uint8_t(int gain, int v)
+{
+    v >>= FPOINT;
+    v = fpmul_int(gain, v);
+    return 0x80 + SOFTCLIP(((int64_t)0x80), v);
+}
+
+static inline int16_t pack_sample_int16_t(int gain, int v)
+{
+    v >>= FPOINT;
+    v = fpmul_int(gain, v);
+    return SOFTCLIP(0x8000, v);
+}
+
+static inline int32_t pack_sample_int32_t(int gain, int64_t v)
+{
+    v >>= FPOINT;
+    v = fpmul_int64_t(gain, v);
+    return SOFTCLIP(((int64_t)0x80000000), v);
+}
+
+static inline float pack_sample_float(int gain, float v)
+{
+    v = fpmul_float(gain, v);
+    return SOFTCLIP(1.0, v);
+}
+
+static inline double pack_sample_double(int gain, double v)
+{
+    v = fpmul_double(gain, v);
+    return SOFTCLIP(1.0, v);
+}
+
+#define PROCESS_SAMPLE(type_smp, type_calc) do { \
+    type_calc iv = unpack_sample_##type_smp(*(samples_in++)); \
+    type_calc outval = 0; \
+    for (; band->k; band++) { \
+        type_calc ov1 = *val += fpmul_##type_calc(band->k, iv - *val); \
+        type_calc ov2 = iv - ov1; \
+        val++; \
+        outval += fpmul_##type_calc(band->g, ov2); \
+        iv = ov1; \
+    } \
+    outval += fpmul_##type_calc(band->g, iv); \
+    band++; \
+    *(samples_out++) = pack_sample_##type_smp(s->global_gain, outval); \
+} while (0);
+
+#define DEFINE_PROCESS_SAMPLES(fmt, type_smp, type_calc) \
+static void process_samples_##fmt(EqContext *s, unsigned channels, unsigned samples, \
+                                  AVFrame *frame, AVFrame *frame_out) \
+{ \
+    type_smp *samples_in = (type_smp *)frame->data[0]; \
+    type_smp *samples_out = (type_smp *)frame_out->data[0]; \
+    unsigned sample, ch; \
+    for (sample = 0; sample < samples; sample++) { \
+        type_calc *val = s->vals; \
+        EqBand *band = s->bands; \
+        for (ch = 0; ch < channels; ch++) { \
+            if (s->channels == 1) \
+                band = s->bands; \
+            PROCESS_SAMPLE(type_smp, type_calc); \
+        } \
+    } \
+} \
+static void process_samples_##fmt##p(EqContext *s, unsigned channels, unsigned samples, \
+                                     AVFrame *frame, AVFrame *frame_out) \
+{ \
+    EqBand *band0, *band; \
+    unsigned sample, ch; \
+    type_calc *val0, *val; \
+    band0 = s->bands; \
+    val0 = s->vals; \
+    for (ch = 0; ch < channels; ch++) { \
+        type_smp *samples_in = (type_smp *)frame->data[ch]; \
+        type_smp *samples_out = (type_smp *)frame_out->data[ch]; \
+        for (sample = 0; sample < samples; sample++) { \
+            band = band0; \
+            val = val0; \
+            PROCESS_SAMPLE(type_smp, type_calc); \
+        } \
+        if (s->channels > 1) \
+            band0 = band; \
+        val0 = val; \
+    } \
+}
+
+DEFINE_PROCESS_SAMPLES(u8,  uint8_t, int)
+DEFINE_PROCESS_SAMPLES(s16, int16_t, int)
+DEFINE_PROCESS_SAMPLES(s32, int32_t, int64_t)
+DEFINE_PROCESS_SAMPLES(flt, float,   float)
+DEFINE_PROCESS_SAMPLES(dbl, double,  double)
+
+static int filter_frame(AVFilterLink *link, AVFrame *frame)
+{
+    AVFilterContext *ctx = link->dst;
+    EqContext *s = ctx->priv;
+    AVFrame *frame_out;
+
+    if (av_frame_is_writable(frame)) {
+        frame_out = frame;
+    } else {
+        frame_out = ff_get_audio_buffer(ctx->outputs[0], frame->nb_samples);
+        if (!frame_out) {
+            av_frame_free(&frame);
+            return AVERROR(ENOMEM);
+        }
+    }
+    s->process_samples(s, link->channels, frame->nb_samples, frame, frame_out);
+    if (frame_out != frame)
+        av_frame_free(&frame);
+    return ff_filter_frame(ctx->outputs[0], frame_out);
+}
+
+static int init(AVFilterContext *ctx)
+{
+    EqContext *s = ctx->priv;
+    EqBand *bands = NULL, *band;
+    unsigned nb_bands = 0, nb_channels = 1;
+    const char *defs = s->defs ? s->defs : "1";
+    char *tail;
+    double gain, freq;
+    int ret = AVERROR(EINVAL);
+
+    s->global_gain = round(s->global_gain_opt * (1 << FPOINT));
+    while (1) {
+        gain = av_strtod(defs, &tail);
+        if (tail == defs) {
+            av_log(ctx, AV_LOG_ERROR, "Error parsing defs: expected gain, got \"%s\"\n", defs);
+            goto error;
+        }
+        defs = tail;
+        band = av_dynarray2_add((void **)&bands, &nb_bands, sizeof(*bands), NULL);
+        if (!band)
+            return AVERROR(ENOMEM);
+        band->g = round(gain * (1 << FPOINT));
+        band->k = 0;
+        if (*defs == 0)
+            break;
+        if (*defs == '/') {
+            defs++;
+            freq = av_strtod(defs, &tail);
+            if (tail == defs || *tail != '/') {
+                av_log(ctx, AV_LOG_ERROR, "Error parsing defs: expected /freq/, got \"%s\"\n", defs - 1);
+                goto error;
+            }
+            defs = tail + 1;
+            band->k = round(1000 * freq);
+        } else if (*defs == '|') {
+            defs++;
+            nb_channels++;
+        }
+    }
+    av_assert0(nb_bands > 0);
+    s->bands = av_realloc_f(bands, nb_bands, sizeof(*bands));
+    if (!s->bands)
+        return AVERROR(ENOMEM);
+    s->channels = nb_channels;
+    return 0;
+
+error:
+    av_free(bands);
+    return ret;
+}
+
+static void uninit(AVFilterContext *ctx)
+{
+    EqContext *s = ctx->priv;
+    av_freep(&s->vals);
+    av_freep(&s->bands);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    EqContext *s = ctx->priv;
+    static const enum AVSampleFormat sample_fmts[] = {
+        AV_SAMPLE_FMT_U8,
+        AV_SAMPLE_FMT_U8P,
+        AV_SAMPLE_FMT_S16,
+        AV_SAMPLE_FMT_S16P,
+        AV_SAMPLE_FMT_S32,
+        AV_SAMPLE_FMT_S32P,
+        AV_SAMPLE_FMT_FLT,
+        AV_SAMPLE_FMT_FLTP,
+        AV_SAMPLE_FMT_DBL,
+        AV_SAMPLE_FMT_DBLP,
+        AV_SAMPLE_FMT_NONE
+
+    };
+    AVFilterFormats *formats;
+    AVFilterChannelLayouts *layouts;
+    int 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;
+    if (s->channels == 1) {
+        layouts = ff_all_channel_counts();
+    } else {
+        ret = ff_add_channel_layout(&layouts, FF_COUNT2LAYOUT(s->channels));
+        if (ret < 0)
+            return ret;
+    }
+    if (!layouts)
+        return AVERROR(ENOMEM);
+    ret = ff_set_common_channel_layouts(ctx, layouts);
+    if (ret < 0)
+        return ret;
+    return 0;
+}
+
+static int config_props(AVFilterLink *link)
+{
+    AVFilterContext *ctx = link->dst;
+    EqContext *s = ctx->priv;
+    EqBand *band0, *band;
+    unsigned nb_vals, ch, val_size;
+
+    av_assert0(ctx->outputs[0]->channels == ctx->inputs[0]->channels);
+    av_assert0(s->channels == 1 || link->channels == s->channels);
+    nb_vals = 0;
+    if (!s->keep_all_bands) {
+        band0 = band = s->bands;
+        for (ch = 0; ch < s->channels; ch++) {
+            while (band->k && band->k <= link->sample_rate * 250)
+                *(band0++) = *(band++);
+            while (band->k)
+                av_log(ctx, AV_LOG_VERBOSE, "Channel %d: skipping band %f Hz\n",
+                       ch, (band++)->k / 1000.0);
+            (band0++)->k = 0;
+            band++;
+        }
+    }
+    band0 = s->bands;
+    for (ch = 0; ch < s->channels; ch++) {
+        band = band0;
+        while ((band++)->k)
+            nb_vals++;
+        reorder_bands(band0, band - band0, link->sample_rate);
+        print_gain(ctx, ch, band0);
+        band0 = band;
+    }
+    switch (link->format) {
+    case AV_SAMPLE_FMT_U8:   s->process_samples = process_samples_u8;   break;
+    case AV_SAMPLE_FMT_U8P:  s->process_samples = process_samples_u8p;  break;
+    case AV_SAMPLE_FMT_S16:  s->process_samples = process_samples_s16;  break;
+    case AV_SAMPLE_FMT_S16P: s->process_samples = process_samples_s16p; break;
+    case AV_SAMPLE_FMT_S32:  s->process_samples = process_samples_s32;  break;
+    case AV_SAMPLE_FMT_S32P: s->process_samples = process_samples_s32p; break;
+    case AV_SAMPLE_FMT_FLT:  s->process_samples = process_samples_flt;  break;
+    case AV_SAMPLE_FMT_FLTP: s->process_samples = process_samples_fltp; break;
+    case AV_SAMPLE_FMT_DBL:  s->process_samples = process_samples_dbl;  break;
+    case AV_SAMPLE_FMT_DBLP: s->process_samples = process_samples_dblp; break;
+    default: av_assert0(0);
+    }
+    switch (link->format) {
+    case AV_SAMPLE_FMT_U8:
+    case AV_SAMPLE_FMT_U8P:
+    case AV_SAMPLE_FMT_S16:
+    case AV_SAMPLE_FMT_S16P: val_size = sizeof(int);     break;
+    case AV_SAMPLE_FMT_S32:
+    case AV_SAMPLE_FMT_S32P: val_size = sizeof(int64_t); break;
+    case AV_SAMPLE_FMT_FLT:
+    case AV_SAMPLE_FMT_FLTP: val_size = sizeof(float);   break;
+    case AV_SAMPLE_FMT_DBL:
+    case AV_SAMPLE_FMT_DBLP: val_size = sizeof(double);  break;
+    default: av_assert0(0);
+    }
+    s->vals = av_calloc(nb_vals, val_size);
+    if (!s->vals)
+        return AVERROR(ENOMEM);
+    return 0;
+}
+
+#define OFFSET(x) offsetof(EqContext, x)
+#define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
+static const AVOption mbfequalizer_options[] = {
+    { "defs", "definitions of the bands", OFFSET(defs), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
+    { "global_gain", "gain applied to all channels", OFFSET(global_gain_opt), AV_OPT_TYPE_DOUBLE,
+                                                     { .dbl = 1 }, 0, INT_MAX >> FPOINT, FLAGS },
+    { "keep_all_bands", "keep bands beyond sample_rate/4", OFFSET(keep_all_bands), AV_OPT_TYPE_BOOL,
+                                                           { .i64 = 0 }, 0, 1, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(mbfequalizer);
+
+static const AVFilterPad mbfequalizer_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_AUDIO,
+        .config_props = config_props,
+        .filter_frame = filter_frame,
+    },
+    { NULL }
+};
+
+static const AVFilterPad mbfequalizer_outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_AUDIO,
+    },
+    { NULL }
+};
+
+AVFilter ff_af_mbfequalizer = {
+    .name          = "mbfequalizer",
+    .description   = NULL_IF_CONFIG_SMALL("Multiband fast equalizer"),
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .priv_size     = sizeof(EqContext),
+    .priv_class    = &mbfequalizer_class,
+    .inputs        = mbfequalizer_inputs,
+    .outputs       = mbfequalizer_outputs,
+};
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index c295f8e403..1f3a1a7827 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -117,6 +117,7 @@ extern AVFilter ff_af_lowpass;
 extern AVFilter ff_af_lowshelf;
 extern AVFilter ff_af_lv2;
 extern AVFilter ff_af_mcompand;
+extern AVFilter ff_af_mbfequalizer;
 extern AVFilter ff_af_pan;
 extern AVFilter ff_af_replaygain;
 extern AVFilter ff_af_resample;
-- 
2.24.0



More information about the ffmpeg-devel mailing list