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

Stefano Sabatini stefasab at gmail.com
Thu Jan 9 13:22:50 CET 2014


On date Monday 2014-01-06 15:59:51 +0100, Federico Simoncelli encoded:
> 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

I think [av]edit would be a shorter and better name (usually a filter
name is a simple verb, not its gerundive form).

> +
> +Select the segments of video and audio to include.

This is pretty ambiguous.

> +
> +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#...

Is this a timestamp or a time?

"|" as separator should be better than "#", which might be used soon
or later to denote comments in the graph syntax.

> + 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

PTS

> +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>

probably useless

> +
> +#include "audio.h"
> +#include "video.h"
> +#include "libavutil/opt.h"
> +
> +
> +typedef struct _MediaSegment {

typedef struct { ... } MediaSegment;

"_MediaSegment" is unnecessary.

> +    double start;
> +    double end;
> +    struct _MediaSegment *next;
> +} MediaSegment;
> +
> +typedef struct {
> +    const AVClass *class;

> +    char *opt_segments;

nit: segments_str is more consistent with the codebase

> +    double ts_base;
> +    double ts_prev;
> +    int frame_out;

please document these

> +    MediaSegment *current;
> +    MediaSegment *segments;
> +} EditingContext;

EditContext?

In case we do: editing -> edit

> +
> +
> +#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;

editing->frame_out = (ret == 0);
should be enough

> +
> +    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)))

strtok_r is not portable, use av_strtok().

> +            break;
> +

> +        segment = av_malloc(sizeof(MediaSegment));

check in case of malloc failure, also you can av_mallocz and avoid next
assignment.


> +        segment->next = NULL;
> +

> +        segment->start = atof(p);

This could be replaced with av_parse_time()

> +
> +        if (j && segment->start < j->end) {

> +            av_log(ctx, AV_LOG_ERROR, "Non-monotonic segments\n");

please provide some context, for example the number of the segments
and/or the overlapping ends values

> +            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;
> +}

Please move parse and init at the begin, so the file can be read from
head to tail with no jumps.

> +
> +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
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel

-- 
FFmpeg = Formidable & Freak Monstrous Programmable Ecletic Gargoyle


More information about the ffmpeg-devel mailing list