History

avfilter: add dialogue enhance audio filter

Paul B Mahol committed 4 years ago

Commit f05c5291 parent 800dfd26 files · +441 −3Browse files

Unified · Split

6 files
  1. Changelog +2 −1
  2. doc/filters.texi +28 −0
  3. libavfilter/Makefile +1 −0
  4. libavfilter/af_dialoguenhance.c +407 −0
  5. libavfilter/allfilters.c +1 −0
  6. libavfilter/version.h +2 −2
Changelog+2−1
@@ -1,7 +1,8 @@
1Entries are sorted chronologically from oldest to youngest within each release,1Entries are sorted chronologically from oldest to youngest within each release,
2releases are sorted from youngest to oldest.2releases are sorted from youngest to oldest.
3 3
4version <next>:4version 5.1:
5- dialogue enhance audio filter
5 6
6 7
7version 5.0:8version 5.0:
doc/filters.texi+28−0
@@ -4182,6 +4182,34 @@ Default value is @var{o}. · 4181 unmodified lines
4182 4182
4183@end table4183@end table
4184 4184
4185@section dialoguenhance
4186Enhance dialogue in stereo audio.
4187
4188This filter accepts stereo input and produce surround (3.0) channels output.
4189The newly produced front center channel have enhanced speech dialogue originally
4190available in both stereo channels.
4191This filter outputs front left and front right channels same as available in stereo input.
4192
4193The filter accepts the following options:
4194
4195@table @option
4196@item original
4197Set the original center factor to keep in front center channel output.
4198Allowed range is from 0 to 1. Default value is 1.
4199
4200@item enhance
4201Set the dialogue enhance factor to put in front center channel output.
4202Allowed range is from 0 to 3. Default value is 1.
4203
4204@item voice
4205Set the voice detection factor.
4206Allowed range is from 2 to 32. Default value is 2.
4207@end table
4208
4209@subsection Commands
4210
4211This filter supports the all above options as @ref{commands}.
4212
4185@section drmeter4213@section drmeter
4186Measure audio dynamic range.4214Measure audio dynamic range.
4187 4215
libavfilter/Makefile+1−0
@@ -124,6 +124,7 @@ OBJS-$(CONFIG_CROSSFEED_FILTER) += af_crossfeed.o · 123 unmodified lines
124OBJS-$(CONFIG_CRYSTALIZER_FILTER) += af_crystalizer.o124OBJS-$(CONFIG_CRYSTALIZER_FILTER) += af_crystalizer.o
125OBJS-$(CONFIG_DCSHIFT_FILTER) += af_dcshift.o125OBJS-$(CONFIG_DCSHIFT_FILTER) += af_dcshift.o
126OBJS-$(CONFIG_DEESSER_FILTER) += af_deesser.o126OBJS-$(CONFIG_DEESSER_FILTER) += af_deesser.o
127OBJS-$(CONFIG_DIALOGUENHANCE_FILTER) += af_dialoguenhance.o
127OBJS-$(CONFIG_DRMETER_FILTER) += af_drmeter.o128OBJS-$(CONFIG_DRMETER_FILTER) += af_drmeter.o
128OBJS-$(CONFIG_DYNAUDNORM_FILTER) += af_dynaudnorm.o129OBJS-$(CONFIG_DYNAUDNORM_FILTER) += af_dynaudnorm.o
129OBJS-$(CONFIG_EARWAX_FILTER) += af_earwax.o130OBJS-$(CONFIG_EARWAX_FILTER) += af_earwax.o
libavfilter/af_dialoguenhance.c+407−0
@@ -0,0 +1,407 @@
1/*
2 * Copyright (c) 2022 Paul B Mahol
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 License
8 * 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
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21#include "libavutil/channel_layout.h"
22#include "libavutil/opt.h"
23#include "libavutil/tx.h"
24#include "audio.h"
25#include "avfilter.h"
26#include "filters.h"
27#include "internal.h"
28#include "window_func.h"
29
30#include <float.h>
31
32typedef struct AudioDialogueEnhancementContext {
33 const AVClass *class;
34
35 double original, enhance, voice;
36
37 int fft_size;
38 int overlap;
39
40 float *window;
41 float prev_vad;
42
43 AVFrame *in;
44 AVFrame *in_frame;
45 AVFrame *out_dist_frame;
46 AVFrame *windowed_frame;
47 AVFrame *windowed_out;
48 AVFrame *windowed_prev;
49 AVFrame *center_frame;
50
51 AVTXContext *tx_ctx[2], *itx_ctx;
52 av_tx_fn tx_fn, itx_fn;
53} AudioDialogueEnhanceContext;
54
55#define OFFSET(x) offsetof(AudioDialogueEnhanceContext, x)
56#define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_RUNTIME_PARAM
57
58static const AVOption dialoguenhance_options[] = {
59 { "original", "set original center factor", OFFSET(original), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 1, FLAGS },
60 { "enhance", "set dialogue enhance factor",OFFSET(enhance), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 3, FLAGS },
61 { "voice", "set voice detection factor", OFFSET(voice), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 2,32, FLAGS },
62 {NULL}
63};
64
65AVFILTER_DEFINE_CLASS(dialoguenhance);
66
67static int query_formats(AVFilterContext *ctx)
68{
69 AVFilterFormats *formats = NULL;
70 AVFilterChannelLayouts *in_layout = NULL, *out_layout = NULL;
71 int ret;
72
73 if ((ret = ff_add_format (&formats, AV_SAMPLE_FMT_FLTP )) < 0 ||
74 (ret = ff_set_common_formats (ctx , formats )) < 0 ||
75 (ret = ff_add_channel_layout (&in_layout , AV_CH_LAYOUT_STEREO)) < 0 ||
76 (ret = ff_channel_layouts_ref(in_layout, &ctx->inputs[0]->outcfg.channel_layouts)) < 0 ||
77 (ret = ff_add_channel_layout (&out_layout , AV_CH_LAYOUT_SURROUND)) < 0 ||
78 (ret = ff_channel_layouts_ref(out_layout, &ctx->outputs[0]->incfg.channel_layouts)) < 0)
79 return ret;
80
81 return ff_set_common_all_samplerates(ctx);
82}
83
84static int config_input(AVFilterLink *inlink)
85{
86 AVFilterContext *ctx = inlink->dst;
87 AudioDialogueEnhanceContext *s = ctx->priv;
88 float scale = 1.f, iscale, overlap;
89 int ret;
90
91 s->fft_size = inlink->sample_rate > 100000 ? 8192 : inlink->sample_rate > 50000 ? 4096 : 2048;
92 s->overlap = s->fft_size / 4;
93
94 s->window = av_calloc(s->fft_size, sizeof(*s->window));
95 if (!s->window)
96 return AVERROR(ENOMEM);
97
98 s->in_frame = ff_get_audio_buffer(inlink, s->fft_size * 4);
99 s->center_frame = ff_get_audio_buffer(inlink, s->fft_size * 4);
100 s->out_dist_frame = ff_get_audio_buffer(inlink, s->fft_size * 4);
101 s->windowed_frame = ff_get_audio_buffer(inlink, s->fft_size * 4);
102 s->windowed_out = ff_get_audio_buffer(inlink, s->fft_size * 4);
103 s->windowed_prev = ff_get_audio_buffer(inlink, s->fft_size * 4);
104 if (!s->in_frame || !s->windowed_out || !s->windowed_prev ||
105 !s->out_dist_frame || !s->windowed_frame || !s->center_frame)
106 return AVERROR(ENOMEM);
107
108 generate_window_func(s->window, s->fft_size, WFUNC_SINE, &overlap);
109
110 iscale = 1.f / s->fft_size;
111
112 ret = av_tx_init(&s->tx_ctx[0], &s->tx_fn, AV_TX_FLOAT_RDFT, 0, s->fft_size, &scale, 0);
113 if (ret < 0)
114 return ret;
115
116 ret = av_tx_init(&s->tx_ctx[1], &s->tx_fn, AV_TX_FLOAT_RDFT, 0, s->fft_size, &scale, 0);
117 if (ret < 0)
118 return ret;
119
120 ret = av_tx_init(&s->itx_ctx, &s->itx_fn, AV_TX_FLOAT_RDFT, 1, s->fft_size, &iscale, 0);
121 if (ret < 0)
122 return ret;
123
124 return 0;
125}
126
127static void apply_window(AudioDialogueEnhanceContext *s,
128 const float *in_frame, float *out_frame, const int add_to_out_frame)
129{
130 const float *window = s->window;
131
132 if (add_to_out_frame) {
133 for (int i = 0; i < s->fft_size; i++)
134 out_frame[i] += in_frame[i] * window[i];
135 } else {
136 for (int i = 0; i < s->fft_size; i++)
137 out_frame[i] = in_frame[i] * window[i];
138 }
139}
140
141static float sqrf(float x)
142{
143 return x * x;
144}
145
146static void get_centere(AVComplexFloat *left, AVComplexFloat *right,
147 AVComplexFloat *center, int N)
148{
149 for (int i = 0; i < N; i++) {
150 const float l_re = left[i].re;
151 const float l_im = left[i].im;
152 const float r_re = right[i].re;
153 const float r_im = right[i].im;
154 const float a = 0.5f * (1.f - sqrtf((sqrf(l_re - r_re) + sqrf(l_im - r_im))/
155 (sqrf(l_re + r_re) + sqrf(l_im + r_im) + FLT_EPSILON)));
156
157 center[i].re = a * (l_re + r_re);
158 center[i].im = a * (l_im + r_im);
159 }
160}
161
162static float flux(float *curf, float *prevf, int N)
163{
164 AVComplexFloat *cur = (AVComplexFloat *)curf;
165 AVComplexFloat *prev = (AVComplexFloat *)prevf;
166 float sum = 0.f;
167
168 for (int i = 0; i < N; i++) {
169 float c_re = cur[i].re;
170 float c_im = cur[i].im;
171 float p_re = prev[i].re;
172 float p_im = prev[i].im;
173
174 sum += sqrf(hypotf(c_re, c_im) - hypotf(p_re, p_im));
175 }
176
177 return sum;
178}
179
180static float fluxlr(float *lf, float *lpf,
181 float *rf, float *rpf,
182 int N)
183{
184 AVComplexFloat *l = (AVComplexFloat *)lf;
185 AVComplexFloat *lp = (AVComplexFloat *)lpf;
186 AVComplexFloat *r = (AVComplexFloat *)rf;
187 AVComplexFloat *rp = (AVComplexFloat *)rpf;
188 float sum = 0.f;
189
190 for (int i = 0; i < N; i++) {
191 float c_re = l[i].re - r[i].re;
192 float c_im = l[i].im - r[i].im;
193 float p_re = lp[i].re - rp[i].re;
194 float p_im = lp[i].im - rp[i].im;
195
196 sum += sqrf(hypotf(c_re, c_im) - hypotf(p_re, p_im));
197 }
198
199 return sum;
200}
201
202static float calc_vad(float fc, float flr, float a)
203{
204 const float vad = a * (fc / (fc + flr) - 0.5f);
205
206 return av_clipf(vad, 0.f, 1.f);
207}
208
209static void get_final(float *c, float *l,
210 float *r, float vad, int N,
211 float original, float enhance)
212{
213 AVComplexFloat *center = (AVComplexFloat *)c;
214 AVComplexFloat *left = (AVComplexFloat *)l;
215 AVComplexFloat *right = (AVComplexFloat *)r;
216
217 for (int i = 0; i < N; i++) {
218 float cP = sqrf(center[i].re) + sqrf(center[i].im);
219 float lrP = sqrf(left[i].re - right[i].re) + sqrf(left[i].im - right[i].im);
220 float G = cP / (cP + lrP + FLT_EPSILON);
221 float re, im;
222
223 re = center[i].re * (original + vad * G * enhance);
224 im = center[i].im * (original + vad * G * enhance);
225
226 center[i].re = re;
227 center[i].im = im;
228 }
229}
230
231static int de_stereo(AVFilterContext *ctx, AVFrame *out)
232{
233 AudioDialogueEnhanceContext *s = ctx->priv;
234 float *center = (float *)s->center_frame->extended_data[0];
235 float *center_prev = (float *)s->center_frame->extended_data[1];
236 float *left_in = (float *)s->in_frame->extended_data[0];
237 float *right_in = (float *)s->in_frame->extended_data[1];
238 float *left_out = (float *)s->out_dist_frame->extended_data[0];
239 float *right_out = (float *)s->out_dist_frame->extended_data[1];
240 float *left_samples = (float *)s->in->extended_data[0];
241 float *right_samples = (float *)s->in->extended_data[1];
242 float *windowed_left = (float *)s->windowed_frame->extended_data[0];
243 float *windowed_right = (float *)s->windowed_frame->extended_data[1];
244 float *windowed_oleft = (float *)s->windowed_out->extended_data[0];
245 float *windowed_oright = (float *)s->windowed_out->extended_data[1];
246 float *windowed_pleft = (float *)s->windowed_prev->extended_data[0];
247 float *windowed_pright = (float *)s->windowed_prev->extended_data[1];
248 float *left_osamples = (float *)out->extended_data[0];
249 float *right_osamples = (float *)out->extended_data[1];
250 float *center_osamples = (float *)out->extended_data[2];
251 const int offset = s->fft_size - s->overlap;
252 float vad;
253
254 // shift in/out buffers
255 memmove(left_in, &left_in[s->overlap], offset * sizeof(float));
256 memmove(right_in, &right_in[s->overlap], offset * sizeof(float));
257 memmove(left_out, &left_out[s->overlap], offset * sizeof(float));
258 memmove(right_out, &right_out[s->overlap], offset * sizeof(float));
259
260 memcpy(&left_in[offset], left_samples, s->overlap * sizeof(float));
261 memcpy(&right_in[offset], right_samples, s->overlap * sizeof(float));
262 memset(&left_out[offset], 0, s->overlap * sizeof(float));
263 memset(&right_out[offset], 0, s->overlap * sizeof(float));
264
265 apply_window(s, left_in, windowed_left, 0);
266 apply_window(s, right_in, windowed_right, 0);
267
268 s->tx_fn(s->tx_ctx[0], windowed_oleft, windowed_left, sizeof(float));
269 s->tx_fn(s->tx_ctx[1], windowed_oright, windowed_right, sizeof(float));
270
271 get_centere((AVComplexFloat *)windowed_oleft,
272 (AVComplexFloat *)windowed_oright,
273 (AVComplexFloat *)center,
274 s->fft_size / 2 + 1);
275
276 vad = calc_vad(flux(center, center_prev, s->fft_size / 2 + 1),
277 fluxlr(windowed_oleft, windowed_pleft,
278 windowed_oright, windowed_pright, s->fft_size / 2 + 1), s->voice);
279 vad = vad * 0.1 + 0.9 * s->prev_vad;
280 s->prev_vad = vad;
281
282 memcpy(center_prev, center, s->fft_size * sizeof(float));
283 memcpy(windowed_pleft, windowed_oleft, s->fft_size * sizeof(float));
284 memcpy(windowed_pright, windowed_oright, s->fft_size * sizeof(float));
285
286 get_final(center, windowed_oleft, windowed_oright, vad, s->fft_size / 2 + 1,
287 s->original, s->enhance);
288
289 s->itx_fn(s->itx_ctx, windowed_oleft, center, sizeof(float));
290
291 apply_window(s, windowed_oleft, left_out, 1);
292
293 for (int i = 0; i < s->overlap; i++) {
294 // 4 times overlap with squared hanning window results in 1.5 time increase in amplitude
295 if (!ctx->is_disabled)
296 center_osamples[i] = left_out[i] / 1.5f;
297 else
298 center_osamples[i] = 0.f;
299 left_osamples[i] = left_in[i];
300 right_osamples[i] = right_in[i];
301 }
302
303 return 0;
304}
305
306static int filter_frame(AVFilterLink *inlink, AVFrame *in)
307{
308 AVFilterContext *ctx = inlink->dst;
309 AVFilterLink *outlink = ctx->outputs[0];
310 AudioDialogueEnhanceContext *s = ctx->priv;
311 AVFrame *out;
312 int ret;
313
314 out = ff_get_audio_buffer(outlink, s->overlap);
315 if (!out) {
316 ret = AVERROR(ENOMEM);
317 goto fail;
318 }
319
320 s->in = in;
321 de_stereo(ctx, out);
322
323 out->pts = in->pts;
324 out->nb_samples = in->nb_samples;
325 ret = ff_filter_frame(outlink, out);
326fail:
327 av_frame_free(&in);
328 s->in = NULL;
329 return ret < 0 ? ret : 0;
330}
331
332static int activate(AVFilterContext *ctx)
333{
334 AVFilterLink *inlink = ctx->inputs[0];
335 AVFilterLink *outlink = ctx->outputs[0];
336 AudioDialogueEnhanceContext *s = ctx->priv;
337 AVFrame *in = NULL;
338 int ret = 0, status;
339 int64_t pts;
340
341 FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
342
343 ret = ff_inlink_consume_samples(inlink, s->overlap, s->overlap, &in);
344 if (ret < 0)
345 return ret;
346
347 if (ret > 0) {
348 return filter_frame(inlink, in);
349 } else if (ff_inlink_acknowledge_status(inlink, &status, &pts)) {
350 ff_outlink_set_status(outlink, status, pts);
351 return 0;
352 } else {
353 if (ff_inlink_queued_samples(inlink) >= s->overlap) {
354 ff_filter_set_ready(ctx, 10);
355 } else if (ff_outlink_frame_wanted(outlink)) {
356 ff_inlink_request_frame(inlink);
357 }
358 return 0;
359 }
360}
361
362static av_cold void uninit(AVFilterContext *ctx)
363{
364 AudioDialogueEnhanceContext *s = ctx->priv;
365
366 av_freep(&s->window);
367
368 av_frame_free(&s->in_frame);
369 av_frame_free(&s->center_frame);
370 av_frame_free(&s->out_dist_frame);
371 av_frame_free(&s->windowed_frame);
372 av_frame_free(&s->windowed_out);
373 av_frame_free(&s->windowed_prev);
374
375 av_tx_uninit(&s->tx_ctx[0]);
376 av_tx_uninit(&s->tx_ctx[1]);
377 av_tx_uninit(&s->itx_ctx);
378}
379
380static const AVFilterPad inputs[] = {
381 {
382 .name = "default",
383 .type = AVMEDIA_TYPE_AUDIO,
384 .config_props = config_input,
385 },
386};
387
388static const AVFilterPad outputs[] = {
389 {
390 .name = "default",
391 .type = AVMEDIA_TYPE_AUDIO,
392 },
393};
394
395const AVFilter ff_af_dialoguenhance = {
396 .name = "dialoguenhance",
397 .description = NULL_IF_CONFIG_SMALL("Audio Dialogue Enhancement."),
398 .priv_size = sizeof(AudioDialogueEnhanceContext),
399 .priv_class = &dialoguenhance_class,
400 .uninit = uninit,
401 FILTER_INPUTS(inputs),
402 FILTER_OUTPUTS(outputs),
403 FILTER_QUERY_FUNC(query_formats),
404 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
405 .activate = activate,
406 .process_command = ff_filter_process_command,
407};
libavfilter/allfilters.c+1−0
@@ -115,6 +115,7 @@ extern const AVFilter ff_af_crossfeed; · 114 unmodified lines
115extern const AVFilter ff_af_crystalizer;115extern const AVFilter ff_af_crystalizer;
116extern const AVFilter ff_af_dcshift;116extern const AVFilter ff_af_dcshift;
117extern const AVFilter ff_af_deesser;117extern const AVFilter ff_af_deesser;
118extern const AVFilter ff_af_dialoguenhance;
118extern const AVFilter ff_af_drmeter;119extern const AVFilter ff_af_drmeter;
119extern const AVFilter ff_af_dynaudnorm;120extern const AVFilter ff_af_dynaudnorm;
120extern const AVFilter ff_af_earwax;121extern const AVFilter ff_af_earwax;
libavfilter/version.h+2−2
@@ -30,8 +30,8 @@ · 29 unmodified lines
30#include "libavutil/version.h"30#include "libavutil/version.h"
31 31
32#define LIBAVFILTER_VERSION_MAJOR 832#define LIBAVFILTER_VERSION_MAJOR 8
33#define LIBAVFILTER_VERSION_MINOR 2633#define LIBAVFILTER_VERSION_MINOR 27
34#define LIBAVFILTER_VERSION_MICRO 10134#define LIBAVFILTER_VERSION_MICRO 100
35 35
36 36
37#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \37#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t