FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_colorcorrect.c
Date: 2022-11-26 13:19:19
Exec Total Coverage
Lines: 0 301 0.0%
Branches: 0 81 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2021 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 <float.h>
22
23 #include "libavutil/opt.h"
24 #include "libavutil/imgutils.h"
25 #include "avfilter.h"
26 #include "formats.h"
27 #include "internal.h"
28 #include "video.h"
29
30 typedef enum AnalyzeMode {
31 MANUAL,
32 AVERAGE,
33 MINMAX,
34 MEDIAN,
35 NB_ANALYZE
36 } AnalyzeMode;
37
38 typedef struct ColorCorrectContext {
39 const AVClass *class;
40
41 float rl, bl;
42 float rh, bh;
43 float saturation;
44 int analyze;
45
46 int depth;
47 float max, imax;
48
49 int chroma_w, chroma_h;
50 int planeheight[4];
51 int planewidth[4];
52
53 unsigned *uhistogram;
54 unsigned *vhistogram;
55
56 float (*analyzeret)[4];
57
58 int (*do_analyze)(AVFilterContext *s, void *arg,
59 int jobnr, int nb_jobs);
60 int (*do_slice)(AVFilterContext *s, void *arg,
61 int jobnr, int nb_jobs);
62 } ColorCorrectContext;
63
64 static int average_slice8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
65 {
66 ColorCorrectContext *s = ctx->priv;
67 AVFrame *frame = arg;
68 const float imax = s->imax;
69 const int width = s->planewidth[1];
70 const int height = s->planeheight[1];
71 const int slice_start = (height * jobnr) / nb_jobs;
72 const int slice_end = (height * (jobnr + 1)) / nb_jobs;
73 const int ulinesize = frame->linesize[1];
74 const int vlinesize = frame->linesize[2];
75 const uint8_t *uptr = (const uint8_t *)frame->data[1] + slice_start * ulinesize;
76 const uint8_t *vptr = (const uint8_t *)frame->data[2] + slice_start * vlinesize;
77 int sum_u = 0, sum_v = 0;
78
79 for (int y = slice_start; y < slice_end; y++) {
80 for (int x = 0; x < width; x++) {
81 sum_u += uptr[x];
82 sum_v += vptr[x];
83 }
84
85 uptr += ulinesize;
86 vptr += vlinesize;
87 }
88
89 s->analyzeret[jobnr][0] = s->analyzeret[jobnr][2] = imax * sum_u / (float)((slice_end - slice_start) * width) - 0.5f;
90 s->analyzeret[jobnr][1] = s->analyzeret[jobnr][3] = imax * sum_v / (float)((slice_end - slice_start) * width) - 0.5f;
91
92 return 0;
93 }
94
95 static int average_slice16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
96 {
97 ColorCorrectContext *s = ctx->priv;
98 AVFrame *frame = arg;
99 const float imax = s->imax;
100 const int width = s->planewidth[1];
101 const int height = s->planeheight[1];
102 const int slice_start = (height * jobnr) / nb_jobs;
103 const int slice_end = (height * (jobnr + 1)) / nb_jobs;
104 const int ulinesize = frame->linesize[1] / 2;
105 const int vlinesize = frame->linesize[2] / 2;
106 const uint16_t *uptr = (const uint16_t *)frame->data[1] + slice_start * ulinesize;
107 const uint16_t *vptr = (const uint16_t *)frame->data[2] + slice_start * vlinesize;
108 int64_t sum_u = 0, sum_v = 0;
109
110 for (int y = slice_start; y < slice_end; y++) {
111 for (int x = 0; x < width; x++) {
112 sum_u += uptr[x];
113 sum_v += vptr[x];
114 }
115
116 uptr += ulinesize;
117 vptr += vlinesize;
118 }
119
120 s->analyzeret[jobnr][0] = s->analyzeret[jobnr][2] = imax * sum_u / (float)((slice_end - slice_start) * width) - 0.5f;
121 s->analyzeret[jobnr][1] = s->analyzeret[jobnr][3] = imax * sum_v / (float)((slice_end - slice_start) * width) - 0.5f;
122
123 return 0;
124 }
125
126 static int minmax_slice8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
127 {
128 ColorCorrectContext *s = ctx->priv;
129 AVFrame *frame = arg;
130 const float imax = s->imax;
131 const int width = s->planewidth[1];
132 const int height = s->planeheight[1];
133 const int slice_start = (height * jobnr) / nb_jobs;
134 const int slice_end = (height * (jobnr + 1)) / nb_jobs;
135 const int ulinesize = frame->linesize[1];
136 const int vlinesize = frame->linesize[2];
137 const uint8_t *uptr = (const uint8_t *)frame->data[1] + slice_start * ulinesize;
138 const uint8_t *vptr = (const uint8_t *)frame->data[2] + slice_start * vlinesize;
139 int min_u = 255, min_v = 255;
140 int max_u = 0, max_v = 0;
141
142 for (int y = slice_start; y < slice_end; y++) {
143 for (int x = 0; x < width; x++) {
144 min_u = FFMIN(min_u, uptr[x]);
145 min_v = FFMIN(min_v, vptr[x]);
146 max_u = FFMAX(max_u, uptr[x]);
147 max_v = FFMAX(max_v, vptr[x]);
148 }
149
150 uptr += ulinesize;
151 vptr += vlinesize;
152 }
153
154 s->analyzeret[jobnr][0] = imax * min_u - 0.5f;
155 s->analyzeret[jobnr][1] = imax * min_v - 0.5f;
156 s->analyzeret[jobnr][2] = imax * max_u - 0.5f;
157 s->analyzeret[jobnr][3] = imax * max_v - 0.5f;
158
159 return 0;
160 }
161
162 static int minmax_slice16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
163 {
164 ColorCorrectContext *s = ctx->priv;
165 AVFrame *frame = arg;
166 const float imax = s->imax;
167 const int width = s->planewidth[1];
168 const int height = s->planeheight[1];
169 const int slice_start = (height * jobnr) / nb_jobs;
170 const int slice_end = (height * (jobnr + 1)) / nb_jobs;
171 const int ulinesize = frame->linesize[1] / 2;
172 const int vlinesize = frame->linesize[2] / 2;
173 const uint16_t *uptr = (const uint16_t *)frame->data[1] + slice_start * ulinesize;
174 const uint16_t *vptr = (const uint16_t *)frame->data[2] + slice_start * vlinesize;
175 int min_u = INT_MAX, min_v = INT_MAX;
176 int max_u = INT_MIN, max_v = INT_MIN;
177
178 for (int y = slice_start; y < slice_end; y++) {
179 for (int x = 0; x < width; x++) {
180 min_u = FFMIN(min_u, uptr[x]);
181 min_v = FFMIN(min_v, vptr[x]);
182 max_u = FFMAX(max_u, uptr[x]);
183 max_v = FFMAX(max_v, vptr[x]);
184 }
185
186 uptr += ulinesize;
187 vptr += vlinesize;
188 }
189
190 s->analyzeret[jobnr][0] = imax * min_u - 0.5f;
191 s->analyzeret[jobnr][1] = imax * min_v - 0.5f;
192 s->analyzeret[jobnr][2] = imax * max_u - 0.5f;
193 s->analyzeret[jobnr][3] = imax * max_v - 0.5f;
194
195 return 0;
196 }
197
198 static int median_8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
199 {
200 ColorCorrectContext *s = ctx->priv;
201 AVFrame *frame = arg;
202 const float imax = s->imax;
203 const int width = s->planewidth[1];
204 const int height = s->planeheight[1];
205 const int ulinesize = frame->linesize[1];
206 const int vlinesize = frame->linesize[2];
207 const uint8_t *uptr = (const uint8_t *)frame->data[1];
208 const uint8_t *vptr = (const uint8_t *)frame->data[2];
209 unsigned *uhistogram = s->uhistogram;
210 unsigned *vhistogram = s->vhistogram;
211 const int half_size = width * height / 2;
212 int umedian = s->max, vmedian = s->max;
213 unsigned ucnt = 0, vcnt = 0;
214
215 memset(uhistogram, 0, sizeof(*uhistogram) * (s->max + 1));
216 memset(vhistogram, 0, sizeof(*vhistogram) * (s->max + 1));
217
218 for (int y = 0; y < height; y++) {
219 for (int x = 0; x < width; x++) {
220 uhistogram[uptr[x]]++;
221 vhistogram[vptr[x]]++;
222 }
223
224 uptr += ulinesize;
225 vptr += vlinesize;
226 }
227
228 for (int i = 0; i < s->max + 1; i++) {
229 ucnt += uhistogram[i];
230 if (ucnt >= half_size) {
231 umedian = i;
232 break;
233 }
234 }
235
236 for (int i = 0; i < s->max + 1; i++) {
237 vcnt += vhistogram[i];
238 if (vcnt >= half_size) {
239 vmedian = i;
240 break;
241 }
242 }
243
244 s->analyzeret[0][0] = imax * umedian - 0.5f;
245 s->analyzeret[0][1] = imax * vmedian - 0.5f;
246 s->analyzeret[0][2] = imax * umedian - 0.5f;
247 s->analyzeret[0][3] = imax * vmedian - 0.5f;
248
249 return 0;
250 }
251
252 static int median_16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
253 {
254 ColorCorrectContext *s = ctx->priv;
255 AVFrame *frame = arg;
256 const float imax = s->imax;
257 const int width = s->planewidth[1];
258 const int height = s->planeheight[1];
259 const int ulinesize = frame->linesize[1] / 2;
260 const int vlinesize = frame->linesize[2] / 2;
261 const uint16_t *uptr = (const uint16_t *)frame->data[1];
262 const uint16_t *vptr = (const uint16_t *)frame->data[2];
263 unsigned *uhistogram = s->uhistogram;
264 unsigned *vhistogram = s->vhistogram;
265 const int half_size = width * height / 2;
266 int umedian = s->max, vmedian = s->max;
267 unsigned ucnt = 0, vcnt = 0;
268
269 memset(uhistogram, 0, sizeof(*uhistogram) * (s->max + 1));
270 memset(vhistogram, 0, sizeof(*vhistogram) * (s->max + 1));
271
272 for (int y = 0; y < height; y++) {
273 for (int x = 0; x < width; x++) {
274 uhistogram[uptr[x]]++;
275 vhistogram[vptr[x]]++;
276 }
277
278 uptr += ulinesize;
279 vptr += vlinesize;
280 }
281
282 for (int i = 0; i < s->max + 1; i++) {
283 ucnt += uhistogram[i];
284 if (ucnt >= half_size) {
285 umedian = i;
286 break;
287 }
288 }
289
290 for (int i = 0; i < s->max + 1; i++) {
291 vcnt += vhistogram[i];
292 if (vcnt >= half_size) {
293 vmedian = i;
294 break;
295 }
296 }
297
298 s->analyzeret[0][0] = imax * umedian - 0.5f;
299 s->analyzeret[0][1] = imax * vmedian - 0.5f;
300 s->analyzeret[0][2] = imax * umedian - 0.5f;
301 s->analyzeret[0][3] = imax * vmedian - 0.5f;
302
303 return 0;
304 }
305
306 #define PROCESS() \
307 float y = yptr[x * chroma_w] * imax; \
308 float u = uptr[x] * imax - .5f; \
309 float v = vptr[x] * imax - .5f; \
310 float nu, nv; \
311 \
312 nu = saturation * (u + y * bd + bl); \
313 nv = saturation * (v + y * rd + rl);
314
315 static int colorcorrect_slice8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
316 {
317 ColorCorrectContext *s = ctx->priv;
318 AVFrame *frame = arg;
319 const float max = s->max;
320 const float imax = s->imax;
321 const int chroma_w = s->chroma_w;
322 const int chroma_h = s->chroma_h;
323 const int width = s->planewidth[1];
324 const int height = s->planeheight[1];
325 const int slice_start = (height * jobnr) / nb_jobs;
326 const int slice_end = (height * (jobnr + 1)) / nb_jobs;
327 const int ylinesize = frame->linesize[0];
328 const int ulinesize = frame->linesize[1];
329 const int vlinesize = frame->linesize[2];
330 uint8_t *yptr = frame->data[0] + slice_start * chroma_h * ylinesize;
331 uint8_t *uptr = frame->data[1] + slice_start * ulinesize;
332 uint8_t *vptr = frame->data[2] + slice_start * vlinesize;
333 const float saturation = s->saturation;
334 const float bl = s->bl;
335 const float rl = s->rl;
336 const float bd = s->bh - bl;
337 const float rd = s->rh - rl;
338
339 for (int y = slice_start; y < slice_end; y++) {
340 for (int x = 0; x < width; x++) {
341 PROCESS()
342
343 uptr[x] = av_clip_uint8((nu + 0.5f) * max);
344 vptr[x] = av_clip_uint8((nv + 0.5f) * max);
345 }
346
347 yptr += ylinesize * chroma_h;
348 uptr += ulinesize;
349 vptr += vlinesize;
350 }
351
352 return 0;
353 }
354
355 static int colorcorrect_slice16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
356 {
357 ColorCorrectContext *s = ctx->priv;
358 AVFrame *frame = arg;
359 const int depth = s->depth;
360 const float max = s->max;
361 const float imax = s->imax;
362 const int chroma_w = s->chroma_w;
363 const int chroma_h = s->chroma_h;
364 const int width = s->planewidth[1];
365 const int height = s->planeheight[1];
366 const int slice_start = (height * jobnr) / nb_jobs;
367 const int slice_end = (height * (jobnr + 1)) / nb_jobs;
368 const int ylinesize = frame->linesize[0] / 2;
369 const int ulinesize = frame->linesize[1] / 2;
370 const int vlinesize = frame->linesize[2] / 2;
371 uint16_t *yptr = (uint16_t *)frame->data[0] + slice_start * chroma_h * ylinesize;
372 uint16_t *uptr = (uint16_t *)frame->data[1] + slice_start * ulinesize;
373 uint16_t *vptr = (uint16_t *)frame->data[2] + slice_start * vlinesize;
374 const float saturation = s->saturation;
375 const float bl = s->bl;
376 const float rl = s->rl;
377 const float bd = s->bh - bl;
378 const float rd = s->rh - rl;
379
380 for (int y = slice_start; y < slice_end; y++) {
381 for (int x = 0; x < width; x++) {
382 PROCESS()
383
384 uptr[x] = av_clip_uintp2_c((nu + 0.5f) * max, depth);
385 vptr[x] = av_clip_uintp2_c((nv + 0.5f) * max, depth);
386 }
387
388 yptr += ylinesize * chroma_h;
389 uptr += ulinesize;
390 vptr += vlinesize;
391 }
392
393 return 0;
394 }
395
396 static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
397 {
398 AVFilterContext *ctx = inlink->dst;
399 ColorCorrectContext *s = ctx->priv;
400 const int nb_threads = s->analyze == MEDIAN ? 1 : FFMIN(s->planeheight[1], ff_filter_get_nb_threads(ctx));
401
402 if (s->analyze) {
403 const int nb_athreads = s->analyze == MEDIAN ? 1 : nb_threads;
404 float bl = 0.f, rl = 0.f, bh = 0.f, rh = 0.f;
405
406 ff_filter_execute(ctx, s->do_analyze, frame, NULL, nb_athreads);
407
408 for (int i = 0; i < nb_athreads; i++) {
409 bl += s->analyzeret[i][0];
410 rl += s->analyzeret[i][1];
411 bh += s->analyzeret[i][2];
412 rh += s->analyzeret[i][3];
413 }
414
415 bl /= nb_athreads;
416 rl /= nb_athreads;
417 bh /= nb_athreads;
418 rh /= nb_athreads;
419
420 s->bl = -bl;
421 s->rl = -rl;
422 s->bh = -bh;
423 s->rh = -rh;
424 }
425
426 ff_filter_execute(ctx, s->do_slice, frame, NULL, nb_threads);
427
428 return ff_filter_frame(ctx->outputs[0], frame);
429 }
430
431 static const enum AVPixelFormat pixel_fmts[] = {
432 AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV444P,
433 AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P,
434 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ411P,
435 AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
436 AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV440P10, AV_PIX_FMT_YUV444P10,
437 AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV440P12, AV_PIX_FMT_YUV420P12,
438 AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14,
439 AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
440 AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
441 AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
442 AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
443 AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
444 AV_PIX_FMT_NONE
445 };
446
447 static av_cold int config_input(AVFilterLink *inlink)
448 {
449 AVFilterContext *ctx = inlink->dst;
450 ColorCorrectContext *s = ctx->priv;
451 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
452
453 s->depth = desc->comp[0].depth;
454 s->max = (1 << s->depth) - 1;
455 s->imax = 1.f / s->max;
456 s->do_slice = s->depth <= 8 ? colorcorrect_slice8 : colorcorrect_slice16;
457
458 s->uhistogram = av_calloc(s->max == 255 ? 256 : 65536, sizeof(*s->uhistogram));
459 if (!s->uhistogram)
460 return AVERROR(ENOMEM);
461
462 s->vhistogram = av_calloc(s->max == 255 ? 256 : 65536, sizeof(*s->vhistogram));
463 if (!s->vhistogram)
464 return AVERROR(ENOMEM);
465
466 s->analyzeret = av_calloc(inlink->h, sizeof(*s->analyzeret));
467 if (!s->analyzeret)
468 return AVERROR(ENOMEM);
469
470 switch (s->analyze) {
471 case MANUAL:
472 break;
473 case AVERAGE:
474 s->do_analyze = s->depth <= 8 ? average_slice8 : average_slice16;
475 break;
476 case MINMAX:
477 s->do_analyze = s->depth <= 8 ? minmax_slice8 : minmax_slice16;
478 break;
479 case MEDIAN:
480 s->do_analyze = s->depth <= 8 ? median_8 : median_16;
481 break;
482 default:
483 return AVERROR_BUG;
484 }
485
486 s->chroma_w = 1 << desc->log2_chroma_w;
487 s->chroma_h = 1 << desc->log2_chroma_h;
488 s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
489 s->planeheight[0] = s->planeheight[3] = inlink->h;
490 s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
491 s->planewidth[0] = s->planewidth[3] = inlink->w;
492
493 return 0;
494 }
495
496 static av_cold void uninit(AVFilterContext *ctx)
497 {
498 ColorCorrectContext *s = ctx->priv;
499
500 av_freep(&s->analyzeret);
501 }
502
503 static const AVFilterPad colorcorrect_inputs[] = {
504 {
505 .name = "default",
506 .type = AVMEDIA_TYPE_VIDEO,
507 .flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE,
508 .filter_frame = filter_frame,
509 .config_props = config_input,
510 },
511 };
512
513 static const AVFilterPad colorcorrect_outputs[] = {
514 {
515 .name = "default",
516 .type = AVMEDIA_TYPE_VIDEO,
517 },
518 };
519
520 #define OFFSET(x) offsetof(ColorCorrectContext, x)
521 #define VF AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
522
523 static const AVOption colorcorrect_options[] = {
524 { "rl", "set the red shadow spot", OFFSET(rl), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, VF },
525 { "bl", "set the blue shadow spot", OFFSET(bl), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, VF },
526 { "rh", "set the red highlight spot", OFFSET(rh), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, VF },
527 { "bh", "set the blue highlight spot", OFFSET(bh), AV_OPT_TYPE_FLOAT, {.dbl=0}, -1, 1, VF },
528 { "saturation", "set the amount of saturation", OFFSET(saturation), AV_OPT_TYPE_FLOAT, {.dbl=1}, -3, 3, VF },
529 { "analyze", "set the analyze mode", OFFSET(analyze), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_ANALYZE-1, VF, "analyze" },
530 { "manual", "manually set options", 0, AV_OPT_TYPE_CONST, {.i64=MANUAL}, 0, 0, VF, "analyze" },
531 { "average", "use average pixels", 0, AV_OPT_TYPE_CONST, {.i64=AVERAGE}, 0, 0, VF, "analyze" },
532 { "minmax", "use minmax pixels", 0, AV_OPT_TYPE_CONST, {.i64=MINMAX}, 0, 0, VF, "analyze" },
533 { "median", "use median pixels", 0, AV_OPT_TYPE_CONST, {.i64=MEDIAN}, 0, 0, VF, "analyze" },
534 { NULL }
535 };
536
537 AVFILTER_DEFINE_CLASS(colorcorrect);
538
539 const AVFilter ff_vf_colorcorrect = {
540 .name = "colorcorrect",
541 .description = NULL_IF_CONFIG_SMALL("Adjust color white balance selectively for blacks and whites."),
542 .priv_size = sizeof(ColorCorrectContext),
543 .priv_class = &colorcorrect_class,
544 .uninit = uninit,
545 FILTER_INPUTS(colorcorrect_inputs),
546 FILTER_OUTPUTS(colorcorrect_outputs),
547 FILTER_PIXFMTS_ARRAY(pixel_fmts),
548 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
549 .process_command = ff_filter_process_command,
550 };
551