From patchwork Mon Oct 11 20:55:33 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 31070 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6602:2084:0:0:0:0 with SMTP id a4csp4102602ioa; Mon, 11 Oct 2021 13:56:03 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyuCDixkqb0iorQj4hCNVd7FyxUkzhtEu0sKR8FwFJ5uh62iVu7Q9KaaPaNKcO1YAlx0CtM X-Received: by 2002:a17:906:2757:: with SMTP id a23mr13833368ejd.230.1633985763594; Mon, 11 Oct 2021 13:56:03 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1633985763; cv=none; d=google.com; s=arc-20160816; b=af9diDQSCM1orkcbbDhfa/draIPPKg3MQjdhacw2ozqvPgxlrSvvsMCkreVnSs+c4t MMW23+nByQqnrJRvLxZDU3GXhm2gdTunsurL/jBtsLGyOdM/RQkDCJq+4E0qRDd2U3x5 K7FceowOUErwbF9McbfwJSamN01WuWdMH5R8yjwCecNUHrqwWV6CaAHSTKwS+vrrRnZR fi3r2EyRytu/MwM/c7vG7B3SCvNSIVyPJK7+MI5H0JfhcAYuBUEO5dFKdWi5gCoSvC+g 6P9OhBAz0BxWaTZhDhQf1rq/o7N9dFzzU9d9ZBoOuZGCUMG0z7cXR/ZZriJbPUWF+LWS 9hwQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:message-id:date:to:from :dkim-signature:delivered-to; bh=5IUjKeqCiD3zYEcIt7DAaPBZj/j8wJrqYu2fh+Ra0DQ=; b=r41IAtEm9LW/LC8n4miOXzuhC+g8PphpqJOa51x0mn8N5Dh+Dp63O2hXGYHcySZTx6 TL5wxbAIDdJ2mpE6aKRkA2cdHZGchDIhobC5PXnVn73IayZtiH3cXn3ZZLr39u61v/vJ rx5y74g9ItMB/0iYLJrupvVVTwfXh8EmOVefMCOFjs0/co2x8ARPaDWp+MXLLBYrlVOp CbRTnogBxoUclPgn9qA6YJa6ucObZ4bNCPXg8TYmLzphNvTd1G3+OEFkqQ97qUmQfy1L rzg6NkyDBdsfP54W4Y7lHEB4jiq1xJ4k4U5YuiZR6dRaF8XCb2U8Y9mBaZVDMepfcfLS VGJw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=ofxteDTH; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id ky2si12648521ejc.302.2021.10.11.13.56.03; Mon, 11 Oct 2021 13:56:03 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=ofxteDTH; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 621E268A133; Mon, 11 Oct 2021 23:56:00 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ed1-f42.google.com (mail-ed1-f42.google.com [209.85.208.42]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id DBC9068A133 for ; Mon, 11 Oct 2021 23:55:53 +0300 (EEST) Received: by mail-ed1-f42.google.com with SMTP id z20so72392299edc.13 for ; Mon, 11 Oct 2021 13:55:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:subject:date:message-id:mime-version :content-transfer-encoding; bh=FLIG+f086amNbKywLE8is6Gaw7gqfBHoS69HJOC7Cn4=; b=ofxteDTHdX0YHqtso1heFRB79SCr1pP5ElQOYlwF0D0G/aODsqKSGljDvbrlRaUZ03 9NoicZWQYGUfBanP7PXdsf0x2p2GLc2T7bwCM/z/P3U5Z8/+Rdp0FrwcjSzuXfbQ12V5 /qNty2wQlQjVZobYWlRPzgmgDDzFn1blT6E0gk18c/Cfcb5oqNASNV2zjCDx1Hm5ip2y nttMjdGibU1l1lcn/g7ysl4MuXhuTB2JIhefzfCbhotPVhe776buZEMyAf5bQZhc8v+O brTiLMTJmQ4yVL1dufb4ZeG1CUNM5atVEvEAwHE5niyNLGgOzLbWlBJA0N47jD3Yq+73 vzrQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:subject:date:message-id:mime-version :content-transfer-encoding; bh=FLIG+f086amNbKywLE8is6Gaw7gqfBHoS69HJOC7Cn4=; b=78N9OtdI1qzihhkiFJdahXOzUX6MawD9R/um4eKZ9z3PedKTH2yD+RnoDXeindL7p8 DGPqoStgM0CLoM175Xrg6N0zPEScXKvvKXbUtYr/AkEwwvbVR+opW3BNZENh2vuJN/0+ 0OsMGsXVny+w7pfzV5PopOWxYL6S0vw4+plHUl8934wuVDZUjbZy8FxChoFow9+SdkiR PweApyxvTQEoVsEcfOUAqUJWeYPHMIdkm/WsnyVdYj7s1ANGIPZFcrt2FUH13yWsl91d iR6n7auefKHd5zchBAG/uZzMEKnk3QEEOViCfbAwFzXn2hGtn1svVygbNLq8xbUJ7axD ClTw== X-Gm-Message-State: AOAM533qhg0NdU95dCqpvkbw0RCdLcJEAU8T14qw50I04T6QLJAfFca1 8M5OOZ6ZhU6ts+CXHKdUW2jQxhiy0J8= X-Received: by 2002:a17:906:ae53:: with SMTP id lf19mr28470696ejb.97.1633985753080; Mon, 11 Oct 2021 13:55:53 -0700 (PDT) Received: from localhost.localdomain ([95.168.116.135]) by smtp.gmail.com with ESMTPSA id h7sm5175157ede.19.2021.10.11.13.55.33 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 11 Oct 2021 13:55:52 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Mon, 11 Oct 2021 22:55:33 +0200 Message-Id: <20211011205533.389016-1-onemda@gmail.com> X-Mailer: git-send-email 2.33.0 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] avfilter: add varblur video filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: d46SlB48uB6U Signed-off-by: Paul B Mahol --- doc/filters.texi | 15 ++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_varblur.c | 362 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 379 insertions(+) create mode 100644 libavfilter/vf_varblur.c diff --git a/doc/filters.texi b/doc/filters.texi index 5e7326d2f2..4a28c1f828 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -21731,6 +21731,21 @@ Threshold used depends also on each decomposition coefficients. Default is universal. @end table +@section varblur +Apply variable blur filter by using 2nd video stream to set blur radius. + +This filter accepts the following options: + +@table @option +@item min_r +Set min allowed radius. By default is 0. Allowed range is from 0 to 254. +@item max_r +Set max allowed radius. By default is 8. Allowed range is from 1 to 255. +@item planes +@item planes +Set which planes to process. By default all are used. +@end table + @section vectorscope Display 2 color component values in the two dimensional graph (which is called diff --git a/libavfilter/Makefile b/libavfilter/Makefile index da2c8b7a5e..358f121cb4 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -486,6 +486,7 @@ OBJS-$(CONFIG_UNTILE_FILTER) += vf_untile.o OBJS-$(CONFIG_USPP_FILTER) += vf_uspp.o qp_table.o OBJS-$(CONFIG_V360_FILTER) += vf_v360.o OBJS-$(CONFIG_VAGUEDENOISER_FILTER) += vf_vaguedenoiser.o +OBJS-$(CONFIG_VARBLUR_FILTER) += vf_varblur.o framesync.o OBJS-$(CONFIG_VECTORSCOPE_FILTER) += vf_vectorscope.o OBJS-$(CONFIG_VFLIP_FILTER) += vf_vflip.o OBJS-$(CONFIG_VFRDET_FILTER) += vf_vfrdet.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 81e1b0965b..409ab5d3c4 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -463,6 +463,7 @@ extern const AVFilter ff_vf_untile; extern const AVFilter ff_vf_uspp; extern const AVFilter ff_vf_v360; extern const AVFilter ff_vf_vaguedenoiser; +extern const AVFilter ff_vf_varblur; extern const AVFilter ff_vf_vectorscope; extern const AVFilter ff_vf_vflip; extern const AVFilter ff_vf_vfrdet; diff --git a/libavfilter/vf_varblur.c b/libavfilter/vf_varblur.c new file mode 100644 index 0000000000..8ac568f55e --- /dev/null +++ b/libavfilter/vf_varblur.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2021 Paul B Mahol + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" +#include "formats.h" +#include "framesync.h" +#include "internal.h" +#include "video.h" + +typedef struct VarBlurContext { + const AVClass *class; + FFFrameSync fs; + + int min_radius; + int max_radius; + int planes; + + int depth; + int planewidth[4]; + int planeheight[4]; + + AVFrame *sat; + int nb_planes; + + int (*blur_plane)(AVFilterContext *s, void *arg, + int jobnr, int nb_jobs); +} VarBlurContext; + +#define OFFSET(x) offsetof(VarBlurContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM + +static const AVOption varblur_options[] = { + { "min_r", "set min blur radius", OFFSET(min_radius), AV_OPT_TYPE_INT, {.i64=0}, 0, 254, FLAGS }, + { "max_r", "set max blur radius", OFFSET(max_radius), AV_OPT_TYPE_INT, {.i64=8}, 1, 255, FLAGS }, + { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=0xF}, 0, 0xF, FLAGS }, + { NULL } +}; + +FRAMESYNC_DEFINE_CLASS(varblur, VarBlurContext, fs); + +static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, + AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, + AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, + AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12, + AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, + AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, + AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, + AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12, + AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16, + 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_GRAY14, AV_PIX_FMT_GRAY16, + AV_PIX_FMT_NONE +}; + +#define COMPUTE_SAT(type, stype, depth) \ +static void compute_sat##depth(const uint8_t *ssrc, \ + int linesize, \ + int w, int h, \ + const uint8_t *dstp, \ + int dst_linesize) \ +{ \ + const type *src = (const type *)ssrc; \ + stype *dst = (stype *)dstp; \ + \ + linesize /= (depth / 8); \ + dst_linesize /= (depth / 2); \ + dst += dst_linesize; \ + \ + for (int y = 0; y < h; y++) { \ + stype sum = 0; \ + \ + for (int x = 1; x < w; x++) { \ + sum += src[x - 1]; \ + dst[x] = sum + dst[x - dst_linesize]; \ + } \ + \ + src += linesize; \ + dst += dst_linesize; \ + } \ +} + +COMPUTE_SAT(uint8_t, uint32_t, 8) +COMPUTE_SAT(uint16_t, uint64_t, 16) + +typedef struct ThreadData { + uint8_t *dst; + int dst_linesize; + const uint8_t *rptr; + int rptr_linesize; + int w, h; + const uint8_t *ptr; + int ptr_linesize; +} ThreadData; + +static float lerpf(float v0, float v1, float f) +{ + return v0 + (v1 - v0) * f; +} + +#define BLUR_PLANE(type, stype, bits) \ +static int blur_plane##bits(AVFilterContext *ctx, void *arg, \ + int jobnr, int nb_jobs) \ +{ \ + VarBlurContext *s = ctx->priv; \ + const int ddepth = s->depth; \ + ThreadData *td = arg; \ + const int w = td->w, h = td->h; \ + const int slice_start = (h * jobnr) / nb_jobs; \ + const int slice_end = (h * (jobnr+1)) / nb_jobs; \ + const int dst_linesize = td->dst_linesize / (bits / 8); \ + const int ptr_linesize = td->ptr_linesize / (bits / 2); \ + const int rptr_linesize = td->rptr_linesize / (bits / 8); \ + const type *rptr = (const type *)td->rptr + slice_start * rptr_linesize; \ + type *dst = (type *)td->dst + slice_start * dst_linesize; \ + const stype *ptr = (stype *)td->ptr; \ + const float minr = 2.f * s->min_radius + 1.f; \ + const float maxr = 2.f * s->max_radius + 1.f; \ + const float scaler = (maxr - minr) / ((1 << ddepth) - 1); \ + \ + for (int y = slice_start; y < slice_end; y++) { \ + for (int x = 0; x < w; x++) { \ + const float radiusf = minr + (FFMAX(0.f, 2 * rptr[x] + 1 - minr)) * scaler; \ + const int radius = floorf(radiusf); \ + const float factor = radiusf - radius; \ + const int nradius = radius + 1; \ + const int l = FFMIN(radius, x); \ + const int r = FFMIN(radius, w - x - 1); \ + const int t = FFMIN(radius, y); \ + const int b = FFMIN(radius, h - y - 1); \ + const int nl = FFMIN(nradius, x); \ + const int nr = FFMIN(nradius, w - x - 1); \ + const int nt = FFMIN(nradius, y); \ + const int nb = FFMIN(nradius, h - y - 1); \ + stype tl = ptr[(y - t) * ptr_linesize + x - l]; \ + stype tr = ptr[(y - t) * ptr_linesize + x + r]; \ + stype bl = ptr[(y + b) * ptr_linesize + x - l]; \ + stype br = ptr[(y + b) * ptr_linesize + x + r]; \ + stype ntl = ptr[(y - nt) * ptr_linesize + x - nl]; \ + stype ntr = ptr[(y - nt) * ptr_linesize + x + nr]; \ + stype nbl = ptr[(y + nb) * ptr_linesize + x - nl]; \ + stype nbr = ptr[(y + nb) * ptr_linesize + x + nr]; \ + stype div = (l + r) * (t + b); \ + stype ndiv = (nl + nr) * (nt + nb); \ + stype p0 = (br + tl - bl - tr) / div; \ + stype n0 = (nbr + ntl - nbl - ntr) / ndiv; \ + \ + dst[x] = av_clip_uintp2_c(lerpf(p0, n0, factor), \ + ddepth); \ + } \ + \ + rptr += rptr_linesize; \ + dst += dst_linesize; \ + } \ + \ + return 0; \ +} + +BLUR_PLANE(uint8_t, uint32_t, 8) +BLUR_PLANE(uint16_t, uint64_t, 16) + +static int blur_frame(AVFilterContext *ctx, AVFrame *in, AVFrame *radius) +{ + VarBlurContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + ThreadData td; + AVFrame *out; + + if (av_frame_is_writable(in)) { + out = in; + } else { + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + } + + for (int plane = 0; plane < s->nb_planes; plane++) { + const int height = s->planeheight[plane]; + const int width = s->planewidth[plane]; + const int linesize = in->linesize[plane]; + const int dst_linesize = out->linesize[plane]; + const uint8_t *rptr = radius->data[plane]; + const int rptr_linesize = radius->linesize[plane]; + uint8_t *ptr = s->sat->data[plane]; + const int ptr_linesize = s->sat->linesize[plane]; + const uint8_t *src = in->data[plane]; + uint8_t *dst = out->data[plane]; + + if (!(s->planes & (1 << plane))) { + if (out != in) + av_image_copy_plane(out->data[plane], out->linesize[plane], + in->data[plane], in->linesize[plane], + width * ((s->depth + 7) / 8), height); + continue; + } + + if (s->depth <= 8) + compute_sat8(src, linesize, width, height, ptr, ptr_linesize); + else + compute_sat16(src, linesize, width, height, ptr, ptr_linesize); + + td.dst = dst; + td.dst_linesize = dst_linesize; + td.ptr = ptr; + td.ptr_linesize = ptr_linesize; + td.rptr = rptr; + td.rptr_linesize = rptr_linesize; + td.w = width; + td.h = height; + ff_filter_execute(ctx, s->blur_plane, &td, NULL, + FFMIN(height, ff_filter_get_nb_threads(ctx))); + } + + if (out != in) + av_frame_free(&in); + return ff_filter_frame(outlink, out); +} + +static int activate(AVFilterContext *ctx) +{ + VarBlurContext *s = ctx->priv; + return ff_framesync_activate(&s->fs); +} + +static int varblur_frame(FFFrameSync *fs) +{ + AVFilterContext *ctx = fs->parent; + VarBlurContext *s = ctx->priv; + AVFrame *in, *radius; + int ret; + + if (s->max_radius <= s->min_radius) + s->max_radius = s->min_radius + 1; + + ret = ff_framesync_dualinput_get(fs, &in, &radius); + if (ret < 0) + return ret; + if (!radius) + return ff_filter_frame(ctx->outputs[0], in); + return blur_frame(ctx, in, radius); +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + AVFilterLink *inlink = ctx->inputs[0]; + AVFilterLink *radiuslink = ctx->inputs[1]; + VarBlurContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format); + int ret; + + if (inlink->w != radiuslink->w || inlink->h != radiuslink->h) { + av_log(ctx, AV_LOG_ERROR, "First input link %s parameters " + "(size %dx%d) do not match the corresponding " + "second input link %s parameters (size %dx%d)\n", + ctx->input_pads[0].name, inlink->w, inlink->h, + ctx->input_pads[1].name, radiuslink->w, radiuslink->h); + return AVERROR(EINVAL); + } + + outlink->w = inlink->w; + outlink->h = inlink->h; + outlink->time_base = inlink->time_base; + outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; + outlink->frame_rate = inlink->frame_rate; + + s->depth = desc->comp[0].depth; + s->blur_plane = s->depth <= 8 ? blur_plane8 : blur_plane16; + + s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(outlink->w, desc->log2_chroma_w); + s->planewidth[0] = s->planewidth[3] = outlink->w; + s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(outlink->h, desc->log2_chroma_h); + s->planeheight[0] = s->planeheight[3] = outlink->h; + + s->nb_planes = av_pix_fmt_count_planes(outlink->format); + + s->sat = ff_get_video_buffer(outlink, (outlink->w + 1) * 4 * ((s->depth + 7) / 8), outlink->h + 1); + if (!s->sat) + return AVERROR(ENOMEM); + + s->fs.on_event = varblur_frame; + if ((ret = ff_framesync_init_dualinput(&s->fs, ctx)) < 0) + return ret; + + ret = ff_framesync_configure(&s->fs); + outlink->time_base = s->fs.time_base; + + return ret; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + VarBlurContext *s = ctx->priv; + + ff_framesync_uninit(&s->fs); + av_frame_free(&s->sat); +} + +static const AVFilterPad varblur_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, + { + .name = "radius", + .type = AVMEDIA_TYPE_VIDEO, + }, +}; + +static const AVFilterPad varblur_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_output, + }, +}; + +const AVFilter ff_vf_varblur = { + .name = "varblur", + .description = NULL_IF_CONFIG_SMALL("Apply Variable Blur filter."), + .priv_size = sizeof(VarBlurContext), + .priv_class = &varblur_class, + .activate = activate, + .preinit = varblur_framesync_preinit, + .uninit = uninit, + FILTER_INPUTS(varblur_inputs), + FILTER_OUTPUTS(varblur_outputs), + FILTER_PIXFMTS_ARRAY(pix_fmts), + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | + AVFILTER_FLAG_SLICE_THREADS, + .process_command = ff_filter_process_command, +};