Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Copyright (c) 2013 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 | |
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/avassert.h" | |
22 | #include "libavutil/avstring.h" | |
23 | #include "libavutil/opt.h" | |
24 | #include "libavutil/samplefmt.h" | |
25 | #include "avfilter.h" | |
26 | #include "audio.h" | |
27 | #include "internal.h" | |
28 | ||
29 | typedef struct AudioEchoContext { | |
30 | const AVClass *class; | |
31 | float in_gain, out_gain; | |
32 | char *delays, *decays; | |
33 | float *delay, *decay; | |
34 | int nb_echoes; | |
35 | int delay_index; | |
36 | uint8_t **delayptrs; | |
37 | int max_samples, fade_out; | |
38 | int *samples; | |
39 | int64_t next_pts; | |
40 | ||
41 | void (*echo_samples)(struct AudioEchoContext *ctx, uint8_t **delayptrs, | |
42 | uint8_t * const *src, uint8_t **dst, | |
43 | int nb_samples, int channels); | |
44 | } AudioEchoContext; | |
45 | ||
46 | #define OFFSET(x) offsetof(AudioEchoContext, x) | |
47 | #define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM | |
48 | ||
49 | static const AVOption aecho_options[] = { | |
50 | { "in_gain", "set signal input gain", OFFSET(in_gain), AV_OPT_TYPE_FLOAT, {.dbl=0.6}, 0, 1, A }, | |
51 | { "out_gain", "set signal output gain", OFFSET(out_gain), AV_OPT_TYPE_FLOAT, {.dbl=0.3}, 0, 1, A }, | |
52 | { "delays", "set list of signal delays", OFFSET(delays), AV_OPT_TYPE_STRING, {.str="1000"}, 0, 0, A }, | |
53 | { "decays", "set list of signal decays", OFFSET(decays), AV_OPT_TYPE_STRING, {.str="0.5"}, 0, 0, A }, | |
54 | { NULL } | |
55 | }; | |
56 | ||
57 | AVFILTER_DEFINE_CLASS(aecho); | |
58 | ||
59 | static void count_items(char *item_str, int *nb_items) | |
60 | { | |
61 | char *p; | |
62 | ||
63 | *nb_items = 1; | |
64 | for (p = item_str; *p; p++) { | |
65 | if (*p == '|') | |
66 | (*nb_items)++; | |
67 | } | |
68 | ||
69 | } | |
70 | ||
71 | static void fill_items(char *item_str, int *nb_items, float *items) | |
72 | { | |
73 | char *p, *saveptr = NULL; | |
74 | int i, new_nb_items = 0; | |
75 | ||
76 | p = item_str; | |
77 | for (i = 0; i < *nb_items; i++) { | |
78 | char *tstr = av_strtok(p, "|", &saveptr); | |
79 | p = NULL; | |
80 | new_nb_items += sscanf(tstr, "%f", &items[i]) == 1; | |
81 | } | |
82 | ||
83 | *nb_items = new_nb_items; | |
84 | } | |
85 | ||
86 | static av_cold void uninit(AVFilterContext *ctx) | |
87 | { | |
88 | AudioEchoContext *s = ctx->priv; | |
89 | ||
90 | av_freep(&s->delay); | |
91 | av_freep(&s->decay); | |
92 | av_freep(&s->samples); | |
93 | ||
94 | if (s->delayptrs) | |
95 | av_freep(&s->delayptrs[0]); | |
96 | av_freep(&s->delayptrs); | |
97 | } | |
98 | ||
99 | static av_cold int init(AVFilterContext *ctx) | |
100 | { | |
101 | AudioEchoContext *s = ctx->priv; | |
102 | int nb_delays, nb_decays, i; | |
103 | ||
104 | if (!s->delays || !s->decays) { | |
105 | av_log(ctx, AV_LOG_ERROR, "Missing delays and/or decays.\n"); | |
106 | return AVERROR(EINVAL); | |
107 | } | |
108 | ||
109 | count_items(s->delays, &nb_delays); | |
110 | count_items(s->decays, &nb_decays); | |
111 | ||
112 | s->delay = av_realloc_f(s->delay, nb_delays, sizeof(*s->delay)); | |
113 | s->decay = av_realloc_f(s->decay, nb_decays, sizeof(*s->decay)); | |
114 | if (!s->delay || !s->decay) | |
115 | return AVERROR(ENOMEM); | |
116 | ||
117 | fill_items(s->delays, &nb_delays, s->delay); | |
118 | fill_items(s->decays, &nb_decays, s->decay); | |
119 | ||
120 | if (nb_delays != nb_decays) { | |
121 | av_log(ctx, AV_LOG_ERROR, "Number of delays %d differs from number of decays %d.\n", nb_delays, nb_decays); | |
122 | return AVERROR(EINVAL); | |
123 | } | |
124 | ||
125 | s->nb_echoes = nb_delays; | |
126 | if (!s->nb_echoes) { | |
127 | av_log(ctx, AV_LOG_ERROR, "At least one decay & delay must be set.\n"); | |
128 | return AVERROR(EINVAL); | |
129 | } | |
130 | ||
131 | s->samples = av_realloc_f(s->samples, nb_delays, sizeof(*s->samples)); | |
132 | if (!s->samples) | |
133 | return AVERROR(ENOMEM); | |
134 | ||
135 | for (i = 0; i < nb_delays; i++) { | |
136 | if (s->delay[i] <= 0 || s->delay[i] > 90000) { | |
137 | av_log(ctx, AV_LOG_ERROR, "delay[%d]: %f is out of allowed range: (0, 90000]\n", i, s->delay[i]); | |
138 | return AVERROR(EINVAL); | |
139 | } | |
140 | if (s->decay[i] <= 0 || s->decay[i] > 1) { | |
141 | av_log(ctx, AV_LOG_ERROR, "decay[%d]: %f is out of allowed range: (0, 1]\n", i, s->decay[i]); | |
142 | return AVERROR(EINVAL); | |
143 | } | |
144 | } | |
145 | ||
146 | s->next_pts = AV_NOPTS_VALUE; | |
147 | ||
148 | av_log(ctx, AV_LOG_DEBUG, "nb_echoes:%d\n", s->nb_echoes); | |
149 | return 0; | |
150 | } | |
151 | ||
152 | static int query_formats(AVFilterContext *ctx) | |
153 | { | |
154 | AVFilterChannelLayouts *layouts; | |
155 | AVFilterFormats *formats; | |
156 | static const enum AVSampleFormat sample_fmts[] = { | |
157 | AV_SAMPLE_FMT_S16P, AV_SAMPLE_FMT_S32P, | |
158 | AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_DBLP, | |
159 | AV_SAMPLE_FMT_NONE | |
160 | }; | |
161 | ||
162 | layouts = ff_all_channel_layouts(); | |
163 | if (!layouts) | |
164 | return AVERROR(ENOMEM); | |
165 | ff_set_common_channel_layouts(ctx, layouts); | |
166 | ||
167 | formats = ff_make_format_list(sample_fmts); | |
168 | if (!formats) | |
169 | return AVERROR(ENOMEM); | |
170 | ff_set_common_formats(ctx, formats); | |
171 | ||
172 | formats = ff_all_samplerates(); | |
173 | if (!formats) | |
174 | return AVERROR(ENOMEM); | |
175 | ff_set_common_samplerates(ctx, formats); | |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
180 | #define MOD(a, b) (((a) >= (b)) ? (a) - (b) : (a)) | |
181 | ||
182 | #define ECHO(name, type, min, max) \ | |
183 | static void echo_samples_## name ##p(AudioEchoContext *ctx, \ | |
184 | uint8_t **delayptrs, \ | |
185 | uint8_t * const *src, uint8_t **dst, \ | |
186 | int nb_samples, int channels) \ | |
187 | { \ | |
188 | const double out_gain = ctx->out_gain; \ | |
189 | const double in_gain = ctx->in_gain; \ | |
190 | const int nb_echoes = ctx->nb_echoes; \ | |
191 | const int max_samples = ctx->max_samples; \ | |
192 | int i, j, chan, av_uninit(index); \ | |
193 | \ | |
194 | av_assert1(channels > 0); /* would corrupt delay_index */ \ | |
195 | \ | |
196 | for (chan = 0; chan < channels; chan++) { \ | |
197 | const type *s = (type *)src[chan]; \ | |
198 | type *d = (type *)dst[chan]; \ | |
199 | type *dbuf = (type *)delayptrs[chan]; \ | |
200 | \ | |
201 | index = ctx->delay_index; \ | |
202 | for (i = 0; i < nb_samples; i++, s++, d++) { \ | |
203 | double out, in; \ | |
204 | \ | |
205 | in = *s; \ | |
206 | out = in * in_gain; \ | |
207 | for (j = 0; j < nb_echoes; j++) { \ | |
208 | int ix = index + max_samples - ctx->samples[j]; \ | |
209 | ix = MOD(ix, max_samples); \ | |
210 | out += dbuf[ix] * ctx->decay[j]; \ | |
211 | } \ | |
212 | out *= out_gain; \ | |
213 | \ | |
214 | *d = av_clipd(out, min, max); \ | |
215 | dbuf[index] = in; \ | |
216 | \ | |
217 | index = MOD(index + 1, max_samples); \ | |
218 | } \ | |
219 | } \ | |
220 | ctx->delay_index = index; \ | |
221 | } | |
222 | ||
223 | ECHO(dbl, double, -1.0, 1.0 ) | |
224 | ECHO(flt, float, -1.0, 1.0 ) | |
225 | ECHO(s16, int16_t, INT16_MIN, INT16_MAX) | |
226 | ECHO(s32, int32_t, INT32_MIN, INT32_MAX) | |
227 | ||
228 | static int config_output(AVFilterLink *outlink) | |
229 | { | |
230 | AVFilterContext *ctx = outlink->src; | |
231 | AudioEchoContext *s = ctx->priv; | |
232 | float volume = 1.0; | |
233 | int i; | |
234 | ||
235 | for (i = 0; i < s->nb_echoes; i++) { | |
236 | s->samples[i] = s->delay[i] * outlink->sample_rate / 1000.0; | |
237 | s->max_samples = FFMAX(s->max_samples, s->samples[i]); | |
238 | volume += s->decay[i]; | |
239 | } | |
240 | ||
241 | if (s->max_samples <= 0) { | |
242 | av_log(ctx, AV_LOG_ERROR, "Nothing to echo - missing delay samples.\n"); | |
243 | return AVERROR(EINVAL); | |
244 | } | |
245 | s->fade_out = s->max_samples; | |
246 | ||
247 | if (volume * s->in_gain * s->out_gain > 1.0) | |
248 | av_log(ctx, AV_LOG_WARNING, | |
249 | "out_gain %f can cause saturation of output\n", s->out_gain); | |
250 | ||
251 | switch (outlink->format) { | |
252 | case AV_SAMPLE_FMT_DBLP: s->echo_samples = echo_samples_dblp; break; | |
253 | case AV_SAMPLE_FMT_FLTP: s->echo_samples = echo_samples_fltp; break; | |
254 | case AV_SAMPLE_FMT_S16P: s->echo_samples = echo_samples_s16p; break; | |
255 | case AV_SAMPLE_FMT_S32P: s->echo_samples = echo_samples_s32p; break; | |
256 | } | |
257 | ||
258 | ||
259 | if (s->delayptrs) | |
260 | av_freep(&s->delayptrs[0]); | |
261 | av_freep(&s->delayptrs); | |
262 | ||
263 | return av_samples_alloc_array_and_samples(&s->delayptrs, NULL, | |
264 | outlink->channels, | |
265 | s->max_samples, | |
266 | outlink->format, 0); | |
267 | } | |
268 | ||
269 | static int filter_frame(AVFilterLink *inlink, AVFrame *frame) | |
270 | { | |
271 | AVFilterContext *ctx = inlink->dst; | |
272 | AudioEchoContext *s = ctx->priv; | |
273 | AVFrame *out_frame; | |
274 | ||
275 | if (av_frame_is_writable(frame)) { | |
276 | out_frame = frame; | |
277 | } else { | |
278 | out_frame = ff_get_audio_buffer(inlink, frame->nb_samples); | |
279 | if (!out_frame) | |
280 | return AVERROR(ENOMEM); | |
281 | av_frame_copy_props(out_frame, frame); | |
282 | } | |
283 | ||
284 | s->echo_samples(s, s->delayptrs, frame->extended_data, out_frame->extended_data, | |
285 | frame->nb_samples, inlink->channels); | |
286 | ||
287 | s->next_pts = frame->pts + av_rescale_q(frame->nb_samples, (AVRational){1, inlink->sample_rate}, inlink->time_base); | |
288 | ||
289 | if (frame != out_frame) | |
290 | av_frame_free(&frame); | |
291 | ||
292 | return ff_filter_frame(ctx->outputs[0], out_frame); | |
293 | } | |
294 | ||
295 | static int request_frame(AVFilterLink *outlink) | |
296 | { | |
297 | AVFilterContext *ctx = outlink->src; | |
298 | AudioEchoContext *s = ctx->priv; | |
299 | int ret; | |
300 | ||
301 | ret = ff_request_frame(ctx->inputs[0]); | |
302 | ||
303 | if (ret == AVERROR_EOF && !ctx->is_disabled && s->fade_out) { | |
304 | int nb_samples = FFMIN(s->fade_out, 2048); | |
305 | AVFrame *frame; | |
306 | ||
307 | frame = ff_get_audio_buffer(outlink, nb_samples); | |
308 | if (!frame) | |
309 | return AVERROR(ENOMEM); | |
310 | s->fade_out -= nb_samples; | |
311 | ||
312 | av_samples_set_silence(frame->extended_data, 0, | |
313 | frame->nb_samples, | |
314 | outlink->channels, | |
315 | frame->format); | |
316 | ||
317 | s->echo_samples(s, s->delayptrs, frame->extended_data, frame->extended_data, | |
318 | frame->nb_samples, outlink->channels); | |
319 | ||
320 | frame->pts = s->next_pts; | |
321 | if (s->next_pts != AV_NOPTS_VALUE) | |
322 | s->next_pts += av_rescale_q(nb_samples, (AVRational){1, outlink->sample_rate}, outlink->time_base); | |
323 | ||
324 | return ff_filter_frame(outlink, frame); | |
325 | } | |
326 | ||
327 | return ret; | |
328 | } | |
329 | ||
330 | static const AVFilterPad aecho_inputs[] = { | |
331 | { | |
332 | .name = "default", | |
333 | .type = AVMEDIA_TYPE_AUDIO, | |
334 | .filter_frame = filter_frame, | |
335 | }, | |
336 | { NULL } | |
337 | }; | |
338 | ||
339 | static const AVFilterPad aecho_outputs[] = { | |
340 | { | |
341 | .name = "default", | |
342 | .request_frame = request_frame, | |
343 | .config_props = config_output, | |
344 | .type = AVMEDIA_TYPE_AUDIO, | |
345 | }, | |
346 | { NULL } | |
347 | }; | |
348 | ||
349 | AVFilter ff_af_aecho = { | |
350 | .name = "aecho", | |
351 | .description = NULL_IF_CONFIG_SMALL("Add echoing to the audio."), | |
352 | .query_formats = query_formats, | |
353 | .priv_size = sizeof(AudioEchoContext), | |
354 | .priv_class = &aecho_class, | |
355 | .init = init, | |
356 | .uninit = uninit, | |
357 | .inputs = aecho_inputs, | |
358 | .outputs = aecho_outputs, | |
359 | }; |