diff mbox

[FFmpeg-devel] avfilter: add tdisplace filter

Message ID 20191016183546.24832-1-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol Oct. 16, 2019, 6:35 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi           |  21 ++++
 libavfilter/Makefile       |   1 +
 libavfilter/allfilters.c   |   1 +
 libavfilter/vf_tdisplace.c | 234 +++++++++++++++++++++++++++++++++++++
 4 files changed, 257 insertions(+)
 create mode 100644 libavfilter/vf_tdisplace.c

Comments

Moritz Barsnick Oct. 16, 2019, 8:05 p.m. UTC | #1
Hi Paul,
interesting concept!

On Wed, Oct 16, 2019 at 20:35:46 +0200, Paul B Mahol wrote:

Doc nits:

> +It takes two input streams and outputs one stream, the first input is the
> +source, and second input in temporal displacement map.

"in" seems misplaced. Probably:

[...]
, the first input is the source, the second input the temporal
displacement map.

> +If temporal displacement map stream terminates, last frame from that stream
> +will be used.

->
If the temporal displacement map stream terminates, the last frame from
that stream will be used.

> +@table @option
> +@item edge
> +Set temporal displacement radius. Any value present in temporal displacement
> +map which is higher/lower than this one will be clipped.
> +Allowed range is from 1 to 127. Default is 127.
> +@end table

What's the unit of this clipping limit, and what's the unit of the
displacement value found in the second stream? PTS, i.e. based in TB?
Number of frames? (Looks like no. of frames from the source.) Needs to
be mentioned somewhere.

Nothing else obvious that comes to my eyes.

Cheers,
Moritz
diff mbox

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index 5cebb26cbd..5d3a36e49f 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -17233,6 +17233,27 @@  the position in the file of the input frame, NAN if unknown
 @section swapuv
 Swap U & V plane.
 
+@section tdisplace
+
+Displace pixels temporally as indicated by second stream.
+
+It takes two input streams and outputs one stream, the first input is the
+source, and second input in temporal displacement map.
+
+The second input specifies how much to displace pixels along the
+time-axis.
+If temporal displacement map stream terminates, last frame from that stream
+will be used.
+
+This filter accepts the following options:
+
+@table @option
+@item edge
+Set temporal displacement radius. Any value present in temporal displacement
+map which is higher/lower than this one will be clipped.
+Allowed range is from 1 to 127. Default is 127.
+@end table
+
 @section telecine
 
 Apply telecine process to the video.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index e9ac54daee..a76347af45 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -394,6 +394,7 @@  OBJS-$(CONFIG_SUPER2XSAI_FILTER)             += vf_super2xsai.o
 OBJS-$(CONFIG_SWAPRECT_FILTER)               += vf_swaprect.o
 OBJS-$(CONFIG_SWAPUV_FILTER)                 += vf_swapuv.o
 OBJS-$(CONFIG_TBLEND_FILTER)                 += vf_blend.o framesync.o
+OBJS-$(CONFIG_TDISPLACE_FILTER)              += vf_tdisplace.o framesync.o
 OBJS-$(CONFIG_TELECINE_FILTER)               += vf_telecine.o
 OBJS-$(CONFIG_THRESHOLD_FILTER)              += vf_threshold.o framesync.o
 OBJS-$(CONFIG_THUMBNAIL_FILTER)              += vf_thumbnail.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index a7a165e0d8..a04dcfbf5b 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -375,6 +375,7 @@  extern AVFilter ff_vf_super2xsai;
 extern AVFilter ff_vf_swaprect;
 extern AVFilter ff_vf_swapuv;
 extern AVFilter ff_vf_tblend;
+extern AVFilter ff_vf_tdisplace;
 extern AVFilter ff_vf_telecine;
 extern AVFilter ff_vf_threshold;
 extern AVFilter ff_vf_thumbnail;
diff --git a/libavfilter/vf_tdisplace.c b/libavfilter/vf_tdisplace.c
new file mode 100644
index 0000000000..abf4beba11
--- /dev/null
+++ b/libavfilter/vf_tdisplace.c
@@ -0,0 +1,234 @@ 
+/*
+ * Copyright (c) 2019 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/opt.h"
+#include "libavutil/pixdesc.h"
+#include "avfilter.h"
+#include "filters.h"
+#include "formats.h"
+#include "framesync.h"
+#include "internal.h"
+#include "video.h"
+
+#define MAX_RADIUS 127
+#define MAX_FRAMES (MAX_RADIUS * 2 + 1)
+
+typedef struct TDisplaceContext {
+    const AVClass *class;
+    int radius;
+
+    int max_frames;
+
+    int planewidth[4], planeheight[4];
+    int nb_planes;
+
+    AVFrame *frames[MAX_FRAMES];
+    int nb_frames;
+
+    FFFrameSync fs;
+} TDisplaceContext;
+
+#define OFFSET(x) offsetof(TDisplaceContext, x)
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption tdisplace_options[] = {
+    { "radius", "set temporal radius", OFFSET(radius), AV_OPT_TYPE_INT, {.i64=MAX_RADIUS}, 1, MAX_RADIUS, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(tdisplace);
+
+static int tdisplace_frame(FFFrameSync *fs)
+{
+    AVFilterContext *ctx = fs->parent;
+    AVFilterLink *outlink = ctx->outputs[0];
+    TDisplaceContext *s = ctx->priv;
+    AVFrame *in, *tmap, *out;
+    int ret;
+
+    ret = ff_framesync_dualinput_get(fs, &in, &tmap);
+    if (ret < 0)
+        return ret;
+
+    if (s->nb_frames < s->max_frames) {
+        s->frames[s->nb_frames] = in;
+        if (s->frames[s->nb_frames])
+            s->nb_frames++;
+    } else {
+        av_frame_free(&s->frames[0]);
+        memmove(&s->frames[0], &s->frames[1], (s->nb_frames - 1) * sizeof(s->frames[0]));
+        s->frames[s->max_frames - 1] = in;
+        if (!s->frames[s->max_frames - 1])
+            s->nb_frames--;
+    }
+
+    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+    if (!out)
+        return AVERROR(ENOMEM);
+    av_frame_copy_props(out, in);
+
+    for (int p = 0; p < s->nb_planes; p++) {
+        uint8_t *dst = out->data[p];
+        const uint8_t *tsrc = tmap->data[p];
+        const int tlinesize = tmap->linesize[p];
+        const int dlinesize = out->linesize[p];
+        const int half = (s->nb_frames + 1) >> 1;
+
+        for (int y = 0; y < s->planeheight[p]; y++) {
+            for (int x = 0; x < s->planewidth[p]; x++) {
+                const int frame = tsrc[x] - s->max_frames / 2;
+                const int index = av_clip(half + frame, 0, s->nb_frames - 1);
+                const int linesize = s->frames[index]->linesize[p];
+
+                dst[x] = s->frames[index]->data[p][linesize * y + x];
+            }
+
+            tsrc += tlinesize;
+            dst += dlinesize;
+        }
+    }
+
+    return ff_filter_frame(outlink, out);
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    TDisplaceContext *s = ctx->priv;
+
+    s->max_frames  = s->radius * 2 + 1;
+    s->fs.on_event = tdisplace_frame;
+
+    return 0;
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    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_GBRP, AV_PIX_FMT_GBRAP,
+        AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE
+    };
+
+    return ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    TDisplaceContext *s = ctx->priv;
+    AVFilterLink *srclink = ctx->inputs[0];
+    AVFilterLink *tlink = ctx->inputs[1];
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format);
+    int ret, vsub, hsub;
+
+    if (srclink->format != tlink->format) {
+        av_log(ctx, AV_LOG_ERROR, "inputs must be of same pixel format\n");
+        return AVERROR(EINVAL);
+    }
+    if (srclink->w != tlink->w ||
+        srclink->h != tlink->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 (%dx%d)\n",
+               ctx->input_pads[0].name, srclink->w, srclink->h,
+               ctx->input_pads[1].name, tlink->w, tlink->h);
+        return AVERROR(EINVAL);
+    }
+
+    outlink->w = srclink->w;
+    outlink->h = srclink->h;
+    outlink->time_base = srclink->time_base;
+    outlink->sample_aspect_ratio = srclink->sample_aspect_ratio;
+    outlink->frame_rate = srclink->frame_rate;
+
+    s->nb_planes = av_pix_fmt_count_planes(outlink->format);
+
+    hsub = desc->log2_chroma_w;
+    vsub = desc->log2_chroma_h;
+
+    s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(outlink->h, vsub);
+    s->planeheight[0] = s->planeheight[3] = outlink->h;
+    s->planewidth[1]  = s->planewidth[2]  = AV_CEIL_RSHIFT(outlink->w, hsub);
+    s->planewidth[0]  = s->planewidth[3]  = outlink->w;
+
+    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 int activate(AVFilterContext *ctx)
+{
+    TDisplaceContext *s = ctx->priv;
+    return ff_framesync_activate(&s->fs);
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    TDisplaceContext *s = ctx->priv;
+
+    ff_framesync_uninit(&s->fs);
+
+    for (int i = 0; i < s->nb_frames; i++) {
+        av_frame_free(&s->frames[i]);
+    }
+    s->nb_frames = 0;
+}
+
+static const AVFilterPad tdisplace_inputs[] = {
+    {
+        .name         = "in",
+        .type         = AVMEDIA_TYPE_VIDEO,
+    },
+    {
+        .name         = "tmap",
+        .type         = AVMEDIA_TYPE_VIDEO,
+    },
+    { NULL }
+};
+
+static const AVFilterPad tdisplace_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_output,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_tdisplace = {
+    .name          = "tdisplace",
+    .description   = NULL_IF_CONFIG_SMALL("Temporal pixel displacement."),
+    .priv_size     = sizeof(TDisplaceContext),
+    .priv_class    = &tdisplace_class,
+    .query_formats = query_formats,
+    .init          = init,
+    .uninit        = uninit,
+    .activate      = activate,
+    .inputs        = tdisplace_inputs,
+    .outputs       = tdisplace_outputs,
+};