[FFmpeg-devel] [PATCH] filters: add the vediting and aediting filters

Federico Simoncelli federico.simoncelli at gmail.com
Mon Jan 6 15:59:51 CET 2014


The vediting and aediting filters allow the user to select segments
of video and audio to include.

Signed-off-by: Federico Simoncelli <fsimonce at redhat.com>
---
 Changelog                |   1 +
 doc/filters.texi         |  23 +++++
 libavfilter/Makefile     |   2 +
 libavfilter/allfilters.c |   2 +
 libavfilter/f_editing.c  | 259 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 287 insertions(+)
 create mode 100644 libavfilter/f_editing.c

diff --git a/Changelog b/Changelog
index 2cab110..c45b2fe 100644
--- a/Changelog
+++ b/Changelog
@@ -18,6 +18,7 @@ version <next>
 - ATRAC3+ decoder
 - VP8 in Ogg demuxing
 - side & metadata support in NUT
+- editing filter
 
 
 version 2.1:
diff --git a/doc/filters.texi b/doc/filters.texi
index a579964..4d0b4f6 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -1787,6 +1787,29 @@ slope
 Determine how steep is the filter's shelf transition.
 @end table
 
+ at section vediting, aediting
+
+Select the segments of video and audio to include.
+
+The filter accepts the following options:
+
+ at table @option
+ at item segments
+The segments of the video or audio to include. The format is:
+ at example
+vediting=START_TS1-END_TS1#START_TS2-END_TS2#...
+ at end example
+ at end table
+
+The segments must be monotonically ordered and cannot overlap.
+In general the same segments are declared both for video and
+audio tracks (vediting/aediting) but there's no limitation in
+this respect.
+
+The algorithm used to calculate the output pts is highly
+resistant to damaged streams (e.g. DVB-T) as it reuses the
+input pts preventing the audio/video de-synchronization.
+
 @section volume
 
 Adjust the input audio volume.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 3d587fe..87f8c5a 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -53,6 +53,7 @@ OBJS-$(CONFIG_AVCODEC)                       += avcodec.o
 OBJS-$(CONFIG_ACONVERT_FILTER)               += af_aconvert.o
 OBJS-$(CONFIG_ADELAY_FILTER)                 += af_adelay.o
 OBJS-$(CONFIG_AECHO_FILTER)                  += af_aecho.o
+OBJS-$(CONFIG_AEDITING_FILTER)               += f_editing.o
 OBJS-$(CONFIG_AEVAL_FILTER)                  += aeval.o
 OBJS-$(CONFIG_AFADE_FILTER)                  += af_afade.o
 OBJS-$(CONFIG_AFORMAT_FILTER)                += af_aformat.o
@@ -205,6 +206,7 @@ OBJS-$(CONFIG_TINTERLACE_FILTER)             += vf_tinterlace.o
 OBJS-$(CONFIG_TRANSPOSE_FILTER)              += vf_transpose.o
 OBJS-$(CONFIG_TRIM_FILTER)                   += trim.o
 OBJS-$(CONFIG_UNSHARP_FILTER)                += vf_unsharp.o
+OBJS-$(CONFIG_VEDITING_FILTER)               += f_editing.o
 OBJS-$(CONFIG_VFLIP_FILTER)                  += vf_vflip.o
 OBJS-$(CONFIG_VIDSTABDETECT_FILTER)          += vidstabutils.o vf_vidstabdetect.o
 OBJS-$(CONFIG_VIDSTABTRANSFORM_FILTER)       += vidstabutils.o vf_vidstabtransform.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index d58e8cc..6640ab6 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -50,6 +50,7 @@ void avfilter_register_all(void)
 #endif
     REGISTER_FILTER(ADELAY,         adelay,         af);
     REGISTER_FILTER(AECHO,          aecho,          af);
+    REGISTER_FILTER(AEDITING,       aediting,       af);
     REGISTER_FILTER(AEVAL,          aeval,          af);
     REGISTER_FILTER(AFADE,          afade,          af);
     REGISTER_FILTER(AFORMAT,        aformat,        af);
@@ -201,6 +202,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER(TRANSPOSE,      transpose,      vf);
     REGISTER_FILTER(TRIM,           trim,           vf);
     REGISTER_FILTER(UNSHARP,        unsharp,        vf);
+    REGISTER_FILTER(VEDITING,       vediting,       vf);
     REGISTER_FILTER(VFLIP,          vflip,          vf);
     REGISTER_FILTER(VIDSTABDETECT,  vidstabdetect,  vf);
     REGISTER_FILTER(VIDSTABTRANSFORM, vidstabtransform, vf);
diff --git a/libavfilter/f_editing.c b/libavfilter/f_editing.c
new file mode 100644
index 0000000..e31d069
--- /dev/null
+++ b/libavfilter/f_editing.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2013 Federico Simoncelli <federico.simoncelli at gmail.com>
+ *
+ * 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
+ * filter for selecting video and audio segments
+ */
+
+#include <string.h>
+
+#include "audio.h"
+#include "video.h"
+#include "libavutil/opt.h"
+
+
+typedef struct _MediaSegment {
+    double start;
+    double end;
+    struct _MediaSegment *next;
+} MediaSegment;
+
+typedef struct {
+    const AVClass *class;
+    char *opt_segments;
+    double ts_base;
+    double ts_prev;
+    int frame_out;
+    MediaSegment *current;
+    MediaSegment *segments;
+} EditingContext;
+
+
+#define FLAGS ( \
+    AV_OPT_FLAG_AUDIO_PARAM | \
+    AV_OPT_FLAG_VIDEO_PARAM | \
+    AV_OPT_FLAG_FILTERING_PARAM \
+)
+#define OFFSET(x) offsetof(EditingContext, x)
+static const AVOption options[] = {
+    { "segments", "set the segment list", OFFSET(opt_segments),
+        AV_OPT_TYPE_STRING, { .str = NULL }, .flags=FLAGS },
+    { NULL }
+};
+
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
+{
+    int ret;
+    double frame_in_ts, frame_out_ts;
+    EditingContext *editing = inlink->dst->priv;
+
+    if (!editing->current) /* fast-forward to the end */
+        goto discard;
+
+    frame_in_ts = (double) frame->pts * av_q2d(inlink->time_base);
+
+    if (editing->ts_prev > frame_in_ts) {
+        av_log(inlink->dst, AV_LOG_ERROR, "Frame discontinuity "
+            "error %f\n", editing->ts_prev - frame_in_ts);
+        av_frame_free(&frame);
+        return AVERROR(EINVAL);
+    }
+
+    editing->ts_prev = frame_in_ts;
+    frame_out_ts = editing->ts_base +
+        (frame_in_ts - editing->current->start);
+
+    if (frame_in_ts >= editing->current->end) {
+        editing->current = editing->current->next;
+        editing->ts_base = frame_out_ts;
+        goto discard;
+    }
+
+    if (frame_in_ts <= editing->current->start) {
+        goto discard;
+    }
+
+    frame->pts = frame_out_ts / av_q2d(inlink->time_base);
+
+    ret = ff_filter_frame(inlink->dst->outputs[0], frame);
+    editing->frame_out = (ret == 0) ? 1 : 0;
+
+    return ret;
+
+  discard:
+    av_frame_free(&frame);
+    editing->frame_out = 0;
+    return 0;
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    int ret;
+    EditingContext *editing = outlink->src->priv;
+
+    if (!editing->current) /* exit after last segment */
+        return AVERROR_EOF;
+
+    do {
+        ret = ff_request_frame(outlink->src->inputs[0]);
+        if (ret < 0)
+            return ret;
+    } while (!editing->frame_out);
+
+    return 0;
+}
+
+static int parse_segments(AVFilterContext *ctx)
+{
+    char *p, *sp;
+    EditingContext *editing = ctx->priv;
+    MediaSegment *segment, *j = NULL, **i = &editing->segments;
+
+    if (!editing->opt_segments) {
+        av_log(ctx, AV_LOG_ERROR, "Missing segments list\n");
+        return AVERROR(EINVAL);
+    }
+
+    for (p = editing->opt_segments;; p = NULL) {
+        if (!(p = strtok_r(p, "-", &sp)))
+            break;
+
+        segment = av_malloc(sizeof(MediaSegment));
+        segment->next = NULL;
+
+        segment->start = atof(p);
+
+        if (j && segment->start < j->end) {
+            av_log(ctx, AV_LOG_ERROR, "Non-monotonic segments\n");
+            return AVERROR(EINVAL);
+        }
+
+        if (!(p = strtok_r(NULL, "#", &sp))) {
+            av_log(ctx, AV_LOG_ERROR, "Invalid segment list\n");
+            return AVERROR(EINVAL);
+        }
+
+        segment->end = atof(p);
+
+        if (segment->start >= segment->end) {
+            av_log(ctx, AV_LOG_ERROR, "Invalid or empty segment\n");
+            return AVERROR(EINVAL);
+        }
+
+        *i = j = segment, i = &segment->next;
+    }
+
+    return 0;
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    int ret;
+    EditingContext *editing = ctx->priv;
+
+    ret = parse_segments(ctx);
+    if (ret < 0)
+        return ret;
+
+    editing->current = editing->segments;
+    editing->ts_base = 0;
+    editing->ts_prev = 0;
+    editing->frame_out = 0;
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx) {
+    MediaSegment *n, *i;
+    EditingContext *editing = ctx->priv;
+
+    for (i = editing->segments; i != NULL; i = n) {
+        n = i->next;
+        av_freep(&i);
+    }
+
+    editing->current = editing->segments = NULL;
+}
+
+#define vediting_options options
+AVFILTER_DEFINE_CLASS(vediting);
+
+#define aediting_options options
+AVFILTER_DEFINE_CLASS(aediting);
+
+static const AVFilterPad avfilter_af_editing_inputs[] = {
+    {
+        .name           = "default",
+        .type           = AVMEDIA_TYPE_AUDIO,
+        .filter_frame   = filter_frame,
+    },
+    {NULL}
+};
+
+static const AVFilterPad avfilter_af_editing_outputs[] = {
+    {
+        .name           = "default",
+        .type           = AVMEDIA_TYPE_AUDIO,
+        .request_frame  = request_frame,
+    },
+    {NULL}
+};
+
+AVFilter ff_af_aediting = {
+    .name           = "aediting",
+    .description    = NULL_IF_CONFIG_SMALL("Select audio segments"),
+    .init           = init,
+    .uninit         = uninit,
+    .priv_size      = sizeof(EditingContext),
+    .priv_class     = &aediting_class,
+    .inputs         = avfilter_af_editing_inputs,
+    .outputs        = avfilter_af_editing_outputs,
+};
+
+static const AVFilterPad avfilter_vf_editing_inputs[] = {
+    {
+        .name           = "default",
+        .type           = AVMEDIA_TYPE_VIDEO,
+        .filter_frame   = filter_frame,
+    },
+    {NULL}
+};
+
+static const AVFilterPad avfilter_vf_editing_outputs[] = {
+    {
+        .name           = "default",
+        .type           = AVMEDIA_TYPE_VIDEO,
+        .request_frame  = request_frame,
+    },
+    {NULL}
+};
+
+AVFilter ff_vf_vediting = {
+    .name           = "vediting",
+    .description    = NULL_IF_CONFIG_SMALL("Select video segments"),
+    .init           = init,
+    .uninit         = uninit,
+    .priv_size      = sizeof(EditingContext),
+    .priv_class     = &vediting_class,
+    .inputs         = avfilter_vf_editing_inputs,
+    .outputs        = avfilter_vf_editing_outputs,
+};
-- 
1.8.4.2



More information about the ffmpeg-devel mailing list