]> git.sesse.net Git - ffmpeg/blob - libavfilter/af_firequalizer.c
c7569bbdf391d5fa753e124212929dacd3be737f
[ffmpeg] / libavfilter / af_firequalizer.c
1 /*
2  * Copyright (c) 2016 Muhammad Faiz <mfcc64@gmail.com>
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20
21 #include "libavutil/opt.h"
22 #include "libavutil/eval.h"
23 #include "libavutil/avassert.h"
24 #include "libavcodec/avfft.h"
25 #include "avfilter.h"
26 #include "internal.h"
27 #include "audio.h"
28
29 #define RDFT_BITS_MIN 4
30 #define RDFT_BITS_MAX 16
31
32 enum WindowFunc {
33     WFUNC_RECTANGULAR,
34     WFUNC_HANN,
35     WFUNC_HAMMING,
36     WFUNC_BLACKMAN,
37     WFUNC_NUTTALL3,
38     WFUNC_MNUTTALL3,
39     WFUNC_NUTTALL,
40     WFUNC_BNUTTALL,
41     WFUNC_BHARRIS,
42     WFUNC_TUKEY,
43     NB_WFUNC
44 };
45
46 #define NB_GAIN_ENTRY_MAX 4096
47 typedef struct {
48     double  freq;
49     double  gain;
50 } GainEntry;
51
52 typedef struct {
53     int buf_idx;
54     int overlap_idx;
55 } OverlapIndex;
56
57 typedef struct {
58     const AVClass *class;
59
60     RDFTContext   *analysis_irdft;
61     RDFTContext   *rdft;
62     RDFTContext   *irdft;
63     int           analysis_rdft_len;
64     int           rdft_len;
65
66     float         *analysis_buf;
67     float         *kernel_tmp_buf;
68     float         *kernel_buf;
69     float         *conv_buf;
70     OverlapIndex  *conv_idx;
71     int           fir_len;
72     int           nsamples_max;
73     int64_t       next_pts;
74     int           frame_nsamples_max;
75     int           remaining;
76
77     char          *gain_cmd;
78     char          *gain_entry_cmd;
79     const char    *gain;
80     const char    *gain_entry;
81     double        delay;
82     double        accuracy;
83     int           wfunc;
84     int           fixed;
85     int           multi;
86     int           zero_phase;
87
88     int           nb_gain_entry;
89     int           gain_entry_err;
90     GainEntry     gain_entry_tbl[NB_GAIN_ENTRY_MAX];
91 } FIREqualizerContext;
92
93 #define OFFSET(x) offsetof(FIREqualizerContext, x)
94 #define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
95
96 static const AVOption firequalizer_options[] = {
97     { "gain", "set gain curve", OFFSET(gain), AV_OPT_TYPE_STRING, { .str = "gain_interpolate(f)" }, 0, 0, FLAGS },
98     { "gain_entry", "set gain entry", OFFSET(gain_entry), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
99     { "delay", "set delay", OFFSET(delay), AV_OPT_TYPE_DOUBLE, { .dbl = 0.01 }, 0.0, 1e10, FLAGS },
100     { "accuracy", "set accuracy", OFFSET(accuracy), AV_OPT_TYPE_DOUBLE, { .dbl = 5.0 }, 0.0, 1e10, FLAGS },
101     { "wfunc", "set window function", OFFSET(wfunc), AV_OPT_TYPE_INT, { .i64 = WFUNC_HANN }, 0, NB_WFUNC-1, FLAGS, "wfunc" },
102         { "rectangular", "rectangular window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_RECTANGULAR }, 0, 0, FLAGS, "wfunc" },
103         { "hann", "hann window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_HANN }, 0, 0, FLAGS, "wfunc" },
104         { "hamming", "hamming window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_HAMMING }, 0, 0, FLAGS, "wfunc" },
105         { "blackman", "blackman window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_BLACKMAN }, 0, 0, FLAGS, "wfunc" },
106         { "nuttall3", "3-term nuttall window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_NUTTALL3 }, 0, 0, FLAGS, "wfunc" },
107         { "mnuttall3", "minimum 3-term nuttall window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_MNUTTALL3 }, 0, 0, FLAGS, "wfunc" },
108         { "nuttall", "nuttall window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_NUTTALL }, 0, 0, FLAGS, "wfunc" },
109         { "bnuttall", "blackman-nuttall window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_BNUTTALL }, 0, 0, FLAGS, "wfunc" },
110         { "bharris", "blackman-harris window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_BHARRIS }, 0, 0, FLAGS, "wfunc" },
111         { "tukey", "tukey window", 0, AV_OPT_TYPE_CONST, { .i64 = WFUNC_TUKEY }, 0, 0, FLAGS, "wfunc" },
112     { "fixed", "set fixed frame samples", OFFSET(fixed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
113     { "multi", "set multi channels mode", OFFSET(multi), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
114     { "zero_phase", "set zero phase mode", OFFSET(zero_phase), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
115     { NULL }
116 };
117
118 AVFILTER_DEFINE_CLASS(firequalizer);
119
120 static void common_uninit(FIREqualizerContext *s)
121 {
122     av_rdft_end(s->analysis_irdft);
123     av_rdft_end(s->rdft);
124     av_rdft_end(s->irdft);
125     s->analysis_irdft = s->rdft = s->irdft = NULL;
126
127     av_freep(&s->analysis_buf);
128     av_freep(&s->kernel_tmp_buf);
129     av_freep(&s->kernel_buf);
130     av_freep(&s->conv_buf);
131     av_freep(&s->conv_idx);
132 }
133
134 static av_cold void uninit(AVFilterContext *ctx)
135 {
136     FIREqualizerContext *s = ctx->priv;
137
138     common_uninit(s);
139     av_freep(&s->gain_cmd);
140     av_freep(&s->gain_entry_cmd);
141 }
142
143 static int query_formats(AVFilterContext *ctx)
144 {
145     AVFilterChannelLayouts *layouts;
146     AVFilterFormats *formats;
147     static const enum AVSampleFormat sample_fmts[] = {
148         AV_SAMPLE_FMT_FLTP,
149         AV_SAMPLE_FMT_NONE
150     };
151     int ret;
152
153     layouts = ff_all_channel_counts();
154     if (!layouts)
155         return AVERROR(ENOMEM);
156     ret = ff_set_common_channel_layouts(ctx, layouts);
157     if (ret < 0)
158         return ret;
159
160     formats = ff_make_format_list(sample_fmts);
161     if (!formats)
162         return AVERROR(ENOMEM);
163     ret = ff_set_common_formats(ctx, formats);
164     if (ret < 0)
165         return ret;
166
167     formats = ff_all_samplerates();
168     if (!formats)
169         return AVERROR(ENOMEM);
170     return ff_set_common_samplerates(ctx, formats);
171 }
172
173 static void fast_convolute(FIREqualizerContext *s, const float *kernel_buf, float *conv_buf,
174                            OverlapIndex *idx, float *data, int nsamples)
175 {
176     if (nsamples <= s->nsamples_max) {
177         float *buf = conv_buf + idx->buf_idx * s->rdft_len;
178         float *obuf = conv_buf + !idx->buf_idx * s->rdft_len + idx->overlap_idx;
179         int k;
180
181         memcpy(buf, data, nsamples * sizeof(*data));
182         memset(buf + nsamples, 0, (s->rdft_len - nsamples) * sizeof(*data));
183         av_rdft_calc(s->rdft, buf);
184
185         buf[0] *= kernel_buf[0];
186         buf[1] *= kernel_buf[1];
187         for (k = 2; k < s->rdft_len; k += 2) {
188             float re, im;
189             re = buf[k] * kernel_buf[k] - buf[k+1] * kernel_buf[k+1];
190             im = buf[k] * kernel_buf[k+1] + buf[k+1] * kernel_buf[k];
191             buf[k] = re;
192             buf[k+1] = im;
193         }
194
195         av_rdft_calc(s->irdft, buf);
196         for (k = 0; k < s->rdft_len - idx->overlap_idx; k++)
197             buf[k] += obuf[k];
198         memcpy(data, buf, nsamples * sizeof(*data));
199         idx->buf_idx = !idx->buf_idx;
200         idx->overlap_idx = nsamples;
201     } else {
202         while (nsamples > s->nsamples_max * 2) {
203             fast_convolute(s, kernel_buf, conv_buf, idx, data, s->nsamples_max);
204             data += s->nsamples_max;
205             nsamples -= s->nsamples_max;
206         }
207         fast_convolute(s, kernel_buf, conv_buf, idx, data, nsamples/2);
208         fast_convolute(s, kernel_buf, conv_buf, idx, data + nsamples/2, nsamples - nsamples/2);
209     }
210 }
211
212 static double entry_func(void *p, double freq, double gain)
213 {
214     AVFilterContext *ctx = p;
215     FIREqualizerContext *s = ctx->priv;
216
217     if (s->nb_gain_entry >= NB_GAIN_ENTRY_MAX) {
218         av_log(ctx, AV_LOG_ERROR, "entry table overflow.\n");
219         s->gain_entry_err = AVERROR(EINVAL);
220         return 0;
221     }
222
223     if (isnan(freq)) {
224         av_log(ctx, AV_LOG_ERROR, "nan frequency (%g, %g).\n", freq, gain);
225         s->gain_entry_err = AVERROR(EINVAL);
226         return 0;
227     }
228
229     if (s->nb_gain_entry > 0 && freq <= s->gain_entry_tbl[s->nb_gain_entry - 1].freq) {
230         av_log(ctx, AV_LOG_ERROR, "unsorted frequency (%g, %g).\n", freq, gain);
231         s->gain_entry_err = AVERROR(EINVAL);
232         return 0;
233     }
234
235     s->gain_entry_tbl[s->nb_gain_entry].freq = freq;
236     s->gain_entry_tbl[s->nb_gain_entry].gain = gain;
237     s->nb_gain_entry++;
238     return 0;
239 }
240
241 static int gain_entry_compare(const void *key, const void *memb)
242 {
243     const double *freq = key;
244     const GainEntry *entry = memb;
245
246     if (*freq < entry[0].freq)
247         return -1;
248     if (*freq > entry[1].freq)
249         return 1;
250     return 0;
251 }
252
253 static double gain_interpolate_func(void *p, double freq)
254 {
255     AVFilterContext *ctx = p;
256     FIREqualizerContext *s = ctx->priv;
257     GainEntry *res;
258     double d0, d1, d;
259
260     if (isnan(freq))
261         return freq;
262
263     if (!s->nb_gain_entry)
264         return 0;
265
266     if (freq <= s->gain_entry_tbl[0].freq)
267         return s->gain_entry_tbl[0].gain;
268
269     if (freq >= s->gain_entry_tbl[s->nb_gain_entry-1].freq)
270         return s->gain_entry_tbl[s->nb_gain_entry-1].gain;
271
272     res = bsearch(&freq, &s->gain_entry_tbl, s->nb_gain_entry - 1, sizeof(*res), gain_entry_compare);
273     av_assert0(res);
274
275     d  = res[1].freq - res[0].freq;
276     d0 = freq - res[0].freq;
277     d1 = res[1].freq - freq;
278
279     if (d0 && d1)
280         return (d0 * res[1].gain + d1 * res[0].gain) / d;
281
282     if (d0)
283         return res[1].gain;
284
285     return res[0].gain;
286 }
287
288 static const char *const var_names[] = {
289     "f",
290     "sr",
291     "ch",
292     "chid",
293     "chs",
294     "chlayout",
295     NULL
296 };
297
298 enum VarOffset {
299     VAR_F,
300     VAR_SR,
301     VAR_CH,
302     VAR_CHID,
303     VAR_CHS,
304     VAR_CHLAYOUT,
305     VAR_NB
306 };
307
308 static int generate_kernel(AVFilterContext *ctx, const char *gain, const char *gain_entry)
309 {
310     FIREqualizerContext *s = ctx->priv;
311     AVFilterLink *inlink = ctx->inputs[0];
312     const char *gain_entry_func_names[] = { "entry", NULL };
313     const char *gain_func_names[] = { "gain_interpolate", NULL };
314     double (*gain_entry_funcs[])(void *, double, double) = { entry_func, NULL };
315     double (*gain_funcs[])(void *, double) = { gain_interpolate_func, NULL };
316     double vars[VAR_NB];
317     AVExpr *gain_expr;
318     int ret, k, center, ch;
319
320     s->nb_gain_entry = 0;
321     s->gain_entry_err = 0;
322     if (gain_entry) {
323         double result = 0.0;
324         ret = av_expr_parse_and_eval(&result, gain_entry, NULL, NULL, NULL, NULL,
325                                      gain_entry_func_names, gain_entry_funcs, ctx, 0, ctx);
326         if (ret < 0)
327             return ret;
328         if (s->gain_entry_err < 0)
329             return s->gain_entry_err;
330     }
331
332     av_log(ctx, AV_LOG_DEBUG, "nb_gain_entry = %d.\n", s->nb_gain_entry);
333
334     ret = av_expr_parse(&gain_expr, gain, var_names,
335                         gain_func_names, gain_funcs, NULL, NULL, 0, ctx);
336     if (ret < 0)
337         return ret;
338
339     vars[VAR_CHS] = inlink->channels;
340     vars[VAR_CHLAYOUT] = inlink->channel_layout;
341     vars[VAR_SR] = inlink->sample_rate;
342     for (ch = 0; ch < inlink->channels; ch++) {
343         vars[VAR_CH] = ch;
344         vars[VAR_CHID] = av_channel_layout_extract_channel(inlink->channel_layout, ch);
345         vars[VAR_F] = 0.0;
346         s->analysis_buf[0] = pow(10.0, 0.05 * av_expr_eval(gain_expr, vars, ctx));
347         vars[VAR_F] = 0.5 * inlink->sample_rate;
348         s->analysis_buf[1] = pow(10.0, 0.05 * av_expr_eval(gain_expr, vars, ctx));
349
350         for (k = 1; k < s->analysis_rdft_len/2; k++) {
351             vars[VAR_F] = k * ((double)inlink->sample_rate /(double)s->analysis_rdft_len);
352             s->analysis_buf[2*k] = pow(10.0, 0.05 * av_expr_eval(gain_expr, vars, ctx));
353             s->analysis_buf[2*k+1] = 0.0;
354         }
355
356         av_rdft_calc(s->analysis_irdft, s->analysis_buf);
357         center = s->fir_len / 2;
358
359         for (k = 0; k <= center; k++) {
360             double u = k * (M_PI/center);
361             double win;
362             switch (s->wfunc) {
363             case WFUNC_RECTANGULAR:
364                 win = 1.0;
365                 break;
366             case WFUNC_HANN:
367                 win = 0.5 + 0.5 * cos(u);
368                 break;
369             case WFUNC_HAMMING:
370                 win = 0.53836 + 0.46164 * cos(u);
371                 break;
372             case WFUNC_BLACKMAN:
373                 win = 0.42 + 0.5 * cos(u) + 0.08 * cos(2*u);
374                 break;
375             case WFUNC_NUTTALL3:
376                 win = 0.40897 + 0.5 * cos(u) + 0.09103 * cos(2*u);
377                 break;
378             case WFUNC_MNUTTALL3:
379                 win = 0.4243801 + 0.4973406 * cos(u) + 0.0782793 * cos(2*u);
380                 break;
381             case WFUNC_NUTTALL:
382                 win = 0.355768 + 0.487396 * cos(u) + 0.144232 * cos(2*u) + 0.012604 * cos(3*u);
383                 break;
384             case WFUNC_BNUTTALL:
385                 win = 0.3635819 + 0.4891775 * cos(u) + 0.1365995 * cos(2*u) + 0.0106411 * cos(3*u);
386                 break;
387             case WFUNC_BHARRIS:
388                 win = 0.35875 + 0.48829 * cos(u) + 0.14128 * cos(2*u) + 0.01168 * cos(3*u);
389                 break;
390             case WFUNC_TUKEY:
391                 win = (u <= 0.5 * M_PI) ? 1.0 : (0.5 + 0.5 * cos(2*u - M_PI));
392                 break;
393             default:
394                 av_assert0(0);
395             }
396             s->analysis_buf[k] *= (2.0/s->analysis_rdft_len) * (2.0/s->rdft_len) * win;
397         }
398
399         for (k = 0; k < center - k; k++) {
400             float tmp = s->analysis_buf[k];
401             s->analysis_buf[k] = s->analysis_buf[center - k];
402             s->analysis_buf[center - k] = tmp;
403         }
404
405         for (k = 1; k <= center; k++)
406             s->analysis_buf[center + k] = s->analysis_buf[center - k];
407
408         memset(s->analysis_buf + s->fir_len, 0, (s->rdft_len - s->fir_len) * sizeof(*s->analysis_buf));
409         av_rdft_calc(s->rdft, s->analysis_buf);
410
411         for (k = 0; k < s->rdft_len; k++) {
412             if (isnan(s->analysis_buf[k]) || isinf(s->analysis_buf[k])) {
413                 av_log(ctx, AV_LOG_ERROR, "filter kernel contains nan or infinity.\n");
414                 av_expr_free(gain_expr);
415                 return AVERROR(EINVAL);
416             }
417         }
418
419         memcpy(s->kernel_tmp_buf + ch * s->rdft_len, s->analysis_buf, s->rdft_len * sizeof(*s->analysis_buf));
420         if (!s->multi)
421             break;
422     }
423
424     memcpy(s->kernel_buf, s->kernel_tmp_buf, (s->multi ? inlink->channels : 1) * s->rdft_len * sizeof(*s->kernel_buf));
425     av_expr_free(gain_expr);
426     return 0;
427 }
428
429 static int config_input(AVFilterLink *inlink)
430 {
431     AVFilterContext *ctx = inlink->dst;
432     FIREqualizerContext *s = ctx->priv;
433     int rdft_bits;
434
435     common_uninit(s);
436
437     s->next_pts = 0;
438     s->frame_nsamples_max = 0;
439
440     s->fir_len = FFMAX(2 * (int)(inlink->sample_rate * s->delay) + 1, 3);
441     s->remaining = s->fir_len - 1;
442
443     for (rdft_bits = RDFT_BITS_MIN; rdft_bits <= RDFT_BITS_MAX; rdft_bits++) {
444         s->rdft_len = 1 << rdft_bits;
445         s->nsamples_max = s->rdft_len - s->fir_len + 1;
446         if (s->nsamples_max * 2 >= s->fir_len)
447             break;
448     }
449
450     if (rdft_bits > RDFT_BITS_MAX) {
451         av_log(ctx, AV_LOG_ERROR, "too large delay, please decrease it.\n");
452         return AVERROR(EINVAL);
453     }
454
455     if (!(s->rdft = av_rdft_init(rdft_bits, DFT_R2C)) || !(s->irdft = av_rdft_init(rdft_bits, IDFT_C2R)))
456         return AVERROR(ENOMEM);
457
458     for ( ; rdft_bits <= RDFT_BITS_MAX; rdft_bits++) {
459         s->analysis_rdft_len = 1 << rdft_bits;
460         if (inlink->sample_rate <= s->accuracy * s->analysis_rdft_len)
461             break;
462     }
463
464     if (rdft_bits > RDFT_BITS_MAX) {
465         av_log(ctx, AV_LOG_ERROR, "too small accuracy, please increase it.\n");
466         return AVERROR(EINVAL);
467     }
468
469     if (!(s->analysis_irdft = av_rdft_init(rdft_bits, IDFT_C2R)))
470         return AVERROR(ENOMEM);
471
472     s->analysis_buf = av_malloc_array(s->analysis_rdft_len, sizeof(*s->analysis_buf));
473     s->kernel_tmp_buf = av_malloc_array(s->rdft_len * (s->multi ? inlink->channels : 1), sizeof(*s->kernel_tmp_buf));
474     s->kernel_buf = av_malloc_array(s->rdft_len * (s->multi ? inlink->channels : 1), sizeof(*s->kernel_buf));
475     s->conv_buf   = av_calloc(2 * s->rdft_len * inlink->channels, sizeof(*s->conv_buf));
476     s->conv_idx   = av_calloc(inlink->channels, sizeof(*s->conv_idx));
477     if (!s->analysis_buf || !s->kernel_tmp_buf || !s->kernel_buf || !s->conv_buf || !s->conv_idx)
478         return AVERROR(ENOMEM);
479
480     av_log(ctx, AV_LOG_DEBUG, "sample_rate = %d, channels = %d, analysis_rdft_len = %d, rdft_len = %d, fir_len = %d, nsamples_max = %d.\n",
481            inlink->sample_rate, inlink->channels, s->analysis_rdft_len, s->rdft_len, s->fir_len, s->nsamples_max);
482
483     if (s->fixed)
484         inlink->min_samples = inlink->max_samples = inlink->partial_buf_size = s->nsamples_max;
485
486     return generate_kernel(ctx, s->gain_cmd ? s->gain_cmd : s->gain,
487                            s->gain_entry_cmd ? s->gain_entry_cmd : s->gain_entry);
488 }
489
490 static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
491 {
492     AVFilterContext *ctx = inlink->dst;
493     FIREqualizerContext *s = ctx->priv;
494     int ch;
495
496     for (ch = 0; ch < inlink->channels; ch++) {
497         fast_convolute(s, s->kernel_buf + (s->multi ? ch * s->rdft_len : 0),
498                        s->conv_buf + 2 * ch * s->rdft_len, s->conv_idx + ch,
499                        (float *) frame->extended_data[ch], frame->nb_samples);
500     }
501
502     s->next_pts = AV_NOPTS_VALUE;
503     if (frame->pts != AV_NOPTS_VALUE) {
504         s->next_pts = frame->pts + av_rescale_q(frame->nb_samples, av_make_q(1, inlink->sample_rate), inlink->time_base);
505         if (s->zero_phase)
506             frame->pts -= av_rescale_q(s->fir_len/2, av_make_q(1, inlink->sample_rate), inlink->time_base);
507     }
508     s->frame_nsamples_max = FFMAX(s->frame_nsamples_max, frame->nb_samples);
509     return ff_filter_frame(ctx->outputs[0], frame);
510 }
511
512 static int request_frame(AVFilterLink *outlink)
513 {
514     AVFilterContext *ctx = outlink->src;
515     FIREqualizerContext *s= ctx->priv;
516     int ret;
517
518     ret = ff_request_frame(ctx->inputs[0]);
519     if (ret == AVERROR_EOF && s->remaining > 0 && s->frame_nsamples_max > 0) {
520         AVFrame *frame = ff_get_audio_buffer(outlink, FFMIN(s->remaining, s->frame_nsamples_max));
521
522         if (!frame)
523             return AVERROR(ENOMEM);
524
525         av_samples_set_silence(frame->extended_data, 0, frame->nb_samples, outlink->channels, frame->format);
526         frame->pts = s->next_pts;
527         s->remaining -= frame->nb_samples;
528         ret = filter_frame(ctx->inputs[0], frame);
529     }
530
531     return ret;
532 }
533
534 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
535                            char *res, int res_len, int flags)
536 {
537     FIREqualizerContext *s = ctx->priv;
538     int ret = AVERROR(ENOSYS);
539
540     if (!strcmp(cmd, "gain")) {
541         char *gain_cmd;
542
543         gain_cmd = av_strdup(args);
544         if (!gain_cmd)
545             return AVERROR(ENOMEM);
546
547         ret = generate_kernel(ctx, gain_cmd, s->gain_entry_cmd ? s->gain_entry_cmd : s->gain_entry);
548         if (ret >= 0) {
549             av_freep(&s->gain_cmd);
550             s->gain_cmd = gain_cmd;
551         } else {
552             av_freep(&gain_cmd);
553         }
554     } else if (!strcmp(cmd, "gain_entry")) {
555         char *gain_entry_cmd;
556
557         gain_entry_cmd = av_strdup(args);
558         if (!gain_entry_cmd)
559             return AVERROR(ENOMEM);
560
561         ret = generate_kernel(ctx, s->gain_cmd ? s->gain_cmd : s->gain, gain_entry_cmd);
562         if (ret >= 0) {
563             av_freep(&s->gain_entry_cmd);
564             s->gain_entry_cmd = gain_entry_cmd;
565         } else {
566             av_freep(&gain_entry_cmd);
567         }
568     }
569
570     return ret;
571 }
572
573 static const AVFilterPad firequalizer_inputs[] = {
574     {
575         .name           = "default",
576         .config_props   = config_input,
577         .filter_frame   = filter_frame,
578         .type           = AVMEDIA_TYPE_AUDIO,
579         .needs_writable = 1,
580     },
581     { NULL }
582 };
583
584 static const AVFilterPad firequalizer_outputs[] = {
585     {
586         .name           = "default",
587         .request_frame  = request_frame,
588         .type           = AVMEDIA_TYPE_AUDIO,
589     },
590     { NULL }
591 };
592
593 AVFilter ff_af_firequalizer = {
594     .name               = "firequalizer",
595     .description        = NULL_IF_CONFIG_SMALL("Finite Impulse Response Equalizer."),
596     .uninit             = uninit,
597     .query_formats      = query_formats,
598     .process_command    = process_command,
599     .priv_size          = sizeof(FIREqualizerContext),
600     .inputs             = firequalizer_inputs,
601     .outputs            = firequalizer_outputs,
602     .priv_class         = &firequalizer_class,
603 };