]> git.sesse.net Git - ffmpeg/blobdiff - libavfilter/vf_convolve.c
avfilter: use the buffer_size_t typedef where required
[ffmpeg] / libavfilter / vf_convolve.c
index 6eb954845650c1059e7caac7ef69490d64e59469..90ec98715bacbc2921301828431e4b552bba0ab4 100644 (file)
@@ -18,6 +18,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include <float.h>
+
 #include "libavutil/imgutils.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
 #include "internal.h"
 #include "video.h"
 
+#define MAX_THREADS 16
+
 typedef struct ConvolveContext {
     const AVClass *class;
     FFFrameSync fs;
 
-    FFTContext *fft[4];
-    FFTContext *ifft[4];
+    FFTContext *fft[4][MAX_THREADS];
+    FFTContext *ifft[4][MAX_THREADS];
 
     int fft_bits[4];
     int fft_len[4];
@@ -49,8 +53,11 @@ typedef struct ConvolveContext {
     int depth;
     int planes;
     int impulse;
+    float noise;
     int nb_planes;
     int got_impulse[4];
+
+    int (*filter)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
 } ConvolveContext;
 
 #define OFFSET(x) offsetof(ConvolveContext, x)
@@ -61,11 +68,10 @@ static const AVOption convolve_options[] = {
     { "impulse", "when to process impulses",                OFFSET(impulse),  AV_OPT_TYPE_INT,   {.i64=1}, 0,  1, FLAGS, "impulse" },
     {   "first", "process only first impulse, ignore rest", 0,                AV_OPT_TYPE_CONST, {.i64=0}, 0,  0, FLAGS, "impulse" },
     {   "all",   "process all impulses",                    0,                AV_OPT_TYPE_CONST, {.i64=1}, 0,  0, FLAGS, "impulse" },
+    { "noise",   "set noise",                               OFFSET(noise),    AV_OPT_TYPE_FLOAT, {.dbl=0.0000001}, 0,  1, FLAGS },
     { NULL },
 };
 
-FRAMESYNC_DEFINE_CLASS(convolve, ConvolveContext, fs);
-
 static int query_formats(AVFilterContext *ctx)
 {
     static const enum AVPixelFormat pixel_fmts_fftfilt[] = {
@@ -85,7 +91,7 @@ static int query_formats(AVFilterContext *ctx)
         AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10,
         AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
         AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
-        AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY16,
+        AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
         AV_PIX_FMT_NONE
     };
 
@@ -119,16 +125,16 @@ static int config_input_main(AVFilterLink *inlink)
         s->fft_bits[i] = fft_bits;
         s->fft_len[i] = 1 << s->fft_bits[i];
 
-        if (!(s->fft_hdata[i] = av_calloc(s->fft_len[i] + 1, s->fft_len[i] * sizeof(FFTComplex))))
+        if (!(s->fft_hdata[i] = av_calloc(s->fft_len[i], s->fft_len[i] * sizeof(FFTComplex))))
             return AVERROR(ENOMEM);
 
-        if (!(s->fft_vdata[i] = av_calloc(s->fft_len[i] + 1, s->fft_len[i] * sizeof(FFTComplex))))
+        if (!(s->fft_vdata[i] = av_calloc(s->fft_len[i], s->fft_len[i] * sizeof(FFTComplex))))
             return AVERROR(ENOMEM);
 
-        if (!(s->fft_hdata_impulse[i] = av_calloc(s->fft_len[i] + 1, s->fft_len[i] * sizeof(FFTComplex))))
+        if (!(s->fft_hdata_impulse[i] = av_calloc(s->fft_len[i], s->fft_len[i] * sizeof(FFTComplex))))
             return AVERROR(ENOMEM);
 
-        if (!(s->fft_vdata_impulse[i] = av_calloc(s->fft_len[i] + 1, s->fft_len[i] * sizeof(FFTComplex))))
+        if (!(s->fft_vdata_impulse[i] = av_calloc(s->fft_len[i], s->fft_len[i] * sizeof(FFTComplex))))
             return AVERROR(ENOMEM);
     }
 
@@ -152,45 +158,67 @@ static int config_input_impulse(AVFilterLink *inlink)
     return 0;
 }
 
-static void fft_horizontal(ConvolveContext *s, FFTComplex *fft_hdata,
-                           AVFrame *in, int w, int h, int n, int plane, float scale)
+typedef struct ThreadData {
+    FFTComplex *hdata, *vdata;
+    int plane, n;
+} ThreadData;
+
+static int fft_horizontal(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    ConvolveContext *s = ctx->priv;
+    ThreadData *td = arg;
+    FFTComplex *hdata = td->hdata;
+    const int plane = td->plane;
+    const int n = td->n;
+    int start = (n * jobnr) / nb_jobs;
+    int end = (n * (jobnr+1)) / nb_jobs;
+    int y;
+
+    for (y = start; y < end; y++) {
+        av_fft_permute(s->fft[plane][jobnr], hdata + y * n);
+        av_fft_calc(s->fft[plane][jobnr], hdata + y * n);
+    }
+
+    return 0;
+}
+
+static void get_input(ConvolveContext *s, FFTComplex *fft_hdata,
+                      AVFrame *in, int w, int h, int n, int plane, float scale)
 {
     const int iw = (n - w) / 2, ih = (n - h) / 2;
     int y, x;
 
-    for (y = 0; y < n; y++) {
-        for (x = 0; x < n; x++) {
-            fft_hdata[y * n + x].re = 0;
-            fft_hdata[y * n + x].im = 0;
-        }
-    }
-
     if (s->depth == 8) {
         for (y = 0; y < h; y++) {
             const uint8_t *src = in->data[plane] + in->linesize[plane] * y;
 
             for (x = 0; x < w; x++) {
                 fft_hdata[(y + ih) * n + iw + x].re = src[x] * scale;
+                fft_hdata[(y + ih) * n + iw + x].im = 0;
             }
 
             for (x = 0; x < iw; x++) {
                 fft_hdata[(y + ih) * n + x].re = fft_hdata[(y + ih) * n + iw].re;
+                fft_hdata[(y + ih) * n + x].im = 0;
             }
 
             for (x = n - iw; x < n; x++) {
                 fft_hdata[(y + ih) * n + x].re = fft_hdata[(y + ih) * n + n - iw - 1].re;
+                fft_hdata[(y + ih) * n + x].im = 0;
             }
         }
 
         for (y = 0; y < ih; y++) {
             for (x = 0; x < n; x++) {
                 fft_hdata[y * n + x].re = fft_hdata[ih * n + x].re;
+                fft_hdata[y * n + x].im = 0;
             }
         }
 
         for (y = n - ih; y < n; y++) {
             for (x = 0; x < n; x++) {
                 fft_hdata[y * n + x].re = fft_hdata[(n - ih - 1) * n + x].re;
+                fft_hdata[y * n + x].im = 0;
             }
         }
     } else {
@@ -199,124 +227,219 @@ static void fft_horizontal(ConvolveContext *s, FFTComplex *fft_hdata,
 
             for (x = 0; x < w; x++) {
                 fft_hdata[(y + ih) * n + iw + x].re = src[x] * scale;
+                fft_hdata[(y + ih) * n + iw + x].im = 0;
             }
 
             for (x = 0; x < iw; x++) {
                 fft_hdata[(y + ih) * n + x].re = fft_hdata[(y + ih) * n + iw].re;
+                fft_hdata[(y + ih) * n + x].im = 0;
             }
 
             for (x = n - iw; x < n; x++) {
                 fft_hdata[(y + ih) * n + x].re = fft_hdata[(y + ih) * n + n - iw - 1].re;
+                fft_hdata[(y + ih) * n + x].im = 0;
             }
         }
 
         for (y = 0; y < ih; y++) {
             for (x = 0; x < n; x++) {
                 fft_hdata[y * n + x].re = fft_hdata[ih * n + x].re;
+                fft_hdata[y * n + x].im = 0;
             }
         }
 
         for (y = n - ih; y < n; y++) {
             for (x = 0; x < n; x++) {
                 fft_hdata[y * n + x].re = fft_hdata[(n - ih - 1) * n + x].re;
+                fft_hdata[y * n + x].im = 0;
             }
         }
     }
-
-    for (y = 0; y < n; y++) {
-        av_fft_permute(s->fft[plane], fft_hdata + y * n);
-        av_fft_calc(s->fft[plane], fft_hdata + y * n);
-    }
 }
 
-static void fft_vertical(ConvolveContext *s, FFTComplex *fft_hdata, FFTComplex *fft_vdata,
-                         int n, int plane)
+static int fft_vertical(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
 {
+    ConvolveContext *s = ctx->priv;
+    ThreadData *td = arg;
+    FFTComplex *hdata = td->hdata;
+    FFTComplex *vdata = td->vdata;
+    const int plane = td->plane;
+    const int n = td->n;
+    int start = (n * jobnr) / nb_jobs;
+    int end = (n * (jobnr+1)) / nb_jobs;
     int y, x;
 
-    for (y = 0; y < n; y++) {
+    for (y = start; y < end; y++) {
         for (x = 0; x < n; x++) {
-            fft_vdata[y * n + x].re = fft_hdata[x * n + y].re;
-            fft_vdata[y * n + x].im = fft_hdata[x * n + y].im;
+            vdata[y * n + x].re = hdata[x * n + y].re;
+            vdata[y * n + x].im = hdata[x * n + y].im;
         }
 
-        av_fft_permute(s->fft[plane], fft_vdata + y * n);
-        av_fft_calc(s->fft[plane], fft_vdata + y * n);
+        av_fft_permute(s->fft[plane][jobnr], vdata + y * n);
+        av_fft_calc(s->fft[plane][jobnr], vdata + y * n);
     }
+
+    return 0;
 }
 
-static void ifft_vertical(ConvolveContext *s, int n, int plane)
+static int ifft_vertical(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
 {
+    ConvolveContext *s = ctx->priv;
+    ThreadData *td = arg;
+    FFTComplex *hdata = td->hdata;
+    FFTComplex *vdata = td->vdata;
+    const int plane = td->plane;
+    const int n = td->n;
+    int start = (n * jobnr) / nb_jobs;
+    int end = (n * (jobnr+1)) / nb_jobs;
     int y, x;
 
-    for (y = 0; y < n; y++) {
-        av_fft_permute(s->ifft[plane], s->fft_vdata[plane] + y * n);
-        av_fft_calc(s->ifft[plane], s->fft_vdata[plane] + y * n);
+    for (y = start; y < end; y++) {
+        av_fft_permute(s->ifft[plane][jobnr], vdata + y * n);
+        av_fft_calc(s->ifft[plane][jobnr], vdata + y * n);
 
         for (x = 0; x < n; x++) {
-            s->fft_hdata[plane][x * n + y].re = s->fft_vdata[plane][y * n + x].re;
-            s->fft_hdata[plane][x * n + y].im = s->fft_vdata[plane][y * n + x].im;
+            hdata[x * n + y].re = vdata[y * n + x].re;
+            hdata[x * n + y].im = vdata[y * n + x].im;
         }
     }
+
+    return 0;
+}
+
+static int ifft_horizontal(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    ConvolveContext *s = ctx->priv;
+    ThreadData *td = arg;
+    FFTComplex *hdata = td->hdata;
+    const int plane = td->plane;
+    const int n = td->n;
+    int start = (n * jobnr) / nb_jobs;
+    int end = (n * (jobnr+1)) / nb_jobs;
+    int y;
+
+    for (y = start; y < end; y++) {
+        av_fft_permute(s->ifft[plane][jobnr], hdata + y * n);
+        av_fft_calc(s->ifft[plane][jobnr], hdata + y * n);
+    }
+
+    return 0;
 }
 
-static void ifft_horizontal(ConvolveContext *s, AVFrame *out,
-                            int w, int h, int n, int plane)
+static void get_output(ConvolveContext *s, FFTComplex *input, AVFrame *out,
+                       int w, int h, int n, int plane, float scale)
 {
-    const float scale = 1.f / (n * n);
     const int max = (1 << s->depth) - 1;
     const int hh = h / 2;
     const int hw = w / 2;
     int y, x;
 
-    for (y = 0; y < n; y++) {
-        av_fft_permute(s->ifft[plane], s->fft_hdata[plane] + y * n);
-        av_fft_calc(s->ifft[plane], s->fft_hdata[plane] + y * n);
-    }
-
     if (s->depth == 8) {
         for (y = 0; y < hh; y++) {
             uint8_t *dst = out->data[plane] + (y + hh) * out->linesize[plane] + hw;
             for (x = 0; x < hw; x++)
-                dst[x] = av_clip_uint8(s->fft_hdata[plane][y * n + x].re * scale);
+                dst[x] = av_clip_uint8(input[y * n + x].re * scale);
         }
         for (y = 0; y < hh; y++) {
             uint8_t *dst = out->data[plane] + (y + hh) * out->linesize[plane];
             for (x = 0; x < hw; x++)
-                dst[x] = av_clip_uint8(s->fft_hdata[plane][y * n + n - hw + x].re * scale);
+                dst[x] = av_clip_uint8(input[y * n + n - hw + x].re * scale);
         }
         for (y = 0; y < hh; y++) {
             uint8_t *dst = out->data[plane] + y * out->linesize[plane] + hw;
             for (x = 0; x < hw; x++)
-                dst[x] = av_clip_uint8(s->fft_hdata[plane][(n - hh + y) * n + x].re * scale);
+                dst[x] = av_clip_uint8(input[(n - hh + y) * n + x].re * scale);
         }
         for (y = 0; y < hh; y++) {
             uint8_t *dst = out->data[plane] + y * out->linesize[plane];
             for (x = 0; x < hw; x++)
-                dst[x] = av_clip_uint8(s->fft_hdata[plane][(n - hh + y) * n + n - hw + x].re * scale);
+                dst[x] = av_clip_uint8(input[(n - hh + y) * n + n - hw + x].re * scale);
         }
     } else {
         for (y = 0; y < hh; y++) {
             uint16_t *dst = (uint16_t *)(out->data[plane] + (y + hh) * out->linesize[plane] + hw * 2);
             for (x = 0; x < hw; x++)
-                dst[x] = av_clip(s->fft_hdata[plane][y * n + x].re * scale, 0, max);
+                dst[x] = av_clip(input[y * n + x].re * scale, 0, max);
         }
         for (y = 0; y < hh; y++) {
             uint16_t *dst = (uint16_t *)(out->data[plane] + (y + hh) * out->linesize[plane]);
             for (x = 0; x < hw; x++)
-                dst[x] = av_clip(s->fft_hdata[plane][y * n + n - hw + x].re * scale, 0, max);
+                dst[x] = av_clip(input[y * n + n - hw + x].re * scale, 0, max);
         }
         for (y = 0; y < hh; y++) {
             uint16_t *dst = (uint16_t *)(out->data[plane] + y * out->linesize[plane] + hw * 2);
             for (x = 0; x < hw; x++)
-                dst[x] = av_clip(s->fft_hdata[plane][(n - hh + y) * n + x].re * scale, 0, max);
+                dst[x] = av_clip(input[(n - hh + y) * n + x].re * scale, 0, max);
         }
         for (y = 0; y < hh; y++) {
             uint16_t *dst = (uint16_t *)(out->data[plane] + y * out->linesize[plane]);
             for (x = 0; x < hw; x++)
-                dst[x] = av_clip(s->fft_hdata[plane][(n - hh + y) * n + n - hw + x].re * scale, 0, max);
+                dst[x] = av_clip(input[(n - hh + y) * n + n - hw + x].re * scale, 0, max);
+        }
+    }
+}
+
+static int complex_multiply(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    ConvolveContext *s = ctx->priv;
+    ThreadData *td = arg;
+    FFTComplex *input = td->hdata;
+    FFTComplex *filter = td->vdata;
+    const float noise = s->noise;
+    const int n = td->n;
+    int start = (n * jobnr) / nb_jobs;
+    int end = (n * (jobnr+1)) / nb_jobs;
+    int y, x;
+
+    for (y = start; y < end; y++) {
+        int yn = y * n;
+
+        for (x = 0; x < n; x++) {
+            FFTSample re, im, ire, iim;
+
+            re = input[yn + x].re;
+            im = input[yn + x].im;
+            ire = filter[yn + x].re + noise;
+            iim = filter[yn + x].im;
+
+            input[yn + x].re = ire * re - iim * im;
+            input[yn + x].im = iim * re + ire * im;
+        }
+    }
+
+    return 0;
+}
+
+static int complex_divide(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    ConvolveContext *s = ctx->priv;
+    ThreadData *td = arg;
+    FFTComplex *input = td->hdata;
+    FFTComplex *filter = td->vdata;
+    const float noise = s->noise;
+    const int n = td->n;
+    int start = (n * jobnr) / nb_jobs;
+    int end = (n * (jobnr+1)) / nb_jobs;
+    int y, x;
+
+    for (y = start; y < end; y++) {
+        int yn = y * n;
+
+        for (x = 0; x < n; x++) {
+            FFTSample re, im, ire, iim, div;
+
+            re = input[yn + x].re;
+            im = input[yn + x].im;
+            ire = filter[yn + x].re;
+            iim = filter[yn + x].im;
+            div = ire * ire + iim * iim + noise;
+
+            input[yn + x].re = (ire * re + iim * im) / div;
+            input[yn + x].im = (ire * im - iim * re) / div;
         }
     }
+
+    return 0;
 }
 
 static int do_convolve(FFFrameSync *fs)
@@ -334,18 +457,26 @@ static int do_convolve(FFFrameSync *fs)
         return ff_filter_frame(outlink, mainpic);
 
     for (plane = 0; plane < s->nb_planes; plane++) {
+        FFTComplex *filter = s->fft_vdata_impulse[plane];
+        FFTComplex *input = s->fft_vdata[plane];
         const int n = s->fft_len[plane];
         const int w = s->planewidth[plane];
         const int h = s->planeheight[plane];
         float total = 0;
+        ThreadData td;
 
         if (!(s->planes & (1 << plane))) {
             continue;
         }
 
-        fft_horizontal(s, s->fft_hdata[plane], mainpic, w, h, n, plane, 1.f);
-        fft_vertical(s, s->fft_hdata[plane], s->fft_vdata[plane],
-                     n, plane);
+        td.plane = plane, td.n = n;
+        get_input(s, s->fft_hdata[plane], mainpic, w, h, n, plane, 1.f);
+
+        td.hdata = s->fft_hdata[plane];
+        td.vdata = s->fft_vdata[plane];
+
+        ctx->internal->execute(ctx, fft_horizontal, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
+        ctx->internal->execute(ctx, fft_vertical, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
 
         if ((!s->impulse && !s->got_impulse[plane]) || s->impulse) {
             if (s->depth == 8) {
@@ -365,29 +496,29 @@ static int do_convolve(FFFrameSync *fs)
             }
             total = FFMAX(1, total);
 
-            fft_horizontal(s, s->fft_hdata_impulse[plane], impulsepic, w, h, n, plane, 1 / total);
-            fft_vertical(s, s->fft_hdata_impulse[plane], s->fft_vdata_impulse[plane],
-                         n, plane);
+            get_input(s, s->fft_hdata_impulse[plane], impulsepic, w, h, n, plane, 1.f / total);
+
+            td.hdata = s->fft_hdata_impulse[plane];
+            td.vdata = s->fft_vdata_impulse[plane];
+
+            ctx->internal->execute(ctx, fft_horizontal, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
+            ctx->internal->execute(ctx, fft_vertical, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
 
             s->got_impulse[plane] = 1;
         }
 
-        for (y = 0; y < n; y++) {
-            for (x = 0; x < n; x++) {
-                FFTSample re, im, ire, iim;
+        td.hdata = input;
+        td.vdata = filter;
 
-                re = s->fft_vdata[plane][y*n + x].re;
-                im = s->fft_vdata[plane][y*n + x].im;
-                ire = s->fft_vdata_impulse[plane][y*n + x].re;
-                iim = s->fft_vdata_impulse[plane][y*n + x].im;
+        ctx->internal->execute(ctx, s->filter, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
 
-                s->fft_vdata[plane][y*n + x].re = ire * re - iim * im;
-                s->fft_vdata[plane][y*n + x].im = iim * re + ire * im;
-            }
-        }
+        td.hdata = s->fft_hdata[plane];
+        td.vdata = s->fft_vdata[plane];
 
-        ifft_vertical(s, n, plane);
-        ifft_horizontal(s, mainpic, w, h, n, plane);
+        ctx->internal->execute(ctx, ifft_vertical, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
+        ctx->internal->execute(ctx, ifft_horizontal, &td, NULL, FFMIN3(MAX_THREADS, n, ff_filter_get_nb_threads(ctx)));
+
+        get_output(s, s->fft_hdata[plane], mainpic, w, h, n, plane, 1.f / (n * n));
     }
 
     return ff_filter_frame(outlink, mainpic);
@@ -398,7 +529,7 @@ static int config_output(AVFilterLink *outlink)
     AVFilterContext *ctx = outlink->src;
     ConvolveContext *s = ctx->priv;
     AVFilterLink *mainlink = ctx->inputs[0];
-    int ret, i;
+    int ret, i, j;
 
     s->fs.on_event = do_convolve;
     ret = ff_framesync_init_dualinput(&s->fs, ctx);
@@ -414,10 +545,12 @@ static int config_output(AVFilterLink *outlink)
         return ret;
 
     for (i = 0; i < s->nb_planes; i++) {
-        s->fft[i]  = av_fft_init(s->fft_bits[i], 0);
-        s->ifft[i] = av_fft_init(s->fft_bits[i], 1);
-        if (!s->fft[i] || !s->ifft[i])
-            return AVERROR(ENOMEM);
+        for (j = 0; j < MAX_THREADS; j++) {
+            s->fft[i][j]  = av_fft_init(s->fft_bits[i], 0);
+            s->ifft[i][j] = av_fft_init(s->fft_bits[i], 1);
+            if (!s->fft[i][j] || !s->ifft[i][j])
+                return AVERROR(ENOMEM);
+        }
     }
 
     return 0;
@@ -429,18 +562,38 @@ static int activate(AVFilterContext *ctx)
     return ff_framesync_activate(&s->fs);
 }
 
+static av_cold int init(AVFilterContext *ctx)
+{
+    ConvolveContext *s = ctx->priv;
+
+    if (!strcmp(ctx->filter->name, "convolve")) {
+        s->filter = complex_multiply;
+    } else if (!strcmp(ctx->filter->name, "deconvolve")) {
+        s->filter = complex_divide;
+    } else {
+        return AVERROR_BUG;
+    }
+
+    return 0;
+}
+
 static av_cold void uninit(AVFilterContext *ctx)
 {
     ConvolveContext *s = ctx->priv;
-    int i;
+    int i, j;
 
     for (i = 0; i < 4; i++) {
         av_freep(&s->fft_hdata[i]);
         av_freep(&s->fft_vdata[i]);
         av_freep(&s->fft_hdata_impulse[i]);
         av_freep(&s->fft_vdata_impulse[i]);
-        av_fft_end(s->fft[i]);
-        av_fft_end(s->ifft[i]);
+
+        for (j = 0; j < MAX_THREADS; j++) {
+            av_fft_end(s->fft[i][j]);
+            s->fft[i][j] = NULL;
+            av_fft_end(s->ifft[i][j]);
+            s->ifft[i][j] = NULL;
+        }
     }
 
     ff_framesync_uninit(&s->fs);
@@ -468,10 +621,15 @@ static const AVFilterPad convolve_outputs[] = {
     { NULL }
 };
 
+#if CONFIG_CONVOLVE_FILTER
+
+FRAMESYNC_DEFINE_CLASS(convolve, ConvolveContext, fs);
+
 AVFilter ff_vf_convolve = {
     .name          = "convolve",
     .description   = NULL_IF_CONFIG_SMALL("Convolve first video stream with second video stream."),
     .preinit       = convolve_framesync_preinit,
+    .init          = init,
     .uninit        = uninit,
     .query_formats = query_formats,
     .activate      = activate,
@@ -479,5 +637,37 @@ AVFilter ff_vf_convolve = {
     .priv_class    = &convolve_class,
     .inputs        = convolve_inputs,
     .outputs       = convolve_outputs,
-    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | AVFILTER_FLAG_SLICE_THREADS,
+};
+
+#endif /* CONFIG_CONVOLVE_FILTER */
+
+#if CONFIG_DECONVOLVE_FILTER
+
+static const AVOption deconvolve_options[] = {
+    { "planes",  "set planes to deconvolve",                OFFSET(planes),   AV_OPT_TYPE_INT,   {.i64=7}, 0, 15, FLAGS },
+    { "impulse", "when to process impulses",                OFFSET(impulse),  AV_OPT_TYPE_INT,   {.i64=1}, 0,  1, FLAGS, "impulse" },
+    {   "first", "process only first impulse, ignore rest", 0,                AV_OPT_TYPE_CONST, {.i64=0}, 0,  0, FLAGS, "impulse" },
+    {   "all",   "process all impulses",                    0,                AV_OPT_TYPE_CONST, {.i64=1}, 0,  0, FLAGS, "impulse" },
+    { "noise",   "set noise",                               OFFSET(noise),    AV_OPT_TYPE_FLOAT, {.dbl=0.0000001}, 0,  1, FLAGS },
+    { NULL },
+};
+
+FRAMESYNC_DEFINE_CLASS(deconvolve, ConvolveContext, fs);
+
+AVFilter ff_vf_deconvolve = {
+    .name          = "deconvolve",
+    .description   = NULL_IF_CONFIG_SMALL("Deconvolve first video stream with second video stream."),
+    .preinit       = deconvolve_framesync_preinit,
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .activate      = activate,
+    .priv_size     = sizeof(ConvolveContext),
+    .priv_class    = &deconvolve_class,
+    .inputs        = convolve_inputs,
+    .outputs       = convolve_outputs,
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | AVFILTER_FLAG_SLICE_THREADS,
 };
+
+#endif /* CONFIG_DECONVOLVE_FILTER */