FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_stack.c
Date: 2026-01-18 00:45:45
Exec Total Coverage
Lines: 170 259 65.6%
Functions: 7 7 100.0%
Branches: 84 176 47.7%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2015 Paul B. Mahol
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "config_components.h"
22
23 #include "libavutil/avstring.h"
24 #include "libavutil/imgutils.h"
25 #include "libavutil/mem.h"
26 #include "libavutil/opt.h"
27 #include "libavutil/parseutils.h"
28 #include "libavutil/pixdesc.h"
29
30 #include "avfilter.h"
31 #include "drawutils.h"
32 #include "filters.h"
33 #include "formats.h"
34 #include "framesync.h"
35 #include "video.h"
36
37 typedef struct StackItem {
38 int x[4], y[4];
39 int linesize[4];
40 int height[4];
41 } StackItem;
42
43 typedef struct StackContext {
44 const AVClass *class;
45 const AVPixFmtDescriptor *desc;
46 int nb_inputs;
47 char *layout;
48 int shortest;
49 int is_vertical;
50 int is_horizontal;
51 int nb_planes;
52 int nb_grid_columns;
53 int nb_grid_rows;
54 uint8_t fillcolor[4];
55 char *fillcolor_str;
56 int fillcolor_enable;
57
58 FFDrawContext draw;
59 FFDrawColor color;
60
61 StackItem *items;
62 AVFrame **frames;
63 FFFrameSync fs;
64 } StackContext;
65
66 6 static int query_formats(const AVFilterContext *ctx,
67 AVFilterFormatsConfig **cfg_in,
68 AVFilterFormatsConfig **cfg_out)
69 {
70 6 const StackContext *s = ctx->priv;
71 6 int reject_flags = AV_PIX_FMT_FLAG_BITSTREAM |
72 AV_PIX_FMT_FLAG_HWACCEL |
73 AV_PIX_FMT_FLAG_PAL;
74
75
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (s->fillcolor_enable) {
76 3 return ff_set_common_formats2(ctx, cfg_in, cfg_out,
77 ff_draw_supported_pixel_formats(0));
78 }
79
80 3 return ff_set_common_formats2(ctx, cfg_in, cfg_out,
81 ff_formats_pixdesc_filter(0, reject_flags));
82 }
83
84 15 static av_cold int init(AVFilterContext *ctx)
85 {
86 15 StackContext *s = ctx->priv;
87 int i, ret;
88
89
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (!strcmp(ctx->filter->name, "vstack"))
90 2 s->is_vertical = 1;
91
92
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 11 times.
15 if (!strcmp(ctx->filter->name, "hstack"))
93 4 s->is_horizontal = 1;
94
95
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 6 times.
15 if (!strcmp(ctx->filter->name, "xstack")) {
96 int is_grid;
97
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
18 if (strcmp(s->fillcolor_str, "none") &&
98 9 av_parse_color(s->fillcolor, s->fillcolor_str, -1, ctx) >= 0) {
99 9 s->fillcolor_enable = 1;
100 } else {
101 s->fillcolor_enable = 0;
102 }
103
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
9 is_grid = s->nb_grid_rows && s->nb_grid_columns;
104
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
9 if (s->layout && is_grid) {
105 av_log(ctx, AV_LOG_ERROR, "Both layout and grid were specified. Only one is allowed.\n");
106 return AVERROR(EINVAL);
107 }
108
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
9 if (!s->layout && !is_grid) {
109 if (s->nb_inputs == 2) {
110 s->nb_grid_rows = 1;
111 s->nb_grid_columns = 2;
112 is_grid = 1;
113 } else {
114 av_log(ctx, AV_LOG_ERROR, "No layout or grid specified.\n");
115 return AVERROR(EINVAL);
116 }
117 }
118
119
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (is_grid)
120 s->nb_inputs = s->nb_grid_rows * s->nb_grid_columns;
121 }
122
123 15 s->frames = av_calloc(s->nb_inputs, sizeof(*s->frames));
124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (!s->frames)
125 return AVERROR(ENOMEM);
126
127 15 s->items = av_calloc(s->nb_inputs, sizeof(*s->items));
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 if (!s->items)
129 return AVERROR(ENOMEM);
130
131
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 15 times.
59 for (i = 0; i < s->nb_inputs; i++) {
132 44 AVFilterPad pad = { 0 };
133
134 44 pad.type = AVMEDIA_TYPE_VIDEO;
135 44 pad.name = av_asprintf("input%d", i);
136
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 if (!pad.name)
137 return AVERROR(ENOMEM);
138
139
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
44 if ((ret = ff_append_inpad_free_name(ctx, &pad)) < 0)
140 return ret;
141 }
142
143 15 return 0;
144 }
145
146 312 static int process_slice(AVFilterContext *ctx, void *arg, int job, int nb_jobs)
147 {
148 312 StackContext *s = ctx->priv;
149 312 AVFrame *out = arg;
150 312 AVFrame **in = s->frames;
151 312 const int start = (s->nb_inputs * job ) / nb_jobs;
152 312 const int end = (s->nb_inputs * (job+1)) / nb_jobs;
153
154
2/2
✓ Branch 0 taken 312 times.
✓ Branch 1 taken 312 times.
624 for (int i = start; i < end; i++) {
155 312 StackItem *item = &s->items[i];
156
157
2/2
✓ Branch 0 taken 936 times.
✓ Branch 1 taken 312 times.
1248 for (int p = 0; p < s->nb_planes; p++) {
158 936 av_image_copy_plane(out->data[p] + out->linesize[p] * item->y[p] + item->x[p],
159 out->linesize[p],
160 936 in[i]->data[p],
161 936 in[i]->linesize[p],
162 item->linesize[p], item->height[p]);
163 }
164 }
165
166 312 return 0;
167 }
168
169 153 static int process_frame(FFFrameSync *fs)
170 {
171 153 AVFilterContext *ctx = fs->parent;
172 153 AVFilterLink *outlink = ctx->outputs[0];
173 153 StackContext *s = fs->opaque;
174 153 AVFrame **in = s->frames;
175 AVFrame *out;
176 int i, ret;
177
178
2/2
✓ Branch 0 taken 312 times.
✓ Branch 1 taken 153 times.
465 for (i = 0; i < s->nb_inputs; i++) {
179
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 312 times.
312 if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
180 return ret;
181 }
182
183 153 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
184
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 153 times.
153 if (!out)
185 return AVERROR(ENOMEM);
186 153 out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
187 153 out->sample_aspect_ratio = outlink->sample_aspect_ratio;
188
189
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 150 times.
153 if (s->fillcolor_enable)
190 3 ff_fill_rectangle(&s->draw, &s->color, out->data, out->linesize,
191 0, 0, outlink->w, outlink->h);
192
193 153 ff_filter_execute(ctx, process_slice, out, NULL,
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 153 times.
153 FFMIN(s->nb_inputs, ff_filter_get_nb_threads(ctx)));
195
196 153 return ff_filter_frame(outlink, out);
197 }
198
199 6 static int config_output(AVFilterLink *outlink)
200 {
201 6 AVFilterContext *ctx = outlink->src;
202 6 StackContext *s = ctx->priv;
203 6 FilterLink *il = ff_filter_link(ctx->inputs[0]);
204 6 FilterLink *ol = ff_filter_link(outlink);
205 6 AVRational frame_rate = il->frame_rate;
206 6 AVRational sar = ctx->inputs[0]->sample_aspect_ratio;
207 6 int height = ctx->inputs[0]->h;
208 6 int width = ctx->inputs[0]->w;
209 FFFrameSyncIn *in;
210 int i, ret;
211
212 6 s->desc = av_pix_fmt_desc_get(outlink->format);
213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!s->desc)
214 return AVERROR_BUG;
215
216
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (s->is_vertical) {
217
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (i = 0; i < s->nb_inputs; i++) {
218 2 AVFilterLink *inlink = ctx->inputs[i];
219 2 StackItem *item = &s->items[i];
220
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ctx->inputs[i]->w != width) {
222 av_log(ctx, AV_LOG_ERROR, "Input %d width %d does not match input %d width %d.\n", i, ctx->inputs[i]->w, 0, width);
223 return AVERROR(EINVAL);
224 }
225
226
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
227 return ret;
228 }
229
230 2 item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
231 2 item->height[0] = item->height[3] = inlink->h;
232
233
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (i) {
234 1 item->y[1] = item->y[2] = AV_CEIL_RSHIFT(height, s->desc->log2_chroma_h);
235 1 item->y[0] = item->y[3] = height;
236
237
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (height > INT_MAX - ctx->inputs[i]->h)
238 return AVERROR(EINVAL);
239 1 height += ctx->inputs[i]->h;
240 }
241 }
242
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 } else if (s->is_horizontal) {
243
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (i = 0; i < s->nb_inputs; i++) {
244 4 AVFilterLink *inlink = ctx->inputs[i];
245 4 StackItem *item = &s->items[i];
246
247
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ctx->inputs[i]->h != height) {
248 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);
249 return AVERROR(EINVAL);
250 }
251
252
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
253 return ret;
254 }
255
256 4 item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
257 4 item->height[0] = item->height[3] = inlink->h;
258
259
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (i) {
260
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((ret = av_image_fill_linesizes(item->x, inlink->format, width)) < 0) {
261 return ret;
262 }
263
264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (width > INT_MAX - ctx->inputs[i]->w)
265 return AVERROR(EINVAL);
266 2 width += ctx->inputs[i]->w;
267 }
268 }
269
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 } else if (s->nb_grid_rows && s->nb_grid_columns) {
270 int inw = 0, inh = 0;
271 int k = 0;
272 int row_height;
273 height = 0;
274 width = 0;
275 for (i = 0; i < s->nb_grid_rows; i++, inh += row_height) {
276 row_height = ctx->inputs[i * s->nb_grid_columns]->h;
277 inw = 0;
278 for (int j = 0; j < s->nb_grid_columns; j++, k++) {
279 AVFilterLink *inlink = ctx->inputs[k];
280 StackItem *item = &s->items[k];
281
282 if (ctx->inputs[k]->h != row_height) {
283 av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match current row's height %d.\n",
284 k, ctx->inputs[k]->h, row_height);
285 return AVERROR(EINVAL);
286 }
287
288 if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
289 return ret;
290 }
291
292 item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
293 item->height[0] = item->height[3] = inlink->h;
294
295 if ((ret = av_image_fill_linesizes(item->x, inlink->format, inw)) < 0) {
296 return ret;
297 }
298
299 item->y[1] = item->y[2] = AV_CEIL_RSHIFT(inh, s->desc->log2_chroma_h);
300 item->y[0] = item->y[3] = inh;
301
302 if (inw > INT_MAX - ctx->inputs[k]->w)
303 return AVERROR(EINVAL);
304 inw += ctx->inputs[k]->w;
305 }
306 if (height > INT_MAX - row_height)
307 return AVERROR(EINVAL);
308 height += row_height;
309 if (!i)
310 width = inw;
311 if (i && width != inw) {
312 av_log(ctx, AV_LOG_ERROR, "Row %d width %d does not match previous row width %d.\n", i, inw, width);
313 return AVERROR(EINVAL);
314 }
315 }
316 } else {
317 3 char *arg, *p = s->layout, *saveptr = NULL;
318 3 char *arg2, *p2, *saveptr2 = NULL;
319 3 char *arg3, *p3, *saveptr3 = NULL;
320 int inw, inh, size;
321
322
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (s->fillcolor_enable) {
323 3 const AVFilterLink *inlink = ctx->inputs[0];
324 3 ret = ff_draw_init_from_link(&s->draw, inlink, 0);
325
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (ret < 0) {
326 av_log(ctx, AV_LOG_ERROR, "Failed to initialize FFDrawContext\n");
327 return ret;
328 }
329 3 ff_draw_color(&s->draw, &s->color, s->fillcolor);
330 }
331
332
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3 times.
15 for (i = 0; i < s->nb_inputs; i++) {
333 12 AVFilterLink *inlink = ctx->inputs[i];
334 12 StackItem *item = &s->items[i];
335
336
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 if (!(arg = av_strtok(p, "|", &saveptr)))
337 return AVERROR(EINVAL);
338
339 12 p = NULL;
340
341
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
342 return ret;
343 }
344
345 12 item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
346 12 item->height[0] = item->height[3] = inlink->h;
347
348 12 p2 = arg;
349 12 inw = inh = 0;
350
351
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 12 times.
36 for (int j = 0; j < 2; j++) {
352
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
24 if (!(arg2 = av_strtok(p2, "_", &saveptr2)))
353 return AVERROR(EINVAL);
354
355 24 p2 = NULL;
356 24 p3 = arg2;
357
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 24 times.
48 while ((arg3 = av_strtok(p3, "+", &saveptr3))) {
358 24 p3 = NULL;
359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (sscanf(arg3, "w%d", &size) == 1) {
360 if (size == i || size < 0 || size >= s->nb_inputs)
361 return AVERROR(EINVAL);
362
363 if (!j) {
364 if (inw > INT_MAX - ctx->inputs[size]->w)
365 return AVERROR(EINVAL);
366 inw += ctx->inputs[size]->w;
367 } else {
368 if (inh > INT_MAX - ctx->inputs[size]->w)
369 return AVERROR(EINVAL);
370 inh += ctx->inputs[size]->w;
371 }
372
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 } else if (sscanf(arg3, "h%d", &size) == 1) {
373 if (size == i || size < 0 || size >= s->nb_inputs)
374 return AVERROR(EINVAL);
375
376 if (!j) {
377 if (inw > INT_MAX - ctx->inputs[size]->h)
378 return AVERROR(EINVAL);
379 inw += ctx->inputs[size]->h;
380 } else {
381 if (inh > INT_MAX - ctx->inputs[size]->h)
382 return AVERROR(EINVAL);
383 inh += ctx->inputs[size]->h;
384 }
385
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 } else if (sscanf(arg3, "%d", &size) == 1) {
386
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (size < 0)
387 return AVERROR(EINVAL);
388
389
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 12 times.
24 if (!j) {
390
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (inw > INT_MAX - size)
391 return AVERROR(EINVAL);
392 12 inw += size;
393 } else {
394
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (inh > INT_MAX - size)
395 return AVERROR(EINVAL);
396 12 inh += size;
397 }
398 } else {
399 return AVERROR(EINVAL);
400 }
401 }
402 }
403
404
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 if ((ret = av_image_fill_linesizes(item->x, inlink->format, inw)) < 0) {
405 return ret;
406 }
407
408 12 item->y[1] = item->y[2] = AV_CEIL_RSHIFT(inh, s->desc->log2_chroma_h);
409 12 item->y[0] = item->y[3] = inh;
410
411
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if (inlink->w > INT_MAX - inw || inlink->h > INT_MAX - inh)
412 return AVERROR(EINVAL);
413 12 width = FFMAX(width, inlink->w + inw);
414 12 height = FFMAX(height, inlink->h + inh);
415 }
416 }
417
418 6 s->nb_planes = av_pix_fmt_count_planes(outlink->format);
419
420 6 outlink->w = width;
421 6 outlink->h = height;
422 6 ol->frame_rate = frame_rate;
423 6 outlink->sample_aspect_ratio = sar;
424
425
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5 times.
17 for (i = 1; i < s->nb_inputs; i++) {
426 12 il = ff_filter_link(ctx->inputs[i]);
427
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 1 times.
12 if (ol->frame_rate.num != il->frame_rate.num ||
428
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 ol->frame_rate.den != il->frame_rate.den) {
429 1 av_log(ctx, AV_LOG_VERBOSE,
430 "Video inputs have different frame rates, output will be VFR\n");
431 1 ol->frame_rate = av_make_q(1, 0);
432 1 break;
433 }
434 }
435
436
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if ((ret = ff_framesync_init(&s->fs, ctx, s->nb_inputs)) < 0)
437 return ret;
438
439 6 in = s->fs.in;
440 6 s->fs.opaque = s;
441 6 s->fs.on_event = process_frame;
442
443
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 6 times.
24 for (i = 0; i < s->nb_inputs; i++) {
444 18 AVFilterLink *inlink = ctx->inputs[i];
445
446 18 in[i].time_base = inlink->time_base;
447 18 in[i].sync = 1;
448 18 in[i].before = EXT_STOP;
449
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 in[i].after = s->shortest ? EXT_STOP : EXT_INFINITY;
450 }
451
452 6 ret = ff_framesync_configure(&s->fs);
453 6 outlink->time_base = s->fs.time_base;
454
455 6 return ret;
456 }
457
458 15 static av_cold void uninit(AVFilterContext *ctx)
459 {
460 15 StackContext *s = ctx->priv;
461
462 15 ff_framesync_uninit(&s->fs);
463 15 av_freep(&s->frames);
464 15 av_freep(&s->items);
465 15 }
466
467 607 static int activate(AVFilterContext *ctx)
468 {
469 607 StackContext *s = ctx->priv;
470 607 return ff_framesync_activate(&s->fs);
471 }
472
473 #define OFFSET(x) offsetof(StackContext, x)
474 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
475 static const AVOption stack_options[] = {
476 { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
477 { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
478 { NULL },
479 };
480
481 AVFILTER_DEFINE_CLASS_EXT(stack, "(h|v)stack", stack_options);
482
483 static const AVFilterPad outputs[] = {
484 {
485 .name = "default",
486 .type = AVMEDIA_TYPE_VIDEO,
487 .config_props = config_output,
488 },
489 };
490
491 #if CONFIG_HSTACK_FILTER
492
493 const FFFilter ff_vf_hstack = {
494 .p.name = "hstack",
495 .p.description = NULL_IF_CONFIG_SMALL("Stack video inputs horizontally."),
496 .p.priv_class = &stack_class,
497 .p.flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
498 .priv_size = sizeof(StackContext),
499 FILTER_OUTPUTS(outputs),
500 FILTER_QUERY_FUNC2(query_formats),
501 .init = init,
502 .uninit = uninit,
503 .activate = activate,
504 };
505
506 #endif /* CONFIG_HSTACK_FILTER */
507
508 #if CONFIG_VSTACK_FILTER
509
510 const FFFilter ff_vf_vstack = {
511 .p.name = "vstack",
512 .p.description = NULL_IF_CONFIG_SMALL("Stack video inputs vertically."),
513 .p.priv_class = &stack_class,
514 .p.flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
515 .priv_size = sizeof(StackContext),
516 FILTER_OUTPUTS(outputs),
517 FILTER_QUERY_FUNC2(query_formats),
518 .init = init,
519 .uninit = uninit,
520 .activate = activate,
521 };
522
523 #endif /* CONFIG_VSTACK_FILTER */
524
525 #if CONFIG_XSTACK_FILTER
526
527 static const AVOption xstack_options[] = {
528 { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
529 { "layout", "set custom layout", OFFSET(layout), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, .flags = FLAGS },
530 { "grid", "set fixed size grid layout", OFFSET(nb_grid_columns), AV_OPT_TYPE_IMAGE_SIZE, {.str=NULL}, 0, 0, .flags = FLAGS },
531 { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
532 { "fill", "set the color for unused pixels", OFFSET(fillcolor_str), AV_OPT_TYPE_STRING, {.str = "none"}, .flags = FLAGS },
533 { NULL },
534 };
535
536 AVFILTER_DEFINE_CLASS(xstack);
537
538 const FFFilter ff_vf_xstack = {
539 .p.name = "xstack",
540 .p.description = NULL_IF_CONFIG_SMALL("Stack video inputs into custom layout."),
541 .p.priv_class = &xstack_class,
542 .p.flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
543 .priv_size = sizeof(StackContext),
544 FILTER_OUTPUTS(outputs),
545 FILTER_QUERY_FUNC2(query_formats),
546 .init = init,
547 .uninit = uninit,
548 .activate = activate,
549 };
550
551 #endif /* CONFIG_XSTACK_FILTER */
552