avfilter: add freezeframes video filter
authorPaul B Mahol <onemda@gmail.com>
Tue, 31 Dec 2019 11:00:30 +0000 (12:00 +0100)
committerPaul B Mahol <onemda@gmail.com>
Sat, 11 Jan 2020 18:05:17 +0000 (19:05 +0100)
Changelog
doc/filters.texi
libavfilter/Makefile
libavfilter/allfilters.c
libavfilter/version.h
libavfilter/vf_freezeframes.c [new file with mode: 0644]

index b9401aa..2ccd264 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -29,6 +29,7 @@ version <next>:
 - mvha decoder
 - MPEG-H 3D Audio support in mp4
 - thistogram filter
+- freezeframes filter
 
 
 version 4.2:
index a2f862e..d51fc4d 100644 (file)
@@ -11219,6 +11219,25 @@ specified value) or as a difference ratio between 0 and 1. Default is -60dB, or
 Set freeze duration until notification (default is 2 seconds).
 @end table
 
+@section freezeframes
+
+Freeze video frames.
+
+This filter freezes video frames using frame from 2nd input.
+
+The filter accepts the following options:
+
+@table @option
+@item first
+Set number of first frame from which to start freeze.
+
+@item last
+Set number of last frame from which to end freeze.
+
+@item replace
+Set number of frame from 2nd input which will be used instead of replaced frames.
+@end table
+
 @anchor{frei0r}
 @section frei0r
 
index 8b8a5bd..58b3077 100644 (file)
@@ -254,6 +254,7 @@ OBJS-$(CONFIG_FRAMEPACK_FILTER)              += vf_framepack.o
 OBJS-$(CONFIG_FRAMERATE_FILTER)              += vf_framerate.o
 OBJS-$(CONFIG_FRAMESTEP_FILTER)              += vf_framestep.o
 OBJS-$(CONFIG_FREEZEDETECT_FILTER)           += vf_freezedetect.o
+OBJS-$(CONFIG_FREEZEFRAMES_FILTER)           += vf_freezeframes.o
 OBJS-$(CONFIG_FREI0R_FILTER)                 += vf_frei0r.o
 OBJS-$(CONFIG_FSPP_FILTER)                   += vf_fspp.o
 OBJS-$(CONFIG_GBLUR_FILTER)                  += vf_gblur.o
index 9f2080f..6270c18 100644 (file)
@@ -239,6 +239,7 @@ extern AVFilter ff_vf_framepack;
 extern AVFilter ff_vf_framerate;
 extern AVFilter ff_vf_framestep;
 extern AVFilter ff_vf_freezedetect;
+extern AVFilter ff_vf_freezeframes;
 extern AVFilter ff_vf_frei0r;
 extern AVFilter ff_vf_fspp;
 extern AVFilter ff_vf_gblur;
index b029905..03b6ce6 100644 (file)
@@ -30,8 +30,8 @@
 #include "libavutil/version.h"
 
 #define LIBAVFILTER_VERSION_MAJOR   7
-#define LIBAVFILTER_VERSION_MINOR  70
-#define LIBAVFILTER_VERSION_MICRO 101
+#define LIBAVFILTER_VERSION_MINOR  71
+#define LIBAVFILTER_VERSION_MICRO 100
 
 
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
diff --git a/libavfilter/vf_freezeframes.c b/libavfilter/vf_freezeframes.c
new file mode 100644 (file)
index 0000000..b6cd5db
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * 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/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/common.h"
+#include "libavutil/internal.h"
+#include "libavutil/opt.h"
+
+#include "avfilter.h"
+#include "filters.h"
+#include "internal.h"
+#include "video.h"
+
+typedef struct FreezeFramesContext {
+    const AVClass *class;
+    int64_t first, last, replace;
+
+    AVFrame *replace_frame;
+} FreezeFramesContext;
+
+#define OFFSET(x) offsetof(FreezeFramesContext, x)
+#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
+
+static const AVOption freezeframes_options[] = {
+    { "first",   "set first frame to freeze", OFFSET(first),   AV_OPT_TYPE_INT64, {.i64=0}, 0, INT64_MAX, FLAGS },
+    { "last",    "set last frame to freeze",  OFFSET(last),    AV_OPT_TYPE_INT64, {.i64=0}, 0, INT64_MAX, FLAGS },
+    { "replace", "set frame to replace",      OFFSET(replace), AV_OPT_TYPE_INT64, {.i64=0}, 0, INT64_MAX, FLAGS },
+    { NULL },
+};
+
+AVFILTER_DEFINE_CLASS(freezeframes);
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    AVFilterLink *sourcelink = ctx->inputs[0];
+    AVFilterLink *replacelink = ctx->inputs[1];
+
+    if (sourcelink->w != replacelink->w || sourcelink->h != replacelink->h) {
+        av_log(ctx, AV_LOG_ERROR,
+               "Input frame sizes do not match (%dx%d vs %dx%d).\n",
+               sourcelink->w, sourcelink->h,
+               replacelink->w, replacelink->h);
+        return AVERROR(EINVAL);
+    }
+
+    outlink->w = sourcelink->w;
+    outlink->h = sourcelink->h;
+    outlink->time_base = sourcelink->time_base;
+    outlink->sample_aspect_ratio = sourcelink->sample_aspect_ratio;
+    outlink->frame_rate = sourcelink->frame_rate;
+
+    return 0;
+}
+
+static int activate(AVFilterContext *ctx)
+{
+    AVFilterLink *outlink = ctx->outputs[0];
+    FreezeFramesContext *s = ctx->priv;
+    AVFrame *frame = NULL;
+    int drop = ctx->inputs[0]->frame_count_out >= s->first &&
+               ctx->inputs[0]->frame_count_out <= s->last;
+    int replace = ctx->inputs[1]->frame_count_out == s->replace;
+    int ret;
+
+    FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, ctx);
+
+    if (drop && s->replace_frame) {
+        ret = ff_inlink_consume_frame(ctx->inputs[0], &frame);
+        if (ret < 0)
+            return ret;
+
+        if (frame) {
+            int64_t dropped_pts = frame->pts;
+
+            av_frame_free(&frame);
+            frame = av_frame_clone(s->replace_frame);
+            if (!frame)
+                return AVERROR(ENOMEM);
+            frame->pts = dropped_pts;
+            return ff_filter_frame(outlink, frame);
+        }
+    } else if (!drop) {
+        ret = ff_inlink_consume_frame(ctx->inputs[0], &frame);
+        if (ret < 0)
+            return ret;
+
+        if (frame)
+            return ff_filter_frame(outlink, frame);
+    }
+
+    ret = ff_inlink_consume_frame(ctx->inputs[1], &frame);
+    if (ret < 0)
+        return ret;
+    if (replace && frame) {
+        s->replace_frame = frame;
+    } else if (frame) {
+        av_frame_free(&frame);
+    }
+
+    FF_FILTER_FORWARD_STATUS(ctx->inputs[0], outlink);
+    FF_FILTER_FORWARD_STATUS(ctx->inputs[1], outlink);
+
+    if (!drop || (drop && s->replace_frame))
+        FF_FILTER_FORWARD_WANTED(outlink, ctx->inputs[0]);
+    if (!s->replace_frame)
+        FF_FILTER_FORWARD_WANTED(outlink, ctx->inputs[1]);
+
+    return FFERROR_NOT_READY;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    FreezeFramesContext *s = ctx->priv;
+
+    av_frame_free(&s->replace_frame);
+}
+
+static const AVFilterPad freezeframes_inputs[] = {
+    {
+        .name = "source",
+        .type = AVMEDIA_TYPE_VIDEO,
+    },
+    {
+        .name = "replace",
+        .type = AVMEDIA_TYPE_VIDEO,
+    },
+    { NULL },
+};
+
+static const AVFilterPad freezeframes_outputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .config_props = config_output,
+    },
+    { NULL },
+};
+
+AVFilter ff_vf_freezeframes = {
+    .name          = "freezeframes",
+    .description   = NULL_IF_CONFIG_SMALL("Freeze video frames."),
+    .priv_size     = sizeof(FreezeFramesContext),
+    .priv_class    = &freezeframes_class,
+    .inputs        = freezeframes_inputs,
+    .outputs       = freezeframes_outputs,
+    .activate      = activate,
+    .uninit        = uninit,
+};