From 2e6af94eec0b3b8dc61b939ab35ed27130be6e15 Mon Sep 17 00:00:00 2001
From: Meredydd Luff <meredydd@senatehouse.org>
Date: Tue, 15 Jan 2019 00:16:13 +0000
Subject: [PATCH] avfilter: Add delay filter

The 'delay' filter delays video by the specified number of frames,
inserting frames of a solid color at the start. Unlike tpad,
delay adjusts the timestamp (pts) of the delayed video frames.

Signed-off-by: Meredydd Luff <meredydd@senatehouse.org>
---
 doc/filters.texi         |  33 +++++++
 libavfilter/Makefile     |   1 +
 libavfilter/allfilters.c |   1 +
 libavfilter/vf_delay.c   | 237 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 272 insertions(+)
 create mode 100644 libavfilter/vf_delay.c

diff --git a/doc/filters.texi b/doc/filters.texi
index fc98323..65eac3d 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -8002,6 +8002,39 @@ delogo=x=0:y=0:w=100:h=77:band=10
 
 @end itemize
 
+@section delay
+
+Delay video frames.
+
+It accepts the following parameters:
+@table @option
+
+@item delay
+Specify the number of frames by which the video will be delayed. (Minimum 1)
+
+@item color
+Specify the color of the initial delay frames (default black)
+
+@end table
+
+@subsection Examples
+
+@itemize
+@item
+Delay video by 25 frames.
+@example
+delay=25
+@end example
+
+@item
+Delay video by 50 frames. The first 50 frames will be solid red.
+@example
+delay=delay=50:color=red
+@end example
+
+@end itemize
+
+
 @section deshake
 
 Attempt to fix small changes in horizontal and/or vertical shift. This
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index bc642ac..ff469a6 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -202,6 +202,7 @@ OBJS-$(CONFIG_DEFLICKER_FILTER)              += vf_deflicker.o
 OBJS-$(CONFIG_DEINTERLACE_QSV_FILTER)        += vf_deinterlace_qsv.o
 OBJS-$(CONFIG_DEINTERLACE_VAAPI_FILTER)      += vf_deinterlace_vaapi.o vaapi_vpp.o
 OBJS-$(CONFIG_DEJUDDER_FILTER)               += vf_dejudder.o
+OBJS-$(CONFIG_DELAY_FILTER)                  += vf_delay.o
 OBJS-$(CONFIG_DELOGO_FILTER)                 += vf_delogo.o
 OBJS-$(CONFIG_DENOISE_VAAPI_FILTER)          += vf_misc_vaapi.o vaapi_vpp.o
 OBJS-$(CONFIG_DESHAKE_FILTER)                += vf_deshake.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index c51ae0f..08248f2 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -190,6 +190,7 @@ extern AVFilter ff_vf_deflicker;
 extern AVFilter ff_vf_deinterlace_qsv;
 extern AVFilter ff_vf_deinterlace_vaapi;
 extern AVFilter ff_vf_dejudder;
+extern AVFilter ff_vf_delay;
 extern AVFilter ff_vf_delogo;
 extern AVFilter ff_vf_denoise_vaapi;
 extern AVFilter ff_vf_deshake;
diff --git a/libavfilter/vf_delay.c b/libavfilter/vf_delay.c
new file mode 100644
index 0000000..0c5e267
--- /dev/null
+++ b/libavfilter/vf_delay.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2019 Meredydd Luff
+ *
+ * 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/avassert.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/opt.h"
+#include "libavutil/timestamp.h"
+#include "avfilter.h"
+#include "audio.h"
+#include "filters.h"
+#include "internal.h"
+#include "formats.h"
+#include "drawutils.h"
+
+typedef struct DelayContext {
+    const AVClass *class;
+
+    AVFrame ** q;
+    unsigned q_next;
+    unsigned q_fill;
+
+    int delay;
+    uint8_t rgba_color[4];  ///< color for the delayed frames
+
+    int64_t frame_incr;
+    int64_t pts;
+    FFDrawContext draw;
+    FFDrawColor color;
+    int eof;
+} DelayContext;
+
+#define OFFSET(x) offsetof(DelayContext, x)
+#define VF AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption delay_options[] = {
+    { "delay", "set the number of frames to delay input",              OFFSET(delay),      AV_OPT_TYPE_INT,   {.i64=0},        0,   INT_MAX, VF },
+    { "color", "set the color of the added frames",                    OFFSET(rgba_color), AV_OPT_TYPE_COLOR, {.str="black"},  0,         0, VF },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(delay);
+
+static int query_formats(AVFilterContext *ctx)
+{
+    return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0));
+}
+
+static inline void advance_queue(DelayContext *s)
+{
+    s->q_next++;
+    if (s->q_next == s->delay) {
+        s->q_next = 0;
+    }
+}
+
+static inline void swap_queue_next(DelayContext *s, AVFrame **f)
+{
+    AVFrame *frame_in = *f;
+    *f = s->q[s->q_next];
+    s->q[s->q_next] = frame_in;
+    advance_queue(s);
+    if (s->q_fill != s->delay && frame_in) {
+        s->q_fill++;
+    }
+}
+
+static inline AVFrame * queue_peek(DelayContext *s)
+{
+    return s->q[s->q_next];
+}
+
+static int filter_frame(AVFilterContext *ctx, AVFrame *frame)
+{
+    AVFilterLink *outlink = ctx->outputs[0];
+    DelayContext *s = ctx->priv;
+    AVFrame *frame_in = frame;
+    swap_queue_next(s, &frame);
+
+    /*if (!frame && s->q[0]) {
+        frame = av_frame_clone(s->q[0]);
+    }*/
+
+    if (!frame) {
+        frame = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+        if (!frame)
+            return AVERROR(ENOMEM);
+        ff_fill_rectangle(&s->draw, &s->color,
+                          frame->data, frame->linesize,
+                          0, 0, frame->width, frame->height);
+    }
+    if (frame_in) {
+        frame->pts = frame_in->pts;
+    } else {
+        frame->pts = s->pts + av_rescale_q(1, av_inv_q(outlink->frame_rate),
+                                           outlink->time_base);
+    }
+    s->pts = frame->pts;
+    return ff_filter_frame(outlink, frame);
+}
+
+static int activate(AVFilterContext *ctx)
+{
+    AVFilterLink *inlink = ctx->inputs[0];
+    AVFilterLink *outlink = ctx->outputs[0];
+    DelayContext *s = ctx->priv;
+    AVFrame *frame = NULL;
+    int ret, status;
+    int64_t pts;
+
+    FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
+
+    if (s->eof) {
+        if (queue_peek(s)) {
+            return filter_frame(ctx, frame);
+        } else {
+            ff_outlink_set_status(outlink, AVERROR_EOF, s->pts);
+            return 0;
+        }
+    } else {
+        ret = ff_inlink_consume_frame(inlink, &frame);
+        if (ret < 0) {
+            return ret;
+        }
+        if (ret > 0) {
+            return filter_frame(ctx, frame);
+        }
+    }
+
+    if (!s->eof && ff_inlink_acknowledge_status(inlink, &status, &pts)) {
+        if (status == AVERROR_EOF) {
+            s->eof = 1;
+            s->pts = pts;
+            if (s->q_fill == 0) {
+                ff_outlink_set_status(outlink, status, pts);
+                return 0;
+            }
+        }
+    }
+
+    if (s->eof && ff_outlink_frame_wanted(outlink)) {
+        if (s->q_fill != 0) {
+            return filter_frame(ctx, NULL);
+        } else {
+            ff_outlink_set_status(outlink, AVERROR_EOF, s->pts);
+            return 0;
+        }
+    }
+
+    FF_FILTER_FORWARD_WANTED(outlink, inlink);
+
+    return FFERROR_NOT_READY;
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    DelayContext *s = ctx->priv;
+
+    ff_draw_init(&s->draw, inlink->format, 0);
+    ff_draw_color(&s->draw, &s->color, s->rgba_color);
+
+    return 0;
+}
+
+static int init(AVFilterContext *ctx)
+{
+    DelayContext *s = ctx->priv;
+    if (s->delay < 1) {
+        av_log(ctx, AV_LOG_ERROR, "The minimum video delay is 1 frame.\n");
+        return AVERROR(EINVAL);
+    }
+    s->q = av_mallocz(s->delay * sizeof(AVFrame *));
+    if (!s->q) {
+        return AVERROR(ENOMEM);
+    }
+    return 0;
+}
+
+static void uninit(AVFilterContext *ctx)
+{
+    DelayContext *s = ctx->priv;
+    int i;
+
+    for (i=0; i < s->delay; i++) {
+        if (s->q[i]) {
+            av_frame_free(&s->q[i]);
+        }
+    }
+    av_free(s->q);
+}
+
+static const AVFilterPad delay_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .config_props = config_input,
+    },
+    { NULL }
+};
+
+static const AVFilterPad delay_outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_VIDEO,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_delay = {
+    .name          = "delay",
+    .description   = NULL_IF_CONFIG_SMALL("Delay video frames."),
+    .priv_size     = sizeof(DelayContext),
+    .priv_class    = &delay_class,
+    .query_formats = query_formats,
+    .activate      = activate,
+    .init          = init,
+    .uninit        = uninit,
+    .inputs        = delay_inputs,
+    .outputs       = delay_outputs,
+};
-- 
2.7.4

