1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2024-12-23 12:43:46 +02:00

avfilter: add xstack filter

This commit is contained in:
Paul B Mahol 2018-10-24 11:00:58 +02:00
parent de43c227fd
commit 4fcfb9c4eb
6 changed files with 199 additions and 12 deletions

View File

@ -39,6 +39,7 @@ version <next>:
- setparams filter
- vibrance filter
- decoding S12M timecode in h264
- xstack filter
version 4.0:

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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, \

View File

@ -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 */