diff mbox

[FFmpeg-devel] avfilter: implement vf_remap_frac

Message ID AB9FC287-2317-47B1-B54B-04B089783ABE@googlemail.com
State New
Headers show

Commit Message

Daniel Oberhoff Feb. 14, 2017, 7:46 p.m. UTC
this is a fractional version of remap that interprets
the x/y maps as 13.3bit fixed point values, i.e
the three least significant bits are used for inter-pixel
interpolation by weighed averaging the four adjacent pixels

Signed-off-by: Daniel Oberhoff <daniel@danieloberhoff.de>
---
 libavfilter/Makefile        |   1 +
 libavfilter/allfilters.c    |   1 +
 libavfilter/vf_remap_frac.c | 491 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 493 insertions(+)
 create mode 100644 libavfilter/vf_remap_frac.c

Comments

Paul B Mahol Feb. 14, 2017, 7:52 p.m. UTC | #1
On 2/14/17, Daniel Oberhoff <danieloberhoff@googlemail.com> wrote:
> this is a fractional version of remap that interprets
> the x/y maps as 13.3bit fixed point values, i.e
> the three least significant bits are used for inter-pixel
> interpolation by weighed averaging the four adjacent pixels
>
> Signed-off-by: Daniel Oberhoff <daniel@danieloberhoff.de>
> ---
>  libavfilter/Makefile        |   1 +
>  libavfilter/allfilters.c    |   1 +
>  libavfilter/vf_remap_frac.c | 491
> ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 493 insertions(+)
>  create mode 100644 libavfilter/vf_remap_frac.c
>

I do not like '_' in filter name.
Daniel Oberhoff April 7, 2017, 10:17 a.m. UTC | #2
>> 
>> Signed-off-by: Daniel Oberhoff <daniel@danieloberhoff.de>
>> ---
>> libavfilter/Makefile        |   1 +
>> libavfilter/allfilters.c    |   1 +
>> libavfilter/vf_remap_frac.c | 491
>> ++++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 493 insertions(+)
>> create mode 100644 libavfilter/vf_remap_frac.c
>> 
> 
> I do not like '_' in filter name.

what do you propose?
Paul B Mahol April 7, 2017, 10:30 a.m. UTC | #3
On 4/7/17, Daniel Oberhoff <danieloberhoff@googlemail.com> wrote:
>>>
>>> Signed-off-by: Daniel Oberhoff <daniel@danieloberhoff.de>
>>> ---
>>> libavfilter/Makefile        |   1 +
>>> libavfilter/allfilters.c    |   1 +
>>> libavfilter/vf_remap_frac.c | 491
>>> ++++++++++++++++++++++++++++++++++++++++++++
>>> 3 files changed, 493 insertions(+)
>>> create mode 100644 libavfilter/vf_remap_frac.c
>>>
>>
>> I do not like '_' in filter name.
>
> what do you propose?

No point to have separate filter that operates like remap.
Just add option to remap filter to interpret values differently.

One could also make use of RGBA64, and use alfa values for interpolation.
Nicolas George April 7, 2017, 10:37 a.m. UTC | #4
L'octidi 18 germinal, an CCXXV, Daniel Oberhoff a écrit :
> what do you propose?

I personally have no objection underscores in filter names.
Spaceswereinventedforareason. And we already have a few filters with
underscores in them.

On the other hand, the patch looks like a big copy-paste of the original
vf_remap with a few changes and some extra features (slice threading,
high bit depth), and that is a big no. I think the features should be
implemented in the original filter. The normal and 16 versions of the
functions also looks nearly identical and should be factored.

Also, it is missing documentation.

Regards,
diff mbox

Patch

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index a986322..504e66a 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -250,6 +250,7 @@  OBJS-$(CONFIG_RANDOM_FILTER)                 += vf_random.o
 OBJS-$(CONFIG_READVITC_FILTER)               += vf_readvitc.o
 OBJS-$(CONFIG_REALTIME_FILTER)               += f_realtime.o
 OBJS-$(CONFIG_REMAP_FILTER)                  += vf_remap.o framesync.o
+OBJS-$(CONFIG_REMAP_FRAC_FILTER)             += vf_remap_frac.o framesync.o
 OBJS-$(CONFIG_HALVE_FILTER)                  += vf_halve.o framesync.o
 OBJS-$(CONFIG_REMOVEGRAIN_FILTER)            += vf_removegrain.o
 OBJS-$(CONFIG_REMOVELOGO_FILTER)             += bbox.o lswsutils.o lavfutils.o vf_removelogo.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 0b9b69c..0edd816 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -265,6 +265,7 @@  void avfilter_register_all(void)
     REGISTER_FILTER(READVITC,       readvitc,       vf);
     REGISTER_FILTER(REALTIME,       realtime,       vf);
     REGISTER_FILTER(REMAP,          remap,          vf);
+    REGISTER_FILTER(REMAP_FRAC,     remap_frac,     vf);
     REGISTER_FILTER(HALVE,          halve,          vf);
     REGISTER_FILTER(REMOVEGRAIN,    removegrain,    vf);
     REGISTER_FILTER(REMOVELOGO,     removelogo,     vf);
diff --git a/libavfilter/vf_remap_frac.c b/libavfilter/vf_remap_frac.c
new file mode 100644
index 0000000..f0e33d4
--- /dev/null
+++ b/libavfilter/vf_remap_frac.c
@@ -0,0 +1,491 @@ 
+/*
+ * Copyright (c) 2016 Floris Sluiter
+ *
+ * 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
+ */
+
+/**
+ * @file
+ * Pixel remap_frac filter
+ * This is a fractional version of the remap filter. The x and y maps
+ * are interpreted as 13.3 fixed point values, i.e. the 3 least significant
+ * bits are used for inter-pixel-interpolation by weighed averaging the four
+ * adjacaent pixels.
+ */
+
+#include "libavutil/imgutils.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "formats.h"
+#include "framesync.h"
+#include "internal.h"
+#include "video.h"
+
+typedef struct RemapFracContext {
+    const AVClass *class;
+    int nb_planes;
+    int nb_components;
+    int step;
+    FFFrameSync fs;
+
+    void (*remap_frac_slice)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs);
+} RemapFracContext;
+
+#define OFFSET(x) offsetof(RemapFracContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+static const AVOption remap_frac_options[] = {
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(remap_frac);
+
+typedef struct ThreadData {
+    AVFrame *in, *xin, *yin, *out;
+    int nb_planes;
+    int nb_components;
+    int step;
+} ThreadData;
+
+static int query_formats(AVFilterContext *ctx)
+{
+    static const enum AVPixelFormat pix_fmts[] = {
+        AV_PIX_FMT_YUVA444P,
+        AV_PIX_FMT_YUV444P,
+        AV_PIX_FMT_YUVJ444P,
+        AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
+        AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA,
+        AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP,
+        AV_PIX_FMT_YUV444P9, AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P12,
+        AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV444P16,
+        AV_PIX_FMT_YUVA444P9, AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P16,
+        AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12,
+        AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
+        AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
+        AV_PIX_FMT_RGB48, AV_PIX_FMT_BGR48,
+        AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64,
+        AV_PIX_FMT_NONE
+    };
+    static const enum AVPixelFormat map_fmts[] = {
+        AV_PIX_FMT_GRAY16,
+        AV_PIX_FMT_NONE
+    };
+    AVFilterFormats *pix_formats = NULL, *map_formats = NULL;
+    int ret;
+
+    if (!(pix_formats = ff_make_format_list(pix_fmts)) ||
+        !(map_formats = ff_make_format_list(map_fmts))) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+    if ((ret = ff_formats_ref(pix_formats, &ctx->inputs[0]->out_formats)) < 0 ||
+        (ret = ff_formats_ref(map_formats, &ctx->inputs[1]->out_formats)) < 0 ||
+        (ret = ff_formats_ref(map_formats, &ctx->inputs[2]->out_formats)) < 0 ||
+        (ret = ff_formats_ref(pix_formats, &ctx->outputs[0]->in_formats)) < 0)
+        goto fail;
+    return 0;
+fail:
+    if (pix_formats)
+        av_freep(&pix_formats->formats);
+    av_freep(&pix_formats);
+    if (map_formats)
+        av_freep(&map_formats->formats);
+    av_freep(&map_formats);
+    return ret;
+}
+
+/**
+ * remap_frac_planar algorithm expects planes of same size
+ */
+static void remap_frac_planar_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    const ThreadData *td = (ThreadData*)arg;
+    const AVFrame *in  = td->in;
+    const AVFrame *xin = td->xin;
+    const AVFrame *yin = td->yin;
+    const AVFrame *out = td->out;
+
+    const int xlinesize = xin->linesize[0] / 2;
+    const int ylinesize = yin->linesize[0] / 2;
+    const uint16_t one = 1 << 3;
+    int x , y, plane;
+
+    for (plane = 0; plane < td->nb_planes ; plane++) {
+        uint8_t *dst         = out->data[plane];
+        const int dlinesize  = out->linesize[plane];
+        const uint8_t *src   = in->data[plane];
+        const int slinesize  = in->linesize[plane];
+        const uint16_t *xmap = (const uint16_t *)xin->data[0];
+        const uint16_t *ymap = (const uint16_t *)yin->data[0];
+
+        for (y = 0; y < out->height; y++) {
+            for (x = 0; x < out->width; x++) {
+                uint16_t x_pos = xmap[x];
+                uint16_t y_pos = ymap[x];
+                uint16_t x_pos_int = x_pos >> 3;
+                uint16_t y_pos_int = y_pos >> 3;
+                uint16_t x_pos_frac = x_pos & (one - 1);
+                uint16_t y_pos_frac = y_pos & (one - 1);
+                if (y_pos_int < (in->height - 1) && x_pos_int < (in->width - 1)) {
+                    dst[x] = 
+                    (uint8_t)((
+                (uint16_t)(src[y_pos_int * slinesize + x_pos_int]) * (one - x_pos_frac) * (one - y_pos_frac) +
+                (uint16_t)(src[y_pos_int * slinesize + x_pos_int + 1]) * x_pos_frac * (one - y_pos_frac) +
+                (uint16_t)(src[(y_pos_int + 1) * slinesize + x_pos_int + 1]) * x_pos_frac * y_pos_frac +
+                (uint16_t)(src[(y_pos_int + 1) * slinesize + x_pos_int]) * (one - x_pos_frac) * y_pos_frac
+                        ) >> 6);
+                } else {
+                    dst[x] = 0;
+                }
+            }
+            dst  += dlinesize;
+            xmap += xlinesize;
+            ymap += ylinesize;
+        }
+    }
+}
+
+static void remap_frac_planar16_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    const ThreadData *td = (ThreadData*)arg;
+    const AVFrame *in  = td->in;
+    const AVFrame *xin = td->xin;
+    const AVFrame *yin = td->yin;
+    const AVFrame *out = td->out;
+
+    const int xlinesize = xin->linesize[0] / 2;
+    const int ylinesize = yin->linesize[0] / 2;
+    const uint32_t one = 1 << 3;
+    int x , y, plane;
+
+    for (plane = 0; plane < td->nb_planes ; plane++) {
+        uint16_t *dst        = (uint16_t *)out->data[plane];
+        const int dlinesize  = out->linesize[plane] / 2;
+        const uint16_t *src  = (const uint16_t *)in->data[plane];
+        const int slinesize  = in->linesize[plane] / 2;
+        const uint16_t *xmap = (const uint16_t *)xin->data[0];
+        const uint16_t *ymap = (const uint16_t *)yin->data[0];
+
+        for (y = 0; y < out->height; y++) {
+            for (x = 0; x < out->width; x++) {
+                uint16_t x_pos = xmap[x];
+                uint16_t y_pos = ymap[x];
+                uint16_t x_pos_int = x_pos >> 3;
+                uint16_t y_pos_int = y_pos >> 3;
+                uint32_t x_pos_frac = x_pos & (one - 1);
+                uint32_t y_pos_frac = y_pos & (one - 1);
+                if (y_pos_int < (in->height - 1) && x_pos_int < (in->width - 1)) {
+                    uint16_t y_offset = y_pos_int * slinesize;
+                    dst[x] = 
+                    (uint16_t)((
+                (uint32_t)(src[y_offset + x_pos_int]) * x_pos_frac * y_pos_frac +
+                (uint32_t)(src[y_offset + x_pos_int + 1]) * (one - x_pos_frac) * y_pos_frac +
+                (uint32_t)(src[y_offset + slinesize + x_pos_int + 1]) * (one - x_pos_frac) * (one - y_pos_frac) +
+                (uint32_t)(src[y_offset + slinesize + x_pos_int]) * x_pos_frac * (one - y_pos_frac)
+                        ) >> 6);
+                } else {
+                    dst[x] = 0;
+                }
+            }
+            dst  += dlinesize;
+            xmap += xlinesize;
+            ymap += ylinesize;
+        }
+    }
+}
+
+/**
+ * remap_frac_packed algorithm expects pixels with both padded bits (step) and
+ * number of components correctly set.
+ */
+static void remap_frac_packed_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    const ThreadData *td = (ThreadData*)arg;
+    const AVFrame *in  = td->in;
+    const AVFrame *xin = td->xin;
+    const AVFrame *yin = td->yin;
+    const AVFrame *out = td->out;
+
+    uint8_t *dst = out->data[0];
+    const uint16_t one = 1 << 3;
+    const uint8_t *src  = in->data[0];
+    const int dlinesize = out->linesize[0];
+    const int slinesize = in->linesize[0];
+    const int xlinesize = xin->linesize[0] / 2;
+    const int ylinesize = yin->linesize[0] / 2;
+    const uint16_t *xmap = (const uint16_t *)xin->data[0];
+    const uint16_t *ymap = (const uint16_t *)yin->data[0];
+    const int step = td->step;
+    int c, x, y;
+
+    for (y = 0; y < out->height; y++) {
+        for (x = 0; x < out->width; x++) {
+            uint16_t x_pos = xmap[x];
+            uint16_t y_pos = ymap[x];
+            uint16_t x_pos_int = x_pos >> 3;
+            uint16_t y_pos_int = y_pos >> 3;
+            uint32_t x_pos_frac = x_pos & (one - 1);
+            uint32_t y_pos_frac = y_pos & (one - 1);
+            if (y_pos_int < (in->height - 1) && x_pos_int < (in->width - 1)) {
+                for (c = 0; c < td->nb_components; c++) {
+                    dst[x * step + c] = 
+                    (uint16_t)((
+                (uint32_t)(src[y_pos_int * slinesize + x_pos_int * step + c]) * x_pos_frac * y_pos_frac +
+                (uint32_t)(src[y_pos_int * slinesize + (x_pos_int + 1) * step + c]) * (one - x_pos_frac) * y_pos_frac +
+                (uint32_t)(src[(y_pos_int + 1) * slinesize + (x_pos_int + 1) * step + c]) * (one - x_pos_frac) * (one - y_pos_frac) +
+                (uint32_t)(src[(y_pos_int + 1) * slinesize + x_pos_int * step + c]) * x_pos_frac * (one - y_pos_frac)
+                        ) >> 6);
+                }
+            } else {
+                for (c = 0; c < td->nb_components; c++) {
+                    dst[x * step + c] = 0;
+                }
+            }
+        }
+        dst  += dlinesize;
+        xmap += xlinesize;
+        ymap += ylinesize;
+    }
+    printf("remappacked8\n");
+}
+
+static void remap_frac_packed16_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
+{
+    const ThreadData *td = (ThreadData*)arg;
+    const AVFrame *in  = td->in;
+    const AVFrame *xin = td->xin;
+    const AVFrame *yin = td->yin;
+    const AVFrame *out = td->out;
+
+    uint16_t *dst = (uint16_t *)out->data[0];
+    const uint16_t one = 1 << 3;
+    const uint16_t *src  = (const uint16_t *)in->data[0];
+    const int dlinesize = out->linesize[0] / 2;
+    const int slinesize = in->linesize[0] / 2;
+    const int xlinesize = xin->linesize[0] / 2;
+    const int ylinesize = yin->linesize[0] / 2;
+    const uint16_t *xmap = (const uint16_t *)xin->data[0];
+    const uint16_t *ymap = (const uint16_t *)yin->data[0];
+    const int step = td->step / 2;
+    int c, x, y;
+
+    for (y = 0; y < out->height; y++) {
+        for (x = 0; x < out->width; x++) {
+            uint16_t x_pos = xmap[x];
+            uint16_t y_pos = ymap[x];
+            uint16_t x_pos_int = x_pos >> 3;
+            uint16_t y_pos_int = y_pos >> 3;
+            uint16_t x_pos_frac = x_pos & (one - 1);
+            uint16_t y_pos_frac = y_pos & (one - 1);
+            if (y_pos_int < (in->height - 1) && x_pos_int < (in->width - 1)) {
+                for (c = 0; c < td->nb_components; c++) {
+                    dst[x * step + c] = 
+                    (uint8_t)((
+                (uint16_t)(src[y_pos_int * slinesize + x_pos_int * step + c]) * x_pos_frac * y_pos_frac +
+                (uint16_t)(src[y_pos_int * slinesize + (x_pos_int + 1) * step + c]) * (one - x_pos_frac) * y_pos_frac +
+                (uint16_t)(src[(y_pos_int + 1) * slinesize + (x_pos_int + 1) * step + c]) * (one - x_pos_frac) * (one - y_pos_frac) +
+                (uint16_t)(src[(y_pos_int + 1) * slinesize + x_pos_int * step + c]) * x_pos_frac * (one - y_pos_frac)
+                        ) >> 6);
+                }
+            } else {
+                for (c = 0; c < td->nb_components; c++) {
+                    dst[x * step + c] = 0;
+                }
+            }
+        }
+        dst  += dlinesize;
+        xmap += xlinesize;
+        ymap += ylinesize;
+    }
+    printf("remappacked16\n");
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    RemapFracContext *s = ctx->priv;
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
+
+    s->nb_planes = av_pix_fmt_count_planes(inlink->format);
+    s->nb_components = desc->nb_components;
+
+    if (desc->comp[0].depth == 8) {
+        if (s->nb_planes > 1 || s->nb_components == 1) {
+            s->remap_frac_slice = remap_frac_planar_slice;
+        } else {
+            s->remap_frac_slice = remap_frac_packed_slice;
+        }
+    } else {
+        if (s->nb_planes > 1 || s->nb_components == 1) {
+            s->remap_frac_slice = remap_frac_planar16_slice;
+        } else {
+            s->remap_frac_slice = remap_frac_packed16_slice;
+        }
+    }
+
+    s->step = av_get_padded_bits_per_pixel(desc) >> 3;
+    return 0;
+}
+
+static int process_frame(FFFrameSync *fs)
+{
+    AVFilterContext *ctx = fs->parent;
+    RemapFracContext *s = fs->opaque;
+    AVFilterLink *outlink = ctx->outputs[0];
+    AVFrame *out, *in, *xpic, *ypic;
+    int ret;
+
+    if ((ret = ff_framesync_get_frame(&s->fs, 0, &in,   0)) < 0 ||
+        (ret = ff_framesync_get_frame(&s->fs, 1, &xpic, 0)) < 0 ||
+        (ret = ff_framesync_get_frame(&s->fs, 2, &ypic, 0)) < 0)
+        return ret;
+
+    if (ctx->is_disabled) {
+        out = av_frame_clone(in);
+        if (!out)
+            return AVERROR(ENOMEM);
+    } else {
+        ThreadData td;
+
+        out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+        if (!out)
+            return AVERROR(ENOMEM);
+        av_frame_copy_props(out, in);
+
+        td.in  = in;
+        td.xin = xpic;
+        td.yin = ypic;
+        td.out = out;
+        td.nb_planes = s->nb_planes;
+        td.nb_components = s->nb_components;
+        td.step = s->step;
+        ctx->internal->execute(ctx, s->remap_frac_slice, &td, NULL, FFMIN(outlink->h, ctx->graph->nb_threads));
+    }
+    out->pts = av_rescale_q(in->pts, s->fs.time_base, outlink->time_base);
+
+    return ff_filter_frame(outlink, out);
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    RemapFracContext *s = ctx->priv;
+    AVFilterLink *srclink = ctx->inputs[0];
+    AVFilterLink *xlink = ctx->inputs[1];
+    AVFilterLink *ylink = ctx->inputs[2];
+    FFFrameSyncIn *in;
+    int ret;
+
+    if (xlink->w != ylink->w || xlink->h != ylink->h) {
+        av_log(ctx, AV_LOG_ERROR, "Second input link %s parameters "
+               "(size %dx%d) do not match the corresponding "
+               "third input link %s parameters (%dx%d)\n",
+               ctx->input_pads[1].name, xlink->w, xlink->h,
+               ctx->input_pads[2].name, ylink->w, ylink->h);
+        return AVERROR(EINVAL);
+    }
+
+    outlink->w = xlink->w;
+    outlink->h = xlink->h;
+    outlink->time_base = srclink->time_base;
+    outlink->sample_aspect_ratio = srclink->sample_aspect_ratio;
+    outlink->frame_rate = srclink->frame_rate;
+
+    ret = ff_framesync_init(&s->fs, ctx, 3);
+    if (ret < 0)
+        return ret;
+
+    in = s->fs.in;
+    in[0].time_base = srclink->time_base;
+    in[1].time_base = xlink->time_base;
+    in[2].time_base = ylink->time_base;
+    in[0].sync   = 2;
+    in[0].before = EXT_STOP;
+    in[0].after  = EXT_STOP;
+    in[1].sync   = 1;
+    in[1].before = EXT_NULL;
+    in[1].after  = EXT_INFINITY;
+    in[2].sync   = 1;
+    in[2].before = EXT_NULL;
+    in[2].after  = EXT_INFINITY;
+    s->fs.opaque   = s;
+    s->fs.on_event = process_frame;
+
+    return ff_framesync_configure(&s->fs);
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
+{
+    RemapFracContext *s = inlink->dst->priv;
+    return ff_framesync_filter_frame(&s->fs, inlink, buf);
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    RemapFracContext *s = outlink->src->priv;
+    return ff_framesync_request_frame(&s->fs, outlink);
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    RemapFracContext *s = ctx->priv;
+
+    ff_framesync_uninit(&s->fs);
+}
+
+static const AVFilterPad remap_frac_inputs[] = {
+    {
+        .name         = "source",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame,
+        .config_props = config_input,
+    },
+    {
+        .name         = "xmap",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame,
+    },
+    {
+        .name         = "ymap",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame,
+    },
+    { NULL }
+};
+
+static const AVFilterPad remap_frac_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_output,
+        .request_frame = request_frame,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_remap_frac = {
+    .name          = "remap_frac",
+    .description   = NULL_IF_CONFIG_SMALL("remap pixels with 3bit subpixel accuracy"),
+    .priv_size     = sizeof(RemapFracContext),
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .inputs        = remap_frac_inputs,
+    .outputs       = remap_frac_outputs,
+    .priv_class    = &remap_frac_class,
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
+};