From patchwork Wed Oct 24 12:21:14 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 10766 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 06F6444B281 for ; Wed, 24 Oct 2018 15:21:35 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 6FB4A68A586; Wed, 24 Oct 2018 15:21:06 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ed1-f66.google.com (mail-ed1-f66.google.com [209.85.208.66]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 9DE3268A571 for ; Wed, 24 Oct 2018 15:20:59 +0300 (EEST) Received: by mail-ed1-f66.google.com with SMTP id b7-v6so4828519edd.9 for ; Wed, 24 Oct 2018 05:21:28 -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=qox+J0M6qO38Jnk2+YWpiHdpUrYWJ4wO/0lo4TwWHP4=; b=oaYyRlc8vCWGlCs/ClhRN412dmvapusK10L1sarA6yWvz6fa4cdBgist8StGPy38zS XIq/m32NSKppgmMbnd3lewMsz87rQQySZ0c2b4JwM+aighuafacjKN615aV2JfOtzctA uORcd6+GEBIfy4V9nw4Nlhx/PDm8gJ+ES+wZmquU2oojigWiYEKx0NP3Fe3sGUoiJeVj rs4K7zfTCpetZApnY5SNKerWcfm2cRAn2FeyALDicjuRrV2io11u0NXSJn3MASyZ1rQl g3vfN2hkulZny8p6ho7fIXunpNtdQHda4dj+d+aEmg5qzF9nznkJV73KFWikRf6t8AIj kNiA== 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=qox+J0M6qO38Jnk2+YWpiHdpUrYWJ4wO/0lo4TwWHP4=; b=PBmOVD5RmEaOX+AGTQgf8cd2N/F2ZIdXj71OJrs3Lx8f7JL7aQ+KhbwQVO7dgsMMXD fwzPOcfEQol3D6DCvdaU/1VHXuv1MObVhHB3wweuvXdlGS1f1LYer+/5Ij7MVFKb3Uet CecrNuLkSkVgIS+6fqGgV/k8+cV105hmPbz8B9Ti0nnSbN7Wfa4CyZqFFSQ/+vwtnBnQ 7FMK6zA66WgahhNVA4VOqPQBIhZBBz0Ile05K2nP2I+AinCA+lOGhUqGFHt3Q39iNsYj sLVjU72+SqI3V6Xi2kpz2oN9Fz4qZpmnUtnoAGCmef4bliHldQ3mc08GXjx6Yn09g32K G+4Q== X-Gm-Message-State: AGRZ1gKtoa/zN4KzyqkOpi6114/ZbROnKR/T81O3nEGLo52vPNNScmlK 4bqO3WyByy6LunZ1Wsumv3RIFCcWSzk= X-Google-Smtp-Source: AJdET5cOvTfokoIPiun7wYcXSwlqdSWOibhHbl/ZG8GDqoaMyZO46QJAwpX7v0XjkSAqGoEYdtjFMw== X-Received: by 2002:a17:906:13d9:: with SMTP id g25-v6mr1299371ejc.176.1540383687108; Wed, 24 Oct 2018 05:21:27 -0700 (PDT) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id q10-v6sm984904ejs.73.2018.10.24.05.21.25 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 24 Oct 2018 05:21:25 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Wed, 24 Oct 2018 14:21:14 +0200 Message-Id: <20181024122114.19097-1-onemda@gmail.com> X-Mailer: git-send-email 2.17.1 Subject: [FFmpeg-devel] [PATCH] avfilter: add xstack 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 | 35 ++++++++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_stack.c | 138 +++++++++++++++++++++++++++++++++++---- 4 files changed, 164 insertions(+), 11 deletions(-) diff --git a/doc/filters.texi b/doc/filters.texi index 740eec670c..5ff14ba780 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -17798,6 +17798,41 @@ Set the scaling dimension: @code{2} for @code{2xBR}, @code{3} for Default is @code{3}. @end table +@section xstack +Stack video inputs into custom layout. + +All streams must be of same pixel format. + +The filter accept the following option: + +@table @option +@item inputs +Set number of input streams. Default is 2. + +@item layout +Specify layout of inputs. This option needs to be always set. +This sets position of each video input in output. Each input +is separated by '|'. First number represents column and second row, +number are separated by '_'. Optionally one can use wX and hX, +where X is video input from which to take width or height. + +@item shortest +If set to 1, force the output to terminate when the shortest input +terminates. Default value is 0. +@end table + +@subsection Examples + +@itemize +@item +Display 4 inputs into 2x2 grid, +note that if inputs are of different sizes unused gaps might appear, +as not all of output video is used. +@example +xstack=inputs=4:layout=0_0|0_h0|w0_0|w0_h0" +@end example +@end itemize + @anchor{yadif} @section yadif diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 7beec310f8..a98c64b7ce 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -405,6 +405,7 @@ OBJS-$(CONFIG_W3FDIF_FILTER) += vf_w3fdif.o OBJS-$(CONFIG_WAVEFORM_FILTER) += vf_waveform.o OBJS-$(CONFIG_WEAVE_FILTER) += vf_weave.o OBJS-$(CONFIG_XBR_FILTER) += vf_xbr.o +OBJS-$(CONFIG_XSTACK_FILTER) += vf_stack.o framesync.o OBJS-$(CONFIG_YADIF_FILTER) += vf_yadif.o OBJS-$(CONFIG_ZMQ_FILTER) += f_zmq.o OBJS-$(CONFIG_ZOOMPAN_FILTER) += vf_zoompan.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 6f1e7cfb7d..b2cb58fc38 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -386,6 +386,7 @@ extern AVFilter ff_vf_w3fdif; extern AVFilter ff_vf_waveform; extern AVFilter ff_vf_weave; extern AVFilter ff_vf_xbr; +extern AVFilter ff_vf_xstack; extern AVFilter ff_vf_yadif; extern AVFilter ff_vf_zmq; extern AVFilter ff_vf_zoompan; diff --git a/libavfilter/vf_stack.c b/libavfilter/vf_stack.c index b2b8c68041..a97250f97c 100644 --- a/libavfilter/vf_stack.c +++ b/libavfilter/vf_stack.c @@ -29,14 +29,23 @@ #include "framesync.h" #include "video.h" +typedef struct StackItem { + int x[4], y[4]; + int linesize[4]; + int height[4]; +} StackItem; + typedef struct StackContext { const AVClass *class; const AVPixFmtDescriptor *desc; int nb_inputs; + char *layout; int shortest; int is_vertical; + int is_horizontal; int nb_planes; + StackItem *items; AVFrame **frames; FFFrameSync fs; } StackContext; @@ -66,10 +75,19 @@ static av_cold int init(AVFilterContext *ctx) if (!strcmp(ctx->filter->name, "vstack")) s->is_vertical = 1; + if (!strcmp(ctx->filter->name, "hstack")) + s->is_horizontal = 1; + s->frames = av_calloc(s->nb_inputs, sizeof(*s->frames)); if (!s->frames) return AVERROR(ENOMEM); + if (!strcmp(ctx->filter->name, "xstack")) { + s->items = av_calloc(s->nb_inputs, sizeof(*s->items)); + if (!s->items) + return AVERROR(ENOMEM); + } + for (i = 0; i < s->nb_inputs; i++) { AVFilterPad pad = { 0 }; @@ -112,13 +130,15 @@ static int process_frame(FFFrameSync *fs) int linesize[4]; int height[4]; - if ((ret = av_image_fill_linesizes(linesize, inlink->format, inlink->w)) < 0) { - av_frame_free(&out); - return ret; - } + if (s->is_horizontal || s->is_vertical) { + if ((ret = av_image_fill_linesizes(linesize, inlink->format, inlink->w)) < 0) { + av_frame_free(&out); + return ret; + } - height[1] = height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h); - height[0] = height[3] = inlink->h; + height[1] = height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h); + height[0] = height[3] = inlink->h; + } for (p = 0; p < s->nb_planes; p++) { if (s->is_vertical) { @@ -128,13 +148,21 @@ static int process_frame(FFFrameSync *fs) in[i]->linesize[p], linesize[p], height[p]); offset[p] += height[p]; - } else { + } else if (s->is_horizontal) { av_image_copy_plane(out->data[p] + offset[p], out->linesize[p], in[i]->data[p], in[i]->linesize[p], linesize[p], height[p]); offset[p] += linesize[p]; + } else { + StackItem *item = &s->items[i]; + + av_image_copy_plane(out->data[p] + out->linesize[p] * item->y[p] + item->x[p], + out->linesize[p], + in[i]->data[p], + in[i]->linesize[p], + item->linesize[p], item->height[p]); } } } @@ -154,6 +182,10 @@ static int config_output(AVFilterLink *outlink) FFFrameSyncIn *in; int i, ret; + s->desc = av_pix_fmt_desc_get(outlink->format); + if (!s->desc) + return AVERROR_BUG; + if (s->is_vertical) { for (i = 1; i < s->nb_inputs; i++) { if (ctx->inputs[i]->w != width) { @@ -162,7 +194,7 @@ static int config_output(AVFilterLink *outlink) } height += ctx->inputs[i]->h; } - } else { + } else if (s->is_horizontal) { for (i = 1; i < s->nb_inputs; i++) { if (ctx->inputs[i]->h != height) { av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match input %d height %d.\n", i, ctx->inputs[i]->h, 0, height); @@ -170,11 +202,68 @@ static int config_output(AVFilterLink *outlink) } width += ctx->inputs[i]->w; } + } else { + char *arg, *p = s->layout, *saveptr = NULL; + int inw, inh; + + for (i = 0; i < s->nb_inputs; i++) { + AVFilterLink *inlink = ctx->inputs[i]; + StackItem *item = &s->items[i]; + + if (!(arg = av_strtok(p, "|", &saveptr))) + return AVERROR(EINVAL); + + p = NULL; + + if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) { + return ret; + } + + item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h); + item->height[0] = item->height[3] = inlink->h; + + if (sscanf(arg, "w%d_h%d", &inw, &inh) == 2) { + if (inw == i || inh == i || + inw < 0 || inh < 0 || + inw >= s->nb_inputs || + inh >= s->nb_inputs) + return AVERROR(EINVAL); + + inw = ctx->inputs[inw]->w; + inh = ctx->inputs[inh]->h; + } else if (sscanf(arg, "w%d_%d", &inw, &inh) == 2) { + if (inw == i || + inw < 0 || + inw >= s->nb_inputs || inh < 0) + return AVERROR(EINVAL); + + inw = ctx->inputs[inw]->w; + } else if (sscanf(arg, "%d_h%d", &inw, &inh) == 2) { + if (inh == i || + inh < 0 || + inh >= s->nb_inputs || inw < 0) + return AVERROR(EINVAL); + + inh = ctx->inputs[inh]->h; + } else if (sscanf(arg, "%d_%d", &inw, &inh) == 2) { + if (inw < 0 || inh < 0) + return AVERROR(EINVAL); + } else { + return AVERROR(EINVAL); + } + + if ((ret = av_image_fill_linesizes(item->x, inlink->format, inw)) < 0) { + return ret; + } + + item->y[1] = item->y[2] = AV_CEIL_RSHIFT(inh, s->desc->log2_chroma_h); + item->y[0] = item->y[3] = inh; + + width = FFMAX(width, inlink->w + inw); + height = FFMAX(height, inlink->h + inh); + } } - s->desc = av_pix_fmt_desc_get(outlink->format); - if (!s->desc) - return AVERROR_BUG; s->nb_planes = av_pix_fmt_count_planes(outlink->format); outlink->w = width; @@ -209,6 +298,7 @@ static av_cold void uninit(AVFilterContext *ctx) ff_framesync_uninit(&s->fs); av_freep(&s->frames); + av_freep(&s->items); for (i = 0; i < ctx->nb_inputs; i++) av_freep(&ctx->input_pads[i].name); @@ -276,3 +366,29 @@ AVFilter ff_vf_vstack = { }; #endif /* CONFIG_VSTACK_FILTER */ + +#if CONFIG_XSTACK_FILTER + +static const AVOption xstack_options[] = { + { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS }, + { "layout", "set custom layout", OFFSET(layout), AV_OPT_TYPE_STRING, {.str=""}, 0, 0, .flags = FLAGS }, + { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS }, + { NULL }, +}; + +AVFILTER_DEFINE_CLASS(xstack); + +AVFilter ff_vf_xstack = { + .name = "xstack", + .description = NULL_IF_CONFIG_SMALL("Stack video inputs into custom layout."), + .priv_size = sizeof(StackContext), + .priv_class = &xstack_class, + .query_formats = query_formats, + .outputs = outputs, + .init = init, + .uninit = uninit, + .activate = activate, + .flags = AVFILTER_FLAG_DYNAMIC_INPUTS, +}; + +#endif /* CONFIG_XSTACK_FILTER */