Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Copyright (c) 2011 Stefano Sabatini | |
3 | * Copyright (c) 2012 Justin Ruggles <justin.ruggles@gmail.com> | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
11 | * | |
12 | * FFmpeg is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * Lesser General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU Lesser General Public | |
18 | * License along with FFmpeg; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | /** | |
23 | * @file | |
24 | * audio volume filter | |
25 | */ | |
26 | ||
27 | #include "libavutil/channel_layout.h" | |
28 | #include "libavutil/common.h" | |
29 | #include "libavutil/eval.h" | |
30 | #include "libavutil/float_dsp.h" | |
31 | #include "libavutil/intreadwrite.h" | |
32 | #include "libavutil/opt.h" | |
33 | #include "libavutil/replaygain.h" | |
34 | ||
35 | #include "audio.h" | |
36 | #include "avfilter.h" | |
37 | #include "formats.h" | |
38 | #include "internal.h" | |
39 | #include "af_volume.h" | |
40 | ||
41 | static const char * const precision_str[] = { | |
42 | "fixed", "float", "double" | |
43 | }; | |
44 | ||
45 | static const char *const var_names[] = { | |
46 | "n", ///< frame number (starting at zero) | |
47 | "nb_channels", ///< number of channels | |
48 | "nb_consumed_samples", ///< number of samples consumed by the filter | |
49 | "nb_samples", ///< number of samples in the current frame | |
50 | "pos", ///< position in the file of the frame | |
51 | "pts", ///< frame presentation timestamp | |
52 | "sample_rate", ///< sample rate | |
53 | "startpts", ///< PTS at start of stream | |
54 | "startt", ///< time at start of stream | |
55 | "t", ///< time in the file of the frame | |
56 | "tb", ///< timebase | |
57 | "volume", ///< last set value | |
58 | NULL | |
59 | }; | |
60 | ||
61 | #define OFFSET(x) offsetof(VolumeContext, x) | |
62 | #define A AV_OPT_FLAG_AUDIO_PARAM | |
63 | #define F AV_OPT_FLAG_FILTERING_PARAM | |
64 | ||
65 | static const AVOption volume_options[] = { | |
66 | { "volume", "set volume adjustment expression", | |
67 | OFFSET(volume_expr), AV_OPT_TYPE_STRING, { .str = "1.0" }, .flags = A|F }, | |
68 | { "precision", "select mathematical precision", | |
69 | OFFSET(precision), AV_OPT_TYPE_INT, { .i64 = PRECISION_FLOAT }, PRECISION_FIXED, PRECISION_DOUBLE, A|F, "precision" }, | |
70 | { "fixed", "select 8-bit fixed-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FIXED }, INT_MIN, INT_MAX, A|F, "precision" }, | |
71 | { "float", "select 32-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FLOAT }, INT_MIN, INT_MAX, A|F, "precision" }, | |
72 | { "double", "select 64-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_DOUBLE }, INT_MIN, INT_MAX, A|F, "precision" }, | |
73 | { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_ONCE}, 0, EVAL_MODE_NB-1, .flags = A|F, "eval" }, | |
74 | { "once", "eval volume expression once", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_ONCE}, .flags = A|F, .unit = "eval" }, | |
75 | { "frame", "eval volume expression per-frame", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = A|F, .unit = "eval" }, | |
76 | { "replaygain", "Apply replaygain side data when present", | |
77 | OFFSET(replaygain), AV_OPT_TYPE_INT, { .i64 = REPLAYGAIN_DROP }, REPLAYGAIN_DROP, REPLAYGAIN_ALBUM, A, "replaygain" }, | |
78 | { "drop", "replaygain side data is dropped", 0, AV_OPT_TYPE_CONST, { .i64 = REPLAYGAIN_DROP }, 0, 0, A, "replaygain" }, | |
79 | { "ignore", "replaygain side data is ignored", 0, AV_OPT_TYPE_CONST, { .i64 = REPLAYGAIN_IGNORE }, 0, 0, A, "replaygain" }, | |
80 | { "track", "track gain is preferred", 0, AV_OPT_TYPE_CONST, { .i64 = REPLAYGAIN_TRACK }, 0, 0, A, "replaygain" }, | |
81 | { "album", "album gain is preferred", 0, AV_OPT_TYPE_CONST, { .i64 = REPLAYGAIN_ALBUM }, 0, 0, A, "replaygain" }, | |
82 | { "replaygain_preamp", "Apply replaygain pre-amplification", | |
83 | OFFSET(replaygain_preamp), AV_OPT_TYPE_DOUBLE, { .dbl = 0.0 }, -15.0, 15.0, A }, | |
84 | { "replaygain_noclip", "Apply replaygain clipping prevention", | |
85 | OFFSET(replaygain_noclip), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, A }, | |
86 | { NULL }, | |
87 | }; | |
88 | ||
89 | AVFILTER_DEFINE_CLASS(volume); | |
90 | ||
91 | static int set_expr(AVExpr **pexpr, const char *expr, void *log_ctx) | |
92 | { | |
93 | int ret; | |
94 | AVExpr *old = NULL; | |
95 | ||
96 | if (*pexpr) | |
97 | old = *pexpr; | |
98 | ret = av_expr_parse(pexpr, expr, var_names, | |
99 | NULL, NULL, NULL, NULL, 0, log_ctx); | |
100 | if (ret < 0) { | |
101 | av_log(log_ctx, AV_LOG_ERROR, | |
102 | "Error when evaluating the volume expression '%s'\n", expr); | |
103 | *pexpr = old; | |
104 | return ret; | |
105 | } | |
106 | ||
107 | av_expr_free(old); | |
108 | return 0; | |
109 | } | |
110 | ||
111 | static av_cold int init(AVFilterContext *ctx) | |
112 | { | |
113 | VolumeContext *vol = ctx->priv; | |
f6fa7814 DM |
114 | |
115 | vol->fdsp = avpriv_float_dsp_alloc(0); | |
116 | if (!vol->fdsp) | |
117 | return AVERROR(ENOMEM); | |
118 | ||
2ba45a60 DM |
119 | return set_expr(&vol->volume_pexpr, vol->volume_expr, ctx); |
120 | } | |
121 | ||
122 | static av_cold void uninit(AVFilterContext *ctx) | |
123 | { | |
124 | VolumeContext *vol = ctx->priv; | |
125 | av_expr_free(vol->volume_pexpr); | |
126 | av_opt_free(vol); | |
f6fa7814 | 127 | av_freep(&vol->fdsp); |
2ba45a60 DM |
128 | } |
129 | ||
130 | static int query_formats(AVFilterContext *ctx) | |
131 | { | |
132 | VolumeContext *vol = ctx->priv; | |
133 | AVFilterFormats *formats = NULL; | |
134 | AVFilterChannelLayouts *layouts; | |
135 | static const enum AVSampleFormat sample_fmts[][7] = { | |
136 | [PRECISION_FIXED] = { | |
137 | AV_SAMPLE_FMT_U8, | |
138 | AV_SAMPLE_FMT_U8P, | |
139 | AV_SAMPLE_FMT_S16, | |
140 | AV_SAMPLE_FMT_S16P, | |
141 | AV_SAMPLE_FMT_S32, | |
142 | AV_SAMPLE_FMT_S32P, | |
143 | AV_SAMPLE_FMT_NONE | |
144 | }, | |
145 | [PRECISION_FLOAT] = { | |
146 | AV_SAMPLE_FMT_FLT, | |
147 | AV_SAMPLE_FMT_FLTP, | |
148 | AV_SAMPLE_FMT_NONE | |
149 | }, | |
150 | [PRECISION_DOUBLE] = { | |
151 | AV_SAMPLE_FMT_DBL, | |
152 | AV_SAMPLE_FMT_DBLP, | |
153 | AV_SAMPLE_FMT_NONE | |
154 | } | |
155 | }; | |
156 | ||
157 | layouts = ff_all_channel_counts(); | |
158 | if (!layouts) | |
159 | return AVERROR(ENOMEM); | |
160 | ff_set_common_channel_layouts(ctx, layouts); | |
161 | ||
162 | formats = ff_make_format_list(sample_fmts[vol->precision]); | |
163 | if (!formats) | |
164 | return AVERROR(ENOMEM); | |
165 | ff_set_common_formats(ctx, formats); | |
166 | ||
167 | formats = ff_all_samplerates(); | |
168 | if (!formats) | |
169 | return AVERROR(ENOMEM); | |
170 | ff_set_common_samplerates(ctx, formats); | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | static inline void scale_samples_u8(uint8_t *dst, const uint8_t *src, | |
176 | int nb_samples, int volume) | |
177 | { | |
178 | int i; | |
179 | for (i = 0; i < nb_samples; i++) | |
180 | dst[i] = av_clip_uint8(((((int64_t)src[i] - 128) * volume + 128) >> 8) + 128); | |
181 | } | |
182 | ||
183 | static inline void scale_samples_u8_small(uint8_t *dst, const uint8_t *src, | |
184 | int nb_samples, int volume) | |
185 | { | |
186 | int i; | |
187 | for (i = 0; i < nb_samples; i++) | |
188 | dst[i] = av_clip_uint8((((src[i] - 128) * volume + 128) >> 8) + 128); | |
189 | } | |
190 | ||
191 | static inline void scale_samples_s16(uint8_t *dst, const uint8_t *src, | |
192 | int nb_samples, int volume) | |
193 | { | |
194 | int i; | |
195 | int16_t *smp_dst = (int16_t *)dst; | |
196 | const int16_t *smp_src = (const int16_t *)src; | |
197 | for (i = 0; i < nb_samples; i++) | |
198 | smp_dst[i] = av_clip_int16(((int64_t)smp_src[i] * volume + 128) >> 8); | |
199 | } | |
200 | ||
201 | static inline void scale_samples_s16_small(uint8_t *dst, const uint8_t *src, | |
202 | int nb_samples, int volume) | |
203 | { | |
204 | int i; | |
205 | int16_t *smp_dst = (int16_t *)dst; | |
206 | const int16_t *smp_src = (const int16_t *)src; | |
207 | for (i = 0; i < nb_samples; i++) | |
208 | smp_dst[i] = av_clip_int16((smp_src[i] * volume + 128) >> 8); | |
209 | } | |
210 | ||
211 | static inline void scale_samples_s32(uint8_t *dst, const uint8_t *src, | |
212 | int nb_samples, int volume) | |
213 | { | |
214 | int i; | |
215 | int32_t *smp_dst = (int32_t *)dst; | |
216 | const int32_t *smp_src = (const int32_t *)src; | |
217 | for (i = 0; i < nb_samples; i++) | |
218 | smp_dst[i] = av_clipl_int32((((int64_t)smp_src[i] * volume + 128) >> 8)); | |
219 | } | |
220 | ||
221 | static av_cold void volume_init(VolumeContext *vol) | |
222 | { | |
223 | vol->samples_align = 1; | |
224 | ||
225 | switch (av_get_packed_sample_fmt(vol->sample_fmt)) { | |
226 | case AV_SAMPLE_FMT_U8: | |
227 | if (vol->volume_i < 0x1000000) | |
228 | vol->scale_samples = scale_samples_u8_small; | |
229 | else | |
230 | vol->scale_samples = scale_samples_u8; | |
231 | break; | |
232 | case AV_SAMPLE_FMT_S16: | |
233 | if (vol->volume_i < 0x10000) | |
234 | vol->scale_samples = scale_samples_s16_small; | |
235 | else | |
236 | vol->scale_samples = scale_samples_s16; | |
237 | break; | |
238 | case AV_SAMPLE_FMT_S32: | |
239 | vol->scale_samples = scale_samples_s32; | |
240 | break; | |
241 | case AV_SAMPLE_FMT_FLT: | |
2ba45a60 DM |
242 | vol->samples_align = 4; |
243 | break; | |
244 | case AV_SAMPLE_FMT_DBL: | |
2ba45a60 DM |
245 | vol->samples_align = 8; |
246 | break; | |
247 | } | |
248 | ||
249 | if (ARCH_X86) | |
250 | ff_volume_init_x86(vol); | |
251 | } | |
252 | ||
253 | static int set_volume(AVFilterContext *ctx) | |
254 | { | |
255 | VolumeContext *vol = ctx->priv; | |
256 | ||
257 | vol->volume = av_expr_eval(vol->volume_pexpr, vol->var_values, NULL); | |
258 | if (isnan(vol->volume)) { | |
259 | if (vol->eval_mode == EVAL_MODE_ONCE) { | |
260 | av_log(ctx, AV_LOG_ERROR, "Invalid value NaN for volume\n"); | |
261 | return AVERROR(EINVAL); | |
262 | } else { | |
263 | av_log(ctx, AV_LOG_WARNING, "Invalid value NaN for volume, setting to 0\n"); | |
264 | vol->volume = 0; | |
265 | } | |
266 | } | |
267 | vol->var_values[VAR_VOLUME] = vol->volume; | |
268 | ||
269 | av_log(ctx, AV_LOG_VERBOSE, "n:%f t:%f pts:%f precision:%s ", | |
270 | vol->var_values[VAR_N], vol->var_values[VAR_T], vol->var_values[VAR_PTS], | |
271 | precision_str[vol->precision]); | |
272 | ||
273 | if (vol->precision == PRECISION_FIXED) { | |
274 | vol->volume_i = (int)(vol->volume * 256 + 0.5); | |
275 | vol->volume = vol->volume_i / 256.0; | |
276 | av_log(ctx, AV_LOG_VERBOSE, "volume_i:%d/255 ", vol->volume_i); | |
277 | } | |
278 | av_log(ctx, AV_LOG_VERBOSE, "volume:%f volume_dB:%f\n", | |
279 | vol->volume, 20.0*log(vol->volume)/M_LN10); | |
280 | ||
281 | volume_init(vol); | |
282 | return 0; | |
283 | } | |
284 | ||
285 | static int config_output(AVFilterLink *outlink) | |
286 | { | |
287 | AVFilterContext *ctx = outlink->src; | |
288 | VolumeContext *vol = ctx->priv; | |
289 | AVFilterLink *inlink = ctx->inputs[0]; | |
290 | ||
291 | vol->sample_fmt = inlink->format; | |
292 | vol->channels = inlink->channels; | |
293 | vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1; | |
294 | ||
295 | vol->var_values[VAR_N] = | |
296 | vol->var_values[VAR_NB_CONSUMED_SAMPLES] = | |
297 | vol->var_values[VAR_NB_SAMPLES] = | |
298 | vol->var_values[VAR_POS] = | |
299 | vol->var_values[VAR_PTS] = | |
300 | vol->var_values[VAR_STARTPTS] = | |
301 | vol->var_values[VAR_STARTT] = | |
302 | vol->var_values[VAR_T] = | |
303 | vol->var_values[VAR_VOLUME] = NAN; | |
304 | ||
305 | vol->var_values[VAR_NB_CHANNELS] = inlink->channels; | |
306 | vol->var_values[VAR_TB] = av_q2d(inlink->time_base); | |
307 | vol->var_values[VAR_SAMPLE_RATE] = inlink->sample_rate; | |
308 | ||
309 | av_log(inlink->src, AV_LOG_VERBOSE, "tb:%f sample_rate:%f nb_channels:%f\n", | |
310 | vol->var_values[VAR_TB], | |
311 | vol->var_values[VAR_SAMPLE_RATE], | |
312 | vol->var_values[VAR_NB_CHANNELS]); | |
313 | ||
314 | return set_volume(ctx); | |
315 | } | |
316 | ||
317 | static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, | |
318 | char *res, int res_len, int flags) | |
319 | { | |
320 | VolumeContext *vol = ctx->priv; | |
321 | int ret = AVERROR(ENOSYS); | |
322 | ||
323 | if (!strcmp(cmd, "volume")) { | |
324 | if ((ret = set_expr(&vol->volume_pexpr, args, ctx)) < 0) | |
325 | return ret; | |
326 | if (vol->eval_mode == EVAL_MODE_ONCE) | |
327 | set_volume(ctx); | |
328 | } | |
329 | ||
330 | return ret; | |
331 | } | |
332 | ||
333 | #define D2TS(d) (isnan(d) ? AV_NOPTS_VALUE : (int64_t)(d)) | |
334 | #define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)) | |
335 | #define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts)*av_q2d(tb)) | |
336 | ||
337 | static int filter_frame(AVFilterLink *inlink, AVFrame *buf) | |
338 | { | |
339 | AVFilterContext *ctx = inlink->dst; | |
340 | VolumeContext *vol = inlink->dst->priv; | |
341 | AVFilterLink *outlink = inlink->dst->outputs[0]; | |
342 | int nb_samples = buf->nb_samples; | |
343 | AVFrame *out_buf; | |
344 | int64_t pos; | |
345 | AVFrameSideData *sd = av_frame_get_side_data(buf, AV_FRAME_DATA_REPLAYGAIN); | |
346 | int ret; | |
347 | ||
348 | if (sd && vol->replaygain != REPLAYGAIN_IGNORE) { | |
349 | if (vol->replaygain != REPLAYGAIN_DROP) { | |
350 | AVReplayGain *replaygain = (AVReplayGain*)sd->data; | |
351 | int32_t gain = 100000; | |
352 | uint32_t peak = 100000; | |
353 | float g, p; | |
354 | ||
355 | if (vol->replaygain == REPLAYGAIN_TRACK && | |
356 | replaygain->track_gain != INT32_MIN) { | |
357 | gain = replaygain->track_gain; | |
358 | ||
359 | if (replaygain->track_peak != 0) | |
360 | peak = replaygain->track_peak; | |
361 | } else if (replaygain->album_gain != INT32_MIN) { | |
362 | gain = replaygain->album_gain; | |
363 | ||
364 | if (replaygain->album_peak != 0) | |
365 | peak = replaygain->album_peak; | |
366 | } else { | |
367 | av_log(inlink->dst, AV_LOG_WARNING, "Both ReplayGain gain " | |
368 | "values are unknown.\n"); | |
369 | } | |
370 | g = gain / 100000.0f; | |
371 | p = peak / 100000.0f; | |
372 | ||
373 | av_log(inlink->dst, AV_LOG_VERBOSE, | |
374 | "Using gain %f dB from replaygain side data.\n", g); | |
375 | ||
376 | vol->volume = pow(10, (g + vol->replaygain_preamp) / 20); | |
377 | if (vol->replaygain_noclip) | |
378 | vol->volume = FFMIN(vol->volume, 1.0 / p); | |
379 | vol->volume_i = (int)(vol->volume * 256 + 0.5); | |
380 | ||
381 | volume_init(vol); | |
382 | } | |
383 | av_frame_remove_side_data(buf, AV_FRAME_DATA_REPLAYGAIN); | |
384 | } | |
385 | ||
386 | if (isnan(vol->var_values[VAR_STARTPTS])) { | |
387 | vol->var_values[VAR_STARTPTS] = TS2D(buf->pts); | |
388 | vol->var_values[VAR_STARTT ] = TS2T(buf->pts, inlink->time_base); | |
389 | } | |
390 | vol->var_values[VAR_PTS] = TS2D(buf->pts); | |
391 | vol->var_values[VAR_T ] = TS2T(buf->pts, inlink->time_base); | |
392 | vol->var_values[VAR_N ] = inlink->frame_count; | |
393 | ||
394 | pos = av_frame_get_pkt_pos(buf); | |
395 | vol->var_values[VAR_POS] = pos == -1 ? NAN : pos; | |
396 | if (vol->eval_mode == EVAL_MODE_FRAME) | |
397 | set_volume(ctx); | |
398 | ||
399 | if (vol->volume == 1.0 || vol->volume_i == 256) { | |
400 | out_buf = buf; | |
401 | goto end; | |
402 | } | |
403 | ||
404 | /* do volume scaling in-place if input buffer is writable */ | |
405 | if (av_frame_is_writable(buf)) { | |
406 | out_buf = buf; | |
407 | } else { | |
408 | out_buf = ff_get_audio_buffer(inlink, nb_samples); | |
409 | if (!out_buf) | |
410 | return AVERROR(ENOMEM); | |
411 | ret = av_frame_copy_props(out_buf, buf); | |
412 | if (ret < 0) { | |
413 | av_frame_free(&out_buf); | |
414 | av_frame_free(&buf); | |
415 | return ret; | |
416 | } | |
417 | } | |
418 | ||
419 | if (vol->precision != PRECISION_FIXED || vol->volume_i > 0) { | |
420 | int p, plane_samples; | |
421 | ||
422 | if (av_sample_fmt_is_planar(buf->format)) | |
423 | plane_samples = FFALIGN(nb_samples, vol->samples_align); | |
424 | else | |
425 | plane_samples = FFALIGN(nb_samples * vol->channels, vol->samples_align); | |
426 | ||
427 | if (vol->precision == PRECISION_FIXED) { | |
428 | for (p = 0; p < vol->planes; p++) { | |
429 | vol->scale_samples(out_buf->extended_data[p], | |
430 | buf->extended_data[p], plane_samples, | |
431 | vol->volume_i); | |
432 | } | |
433 | } else if (av_get_packed_sample_fmt(vol->sample_fmt) == AV_SAMPLE_FMT_FLT) { | |
434 | for (p = 0; p < vol->planes; p++) { | |
f6fa7814 | 435 | vol->fdsp->vector_fmul_scalar((float *)out_buf->extended_data[p], |
2ba45a60 DM |
436 | (const float *)buf->extended_data[p], |
437 | vol->volume, plane_samples); | |
438 | } | |
439 | } else { | |
440 | for (p = 0; p < vol->planes; p++) { | |
f6fa7814 | 441 | vol->fdsp->vector_dmul_scalar((double *)out_buf->extended_data[p], |
2ba45a60 DM |
442 | (const double *)buf->extended_data[p], |
443 | vol->volume, plane_samples); | |
444 | } | |
445 | } | |
446 | } | |
447 | ||
448 | emms_c(); | |
449 | ||
450 | if (buf != out_buf) | |
451 | av_frame_free(&buf); | |
452 | ||
453 | end: | |
454 | vol->var_values[VAR_NB_CONSUMED_SAMPLES] += out_buf->nb_samples; | |
455 | return ff_filter_frame(outlink, out_buf); | |
456 | } | |
457 | ||
458 | static const AVFilterPad avfilter_af_volume_inputs[] = { | |
459 | { | |
460 | .name = "default", | |
461 | .type = AVMEDIA_TYPE_AUDIO, | |
462 | .filter_frame = filter_frame, | |
463 | }, | |
464 | { NULL } | |
465 | }; | |
466 | ||
467 | static const AVFilterPad avfilter_af_volume_outputs[] = { | |
468 | { | |
469 | .name = "default", | |
470 | .type = AVMEDIA_TYPE_AUDIO, | |
471 | .config_props = config_output, | |
472 | }, | |
473 | { NULL } | |
474 | }; | |
475 | ||
476 | AVFilter ff_af_volume = { | |
477 | .name = "volume", | |
478 | .description = NULL_IF_CONFIG_SMALL("Change input volume."), | |
479 | .query_formats = query_formats, | |
480 | .priv_size = sizeof(VolumeContext), | |
481 | .priv_class = &volume_class, | |
482 | .init = init, | |
483 | .uninit = uninit, | |
484 | .inputs = avfilter_af_volume_inputs, | |
485 | .outputs = avfilter_af_volume_outputs, | |
486 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, | |
487 | .process_command = process_command, | |
488 | }; |