Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (C) 2001-2010 Krzysztof Foltman, Markus Schmidt, Thor Harald Johansen, Damien Zammit | ||
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 | /** | ||
22 | * @file | ||
23 | * Audio (Sidechain) Gate filter | ||
24 | */ | ||
25 | |||
26 | #include "config_components.h" | ||
27 | |||
28 | #include "libavutil/audio_fifo.h" | ||
29 | #include "libavutil/channel_layout.h" | ||
30 | #include "libavutil/opt.h" | ||
31 | #include "avfilter.h" | ||
32 | #include "audio.h" | ||
33 | #include "filters.h" | ||
34 | #include "formats.h" | ||
35 | #include "hermite.h" | ||
36 | |||
37 | typedef struct AudioGateContext { | ||
38 | const AVClass *class; | ||
39 | |||
40 | double level_in; | ||
41 | double level_sc; | ||
42 | double attack; | ||
43 | double release; | ||
44 | double threshold; | ||
45 | double ratio; | ||
46 | double knee; | ||
47 | double makeup; | ||
48 | double range; | ||
49 | int link; | ||
50 | int detection; | ||
51 | int mode; | ||
52 | |||
53 | double thres; | ||
54 | double knee_start; | ||
55 | double knee_stop; | ||
56 | double lin_knee_start; | ||
57 | double lin_knee_stop; | ||
58 | double lin_slope; | ||
59 | double attack_coeff; | ||
60 | double release_coeff; | ||
61 | |||
62 | AVAudioFifo *fifo[2]; | ||
63 | int64_t pts; | ||
64 | } AudioGateContext; | ||
65 | |||
66 | #define OFFSET(x) offsetof(AudioGateContext, x) | ||
67 | #define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM | ||
68 | |||
69 | static const AVOption options[] = { | ||
70 | { "level_in", "set input level", OFFSET(level_in), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.015625, 64, A }, | ||
71 | { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, A, .unit = "mode" }, | ||
72 | { "downward",0, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, A, .unit = "mode" }, | ||
73 | { "upward", 0, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, A, .unit = "mode" }, | ||
74 | { "range", "set max gain reduction", OFFSET(range), AV_OPT_TYPE_DOUBLE, {.dbl=0.06125}, 0, 1, A }, | ||
75 | { "threshold", "set threshold", OFFSET(threshold), AV_OPT_TYPE_DOUBLE, {.dbl=0.125}, 0, 1, A }, | ||
76 | { "ratio", "set ratio", OFFSET(ratio), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 1, 9000, A }, | ||
77 | { "attack", "set attack", OFFSET(attack), AV_OPT_TYPE_DOUBLE, {.dbl=20}, 0.01, 9000, A }, | ||
78 | { "release", "set release", OFFSET(release), AV_OPT_TYPE_DOUBLE, {.dbl=250}, 0.01, 9000, A }, | ||
79 | { "makeup", "set makeup gain", OFFSET(makeup), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 1, 64, A }, | ||
80 | { "knee", "set knee", OFFSET(knee), AV_OPT_TYPE_DOUBLE, {.dbl=2.828427125}, 1, 8, A }, | ||
81 | { "detection", "set detection", OFFSET(detection), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, A, .unit = "detection" }, | ||
82 | { "peak", 0, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, A, .unit = "detection" }, | ||
83 | { "rms", 0, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, A, .unit = "detection" }, | ||
84 | { "link", "set link", OFFSET(link), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, A, .unit = "link" }, | ||
85 | { "average", 0, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, A, .unit = "link" }, | ||
86 | { "maximum", 0, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, A, .unit = "link" }, | ||
87 | { "level_sc", "set sidechain gain", OFFSET(level_sc), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.015625, 64, A }, | ||
88 | { NULL } | ||
89 | }; | ||
90 | |||
91 | AVFILTER_DEFINE_CLASS_EXT(agate_sidechaingate, "agate/sidechaingate", options); | ||
92 | |||
93 | 1 | static int agate_config_input(AVFilterLink *inlink) | |
94 | { | ||
95 | 1 | AVFilterContext *ctx = inlink->dst; | |
96 | 1 | AudioGateContext *s = ctx->priv; | |
97 | 1 | double lin_threshold = s->threshold; | |
98 | 1 | double lin_knee_sqrt = sqrt(s->knee); | |
99 | |||
100 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (s->detection) |
101 | 1 | lin_threshold *= lin_threshold; | |
102 | |||
103 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | s->attack_coeff = FFMIN(1., 1. / (s->attack * inlink->sample_rate / 4000.)); |
104 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | s->release_coeff = FFMIN(1., 1. / (s->release * inlink->sample_rate / 4000.)); |
105 | 1 | s->lin_knee_stop = lin_threshold * lin_knee_sqrt; | |
106 | 1 | s->lin_knee_start = lin_threshold / lin_knee_sqrt; | |
107 | 1 | s->thres = log(lin_threshold); | |
108 | 1 | s->knee_start = log(s->lin_knee_start); | |
109 | 1 | s->knee_stop = log(s->lin_knee_stop); | |
110 | |||
111 | 1 | return 0; | |
112 | } | ||
113 | |||
114 | // A fake infinity value (because real infinity may break some hosts) | ||
115 | #define FAKE_INFINITY (65536.0 * 65536.0) | ||
116 | |||
117 | // Check for infinity (with appropriate-ish tolerance) | ||
118 | #define IS_FAKE_INFINITY(value) (fabs(value-FAKE_INFINITY) < 1.0) | ||
119 | |||
120 | 1 | static double output_gain(double lin_slope, double ratio, double thres, | |
121 | double knee, double knee_start, double knee_stop, | ||
122 | double range, int mode) | ||
123 | { | ||
124 | 1 | double slope = log(lin_slope); | |
125 | 1 | double tratio = ratio; | |
126 | 1 | double gain = 0.; | |
127 | 1 | double delta = 0.; | |
128 | |||
129 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (IS_FAKE_INFINITY(ratio)) |
130 | ✗ | tratio = 1000.; | |
131 | 1 | gain = (slope - thres) * tratio + thres; | |
132 | 1 | delta = tratio; | |
133 | |||
134 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (mode) { |
135 | ✗ | if (knee > 1. && slope < knee_stop) | |
136 | ✗ | gain = hermite_interpolation(slope, knee_stop, knee_start, ((knee_stop - thres) * tratio + thres), knee_start, delta, 1.); | |
137 | } else { | ||
138 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | if (knee > 1. && slope > knee_start) |
139 | ✗ | gain = hermite_interpolation(slope, knee_start, knee_stop, ((knee_start - thres) * tratio + thres), knee_stop, delta, 1.); | |
140 | } | ||
141 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | return FFMAX(range, exp(gain - slope)); |
142 | } | ||
143 | |||
144 | 65 | static void gate(AudioGateContext *s, | |
145 | const double *src, double *dst, const double *scsrc, | ||
146 | int nb_samples, double level_in, double level_sc, | ||
147 | AVFilterLink *inlink, AVFilterLink *sclink) | ||
148 | { | ||
149 | 65 | AVFilterContext *ctx = inlink->dst; | |
150 | 65 | const double makeup = s->makeup; | |
151 | 65 | const double attack_coeff = s->attack_coeff; | |
152 | 65 | const double release_coeff = s->release_coeff; | |
153 | int n, c; | ||
154 | |||
155 |
2/2✓ Branch 0 taken 264600 times.
✓ Branch 1 taken 65 times.
|
264665 | for (n = 0; n < nb_samples; n++, src += inlink->ch_layout.nb_channels, dst += inlink->ch_layout.nb_channels, scsrc += sclink->ch_layout.nb_channels) { |
156 | 264600 | double abs_sample = fabs(scsrc[0] * level_sc), gain = 1.0; | |
157 | double factor; | ||
158 | int detected; | ||
159 | |||
160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 264600 times.
|
264600 | if (s->link == 1) { |
161 | ✗ | for (c = 1; c < sclink->ch_layout.nb_channels; c++) | |
162 | ✗ | abs_sample = FFMAX(fabs(scsrc[c] * level_sc), abs_sample); | |
163 | } else { | ||
164 |
2/2✓ Branch 0 taken 264600 times.
✓ Branch 1 taken 264600 times.
|
529200 | for (c = 1; c < sclink->ch_layout.nb_channels; c++) |
165 | 264600 | abs_sample += fabs(scsrc[c] * level_sc); | |
166 | |||
167 | 264600 | abs_sample /= sclink->ch_layout.nb_channels; | |
168 | } | ||
169 | |||
170 |
1/2✓ Branch 0 taken 264600 times.
✗ Branch 1 not taken.
|
264600 | if (s->detection) |
171 | 264600 | abs_sample *= abs_sample; | |
172 | |||
173 |
2/2✓ Branch 0 taken 21902 times.
✓ Branch 1 taken 242698 times.
|
264600 | s->lin_slope += (abs_sample - s->lin_slope) * (abs_sample > s->lin_slope ? attack_coeff : release_coeff); |
174 | |||
175 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 264600 times.
|
264600 | if (s->mode) |
176 | ✗ | detected = s->lin_slope > s->lin_knee_start; | |
177 | else | ||
178 | 264600 | detected = s->lin_slope < s->lin_knee_stop; | |
179 | |||
180 |
3/4✓ Branch 0 taken 264600 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 264599 times.
|
264600 | if (s->lin_slope > 0.0 && detected) |
181 | 1 | gain = output_gain(s->lin_slope, s->ratio, s->thres, | |
182 | s->knee, s->knee_start, s->knee_stop, | ||
183 | s->range, s->mode); | ||
184 | |||
185 |
1/2✓ Branch 0 taken 264600 times.
✗ Branch 1 not taken.
|
264600 | factor = ctx->is_disabled ? 1.f : level_in * gain * makeup; |
186 |
2/2✓ Branch 0 taken 529200 times.
✓ Branch 1 taken 264600 times.
|
793800 | for (c = 0; c < inlink->ch_layout.nb_channels; c++) |
187 | 529200 | dst[c] = src[c] * factor; | |
188 | } | ||
189 | 65 | } | |
190 | |||
191 | #if CONFIG_AGATE_FILTER | ||
192 | |||
193 | 65 | static int filter_frame(AVFilterLink *inlink, AVFrame *in) | |
194 | { | ||
195 | 65 | const double *src = (const double *)in->data[0]; | |
196 | 65 | AVFilterContext *ctx = inlink->dst; | |
197 | 65 | AVFilterLink *outlink = ctx->outputs[0]; | |
198 | 65 | AudioGateContext *s = ctx->priv; | |
199 | AVFrame *out; | ||
200 | double *dst; | ||
201 | |||
202 |
1/2✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
|
65 | if (av_frame_is_writable(in)) { |
203 | 65 | out = in; | |
204 | } else { | ||
205 | ✗ | out = ff_get_audio_buffer(outlink, in->nb_samples); | |
206 | ✗ | if (!out) { | |
207 | ✗ | av_frame_free(&in); | |
208 | ✗ | return AVERROR(ENOMEM); | |
209 | } | ||
210 | ✗ | av_frame_copy_props(out, in); | |
211 | } | ||
212 | 65 | dst = (double *)out->data[0]; | |
213 | |||
214 | 65 | gate(s, src, dst, src, in->nb_samples, | |
215 | s->level_in, s->level_in, inlink, inlink); | ||
216 | |||
217 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
|
65 | if (out != in) |
218 | ✗ | av_frame_free(&in); | |
219 | 65 | return ff_filter_frame(outlink, out); | |
220 | } | ||
221 | |||
222 | static const AVFilterPad inputs[] = { | ||
223 | { | ||
224 | .name = "default", | ||
225 | .type = AVMEDIA_TYPE_AUDIO, | ||
226 | .filter_frame = filter_frame, | ||
227 | .config_props = agate_config_input, | ||
228 | }, | ||
229 | }; | ||
230 | |||
231 | const AVFilter ff_af_agate = { | ||
232 | .name = "agate", | ||
233 | .description = NULL_IF_CONFIG_SMALL("Audio gate."), | ||
234 | .priv_class = &agate_sidechaingate_class, | ||
235 | .priv_size = sizeof(AudioGateContext), | ||
236 | FILTER_INPUTS(inputs), | ||
237 | FILTER_OUTPUTS(ff_audio_default_filterpad), | ||
238 | FILTER_SINGLE_SAMPLEFMT(AV_SAMPLE_FMT_DBL), | ||
239 | .process_command = ff_filter_process_command, | ||
240 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, | ||
241 | }; | ||
242 | |||
243 | #endif /* CONFIG_AGATE_FILTER */ | ||
244 | |||
245 | #if CONFIG_SIDECHAINGATE_FILTER | ||
246 | |||
247 | ✗ | static int activate(AVFilterContext *ctx) | |
248 | { | ||
249 | ✗ | AudioGateContext *s = ctx->priv; | |
250 | ✗ | AVFrame *out = NULL, *in[2] = { NULL }; | |
251 | int ret, i, nb_samples; | ||
252 | double *dst; | ||
253 | |||
254 | ✗ | FF_FILTER_FORWARD_STATUS_BACK_ALL(ctx->outputs[0], ctx); | |
255 | ✗ | if ((ret = ff_inlink_consume_frame(ctx->inputs[0], &in[0])) > 0) { | |
256 | ✗ | av_audio_fifo_write(s->fifo[0], (void **)in[0]->extended_data, | |
257 | ✗ | in[0]->nb_samples); | |
258 | ✗ | av_frame_free(&in[0]); | |
259 | } | ||
260 | ✗ | if (ret < 0) | |
261 | ✗ | return ret; | |
262 | ✗ | if ((ret = ff_inlink_consume_frame(ctx->inputs[1], &in[1])) > 0) { | |
263 | ✗ | av_audio_fifo_write(s->fifo[1], (void **)in[1]->extended_data, | |
264 | ✗ | in[1]->nb_samples); | |
265 | ✗ | av_frame_free(&in[1]); | |
266 | } | ||
267 | ✗ | if (ret < 0) | |
268 | ✗ | return ret; | |
269 | |||
270 | ✗ | nb_samples = FFMIN(av_audio_fifo_size(s->fifo[0]), av_audio_fifo_size(s->fifo[1])); | |
271 | ✗ | if (nb_samples) { | |
272 | ✗ | out = ff_get_audio_buffer(ctx->outputs[0], nb_samples); | |
273 | ✗ | if (!out) | |
274 | ✗ | return AVERROR(ENOMEM); | |
275 | ✗ | for (i = 0; i < 2; i++) { | |
276 | ✗ | in[i] = ff_get_audio_buffer(ctx->inputs[i], nb_samples); | |
277 | ✗ | if (!in[i]) { | |
278 | ✗ | av_frame_free(&in[0]); | |
279 | ✗ | av_frame_free(&in[1]); | |
280 | ✗ | av_frame_free(&out); | |
281 | ✗ | return AVERROR(ENOMEM); | |
282 | } | ||
283 | ✗ | av_audio_fifo_read(s->fifo[i], (void **)in[i]->data, nb_samples); | |
284 | } | ||
285 | |||
286 | ✗ | dst = (double *)out->data[0]; | |
287 | ✗ | out->pts = s->pts; | |
288 | ✗ | s->pts += av_rescale_q(nb_samples, (AVRational){1, ctx->outputs[0]->sample_rate}, ctx->outputs[0]->time_base); | |
289 | |||
290 | ✗ | gate(s, (double *)in[0]->data[0], dst, | |
291 | ✗ | (double *)in[1]->data[0], nb_samples, | |
292 | s->level_in, s->level_sc, | ||
293 | ✗ | ctx->inputs[0], ctx->inputs[1]); | |
294 | |||
295 | ✗ | av_frame_free(&in[0]); | |
296 | ✗ | av_frame_free(&in[1]); | |
297 | |||
298 | ✗ | ret = ff_filter_frame(ctx->outputs[0], out); | |
299 | ✗ | if (ret < 0) | |
300 | ✗ | return ret; | |
301 | } | ||
302 | ✗ | FF_FILTER_FORWARD_STATUS(ctx->inputs[0], ctx->outputs[0]); | |
303 | ✗ | FF_FILTER_FORWARD_STATUS(ctx->inputs[1], ctx->outputs[0]); | |
304 | ✗ | if (ff_outlink_frame_wanted(ctx->outputs[0])) { | |
305 | ✗ | if (!av_audio_fifo_size(s->fifo[0])) | |
306 | ✗ | ff_inlink_request_frame(ctx->inputs[0]); | |
307 | ✗ | if (!av_audio_fifo_size(s->fifo[1])) | |
308 | ✗ | ff_inlink_request_frame(ctx->inputs[1]); | |
309 | } | ||
310 | ✗ | return 0; | |
311 | } | ||
312 | |||
313 | ✗ | static int scquery_formats(const AVFilterContext *ctx, | |
314 | AVFilterFormatsConfig **cfg_in, | ||
315 | AVFilterFormatsConfig **cfg_out) | ||
316 | { | ||
317 | static const enum AVSampleFormat sample_fmts[] = { | ||
318 | AV_SAMPLE_FMT_DBL, | ||
319 | AV_SAMPLE_FMT_NONE | ||
320 | }; | ||
321 | int ret; | ||
322 | |||
323 | /* Generic code will link the channel properties of the main input and the | ||
324 | * output; it won't touch the second input as its channel_layouts is already | ||
325 | * set. */ | ||
326 | ✗ | ret = ff_channel_layouts_ref(ff_all_channel_counts(), | |
327 | ✗ | &cfg_in[1]->channel_layouts); | |
328 | ✗ | if (ret < 0) | |
329 | ✗ | return ret; | |
330 | |||
331 | ✗ | if ((ret = ff_set_common_formats_from_list2(ctx, cfg_in, cfg_out, sample_fmts)) < 0) | |
332 | ✗ | return ret; | |
333 | |||
334 | ✗ | return 0; | |
335 | } | ||
336 | |||
337 | ✗ | static int scconfig_output(AVFilterLink *outlink) | |
338 | { | ||
339 | ✗ | AVFilterContext *ctx = outlink->src; | |
340 | ✗ | AudioGateContext *s = ctx->priv; | |
341 | |||
342 | ✗ | outlink->time_base = ctx->inputs[0]->time_base; | |
343 | |||
344 | ✗ | s->fifo[0] = av_audio_fifo_alloc(ctx->inputs[0]->format, ctx->inputs[0]->ch_layout.nb_channels, 1024); | |
345 | ✗ | s->fifo[1] = av_audio_fifo_alloc(ctx->inputs[1]->format, ctx->inputs[1]->ch_layout.nb_channels, 1024); | |
346 | ✗ | if (!s->fifo[0] || !s->fifo[1]) | |
347 | ✗ | return AVERROR(ENOMEM); | |
348 | |||
349 | |||
350 | ✗ | agate_config_input(ctx->inputs[0]); | |
351 | |||
352 | ✗ | return 0; | |
353 | } | ||
354 | |||
355 | ✗ | static av_cold void uninit(AVFilterContext *ctx) | |
356 | { | ||
357 | ✗ | AudioGateContext *s = ctx->priv; | |
358 | |||
359 | ✗ | av_audio_fifo_free(s->fifo[0]); | |
360 | ✗ | av_audio_fifo_free(s->fifo[1]); | |
361 | ✗ | } | |
362 | |||
363 | static const AVFilterPad sidechaingate_inputs[] = { | ||
364 | { | ||
365 | .name = "main", | ||
366 | .type = AVMEDIA_TYPE_AUDIO, | ||
367 | },{ | ||
368 | .name = "sidechain", | ||
369 | .type = AVMEDIA_TYPE_AUDIO, | ||
370 | }, | ||
371 | }; | ||
372 | |||
373 | static const AVFilterPad sidechaingate_outputs[] = { | ||
374 | { | ||
375 | .name = "default", | ||
376 | .type = AVMEDIA_TYPE_AUDIO, | ||
377 | .config_props = scconfig_output, | ||
378 | }, | ||
379 | }; | ||
380 | |||
381 | const AVFilter ff_af_sidechaingate = { | ||
382 | .name = "sidechaingate", | ||
383 | .description = NULL_IF_CONFIG_SMALL("Audio sidechain gate."), | ||
384 | .priv_class = &agate_sidechaingate_class, | ||
385 | .priv_size = sizeof(AudioGateContext), | ||
386 | .activate = activate, | ||
387 | .uninit = uninit, | ||
388 | FILTER_INPUTS(sidechaingate_inputs), | ||
389 | FILTER_OUTPUTS(sidechaingate_outputs), | ||
390 | FILTER_QUERY_FUNC2(scquery_formats), | ||
391 | .process_command = ff_filter_process_command, | ||
392 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL, | ||
393 | }; | ||
394 | #endif /* CONFIG_SIDECHAINGATE_FILTER */ | ||
395 |