[FFmpeg-devel] [PATCH] lavfi: add splice filters

Stefano Sabatini stefasab at gmail.com
Wed Apr 10 01:43:18 CEST 2013


---
 doc/filters.texi         |   38 ++++++
 libavfilter/Makefile     |    2 +
 libavfilter/allfilters.c |    2 +
 libavfilter/f_splice.c   |  295 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 337 insertions(+)
 create mode 100644 libavfilter/f_splice.c

diff --git a/doc/filters.texi b/doc/filters.texi
index d01ae82..dbbfafe 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -6792,6 +6792,44 @@ setpts='(RTCTIME - RTCSTART) / (TB * 1000000)'
 @end example
 @end itemize
 
+ at section asplice, splice
+
+Mux frame from sevaral inputs together.
+
+ at code{asplice} works on audio frames, @code{splice} on video frames.
+
+These filters read frames from several inputs, and send the oldest
+frame cached so far to output.
+
+In order to work, they need to cache at least one frame for each
+input, so they cannot work in case one input is not terminated and
+will not receive input frames. Also, in order to work properly, the
+input frames must have a well defined timestamp value, and should be
+such that they are monotonically increasing.
+
+These filters accept parameters as a list of @var{key}=@var{value}
+pairs, separated by ":". They accept the following options.
+
+ at table @option
+ at item nb_inputs, n
+Set the number of different inputs, it is 2 by default.
+ at end table
+
+ at subsection Examples
+
+ at item
+Interleave frames belonging to different streams using @command{ffmpeg}:
+ at example
+ffmpeg -i bambi.avi -i pr0n.mkv -filter_complex "[0:v][1:v] splice" out.avi
+ at end example
+
+ at item
+Add flickering blur effect:
+ at example
+select='gt(random(0), 0.2):branch=1 [tmp], boxblur=2:2, [tmp] splice"
+ at end example
+ at item
+
 @section ebur128
 
 EBU R128 scanner filter. This filter takes an audio stream as input and outputs
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index e1eb35d..a315e17 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -66,6 +66,7 @@ OBJS-$(CONFIG_ASETNSAMPLES_FILTER)           += af_asetnsamples.o
 OBJS-$(CONFIG_ASETPTS_FILTER)                += f_setpts.o
 OBJS-$(CONFIG_ASETTB_FILTER)                 += f_settb.o
 OBJS-$(CONFIG_ASHOWINFO_FILTER)              += af_ashowinfo.o
+OBJS-$(CONFIG_ASPLICE_FILTER)                += f_splice.o
 OBJS-$(CONFIG_ASPLIT_FILTER)                 += split.o
 OBJS-$(CONFIG_ASTREAMSYNC_FILTER)            += af_astreamsync.o
 OBJS-$(CONFIG_ASYNCTS_FILTER)                += af_asyncts.o
@@ -158,6 +159,7 @@ OBJS-$(CONFIG_SETSAR_FILTER)                 += vf_aspect.o
 OBJS-$(CONFIG_SETTB_FILTER)                  += f_settb.o
 OBJS-$(CONFIG_SHOWINFO_FILTER)               += vf_showinfo.o
 OBJS-$(CONFIG_SMARTBLUR_FILTER)              += vf_smartblur.o
+OBJS-$(CONFIG_SPLICE_FILTER)                 += f_splice.o
 OBJS-$(CONFIG_SPLIT_FILTER)                  += split.o
 OBJS-$(CONFIG_STEREO3D_FILTER)               += vf_stereo3d.o
 OBJS-$(CONFIG_SUBTITLES_FILTER)              += vf_subtitles.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 4972322..4ab7167 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -62,6 +62,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER(ASETPTS,        asetpts,        af);
     REGISTER_FILTER(ASETTB,         asettb,         af);
     REGISTER_FILTER(ASHOWINFO,      ashowinfo,      af);
+    REGISTER_FILTER(ASPLICE,        asplice,        af);
     REGISTER_FILTER(ASPLIT,         asplit,         af);
     REGISTER_FILTER(ASTREAMSYNC,    astreamsync,    af);
     REGISTER_FILTER(ASYNCTS,        asyncts,        af);
@@ -153,6 +154,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER(SETTB,          settb,          vf);
     REGISTER_FILTER(SHOWINFO,       showinfo,       vf);
     REGISTER_FILTER(SMARTBLUR,      smartblur,      vf);
+    REGISTER_FILTER(SPLICE,         splice,         vf);
     REGISTER_FILTER(SPLIT,          split,          vf);
     REGISTER_FILTER(STEREO3D,       stereo3d,       vf);
     REGISTER_FILTER(SUBTITLES,      subtitles,      vf);
diff --git a/libavfilter/f_splice.c b/libavfilter/f_splice.c
new file mode 100644
index 0000000..89f7ce2
--- /dev/null
+++ b/libavfilter/f_splice.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2013 Stefano Sabatini
+ *
+ * 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
+ * audio and video splicer
+ */
+
+#include <float.h>              /* DBL_MAX */
+
+#include "libavutil/avassert.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "bufferqueue.h"
+#include "formats.h"
+#include "internal.h"
+#include "audio.h"
+#include "video.h"
+
+typedef struct {
+    const AVClass *class;
+    int nb_inputs;
+    struct FFBufQueue *queues;
+    int req_fulfilled;
+} SpliceContext;
+
+#define OFFSET(x) offsetof(SpliceContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM
+
+#define DEFINE_OPTIONS(filt_name, filt_type)                                  \
+static const AVOption filt_name##_options[] = {                               \
+    { "nb_inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64 = 2}, 1, INT_MAX, \
+      .flags = AV_OPT_FLAG_##filt_type##_PARAM|AV_OPT_FLAG_FILTERING_PARAM }, \
+    { "n",         "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64 = 2}, 1, INT_MAX, \
+      .flags = AV_OPT_FLAG_##filt_type##_PARAM|AV_OPT_FLAG_FILTERING_PARAM }, \
+    { NULL },                                                                 \
+}
+
+inline static int push_frame(AVFilterContext *ctx, int queue_idx)
+{
+    SpliceContext *splice = ctx->priv;
+    AVFrame *frame = ff_bufqueue_get(&splice->queues[queue_idx]);
+    int ret;
+
+    av_log(ctx, AV_LOG_DEBUG, "queue:%d -> frame time:%f\n",
+           queue_idx, frame->pts * av_q2d(AV_TIME_BASE_Q));
+    ret = ff_filter_frame(ctx->outputs[0], frame);
+    if (ret >= 0)
+        splice->req_fulfilled = 1;
+    return ret;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
+{
+    AVFilterContext *ctx = inlink->dst;
+    SpliceContext *splice = ctx->priv;
+    int i, queue_idx = -1;
+    int64_t pts_min = INT64_MAX;
+
+    if (frame->pts == AV_NOPTS_VALUE) {
+        av_log(ctx, AV_LOG_WARNING,
+               "NOPTS value for input frame cannot be accepted, frame discarded\n");
+        av_frame_free(&frame);
+        return 0;
+    }
+
+    /* queue frame */
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        if (inlink == ctx->inputs[i]) {
+            frame->pts = av_rescale_q(frame->pts,
+                                      inlink->time_base, AV_TIME_BASE_Q);
+            av_log(ctx, AV_LOG_DEBUG, "frame pts:%f -> queue idx:%d available:%d\n",
+                   frame->pts * av_q2d(AV_TIME_BASE_Q), i, splice->queues[i].available);
+            ff_bufqueue_add(ctx, &splice->queues[i], frame);
+        }
+    }
+
+    /* check if all the active queues have available frames */
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        AVFrame *frame;
+        struct FFBufQueue *q = &splice->queues[i];
+
+        if (!ctx->inputs[i]->closed && !q->available)
+            return 0;
+        if (q->available) {
+            frame = ff_bufqueue_peek(q, 0);
+            if (frame->pts < pts_min) {
+                pts_min = frame->pts;
+                queue_idx = i;
+            }
+        }
+    }
+    if (queue_idx < 0)
+        return 0;
+
+    return push_frame(ctx, queue_idx);
+}
+
+static int init(AVFilterContext *ctx, const char *args)
+{
+    SpliceContext *splice = ctx->priv;
+    const AVFilterPad *outpad = &ctx->filter->outputs[0];
+    int i;
+
+    splice->queues = av_calloc(splice->nb_inputs, sizeof(splice->queues[0]));
+    if (!splice->queues)
+        return AVERROR(ENOMEM);
+
+    for (i = 0; i < splice->nb_inputs; i++) {
+        char name[32];
+        AVFilterPad inpad = { 0 };
+
+        snprintf(name, sizeof(name), "input%d", i);
+        inpad.type             = outpad->type;
+        inpad.name             = av_strdup(name);
+        inpad.filter_frame     = filter_frame;
+        if (!inpad.name)
+            return AVERROR(ENOMEM);
+
+        switch (outpad->type) {
+        case AVMEDIA_TYPE_VIDEO:
+            inpad.get_video_buffer = ff_null_get_video_buffer; break;
+        case AVMEDIA_TYPE_AUDIO:
+            inpad.get_audio_buffer = ff_null_get_audio_buffer; break;
+        default:
+            av_assert0(0);
+        }
+        ff_insert_inpad(ctx, i, &inpad);
+    }
+
+    return 0;
+}
+
+static void uninit(AVFilterContext *ctx)
+{
+    SpliceContext *splice = ctx->priv;
+    int i;
+
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        ff_bufqueue_discard_all(&splice->queues[i]);
+        av_freep(&splice->queues[i]);
+        av_freep(&ctx->input_pads[i].name);
+    }
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    AVFilterLink *inlink0 = ctx->inputs[0];
+    int i;
+
+    if (outlink->type == AVMEDIA_TYPE_VIDEO) {
+        outlink->time_base           = AV_TIME_BASE_Q;
+        outlink->w                   = inlink0->w;
+        outlink->h                   = inlink0->h;
+        outlink->sample_aspect_ratio = inlink0->sample_aspect_ratio;
+        outlink->format              = inlink0->format;
+        outlink->frame_rate = (AVRational) {0, 0};
+        for (i = 1; i < ctx->nb_inputs; i++) {
+            AVFilterLink *inlink = ctx->inputs[i];
+
+            if (outlink->w                       != inlink->w                       ||
+                outlink->h                       != inlink->h                       ||
+                outlink->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num ||
+                outlink->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den) {
+                av_log(ctx, AV_LOG_ERROR, "Parameters for input link %s "
+                       "(size %dx%d, SAR %d:%d) do not match the corresponding "
+                       "output link parameters (%dx%d, SAR %d:%d)\n",
+                       ctx->input_pads[i].name, inlink->w, inlink->h,
+                       inlink->sample_aspect_ratio.num,
+                       inlink->sample_aspect_ratio.den,
+                       outlink->w, outlink->h,
+                       outlink->sample_aspect_ratio.num,
+                       outlink->sample_aspect_ratio.den);
+                return AVERROR(EINVAL);
+            }
+        }
+    }
+
+    return 0;
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    SpliceContext *splice = ctx->priv;
+    int i, queue_idx = -1;
+    int64_t pts_min = INT64_MAX;
+    AVFrame *frame;
+
+    splice->req_fulfilled = 0;
+    /* check if all the queues have available frames */
+    for (i = 0; i < ctx->nb_inputs; i++) {
+        struct FFBufQueue *q = &splice->queues[i];
+
+        while (!ctx->inputs[i]->closed && !q->available &&
+               !splice->req_fulfilled) {
+            int ret = ff_request_frame(ctx->inputs[i]);
+            if (ret < 0 && ret != AVERROR_EOF)
+                return ret;
+        }
+
+        if (splice->req_fulfilled)
+            return 0;
+
+        if (q->available) {
+            frame = ff_bufqueue_peek(q, 0);
+            if (frame->pts < pts_min) {
+                pts_min = frame->pts;
+                queue_idx = i;
+            }
+        }
+    }
+
+    if (queue_idx < 0)
+        return AVERROR_EOF;
+
+    /* send out oldest frame */
+    return push_frame(ctx, queue_idx);
+}
+
+static const char *const shorthand[] = { "n", NULL };
+
+#if CONFIG_SPLICE_FILTER
+
+DEFINE_OPTIONS(splice, VIDEO);
+AVFILTER_DEFINE_CLASS(splice);
+
+static const AVFilterPad splice_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_output,
+        .request_frame = request_frame,
+    },
+    { NULL }
+};
+
+AVFilter avfilter_vf_splice = {
+    .name        = "splice",
+    .description = NULL_IF_CONFIG_SMALL("Temporally interleave several video inputs."),
+    .priv_size   = sizeof(SpliceContext),
+    .init        = init,
+    .uninit      = uninit,
+    .outputs     = splice_outputs,
+    .priv_class  = &splice_class,
+    .shorthand   = shorthand,
+};
+
+#endif
+
+#if CONFIG_ASPLICE_FILTER
+
+DEFINE_OPTIONS(asplice, AUDIO);
+AVFILTER_DEFINE_CLASS(asplice);
+
+static const AVFilterPad asplice_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_AUDIO,
+        .config_props  = config_output,
+        .request_frame = request_frame,
+    },
+    { NULL }
+};
+
+AVFilter avfilter_af_asplice = {
+    .name        = "asplice",
+    .description = NULL_IF_CONFIG_SMALL("Mux several audio inputs together."),
+    .priv_size   = sizeof(SpliceContext),
+    .init        = init,
+    .uninit      = uninit,
+    .outputs     = asplice_outputs,
+    .priv_class  = &asplice_class,
+    .shorthand   = shorthand,
+};
+
+#endif
-- 
1.7.9.5



More information about the ffmpeg-devel mailing list