avfilter/xstack: Add support for fixed size grid

Add a short hand parameter for making a fixed size grid. The existing
xstack layout parameter syntax gets tedious if all one wants is a
matrix like grid of the input streams. Add a grid option to the xstack
filter that simplifies this use case by simply specifying the number of
rows and columns instead of specific x/y co-ordinate for each stream.

Also updating the filter documentation to explain the new option.

Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com>
release/5.1
Vignesh Venkatasubramanian 3 years ago committed by Paul B Mahol
parent 0afdc95767
commit aa8905a1b1
  1. 22
      doc/filters.texi
  2. 80
      libavfilter/vf_stack.c

@ -24381,8 +24381,26 @@ the output video frame will be filled. Similarly, videos can overlap each
other if their position doesn't leave enough space for the full frame of other if their position doesn't leave enough space for the full frame of
adjoining videos. adjoining videos.
For 2 inputs, a default layout of @code{0_0|w0_0} is set. In all other cases, For 2 inputs, a default layout of @code{0_0|w0_0} (equivalent to
a layout must be set by the user. @code{grid=2x1}) is set. In all other cases, a layout or a grid must be set by
the user. Either @code{grid} or @code{layout} can be specified at a time.
Specifying both will result in an error.
@item grid
Specify a fixed size grid of inputs.
This option is used to create a fixed size grid of the input streams. Set the
grid size in the form @code{COLUMNSxROWS}. There must be @code{ROWS * COLUMNS}
input streams and they will be arranged as a grid with @code{ROWS} rows and
@code{COLUMNS} columns. When using this option, each input stream within a row
must have the same height and all the rows must have the same width.
If @code{grid} is set, then @code{inputs} option is ignored and is implicitly
set to @code{ROWS * COLUMNS}.
For 2 inputs, a default grid of @code{2x1} (equivalent to
@code{layout=0_0|w0_0}) is set. In all other cases, a layout or a grid must be
set by the user. Either @code{grid} or @code{layout} can be specified at a time.
Specifying both will result in an error.
@item shortest @item shortest
If set to 1, force the output to terminate when the shortest input If set to 1, force the output to terminate when the shortest input

@ -48,6 +48,8 @@ typedef struct StackContext {
int is_vertical; int is_vertical;
int is_horizontal; int is_horizontal;
int nb_planes; int nb_planes;
int nb_grid_columns;
int nb_grid_rows;
uint8_t fillcolor[4]; uint8_t fillcolor[4];
char *fillcolor_str; char *fillcolor_str;
int fillcolor_enable; int fillcolor_enable;
@ -85,33 +87,42 @@ static av_cold int init(AVFilterContext *ctx)
if (!strcmp(ctx->filter->name, "hstack")) if (!strcmp(ctx->filter->name, "hstack"))
s->is_horizontal = 1; s->is_horizontal = 1;
s->frames = av_calloc(s->nb_inputs, sizeof(*s->frames));
if (!s->frames)
return AVERROR(ENOMEM);
s->items = av_calloc(s->nb_inputs, sizeof(*s->items));
if (!s->items)
return AVERROR(ENOMEM);
if (!strcmp(ctx->filter->name, "xstack")) { if (!strcmp(ctx->filter->name, "xstack")) {
int is_grid;
if (strcmp(s->fillcolor_str, "none") && if (strcmp(s->fillcolor_str, "none") &&
av_parse_color(s->fillcolor, s->fillcolor_str, -1, ctx) >= 0) { av_parse_color(s->fillcolor, s->fillcolor_str, -1, ctx) >= 0) {
s->fillcolor_enable = 1; s->fillcolor_enable = 1;
} else { } else {
s->fillcolor_enable = 0; s->fillcolor_enable = 0;
} }
if (!s->layout) { is_grid = s->nb_grid_rows && s->nb_grid_columns;
if (s->layout && is_grid) {
av_log(ctx, AV_LOG_ERROR, "Both layout and grid were specified. Only one is allowed.\n");
return AVERROR(EINVAL);
}
if (!s->layout && !is_grid) {
if (s->nb_inputs == 2) { if (s->nb_inputs == 2) {
s->layout = av_strdup("0_0|w0_0"); s->nb_grid_rows = 1;
if (!s->layout) s->nb_grid_columns = 2;
return AVERROR(ENOMEM); is_grid = 1;
} else { } else {
av_log(ctx, AV_LOG_ERROR, "No layout specified.\n"); av_log(ctx, AV_LOG_ERROR, "No layout or grid specified.\n");
return AVERROR(EINVAL); return AVERROR(EINVAL);
} }
} }
if (is_grid)
s->nb_inputs = s->nb_grid_rows * s->nb_grid_columns;
} }
s->frames = av_calloc(s->nb_inputs, sizeof(*s->frames));
if (!s->frames)
return AVERROR(ENOMEM);
s->items = av_calloc(s->nb_inputs, sizeof(*s->items));
if (!s->items)
return AVERROR(ENOMEM);
for (i = 0; i < s->nb_inputs; i++) { for (i = 0; i < s->nb_inputs; i++) {
AVFilterPad pad = { 0 }; AVFilterPad pad = { 0 };
@ -244,6 +255,48 @@ static int config_output(AVFilterLink *outlink)
width += ctx->inputs[i]->w; width += ctx->inputs[i]->w;
} }
} }
} else if (s->nb_grid_rows && s->nb_grid_columns) {
int inw = 0, inh = 0;
int k = 0;
int row_height;
height = 0;
width = 0;
for (i = 0; i < s->nb_grid_rows; i++, inh += row_height) {
row_height = ctx->inputs[i * s->nb_grid_columns]->h;
inw = 0;
for (int j = 0; j < s->nb_grid_columns; j++, k++) {
AVFilterLink *inlink = ctx->inputs[k];
StackItem *item = &s->items[k];
if (ctx->inputs[k]->h != row_height) {
av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match current row's height %d.\n",
k, ctx->inputs[k]->h, row_height);
return AVERROR(EINVAL);
}
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 ((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;
inw += ctx->inputs[k]->w;
}
height += row_height;
if (!i)
width = inw;
if (i && width != inw) {
av_log(ctx, AV_LOG_ERROR, "Row %d width %d does not match previous row width %d.\n", i, inw, width);
return AVERROR(EINVAL);
}
}
} else { } else {
char *arg, *p = s->layout, *saveptr = NULL; char *arg, *p = s->layout, *saveptr = NULL;
char *arg2, *p2, *saveptr2 = NULL; char *arg2, *p2, *saveptr2 = NULL;
@ -436,6 +489,7 @@ const AVFilter ff_vf_vstack = {
static const AVOption xstack_options[] = { static const AVOption xstack_options[] = {
{ "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS }, { "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=NULL}, 0, 0, .flags = FLAGS }, { "layout", "set custom layout", OFFSET(layout), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, .flags = FLAGS },
{ "grid", "set fixed size grid layout", OFFSET(nb_grid_columns), AV_OPT_TYPE_IMAGE_SIZE, {.str=NULL}, 0, 0, .flags = FLAGS },
{ "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS }, { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
{ "fill", "set the color for unused pixels", OFFSET(fillcolor_str), AV_OPT_TYPE_STRING, {.str = "none"}, .flags = FLAGS }, { "fill", "set the color for unused pixels", OFFSET(fillcolor_str), AV_OPT_TYPE_STRING, {.str = "none"}, .flags = FLAGS },
{ NULL }, { NULL },

Loading…
Cancel
Save