From 4fcfb9c4ebf3650fd7e82ba78d81c8aebdabd228 Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Wed, 24 Oct 2018 11:00:58 +0200 Subject: [PATCH] avfilter: add xstack filter --- Changelog | 1 + doc/filters.texi | 55 ++++++++++++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/version.h | 2 +- libavfilter/vf_stack.c | 151 ++++++++++++++++++++++++++++++++++++--- 6 files changed, 199 insertions(+), 12 deletions(-) diff --git a/Changelog b/Changelog index 59c1b50aca0..54ff28f252d 100644 --- a/Changelog +++ b/Changelog @@ -39,6 +39,7 @@ version : - setparams filter - vibrance filter - decoding S12M timecode in h264 +- xstack filter version 4.0: diff --git a/doc/filters.texi b/doc/filters.texi index 740eec670c6..7811c25ddb1 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -17798,6 +17798,61 @@ 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 requires the desired layout configuration to be explicitly set by the user. +This sets position of each video input in output. Each input +is separated by '|'. +The first number represents the column, and the second number represents the row. +Numbers start at 0 and are separated by '_'. Optionally one can use wX and hX, +where X is video input from which to take width or height. +Multiple values can be used when separated by '+'. In such +case values are summed together. + +@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 + +@item +Display 4 inputs into 1x4 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|0_h0+h1|0_h0+h1+h2 +@end example + +@item +Display 9 inputs into 3x3 grid, +note that if inputs are of different sizes unused gaps might appear, +as not all of output video is used. +@example +xstack=inputs=9:layout=w3_0|w3_h0+h2|w3_h0|0_h4|0_0|w3+w1_0|0_h1+h2|w3+w1_h0|w3+w1_h1+h2 +@end example +@end itemize + @anchor{yadif} @section yadif diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 7beec310f84..a98c64b7ce7 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 6f1e7cfb7d8..b2cb58fc383 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/version.h b/libavfilter/version.h index bf81e77a5cf..77e1a77b504 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFILTER_VERSION_MAJOR 7 -#define LIBAVFILTER_VERSION_MINOR 37 +#define LIBAVFILTER_VERSION_MINOR 38 #define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ diff --git a/libavfilter/vf_stack.c b/libavfilter/vf_stack.c index b2b8c68041d..8731674aa71 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,81 @@ static int config_output(AVFilterLink *outlink) } width += ctx->inputs[i]->w; } + } else { + char *arg, *p = s->layout, *saveptr = NULL; + char *arg2, *p2, *saveptr2 = NULL; + char *arg3, *p3, *saveptr3 = NULL; + int inw, inh, size; + + 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; + + p2 = arg; + inw = inh = 0; + + for (int j = 0; j < 2; j++) { + if (!(arg2 = av_strtok(p2, "_", &saveptr2))) + return AVERROR(EINVAL); + + p2 = NULL; + p3 = arg2; + while ((arg3 = av_strtok(p3, "+", &saveptr3))) { + p3 = NULL; + if (sscanf(arg3, "w%d", &size) == 1) { + if (size == i || size < 0 || size >= s->nb_inputs) + return AVERROR(EINVAL); + + if (!j) + inw += ctx->inputs[size]->w; + else + inh += ctx->inputs[size]->w; + } else if (sscanf(arg3, "h%d", &size) == 1) { + if (size == i || size < 0 || size >= s->nb_inputs) + return AVERROR(EINVAL); + + if (!j) + inw += ctx->inputs[size]->h; + else + inh += ctx->inputs[size]->h; + } else if (sscanf(arg3, "%d", &size) == 1) { + if (size < 0) + return AVERROR(EINVAL); + + if (!j) + inw += size; + else + inh += size; + } 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 +311,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 +379,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|w0_0"}, 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 */ -- 2.39.2