[FFmpeg-devel] [PATCH] avfilter: add crossfade filter

Gyan ffmpeg at gyani.pro
Sat Jan 11 09:00:17 EET 2020


Paul, this is useful. When can you merge this?

On 24-10-2019 02:19 am, Paul B Mahol wrote:
> Signed-off-by: Paul B Mahol <onemda at gmail.com>
> ---
>   doc/filters.texi         |  18 +++++
>   libavfilter/Makefile     |   1 +
>   libavfilter/allfilters.c |   1 +
>   libavfilter/vf_blend.c   | 157 ++++++++++++++++++++++++++++++++++++++-
>   4 files changed, 175 insertions(+), 2 deletions(-)
>
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 7400e7dd31..eea0be060d 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -6450,6 +6450,7 @@ The threshold below which a pixel value is considered black; it defaults to
>   
>   @end table
>   
> + at anchor{blend}
>   @section blend, tblend
>   
>   Blend two video frames into each other.
> @@ -8068,6 +8069,23 @@ indicates 'never reset', and returns the largest area encountered during
>   playback.
>   @end table
>   
> + at section crossfade
> +
> +Apply cross fade from one input video stream to another input video stream.
> +The cross fade is applied for specified duration.
> +
> +The filter accepts the following options:
> +
> + at table @option
> + at item duration
> +Set cross fade duration in seconds.
> +
> + at item offset
> +Set cross fade start relative to first input stream.
> +
> +For rest of options explanation see @ref{blend} filter.
> + at end table
> +
>   @anchor{cue}
>   @section cue
>   
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 63d2fba861..e02c7d3614 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -197,6 +197,7 @@ OBJS-$(CONFIG_COREIMAGE_FILTER)              += vf_coreimage.o
>   OBJS-$(CONFIG_COVER_RECT_FILTER)             += vf_cover_rect.o lavfutils.o
>   OBJS-$(CONFIG_CROP_FILTER)                   += vf_crop.o
>   OBJS-$(CONFIG_CROPDETECT_FILTER)             += vf_cropdetect.o
> +OBJS-$(CONFIG_CROSSFADE_FILTER)              += vf_blend.o framesync.o
>   OBJS-$(CONFIG_CUE_FILTER)                    += f_cue.o
>   OBJS-$(CONFIG_CURVES_FILTER)                 += vf_curves.o
>   OBJS-$(CONFIG_DATASCOPE_FILTER)              += vf_datascope.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index e4186f93db..7838002230 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -185,6 +185,7 @@ extern AVFilter ff_vf_coreimage;
>   extern AVFilter ff_vf_cover_rect;
>   extern AVFilter ff_vf_crop;
>   extern AVFilter ff_vf_cropdetect;
> +extern AVFilter ff_vf_crossfade;
>   extern AVFilter ff_vf_cue;
>   extern AVFilter ff_vf_curves;
>   extern AVFilter ff_vf_datascope;
> diff --git a/libavfilter/vf_blend.c b/libavfilter/vf_blend.c
> index 67163be3e7..c4411ca5f9 100644
> --- a/libavfilter/vf_blend.c
> +++ b/libavfilter/vf_blend.c
> @@ -26,6 +26,7 @@
>   #include "formats.h"
>   #include "framesync.h"
>   #include "internal.h"
> +#include "filters.h"
>   #include "video.h"
>   #include "blend.h"
>   
> @@ -44,6 +45,17 @@ typedef struct BlendContext {
>       int depth;
>       FilterParams params[4];
>       int tblend;
> +    int crossfade;
> +    int64_t duration;
> +    int64_t offset;
> +    int64_t duration_pts;
> +    int64_t offset_pts;
> +    int64_t first_pts;
> +    int64_t pts;
> +    int crossfade_is_over;
> +    int need_second;
> +    int eof[2];
> +    AVFrame *cf[2];
>       AVFrame *prev_frame;        /* only used with tblend */
>   } BlendContext;
>   
> @@ -557,6 +569,7 @@ static av_cold int init(AVFilterContext *ctx)
>       BlendContext *s = ctx->priv;
>   
>       s->tblend = !strcmp(ctx->filter->name, "tblend");
> +    s->crossfade = !strcmp(ctx->filter->name, "crossfade");
>   
>       s->fs.on_event = blend_frame_for_dualinput;
>       return 0;
> @@ -715,7 +728,7 @@ static int config_output(AVFilterLink *outlink)
>       s->depth = pix_desc->comp[0].depth;
>       s->nb_planes = av_pix_fmt_count_planes(toplink->format);
>   
> -    if (!s->tblend)
> +    if (!s->tblend && !s->crossfade)
>           if ((ret = ff_framesync_init_dualinput(&s->fs, ctx)) < 0)
>               return ret;
>   
> @@ -743,7 +756,14 @@ static int config_output(AVFilterLink *outlink)
>           }
>       }
>   
> -    if (s->tblend)
> +    s->first_pts = s->pts = AV_NOPTS_VALUE;
> +
> +    if (s->duration)
> +        s->duration_pts = av_rescale_q(s->duration, AV_TIME_BASE_Q, outlink->time_base);
> +    if (s->offset)
> +        s->offset_pts = av_rescale_q(s->offset, AV_TIME_BASE_Q, outlink->time_base);
> +
> +    if (s->tblend || s->crossfade)
>           return 0;
>   
>       ret = ff_framesync_configure(&s->fs);
> @@ -859,3 +879,136 @@ AVFilter ff_vf_tblend = {
>   };
>   
>   #endif
> +
> +static const AVOption crossfade_options[] = {
> +    { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS },
> +    { "offset",   "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, 0, 60000000, FLAGS },
> +    COMMON_OPTIONS,
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(crossfade);
> +
> +static int crossfade_activate(AVFilterContext *ctx)
> +{
> +    BlendContext *s   = ctx->priv;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    AVFrame *in = NULL, *out = NULL;
> +    int ret = 0, status;
> +    int64_t pts;
> +
> +    FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, ctx);
> +
> +    if (s->crossfade_is_over) {
> +        ret = ff_inlink_consume_frame(ctx->inputs[1], &in);
> +        if (ret < 0) {
> +            return ret;
> +        } else if (ff_inlink_acknowledge_status(ctx->inputs[1], &status, &pts)) {
> +            ff_outlink_set_status(outlink, status, s->pts);
> +            return 0;
> +        } else if (!ret) {
> +            if (ff_outlink_frame_wanted(outlink)) {
> +                ff_inlink_request_frame(ctx->inputs[1]);
> +                return 0;
> +            }
> +        } else {
> +            in->pts = s->pts;
> +            s->pts += av_rescale_q(1, av_inv_q(outlink->frame_rate), outlink->time_base);
> +            return ff_filter_frame(outlink, in);
> +        }
> +    }
> +
> +    if (ff_inlink_queued_frames(ctx->inputs[0]) > 0) {
> +        s->cf[0] = ff_inlink_peek_frame(ctx->inputs[0], 0);
> +        if (s->cf[0]) {
> +            if (s->first_pts == AV_NOPTS_VALUE) {
> +                s->first_pts = s->cf[0]->pts;
> +            }
> +            s->pts = s->cf[0]->pts;
> +            if (s->first_pts + s->offset_pts > s->cf[0]->pts) {
> +                s->cf[0] = NULL;
> +                s->need_second = 0;
> +                ff_inlink_consume_frame(ctx->inputs[0], &in);
> +                return ff_filter_frame(outlink, in);
> +            }
> +
> +            s->need_second = 1;
> +        }
> +    }
> +
> +    if (s->cf[0] && ff_inlink_queued_frames(ctx->inputs[1]) > 0) {
> +        ff_inlink_consume_frame(ctx->inputs[0], &s->cf[0]);
> +        ff_inlink_consume_frame(ctx->inputs[1], &s->cf[1]);
> +
> +        s->pts = s->cf[0]->pts;
> +        if (s->cf[0]->pts - (s->first_pts + s->offset_pts) > s->duration_pts)
> +            s->crossfade_is_over = 1;
> +        out = blend_frame(ctx, s->cf[0], s->cf[1]);
> +        s->cf[0] = NULL;
> +        av_frame_free(&s->cf[1]);
> +        out->pts = s->pts;
> +        return ff_filter_frame(outlink, out);
> +    }
> +
> +    if (ff_inlink_queued_frames(ctx->inputs[0]) > 0 &&
> +        ff_inlink_queued_frames(ctx->inputs[1]) > 0) {
> +        ff_filter_set_ready(ctx, 100);
> +        return 0;
> +    }
> +
> +    if (ff_outlink_frame_wanted(outlink)) {
> +        if (!s->eof[0] && ff_outlink_get_status(ctx->inputs[0])) {
> +            s->eof[0] = 1;
> +            s->crossfade_is_over = 1;
> +        }
> +        if (!s->eof[1] && ff_outlink_get_status(ctx->inputs[1])) {
> +            s->eof[1] = 1;
> +        }
> +        if (!s->eof[0] && !s->cf[0])
> +            ff_inlink_request_frame(ctx->inputs[0]);
> +        if (!s->eof[1] && (s->need_second || s->eof[0]))
> +            ff_inlink_request_frame(ctx->inputs[1]);
> +        if (s->eof[0] && s->eof[1] && (
> +            ff_inlink_queued_frames(ctx->inputs[0]) <= 0 ||
> +            ff_inlink_queued_frames(ctx->inputs[1]) <= 0))
> +            ff_outlink_set_status(outlink, AVERROR_EOF, AV_NOPTS_VALUE);
> +        return 0;
> +    }
> +
> +    return FFERROR_NOT_READY;
> +}
> +
> +static const AVFilterPad crossfade_inputs[] = {
> +    {
> +        .name          = "crossfade0",
> +        .type          = AVMEDIA_TYPE_VIDEO,
> +    },
> +    {
> +        .name          = "crossfade1",
> +        .type          = AVMEDIA_TYPE_VIDEO,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad crossfade_outputs[] = {
> +    {
> +        .name          = "default",
> +        .type          = AVMEDIA_TYPE_VIDEO,
> +        .config_props  = config_output,
> +    },
> +    { NULL }
> +};
> +
> +AVFilter ff_vf_crossfade = {
> +    .name          = "crossfade",
> +    .description   = NULL_IF_CONFIG_SMALL("Cross fade two input video streams."),
> +    .priv_size     = sizeof(BlendContext),
> +    .priv_class    = &crossfade_class,
> +    .query_formats = query_formats,
> +    .init          = init,
> +    .activate      = crossfade_activate,
> +    .uninit        = uninit,
> +    .inputs        = crossfade_inputs,
> +    .outputs       = crossfade_outputs,
> +    .flags         = AVFILTER_FLAG_SLICE_THREADS,
> +};



More information about the ffmpeg-devel mailing list