]> git.sesse.net Git - ffmpeg/blob - libavfilter/vf_convolve.c
88ae884a19578e895cf7e75b2ccd03c12b621383
[ffmpeg] / libavfilter / vf_convolve.c
1 /*
2  * Copyright (c) 2017 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/imgutils.h"
22 #include "libavutil/opt.h"
23 #include "libavutil/pixdesc.h"
24 #include "libavcodec/avfft.h"
25
26 #include "avfilter.h"
27 #include "formats.h"
28 #include "framesync.h"
29 #include "internal.h"
30 #include "video.h"
31
32 #define MAX_THREADS 16
33
34 typedef struct ConvolveContext {
35     const AVClass *class;
36     FFFrameSync fs;
37
38     FFTContext *fft[4][MAX_THREADS];
39     FFTContext *ifft[4][MAX_THREADS];
40
41     int fft_bits[4];
42     int fft_len[4];
43     int planewidth[4];
44     int planeheight[4];
45
46     FFTComplex *fft_hdata[4];
47     FFTComplex *fft_vdata[4];
48     FFTComplex *fft_hdata_impulse[4];
49     FFTComplex *fft_vdata_impulse[4];
50
51     int depth;
52     int planes;
53     int impulse;
54     int nb_planes;
55     int got_impulse[4];
56 } ConvolveContext;
57
58 #define OFFSET(x) offsetof(ConvolveContext, x)
59 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
60
61 static const AVOption convolve_options[] = {
62     { "planes",  "set planes to convolve",                  OFFSET(planes),   AV_OPT_TYPE_INT,   {.i64=7}, 0, 15, FLAGS },
63     { "impulse", "when to process impulses",                OFFSET(impulse),  AV_OPT_TYPE_INT,   {.i64=1}, 0,  1, FLAGS, "impulse" },
64     {   "first", "process only first impulse, ignore rest", 0,                AV_OPT_TYPE_CONST, {.i64=0}, 0,  0, FLAGS, "impulse" },
65     {   "all",   "process all impulses",                    0,                AV_OPT_TYPE_CONST, {.i64=1}, 0,  0, FLAGS, "impulse" },
66     { NULL },
67 };
68
69 FRAMESYNC_DEFINE_CLASS(convolve, ConvolveContext, fs);
70
71 static int query_formats(AVFilterContext *ctx)
72 {
73     static const enum AVPixelFormat pixel_fmts_fftfilt[] = {
74         AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
75         AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
76         AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P,
77         AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
78         AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
79         AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
80         AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
81         AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12,
82         AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
83         AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
84         AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
85         AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
86         AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
87         AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10,
88         AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
89         AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
90         AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY16,
91         AV_PIX_FMT_NONE
92     };
93
94     AVFilterFormats *fmts_list = ff_make_format_list(pixel_fmts_fftfilt);
95     if (!fmts_list)
96         return AVERROR(ENOMEM);
97     return ff_set_common_formats(ctx, fmts_list);
98 }
99
100 static int config_input_main(AVFilterLink *inlink)
101 {
102     ConvolveContext *s = inlink->dst->priv;
103     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
104     int fft_bits, i;
105
106     s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
107     s->planewidth[0] = s->planewidth[3] = inlink->w;
108     s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
109     s->planeheight[0] = s->planeheight[3] = inlink->h;
110
111     s->nb_planes = desc->nb_components;
112     s->depth = desc->comp[0].depth;
113
114     for (i = 0; i < s->nb_planes; i++) {
115         int w = s->planewidth[i];
116         int h = s->planeheight[i];
117         int n = FFMAX(w, h);
118
119         for (fft_bits = 1; 1 << fft_bits < n; fft_bits++);
120
121         s->fft_bits[i] = fft_bits;
122         s->fft_len[i] = 1 << s->fft_bits[i];
123
124         if (!(s->fft_hdata[i] = av_calloc(s->fft_len[i] + 1, s->fft_len[i] * sizeof(FFTComplex))))
125             return AVERROR(ENOMEM);
126
127         if (!(s->fft_vdata[i] = av_calloc(s->fft_len[i] + 1, s->fft_len[i] * sizeof(FFTComplex))))
128             return AVERROR(ENOMEM);
129
130         if (!(s->fft_hdata_impulse[i] = av_calloc(s->fft_len[i] + 1, s->fft_len[i] * sizeof(FFTComplex))))
131             return AVERROR(ENOMEM);
132
133         if (!(s->fft_vdata_impulse[i] = av_calloc(s->fft_len[i] + 1, s->fft_len[i] * sizeof(FFTComplex))))
134             return AVERROR(ENOMEM);
135     }
136
137     return 0;
138 }
139
140 static int config_input_impulse(AVFilterLink *inlink)
141 {
142     AVFilterContext *ctx  = inlink->dst;
143
144     if (ctx->inputs[0]->w != ctx->inputs[1]->w ||
145         ctx->inputs[0]->h != ctx->inputs[1]->h) {
146         av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n");
147         return AVERROR(EINVAL);
148     }
149     if (ctx->inputs[0]->format != ctx->inputs[1]->format) {
150         av_log(ctx, AV_LOG_ERROR, "Inputs must be of same pixel format.\n");
151         return AVERROR(EINVAL);
152     }
153
154     return 0;
155 }
156
157 typedef struct ThreadData {
158     FFTComplex *hdata, *vdata;
159     int plane, n;
160 } ThreadData;
161
162 static int fft_horizontal(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
163 {
164     ConvolveContext *s = ctx->priv;
165     ThreadData *td = arg;
166     FFTComplex *hdata = td->hdata;
167     const int plane = td->plane;
168     const int n = td->n;
169     int start = (n *  jobnr   ) / nb_jobs;
170     int end = (n * (jobnr+1)) / nb_jobs;
171     int y;
172
173     for (y = start; y < end; y++) {
174         av_fft_permute(s->fft[plane][jobnr], hdata + y * n);
175         av_fft_calc(s->fft[plane][jobnr], hdata + y * n);
176     }
177
178     return 0;
179 }
180
181 static void get_input(ConvolveContext *s, FFTComplex *fft_hdata,
182                       AVFrame *in, int w, int h, int n, int plane, float scale)
183 {
184     const int iw = (n - w) / 2, ih = (n - h) / 2;
185     int y, x;
186
187     if (s->depth == 8) {
188         for (y = 0; y < h; y++) {
189             const uint8_t *src = in->data[plane] + in->linesize[plane] * y;
190
191             for (x = 0; x < w; x++) {
192                 fft_hdata[(y + ih) * n + iw + x].re = src[x] * scale;
193                 fft_hdata[(y + ih) * n + iw + x].im = 0;
194             }
195
196             for (x = 0; x < iw; x++) {
197                 fft_hdata[(y + ih) * n + x].re = fft_hdata[(y + ih) * n + iw].re;
198                 fft_hdata[(y + ih) * n + x].im = 0;
199             }
200
201             for (x = n - iw; x < n; x++) {
202                 fft_hdata[(y + ih) * n + x].re = fft_hdata[(y + ih) * n + n - iw - 1].re;
203                 fft_hdata[(y + ih) * n + x].im = 0;
204             }
205         }
206
207         for (y = 0; y < ih; y++) {
208             for (x = 0; x < n; x++) {
209                 fft_hdata[y * n + x].re = fft_hdata[ih * n + x].re;
210                 fft_hdata[y * n + x].im = 0;
211             }
212         }
213
214         for (y = n - ih; y < n; y++) {
215             for (x = 0; x < n; x++) {
216                 fft_hdata[y * n + x].re = fft_hdata[(n - ih - 1) * n + x].re;
217                 fft_hdata[y * n + x].im = 0;
218             }
219         }
220     } else {
221         for (y = 0; y < h; y++) {
222             const uint16_t *src = (const uint16_t *)(in->data[plane] + in->linesize[plane] * y);
223
224             for (x = 0; x < w; x++) {
225                 fft_hdata[(y + ih) * n + iw + x].re = src[x] * scale;
226                 fft_hdata[(y + ih) * n + iw + x].im = 0;
227             }
228
229             for (x = 0; x < iw; x++) {
230                 fft_hdata[(y + ih) * n + x].re = fft_hdata[(y + ih) * n + iw].re;
231                 fft_hdata[(y + ih) * n + x].im = 0;
232             }
233
234             for (x = n - iw; x < n; x++) {
235                 fft_hdata[(y + ih) * n + x].re = fft_hdata[(y + ih) * n + n - iw - 1].re;
236                 fft_hdata[(y + ih) * n + x].im = 0;
237             }
238         }
239
240         for (y = 0; y < ih; y++) {
241             for (x = 0; x < n; x++) {
242                 fft_hdata[y * n + x].re = fft_hdata[ih * n + x].re;
243                 fft_hdata[y * n + x].im = 0;
244             }
245         }
246
247         for (y = n - ih; y < n; y++) {
248             for (x = 0; x < n; x++) {
249                 fft_hdata[y * n + x].re = fft_hdata[(n - ih - 1) * n + x].re;
250                 fft_hdata[y * n + x].im = 0;
251             }
252         }
253     }
254 }
255
256 static int fft_vertical(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
257 {
258     ConvolveContext *s = ctx->priv;
259     ThreadData *td = arg;
260     FFTComplex *hdata = td->hdata;
261     FFTComplex *vdata = td->vdata;
262     const int plane = td->plane;
263     const int n = td->n;
264     int start = (n *  jobnr   ) / nb_jobs;
265     int end = (n * (jobnr+1)) / nb_jobs;
266     int y, x;
267
268     for (y = start; y < end; y++) {
269         for (x = 0; x < n; x++) {
270             vdata[y * n + x].re = hdata[x * n + y].re;
271             vdata[y * n + x].im = hdata[x * n + y].im;
272         }
273
274         av_fft_permute(s->fft[plane][jobnr], vdata + y * n);
275         av_fft_calc(s->fft[plane][jobnr], vdata + y * n);
276     }
277
278     return 0;
279 }
280
281 static int ifft_vertical(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
282 {
283     ConvolveContext *s = ctx->priv;
284     ThreadData *td = arg;
285     FFTComplex *hdata = td->hdata;
286     FFTComplex *vdata = td->vdata;
287     const int plane = td->plane;
288     const int n = td->n;
289     int start = (n *  jobnr   ) / nb_jobs;
290     int end = (n * (jobnr+1)) / nb_jobs;
291     int y, x;
292
293     for (y = start; y < end; y++) {
294         av_fft_permute(s->ifft[plane][jobnr], vdata + y * n);
295         av_fft_calc(s->ifft[plane][jobnr], vdata + y * n);
296
297         for (x = 0; x < n; x++) {
298             hdata[x * n + y].re = vdata[y * n + x].re;
299             hdata[x * n + y].im = vdata[y * n + x].im;
300         }
301     }
302
303     return 0;
304 }
305
306 static int ifft_horizontal(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
307 {
308     ConvolveContext *s = ctx->priv;
309     ThreadData *td = arg;
310     FFTComplex *hdata = td->hdata;
311     const int plane = td->plane;
312     const int n = td->n;
313     int start = (n *  jobnr   ) / nb_jobs;
314     int end = (n * (jobnr+1)) / nb_jobs;
315     int y;
316
317     for (y = start; y < end; y++) {
318         av_fft_permute(s->ifft[plane][jobnr], hdata + y * n);
319         av_fft_calc(s->ifft[plane][jobnr], hdata + y * n);
320     }
321
322     return 0;
323 }
324
325 static void get_output(ConvolveContext *s, AVFrame *out,
326                        int w, int h, int n, int plane)
327 {
328     FFTComplex *input = s->fft_hdata[plane];
329     const float scale = 1.f / (n * n);
330     const int max = (1 << s->depth) - 1;
331     const int hh = h / 2;
332     const int hw = w / 2;
333     int y, x;
334
335     if (s->depth == 8) {
336         for (y = 0; y < hh; y++) {
337             uint8_t *dst = out->data[plane] + (y + hh) * out->linesize[plane] + hw;
338             for (x = 0; x < hw; x++)
339                 dst[x] = av_clip_uint8(input[y * n + x].re * scale);
340         }
341         for (y = 0; y < hh; y++) {
342             uint8_t *dst = out->data[plane] + (y + hh) * out->linesize[plane];
343             for (x = 0; x < hw; x++)
344                 dst[x] = av_clip_uint8(input[y * n + n - hw + x].re * scale);
345         }
346         for (y = 0; y < hh; y++) {
347             uint8_t *dst = out->data[plane] + y * out->linesize[plane] + hw;
348             for (x = 0; x < hw; x++)
349                 dst[x] = av_clip_uint8(input[(n - hh + y) * n + x].re * scale);
350         }
351         for (y = 0; y < hh; y++) {
352             uint8_t *dst = out->data[plane] + y * out->linesize[plane];
353             for (x = 0; x < hw; x++)
354                 dst[x] = av_clip_uint8(input[(n - hh + y) * n + n - hw + x].re * scale);
355         }
356     } else {
357         for (y = 0; y < hh; y++) {
358             uint16_t *dst = (uint16_t *)(out->data[plane] + (y + hh) * out->linesize[plane] + hw * 2);
359             for (x = 0; x < hw; x++)
360                 dst[x] = av_clip(input[y * n + x].re * scale, 0, max);
361         }
362         for (y = 0; y < hh; y++) {
363             uint16_t *dst = (uint16_t *)(out->data[plane] + (y + hh) * out->linesize[plane]);
364             for (x = 0; x < hw; x++)
365                 dst[x] = av_clip(input[y * n + n - hw + x].re * scale, 0, max);
366         }
367         for (y = 0; y < hh; y++) {
368             uint16_t *dst = (uint16_t *)(out->data[plane] + y * out->linesize[plane] + hw * 2);
369             for (x = 0; x < hw; x++)
370                 dst[x] = av_clip(input[(n - hh + y) * n + x].re * scale, 0, max);
371         }
372         for (y = 0; y < hh; y++) {
373             uint16_t *dst = (uint16_t *)(out->data[plane] + y * out->linesize[plane]);
374             for (x = 0; x < hw; x++)
375                 dst[x] = av_clip(input[(n - hh + y) * n + n - hw + x].re * scale, 0, max);
376         }
377     }
378 }
379
380 static int complex_multiply(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
381 {
382     ThreadData *td = arg;
383     FFTComplex *input = td->hdata;
384     FFTComplex *filter = td->vdata;
385     const int n = td->n;
386     int start = (n *  jobnr   ) / nb_jobs;
387     int end = (n * (jobnr+1)) / nb_jobs;
388     int y, x;
389
390     for (y = start; y < end; y++) {
391         int yn = y * n;
392
393         for (x = 0; x < n; x++) {
394             FFTSample re, im, ire, iim;
395
396             re = input[yn + x].re;
397             im = input[yn + x].im;
398             ire = filter[yn + x].re;
399             iim = filter[yn + x].im;
400
401             input[yn + x].re = ire * re - iim * im;
402             input[yn + x].im = iim * re + ire * im;
403         }
404     }
405
406     return 0;
407 }
408
409 static int do_convolve(FFFrameSync *fs)
410 {
411     AVFilterContext *ctx = fs->parent;
412     AVFilterLink *outlink = ctx->outputs[0];
413     ConvolveContext *s = ctx->priv;
414     AVFrame *mainpic = NULL, *impulsepic = NULL;
415     int ret, y, x, plane;
416
417     ret = ff_framesync_dualinput_get(fs, &mainpic, &impulsepic);
418     if (ret < 0)
419         return ret;
420     if (!impulsepic)
421         return ff_filter_frame(outlink, mainpic);
422
423     for (plane = 0; plane < s->nb_planes; plane++) {
424         FFTComplex *filter = s->fft_vdata_impulse[plane];
425         FFTComplex *input = s->fft_vdata[plane];
426         const int n = s->fft_len[plane];
427         const int w = s->planewidth[plane];
428         const int h = s->planeheight[plane];
429         float total = 0;
430         ThreadData td;
431
432         if (!(s->planes & (1 << plane))) {
433             continue;
434         }
435
436         td.plane = plane, td.n = n;
437         get_input(s, s->fft_hdata[plane], mainpic, w, h, n, plane, 1.f);
438
439         td.hdata = s->fft_hdata[plane];
440         td.vdata = s->fft_vdata[plane];
441
442         ctx->internal->execute(ctx, fft_horizontal, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
443         ctx->internal->execute(ctx, fft_vertical, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
444
445         if ((!s->impulse && !s->got_impulse[plane]) || s->impulse) {
446             if (s->depth == 8) {
447                 for (y = 0; y < h; y++) {
448                     const uint8_t *src = (const uint8_t *)(impulsepic->data[plane] + y * impulsepic->linesize[plane]) ;
449                     for (x = 0; x < w; x++) {
450                         total += src[x];
451                     }
452                 }
453             } else {
454                 for (y = 0; y < h; y++) {
455                     const uint16_t *src = (const uint16_t *)(impulsepic->data[plane] + y * impulsepic->linesize[plane]) ;
456                     for (x = 0; x < w; x++) {
457                         total += src[x];
458                     }
459                 }
460             }
461             total = FFMAX(1, total);
462
463             get_input(s, s->fft_hdata_impulse[plane], impulsepic, w, h, n, plane, 1 / total);
464
465             td.hdata = s->fft_hdata_impulse[plane];
466             td.vdata = s->fft_vdata_impulse[plane];
467
468             ctx->internal->execute(ctx, fft_horizontal, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
469             ctx->internal->execute(ctx, fft_vertical, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
470
471             s->got_impulse[plane] = 1;
472         }
473
474         td.hdata = input;
475         td.vdata = filter;
476
477         ctx->internal->execute(ctx, complex_multiply, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
478
479         td.hdata = s->fft_hdata[plane];
480         td.vdata = s->fft_vdata[plane];
481
482         ctx->internal->execute(ctx, ifft_vertical, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
483         ctx->internal->execute(ctx, ifft_horizontal, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
484         get_output(s, mainpic, w, h, n, plane);
485     }
486
487     return ff_filter_frame(outlink, mainpic);
488 }
489
490 static int config_output(AVFilterLink *outlink)
491 {
492     AVFilterContext *ctx = outlink->src;
493     ConvolveContext *s = ctx->priv;
494     AVFilterLink *mainlink = ctx->inputs[0];
495     int ret, i, j;
496
497     s->fs.on_event = do_convolve;
498     ret = ff_framesync_init_dualinput(&s->fs, ctx);
499     if (ret < 0)
500         return ret;
501     outlink->w = mainlink->w;
502     outlink->h = mainlink->h;
503     outlink->time_base = mainlink->time_base;
504     outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio;
505     outlink->frame_rate = mainlink->frame_rate;
506
507     if ((ret = ff_framesync_configure(&s->fs)) < 0)
508         return ret;
509
510     for (i = 0; i < s->nb_planes; i++) {
511         for (j = 0; j < MAX_THREADS; j++) {
512             s->fft[i][j]  = av_fft_init(s->fft_bits[i], 0);
513             s->ifft[i][j] = av_fft_init(s->fft_bits[i], 1);
514             if (!s->fft[i][j] || !s->ifft[i][j])
515                 return AVERROR(ENOMEM);
516         }
517     }
518
519     return 0;
520 }
521
522 static int activate(AVFilterContext *ctx)
523 {
524     ConvolveContext *s = ctx->priv;
525     return ff_framesync_activate(&s->fs);
526 }
527
528 static av_cold void uninit(AVFilterContext *ctx)
529 {
530     ConvolveContext *s = ctx->priv;
531     int i, j;
532
533     for (i = 0; i < 4; i++) {
534         av_freep(&s->fft_hdata[i]);
535         av_freep(&s->fft_vdata[i]);
536         av_freep(&s->fft_hdata_impulse[i]);
537         av_freep(&s->fft_vdata_impulse[i]);
538
539         for (j = 0; j < MAX_THREADS; j++) {
540             av_fft_end(s->fft[i][j]);
541             av_fft_end(s->ifft[i][j]);
542         }
543     }
544
545     ff_framesync_uninit(&s->fs);
546 }
547
548 static const AVFilterPad convolve_inputs[] = {
549     {
550         .name          = "main",
551         .type          = AVMEDIA_TYPE_VIDEO,
552         .config_props  = config_input_main,
553     },{
554         .name          = "impulse",
555         .type          = AVMEDIA_TYPE_VIDEO,
556         .config_props  = config_input_impulse,
557     },
558     { NULL }
559 };
560
561 static const AVFilterPad convolve_outputs[] = {
562     {
563         .name          = "default",
564         .type          = AVMEDIA_TYPE_VIDEO,
565         .config_props  = config_output,
566     },
567     { NULL }
568 };
569
570 AVFilter ff_vf_convolve = {
571     .name          = "convolve",
572     .description   = NULL_IF_CONFIG_SMALL("Convolve first video stream with second video stream."),
573     .preinit       = convolve_framesync_preinit,
574     .uninit        = uninit,
575     .query_formats = query_formats,
576     .activate      = activate,
577     .priv_size     = sizeof(ConvolveContext),
578     .priv_class    = &convolve_class,
579     .inputs        = convolve_inputs,
580     .outputs       = convolve_outputs,
581     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | AVFILTER_FLAG_SLICE_THREADS,
582 };