From patchwork Wed Oct 23 20:49:43 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 15929 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 82E33449188 for ; Wed, 23 Oct 2019 23:50:01 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 58FA468AFED; Wed, 23 Oct 2019 23:50:01 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wr1-f65.google.com (mail-wr1-f65.google.com [209.85.221.65]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id AFFA668AD32 for ; Wed, 23 Oct 2019 23:49:54 +0300 (EEST) Received: by mail-wr1-f65.google.com with SMTP id w18so23025215wrt.3 for ; Wed, 23 Oct 2019 13:49:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=eio0rmLoWv1Mu1OKPVS0MpBBpc4/u+8K3NFKud77yLg=; b=NZ2N+dx/jzKB8zho6MaQkDF+ViEP82dgJ/ETCw0hZGlRy1deNDu+fxHysandc2QY0i aCn4ftQnRvKjmzIS0KZ4jAEHiV+kWQ1ma90/4VFUVmjyJpAAVFlzAjSb9lGv0hS+P5Kn 2zPyhmdYhEDGzld0Wyzuyea6Mf+ojASZdYL8ogLGNw6oLkzW6dE3VZx/pP01DTqkbva9 unN5v1ZJWRqNLc5NMBI9XOjx1Jn6XVPS+WA3rvUpLSVJ3xEqKOR3Fqu174WNtqja3CFW OijSMjJxs7go+tWmtya6XW5zjDdf+5AJQO6Xq19U38LQbsDQy3Tsg1jmhwJkeN3Nkyrc QsXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=eio0rmLoWv1Mu1OKPVS0MpBBpc4/u+8K3NFKud77yLg=; b=AYpYSx7Hx9WEgPz/kwKeo1a6YdDuyFhkZD9fqfsslX+8HSpEITGzTbEpQC8VZpaJPU jpkeNytbd2/NC721ClhTucnb0+Oi+lhULhBxLniN82midCAtL7S6oODsfbiahKInjSxq WfJgf1rHlTfHvry5it1OupFRoAJGSZObJiU1WQGITwC+lIWXuicy0MPsaCe17OFgybCm kG6WAhuovgWviqSnYuyywM2DmnAW5CJiNfDyl6Eh5gAEwAFr2mwQOHh6OcGff8CtywY+ yh0YveJOt940ZcsA+WvKQbqr7N4/r6RDjqULki/3LiQqrKyZJByph3e/u1yVrKABtvfK qM5A== X-Gm-Message-State: APjAAAWMLVFoDK6EHnWc8xZO+KNPf/YJK+xNoJ49eZce64Okwust6JPt FOTKBgtgErZFo4ES/wxd6viW+hXSwlY= X-Google-Smtp-Source: APXvYqxiFi8YKYuUZl7qfZttJFlWFEO8D/OlDxUUq59F1a8Ao7KpbCcJPrCIEG4buhesObG4svpebw== X-Received: by 2002:a05:6000:108:: with SMTP id o8mr527286wrx.369.1571863793638; Wed, 23 Oct 2019 13:49:53 -0700 (PDT) Received: from localhost.localdomain ([212.15.160.218]) by smtp.gmail.com with ESMTPSA id r15sm11398632wro.20.2019.10.23.13.49.51 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 23 Oct 2019 13:49:52 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Wed, 23 Oct 2019 22:49:43 +0200 Message-Id: <20191023204943.2360-1-onemda@gmail.com> X-Mailer: git-send-email 2.17.1 Subject: [FFmpeg-devel] [PATCH] avfilter: add crossfade filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- 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 +@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 +@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: + +@table @option +@item duration +Set cross fade duration in seconds. + +@item offset +Set cross fade start relative to first input stream. + +For rest of options explanation see @ref{blend} filter. +@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, +};