FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_stack.c
Date: 2024-11-20 23:03:26
Exec Total Coverage
Lines: 119 234 50.9%
Functions: 7 7 100.0%
Branches: 48 150 32.0%

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 3 static int query_formats(const AVFilterContext *ctx,
67 AVFilterFormatsConfig **cfg_in,
68 AVFilterFormatsConfig **cfg_out)
69 {
70 3 const StackContext *s = ctx->priv;
71 3 int reject_flags = AV_PIX_FMT_FLAG_BITSTREAM |
72 AV_PIX_FMT_FLAG_HWACCEL |
73 AV_PIX_FMT_FLAG_PAL;
74
75
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (s->fillcolor_enable) {
76 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 6 static av_cold int init(AVFilterContext *ctx)
85 {
86 6 StackContext *s = ctx->priv;
87 int i, ret;
88
89
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (!strcmp(ctx->filter->name, "vstack"))
90 2 s->is_vertical = 1;
91
92
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if (!strcmp(ctx->filter->name, "hstack"))
93 4 s->is_horizontal = 1;
94
95
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!strcmp(ctx->filter->name, "xstack")) {
96 int is_grid;
97 if (strcmp(s->fillcolor_str, "none") &&
98 av_parse_color(s->fillcolor, s->fillcolor_str, -1, ctx) >= 0) {
99 s->fillcolor_enable = 1;
100 } else {
101 s->fillcolor_enable = 0;
102 }
103 is_grid = s->nb_grid_rows && s->nb_grid_columns;
104 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 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 if (is_grid)
120 s->nb_inputs = s->nb_grid_rows * s->nb_grid_columns;
121 }
122
123 6 s->frames = av_calloc(s->nb_inputs, sizeof(*s->frames));
124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!s->frames)
125 return AVERROR(ENOMEM);
126
127 6 s->items = av_calloc(s->nb_inputs, sizeof(*s->items));
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!s->items)
129 return AVERROR(ENOMEM);
130
131
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
18 for (i = 0; i < s->nb_inputs; i++) {
132 12 AVFilterPad pad = { 0 };
133
134 12 pad.type = AVMEDIA_TYPE_VIDEO;
135 12 pad.name = av_asprintf("input%d", i);
136
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (!pad.name)
137 return AVERROR(ENOMEM);
138
139
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 if ((ret = ff_append_inpad_free_name(ctx, &pad)) < 0)
140 return ret;
141 }
142
143 6 return 0;
144 }
145
146 300 static int process_slice(AVFilterContext *ctx, void *arg, int job, int nb_jobs)
147 {
148 300 StackContext *s = ctx->priv;
149 300 AVFrame *out = arg;
150 300 AVFrame **in = s->frames;
151 300 const int start = (s->nb_inputs * job ) / nb_jobs;
152 300 const int end = (s->nb_inputs * (job+1)) / nb_jobs;
153
154
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 300 times.
600 for (int i = start; i < end; i++) {
155 300 StackItem *item = &s->items[i];
156
157
2/2
✓ Branch 0 taken 900 times.
✓ Branch 1 taken 300 times.
1200 for (int p = 0; p < s->nb_planes; p++) {
158 900 av_image_copy_plane(out->data[p] + out->linesize[p] * item->y[p] + item->x[p],
159 out->linesize[p],
160 900 in[i]->data[p],
161 900 in[i]->linesize[p],
162 item->linesize[p], item->height[p]);
163 }
164 }
165
166 300 return 0;
167 }
168
169 150 static int process_frame(FFFrameSync *fs)
170 {
171 150 AVFilterContext *ctx = fs->parent;
172 150 AVFilterLink *outlink = ctx->outputs[0];
173 150 StackContext *s = fs->opaque;
174 150 AVFrame **in = s->frames;
175 AVFrame *out;
176 int i, ret;
177
178
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 150 times.
450 for (i = 0; i < s->nb_inputs; i++) {
179
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 300 times.
300 if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0)
180 return ret;
181 }
182
183 150 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
184
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 150 times.
150 if (!out)
185 return AVERROR(ENOMEM);
186 150 out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
187 150 out->sample_aspect_ratio = outlink->sample_aspect_ratio;
188
189
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 150 times.
150 if (s->fillcolor_enable)
190 ff_fill_rectangle(&s->draw, &s->color, out->data, out->linesize,
191 0, 0, outlink->w, outlink->h);
192
193 150 ff_filter_execute(ctx, process_slice, out, NULL,
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 150 times.
150 FFMIN(s->nb_inputs, ff_filter_get_nb_threads(ctx)));
195
196 150 return ff_filter_frame(outlink, out);
197 }
198
199 3 static int config_output(AVFilterLink *outlink)
200 {
201 3 AVFilterContext *ctx = outlink->src;
202 3 StackContext *s = ctx->priv;
203 3 FilterLink *il = ff_filter_link(ctx->inputs[0]);
204 3 FilterLink *ol = ff_filter_link(outlink);
205 3 AVRational frame_rate = il->frame_rate;
206 3 AVRational sar = ctx->inputs[0]->sample_aspect_ratio;
207 3 int height = ctx->inputs[0]->h;
208 3 int width = ctx->inputs[0]->w;
209 FFFrameSyncIn *in;
210 int i, ret;
211
212 3 s->desc = av_pix_fmt_desc_get(outlink->format);
213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!s->desc)
214 return AVERROR_BUG;
215
216
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 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 height += ctx->inputs[i]->h;
238 }
239 }
240
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 } else if (s->is_horizontal) {
241
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (i = 0; i < s->nb_inputs; i++) {
242 4 AVFilterLink *inlink = ctx->inputs[i];
243 4 StackItem *item = &s->items[i];
244
245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ctx->inputs[i]->h != height) {
246 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);
247 return AVERROR(EINVAL);
248 }
249
250
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) {
251 return ret;
252 }
253
254 4 item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
255 4 item->height[0] = item->height[3] = inlink->h;
256
257
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (i) {
258
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((ret = av_image_fill_linesizes(item->x, inlink->format, width)) < 0) {
259 return ret;
260 }
261
262 2 width += ctx->inputs[i]->w;
263 }
264 }
265 } else if (s->nb_grid_rows && s->nb_grid_columns) {
266 int inw = 0, inh = 0;
267 int k = 0;
268 int row_height;
269 height = 0;
270 width = 0;
271 for (i = 0; i < s->nb_grid_rows; i++, inh += row_height) {
272 row_height = ctx->inputs[i * s->nb_grid_columns]->h;
273 inw = 0;
274 for (int j = 0; j < s->nb_grid_columns; j++, k++) {
275 AVFilterLink *inlink = ctx->inputs[k];
276 StackItem *item = &s->items[k];
277
278 if (ctx->inputs[k]->h != row_height) {
279 av_log(ctx, AV_LOG_ERROR, "Input %d height %d does not match current row's height %d.\n",
280 k, ctx->inputs[k]->h, row_height);
281 return AVERROR(EINVAL);
282 }
283
284 if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
285 return ret;
286 }
287
288 item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
289 item->height[0] = item->height[3] = inlink->h;
290
291 if ((ret = av_image_fill_linesizes(item->x, inlink->format, inw)) < 0) {
292 return ret;
293 }
294
295 item->y[1] = item->y[2] = AV_CEIL_RSHIFT(inh, s->desc->log2_chroma_h);
296 item->y[0] = item->y[3] = inh;
297 inw += ctx->inputs[k]->w;
298 }
299 height += row_height;
300 if (!i)
301 width = inw;
302 if (i && width != inw) {
303 av_log(ctx, AV_LOG_ERROR, "Row %d width %d does not match previous row width %d.\n", i, inw, width);
304 return AVERROR(EINVAL);
305 }
306 }
307 } else {
308 char *arg, *p = s->layout, *saveptr = NULL;
309 char *arg2, *p2, *saveptr2 = NULL;
310 char *arg3, *p3, *saveptr3 = NULL;
311 int inw, inh, size;
312
313 if (s->fillcolor_enable) {
314 const AVFilterLink *inlink = ctx->inputs[0];
315 ff_draw_init2(&s->draw, inlink->format, inlink->colorspace, inlink->color_range, 0);
316 ff_draw_color(&s->draw, &s->color, s->fillcolor);
317 }
318
319 for (i = 0; i < s->nb_inputs; i++) {
320 AVFilterLink *inlink = ctx->inputs[i];
321 StackItem *item = &s->items[i];
322
323 if (!(arg = av_strtok(p, "|", &saveptr)))
324 return AVERROR(EINVAL);
325
326 p = NULL;
327
328 if ((ret = av_image_fill_linesizes(item->linesize, inlink->format, inlink->w)) < 0) {
329 return ret;
330 }
331
332 item->height[1] = item->height[2] = AV_CEIL_RSHIFT(inlink->h, s->desc->log2_chroma_h);
333 item->height[0] = item->height[3] = inlink->h;
334
335 p2 = arg;
336 inw = inh = 0;
337
338 for (int j = 0; j < 2; j++) {
339 if (!(arg2 = av_strtok(p2, "_", &saveptr2)))
340 return AVERROR(EINVAL);
341
342 p2 = NULL;
343 p3 = arg2;
344 while ((arg3 = av_strtok(p3, "+", &saveptr3))) {
345 p3 = NULL;
346 if (sscanf(arg3, "w%d", &size) == 1) {
347 if (size == i || size < 0 || size >= s->nb_inputs)
348 return AVERROR(EINVAL);
349
350 if (!j)
351 inw += ctx->inputs[size]->w;
352 else
353 inh += ctx->inputs[size]->w;
354 } else if (sscanf(arg3, "h%d", &size) == 1) {
355 if (size == i || size < 0 || size >= s->nb_inputs)
356 return AVERROR(EINVAL);
357
358 if (!j)
359 inw += ctx->inputs[size]->h;
360 else
361 inh += ctx->inputs[size]->h;
362 } else if (sscanf(arg3, "%d", &size) == 1) {
363 if (size < 0)
364 return AVERROR(EINVAL);
365
366 if (!j)
367 inw += size;
368 else
369 inh += size;
370 } else {
371 return AVERROR(EINVAL);
372 }
373 }
374 }
375
376 if ((ret = av_image_fill_linesizes(item->x, inlink->format, inw)) < 0) {
377 return ret;
378 }
379
380 item->y[1] = item->y[2] = AV_CEIL_RSHIFT(inh, s->desc->log2_chroma_h);
381 item->y[0] = item->y[3] = inh;
382
383 width = FFMAX(width, inlink->w + inw);
384 height = FFMAX(height, inlink->h + inh);
385 }
386 }
387
388 3 s->nb_planes = av_pix_fmt_count_planes(outlink->format);
389
390 3 outlink->w = width;
391 3 outlink->h = height;
392 3 ol->frame_rate = frame_rate;
393 3 outlink->sample_aspect_ratio = sar;
394
395
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 for (i = 1; i < s->nb_inputs; i++) {
396 3 il = ff_filter_link(ctx->inputs[i]);
397
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (ol->frame_rate.num != il->frame_rate.num ||
398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 ol->frame_rate.den != il->frame_rate.den) {
399 1 av_log(ctx, AV_LOG_VERBOSE,
400 "Video inputs have different frame rates, output will be VFR\n");
401 1 ol->frame_rate = av_make_q(1, 0);
402 1 break;
403 }
404 }
405
406
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((ret = ff_framesync_init(&s->fs, ctx, s->nb_inputs)) < 0)
407 return ret;
408
409 3 in = s->fs.in;
410 3 s->fs.opaque = s;
411 3 s->fs.on_event = process_frame;
412
413
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
9 for (i = 0; i < s->nb_inputs; i++) {
414 6 AVFilterLink *inlink = ctx->inputs[i];
415
416 6 in[i].time_base = inlink->time_base;
417 6 in[i].sync = 1;
418 6 in[i].before = EXT_STOP;
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 in[i].after = s->shortest ? EXT_STOP : EXT_INFINITY;
420 }
421
422 3 ret = ff_framesync_configure(&s->fs);
423 3 outlink->time_base = s->fs.time_base;
424
425 3 return ret;
426 }
427
428 6 static av_cold void uninit(AVFilterContext *ctx)
429 {
430 6 StackContext *s = ctx->priv;
431
432 6 ff_framesync_uninit(&s->fs);
433 6 av_freep(&s->frames);
434 6 av_freep(&s->items);
435 6 }
436
437 421 static int activate(AVFilterContext *ctx)
438 {
439 421 StackContext *s = ctx->priv;
440 421 return ff_framesync_activate(&s->fs);
441 }
442
443 #define OFFSET(x) offsetof(StackContext, x)
444 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
445 static const AVOption stack_options[] = {
446 { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
447 { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
448 { NULL },
449 };
450
451 AVFILTER_DEFINE_CLASS_EXT(stack, "(h|v)stack", stack_options);
452
453 static const AVFilterPad outputs[] = {
454 {
455 .name = "default",
456 .type = AVMEDIA_TYPE_VIDEO,
457 .config_props = config_output,
458 },
459 };
460
461 #if CONFIG_HSTACK_FILTER
462
463 const AVFilter ff_vf_hstack = {
464 .name = "hstack",
465 .description = NULL_IF_CONFIG_SMALL("Stack video inputs horizontally."),
466 .priv_class = &stack_class,
467 .priv_size = sizeof(StackContext),
468 FILTER_OUTPUTS(outputs),
469 FILTER_QUERY_FUNC2(query_formats),
470 .init = init,
471 .uninit = uninit,
472 .activate = activate,
473 .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
474 };
475
476 #endif /* CONFIG_HSTACK_FILTER */
477
478 #if CONFIG_VSTACK_FILTER
479
480 const AVFilter ff_vf_vstack = {
481 .name = "vstack",
482 .description = NULL_IF_CONFIG_SMALL("Stack video inputs vertically."),
483 .priv_class = &stack_class,
484 .priv_size = sizeof(StackContext),
485 FILTER_OUTPUTS(outputs),
486 FILTER_QUERY_FUNC2(query_formats),
487 .init = init,
488 .uninit = uninit,
489 .activate = activate,
490 .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
491 };
492
493 #endif /* CONFIG_VSTACK_FILTER */
494
495 #if CONFIG_XSTACK_FILTER
496
497 static const AVOption xstack_options[] = {
498 { "inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags = FLAGS },
499 { "layout", "set custom layout", OFFSET(layout), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, .flags = FLAGS },
500 { "grid", "set fixed size grid layout", OFFSET(nb_grid_columns), AV_OPT_TYPE_IMAGE_SIZE, {.str=NULL}, 0, 0, .flags = FLAGS },
501 { "shortest", "force termination when the shortest input terminates", OFFSET(shortest), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, .flags = FLAGS },
502 { "fill", "set the color for unused pixels", OFFSET(fillcolor_str), AV_OPT_TYPE_STRING, {.str = "none"}, .flags = FLAGS },
503 { NULL },
504 };
505
506 AVFILTER_DEFINE_CLASS(xstack);
507
508 const AVFilter ff_vf_xstack = {
509 .name = "xstack",
510 .description = NULL_IF_CONFIG_SMALL("Stack video inputs into custom layout."),
511 .priv_size = sizeof(StackContext),
512 .priv_class = &xstack_class,
513 FILTER_OUTPUTS(outputs),
514 FILTER_QUERY_FUNC2(query_formats),
515 .init = init,
516 .uninit = uninit,
517 .activate = activate,
518 .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_SLICE_THREADS,
519 };
520
521 #endif /* CONFIG_XSTACK_FILTER */
522