2 * Copyright (c) 2020 Paul B Mahol
4 * This file is part of FFmpeg.
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.
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.
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
21 #include "libavutil/imgutils.h"
22 #include "libavutil/eval.h"
23 #include "libavutil/opt.h"
24 #include "libavutil/pixfmt.h"
27 #include "framesync.h"
32 enum XFadeTransitions {
56 typedef struct XFadeContext {
80 void (*transitionf)(AVFilterContext *ctx, const AVFrame *a, const AVFrame *b, AVFrame *out, float progress,
81 int slice_start, int slice_end, int jobnr);
86 static const char *const var_names[] = { "X", "Y", "W", "H", "A", "B", "PLANE", "P", NULL };
87 enum { VAR_X, VAR_Y, VAR_W, VAR_H, VAR_A, VAR_B, VAR_PLANE, VAR_PROGRESS, VAR_VARS_NB };
89 typedef struct ThreadData {
95 static int query_formats(AVFilterContext *ctx)
97 static const enum AVPixelFormat pix_fmts[] = {
101 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8,
102 AV_PIX_FMT_YUVA444P9, AV_PIX_FMT_GBRP9,
103 AV_PIX_FMT_YUV444P10,
104 AV_PIX_FMT_YUVA444P10,
105 AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GRAY10,
106 AV_PIX_FMT_YUV444P12,
107 AV_PIX_FMT_YUVA444P12,
108 AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GRAY12,
109 AV_PIX_FMT_YUV444P14, AV_PIX_FMT_GBRP14,
110 AV_PIX_FMT_YUV444P16,
111 AV_PIX_FMT_YUVA444P16,
112 AV_PIX_FMT_GBRP16, AV_PIX_FMT_GBRAP16, AV_PIX_FMT_GRAY16,
116 AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
118 return AVERROR(ENOMEM);
119 return ff_set_common_formats(ctx, fmts_list);
122 static av_cold void uninit(AVFilterContext *ctx)
124 XFadeContext *s = ctx->priv;
129 #define OFFSET(x) offsetof(XFadeContext, x)
130 #define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
132 static const AVOption xfade_options[] = {
133 { "transition", "set cross fade transition", OFFSET(transition), AV_OPT_TYPE_INT, {.i64=FADE}, -1, NB_TRANSITIONS-1, FLAGS, "transition" },
134 { "custom", "custom transition", 0, AV_OPT_TYPE_CONST, {.i64=CUSTOM}, 0, 0, FLAGS, "transition" },
135 { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, "transition" },
136 { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" },
137 { "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, "transition" },
138 { "wipeup", "wipe up transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEUP}, 0, 0, FLAGS, "transition" },
139 { "wipedown", "wipe down transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEDOWN}, 0, 0, FLAGS, "transition" },
140 { "slideleft", "slide left transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDELEFT}, 0, 0, FLAGS, "transition" },
141 { "slideright", "slide right transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDERIGHT}, 0, 0, FLAGS, "transition" },
142 { "slideup", "slide up transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEUP}, 0, 0, FLAGS, "transition" },
143 { "slidedown", "slide down transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEDOWN}, 0, 0, FLAGS, "transition" },
144 { "circlecrop", "circle crop transition", 0, AV_OPT_TYPE_CONST, {.i64=CIRCLECROP}, 0, 0, FLAGS, "transition" },
145 { "rectcrop", "rect crop transition", 0, AV_OPT_TYPE_CONST, {.i64=RECTCROP}, 0, 0, FLAGS, "transition" },
146 { "distance", "distance transition", 0, AV_OPT_TYPE_CONST, {.i64=DISTANCE}, 0, 0, FLAGS, "transition" },
147 { "fadeblack", "fadeblack transition", 0, AV_OPT_TYPE_CONST, {.i64=FADEBLACK}, 0, 0, FLAGS, "transition" },
148 { "fadewhite", "fadewhite transition", 0, AV_OPT_TYPE_CONST, {.i64=FADEWHITE}, 0, 0, FLAGS, "transition" },
149 { "radial", "radial transition", 0, AV_OPT_TYPE_CONST, {.i64=RADIAL}, 0, 0, FLAGS, "transition" },
150 { "smoothleft", "smoothleft transition", 0, AV_OPT_TYPE_CONST, {.i64=SMOOTHLEFT}, 0, 0, FLAGS, "transition" },
151 { "smoothright","smoothright transition", 0, AV_OPT_TYPE_CONST, {.i64=SMOOTHRIGHT},0, 0, FLAGS, "transition" },
152 { "smoothup", "smoothup transition", 0, AV_OPT_TYPE_CONST, {.i64=SMOOTHUP}, 0, 0, FLAGS, "transition" },
153 { "smoothdown", "smoothdown transition", 0, AV_OPT_TYPE_CONST, {.i64=SMOOTHDOWN}, 0, 0, FLAGS, "transition" },
154 { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS },
155 { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS },
156 { "expr", "set expression for custom transition", OFFSET(custom_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
160 AVFILTER_DEFINE_CLASS(xfade);
162 #define CUSTOM_TRANSITION(name, type, div) \
163 static void custom##name##_transition(AVFilterContext *ctx, \
164 const AVFrame *a, const AVFrame *b, AVFrame *out, \
166 int slice_start, int slice_end, int jobnr) \
168 XFadeContext *s = ctx->priv; \
169 const int height = slice_end - slice_start; \
171 double values[VAR_VARS_NB]; \
172 values[VAR_W] = out->width; \
173 values[VAR_H] = out->height; \
174 values[VAR_PROGRESS] = progress; \
176 for (int p = 0; p < s->nb_planes; p++) { \
177 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
178 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
179 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
181 values[VAR_PLANE] = p; \
183 for (int y = 0; y < height; y++) { \
184 values[VAR_Y] = slice_start + y; \
185 for (int x = 0; x < out->width; x++) { \
187 values[VAR_A] = xf0[x]; \
188 values[VAR_B] = xf1[x]; \
189 dst[x] = av_expr_eval(s->e, values, s); \
192 dst += out->linesize[p] / div; \
193 xf0 += a->linesize[p] / div; \
194 xf1 += b->linesize[p] / div; \
199 CUSTOM_TRANSITION(8, uint8_t, 1)
200 CUSTOM_TRANSITION(16, uint16_t, 2)
202 static inline float mix(float a, float b, float mix)
204 return a * mix + b * (1.f - mix);
207 static inline float smoothstep(float edge0, float edge1, float x)
211 t = av_clipf((x - edge0) / (edge1 - edge0), 0.f, 1.f);
213 return t * t * (3.f - 2.f * t);
216 #define FADE_TRANSITION(name, type, div) \
217 static void fade##name##_transition(AVFilterContext *ctx, \
218 const AVFrame *a, const AVFrame *b, AVFrame *out, \
220 int slice_start, int slice_end, int jobnr) \
222 XFadeContext *s = ctx->priv; \
223 const int height = slice_end - slice_start; \
225 for (int p = 0; p < s->nb_planes; p++) { \
226 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
227 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
228 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
230 for (int y = 0; y < height; y++) { \
231 for (int x = 0; x < out->width; x++) { \
232 dst[x] = mix(xf0[x], xf1[x], progress); \
235 dst += out->linesize[p] / div; \
236 xf0 += a->linesize[p] / div; \
237 xf1 += b->linesize[p] / div; \
242 FADE_TRANSITION(8, uint8_t, 1)
243 FADE_TRANSITION(16, uint16_t, 2)
245 #define WIPELEFT_TRANSITION(name, type, div) \
246 static void wipeleft##name##_transition(AVFilterContext *ctx, \
247 const AVFrame *a, const AVFrame *b, AVFrame *out, \
249 int slice_start, int slice_end, int jobnr) \
251 XFadeContext *s = ctx->priv; \
252 const int height = slice_end - slice_start; \
253 const int z = out->width * progress; \
255 for (int p = 0; p < s->nb_planes; p++) { \
256 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
257 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
258 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
260 for (int y = 0; y < height; y++) { \
261 for (int x = 0; x < out->width; x++) { \
262 dst[x] = x > z ? xf1[x] : xf0[x]; \
265 dst += out->linesize[p] / div; \
266 xf0 += a->linesize[p] / div; \
267 xf1 += b->linesize[p] / div; \
272 WIPELEFT_TRANSITION(8, uint8_t, 1)
273 WIPELEFT_TRANSITION(16, uint16_t, 2)
275 #define WIPERIGHT_TRANSITION(name, type, div) \
276 static void wiperight##name##_transition(AVFilterContext *ctx, \
277 const AVFrame *a, const AVFrame *b, AVFrame *out, \
279 int slice_start, int slice_end, int jobnr) \
281 XFadeContext *s = ctx->priv; \
282 const int height = slice_end - slice_start; \
283 const int z = out->width * (1.f - progress); \
285 for (int p = 0; p < s->nb_planes; p++) { \
286 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
287 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
288 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
290 for (int y = 0; y < height; y++) { \
291 for (int x = 0; x < out->width; x++) { \
292 dst[x] = x > z ? xf0[x] : xf1[x]; \
295 dst += out->linesize[p] / div; \
296 xf0 += a->linesize[p] / div; \
297 xf1 += b->linesize[p] / div; \
302 WIPERIGHT_TRANSITION(8, uint8_t, 1)
303 WIPERIGHT_TRANSITION(16, uint16_t, 2)
305 #define WIPEUP_TRANSITION(name, type, div) \
306 static void wipeup##name##_transition(AVFilterContext *ctx, \
307 const AVFrame *a, const AVFrame *b, AVFrame *out, \
309 int slice_start, int slice_end, int jobnr) \
311 XFadeContext *s = ctx->priv; \
312 const int height = slice_end - slice_start; \
313 const int z = out->height * progress; \
315 for (int p = 0; p < s->nb_planes; p++) { \
316 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
317 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
318 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
320 for (int y = 0; y < height; y++) { \
321 for (int x = 0; x < out->width; x++) { \
322 dst[x] = slice_start + y > z ? xf1[x] : xf0[x]; \
325 dst += out->linesize[p] / div; \
326 xf0 += a->linesize[p] / div; \
327 xf1 += b->linesize[p] / div; \
332 WIPEUP_TRANSITION(8, uint8_t, 1)
333 WIPEUP_TRANSITION(16, uint16_t, 2)
335 #define WIPEDOWN_TRANSITION(name, type, div) \
336 static void wipedown##name##_transition(AVFilterContext *ctx, \
337 const AVFrame *a, const AVFrame *b, AVFrame *out, \
339 int slice_start, int slice_end, int jobnr) \
341 XFadeContext *s = ctx->priv; \
342 const int height = slice_end - slice_start; \
343 const int z = out->height * (1.f - progress); \
345 for (int p = 0; p < s->nb_planes; p++) { \
346 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
347 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
348 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
350 for (int y = 0; y < height; y++) { \
351 for (int x = 0; x < out->width; x++) { \
352 dst[x] = slice_start + y > z ? xf0[x] : xf1[x]; \
355 dst += out->linesize[p] / div; \
356 xf0 += a->linesize[p] / div; \
357 xf1 += b->linesize[p] / div; \
362 WIPEDOWN_TRANSITION(8, uint8_t, 1)
363 WIPEDOWN_TRANSITION(16, uint16_t, 2)
365 #define SLIDELEFT_TRANSITION(name, type, div) \
366 static void slideleft##name##_transition(AVFilterContext *ctx, \
367 const AVFrame *a, const AVFrame *b, AVFrame *out, \
369 int slice_start, int slice_end, int jobnr) \
371 XFadeContext *s = ctx->priv; \
372 const int height = slice_end - slice_start; \
373 const int width = out->width; \
374 const int z = -progress * width; \
376 for (int p = 0; p < s->nb_planes; p++) { \
377 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
378 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
379 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
381 for (int y = 0; y < height; y++) { \
382 for (int x = 0; x < width; x++) { \
383 const int zx = z + x; \
384 const int zz = zx % width + width * (zx < 0); \
385 dst[x] = (zx > 0) && (zx < width) ? xf1[zz] : xf0[zz]; \
388 dst += out->linesize[p] / div; \
389 xf0 += a->linesize[p] / div; \
390 xf1 += b->linesize[p] / div; \
395 SLIDELEFT_TRANSITION(8, uint8_t, 1)
396 SLIDELEFT_TRANSITION(16, uint16_t, 2)
398 #define SLIDERIGHT_TRANSITION(name, type, div) \
399 static void slideright##name##_transition(AVFilterContext *ctx, \
400 const AVFrame *a, const AVFrame *b, AVFrame *out, \
402 int slice_start, int slice_end, int jobnr) \
404 XFadeContext *s = ctx->priv; \
405 const int height = slice_end - slice_start; \
406 const int width = out->width; \
407 const int z = progress * width; \
409 for (int p = 0; p < s->nb_planes; p++) { \
410 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
411 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
412 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
414 for (int y = 0; y < height; y++) { \
415 for (int x = 0; x < out->width; x++) { \
416 const int zx = z + x; \
417 const int zz = zx % width + width * (zx < 0); \
418 dst[x] = (zx > 0) && (zx < width) ? xf1[zz] : xf0[zz]; \
421 dst += out->linesize[p] / div; \
422 xf0 += a->linesize[p] / div; \
423 xf1 += b->linesize[p] / div; \
428 SLIDERIGHT_TRANSITION(8, uint8_t, 1)
429 SLIDERIGHT_TRANSITION(16, uint16_t, 2)
431 #define SLIDEUP_TRANSITION(name, type, div) \
432 static void slideup##name##_transition(AVFilterContext *ctx, \
433 const AVFrame *a, const AVFrame *b, AVFrame *out, \
435 int slice_start, int slice_end, int jobnr) \
437 XFadeContext *s = ctx->priv; \
438 const int height = out->height; \
439 const int z = -progress * height; \
441 for (int p = 0; p < s->nb_planes; p++) { \
442 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
444 for (int y = slice_start; y < slice_end; y++) { \
445 const int zy = z + y; \
446 const int zz = zy % height + height * (zy < 0); \
447 const type *xf0 = (const type *)(a->data[p] + zz * a->linesize[p]); \
448 const type *xf1 = (const type *)(b->data[p] + zz * b->linesize[p]); \
450 for (int x = 0; x < out->width; x++) { \
451 dst[x] = (zy > 0) && (zy < height) ? xf1[x] : xf0[x]; \
454 dst += out->linesize[p] / div; \
459 SLIDEUP_TRANSITION(8, uint8_t, 1)
460 SLIDEUP_TRANSITION(16, uint16_t, 2)
462 #define SLIDEDOWN_TRANSITION(name, type, div) \
463 static void slidedown##name##_transition(AVFilterContext *ctx, \
464 const AVFrame *a, const AVFrame *b, AVFrame *out, \
466 int slice_start, int slice_end, int jobnr) \
468 XFadeContext *s = ctx->priv; \
469 const int height = out->height; \
470 const int z = progress * height; \
472 for (int p = 0; p < s->nb_planes; p++) { \
473 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
475 for (int y = slice_start; y < slice_end; y++) { \
476 const int zy = z + y; \
477 const int zz = zy % height + height * (zy < 0); \
478 const type *xf0 = (const type *)(a->data[p] + zz * a->linesize[p]); \
479 const type *xf1 = (const type *)(b->data[p] + zz * b->linesize[p]); \
481 for (int x = 0; x < out->width; x++) { \
482 dst[x] = (zy > 0) && (zy < height) ? xf1[x] : xf0[x]; \
485 dst += out->linesize[p] / div; \
490 SLIDEDOWN_TRANSITION(8, uint8_t, 1)
491 SLIDEDOWN_TRANSITION(16, uint16_t, 2)
493 #define CIRCLECROP_TRANSITION(name, type, div) \
494 static void circlecrop##name##_transition(AVFilterContext *ctx, \
495 const AVFrame *a, const AVFrame *b, AVFrame *out, \
497 int slice_start, int slice_end, int jobnr) \
499 XFadeContext *s = ctx->priv; \
500 const int width = out->width; \
501 const int height = out->height; \
502 float z = powf(2.f * fabsf(progress - 0.5f), 3.f) * hypotf(width/2, height/2); \
504 for (int p = 0; p < s->nb_planes; p++) { \
505 const int bg = s->black[p]; \
506 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
508 for (int y = slice_start; y < slice_end; y++) { \
509 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
510 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
512 for (int x = 0; x < width; x++) { \
513 float dist = hypotf(x - width / 2, y - height / 2); \
514 int val = progress < 0.5f ? xf1[x] : xf0[x]; \
515 dst[x] = (z < dist) ? bg : val; \
518 dst += out->linesize[p] / div; \
523 CIRCLECROP_TRANSITION(8, uint8_t, 1)
524 CIRCLECROP_TRANSITION(16, uint16_t, 2)
526 #define RECTCROP_TRANSITION(name, type, div) \
527 static void rectcrop##name##_transition(AVFilterContext *ctx, \
528 const AVFrame *a, const AVFrame *b, AVFrame *out, \
530 int slice_start, int slice_end, int jobnr) \
532 XFadeContext *s = ctx->priv; \
533 const int width = out->width; \
534 const int height = out->height; \
535 int zh = fabsf(progress - 0.5f) * height; \
536 int zw = fabsf(progress - 0.5f) * width; \
538 for (int p = 0; p < s->nb_planes; p++) { \
539 const int bg = s->black[p]; \
540 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
542 for (int y = slice_start; y < slice_end; y++) { \
543 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
544 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
546 for (int x = 0; x < width; x++) { \
547 int dist = FFABS(x - width / 2) < zw && \
548 FFABS(y - height / 2) < zh; \
549 int val = progress < 0.5f ? xf1[x] : xf0[x]; \
550 dst[x] = !dist ? bg : val; \
553 dst += out->linesize[p] / div; \
558 RECTCROP_TRANSITION(8, uint8_t, 1)
559 RECTCROP_TRANSITION(16, uint16_t, 2)
561 #define DISTANCE_TRANSITION(name, type, div) \
562 static void distance##name##_transition(AVFilterContext *ctx, \
563 const AVFrame *a, const AVFrame *b, AVFrame *out, \
565 int slice_start, int slice_end, int jobnr) \
567 XFadeContext *s = ctx->priv; \
568 const int width = out->width; \
569 const float max = s->max_value; \
571 for (int y = slice_start; y < slice_end; y++) { \
572 for (int x = 0; x < width; x++) { \
574 for (int p = 0; p < s->nb_planes; p++) { \
575 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
576 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
578 dist += (xf0[x] / max - xf1[x] / max) * \
579 (xf0[x] / max - xf1[x] / max); \
582 dist = sqrtf(dist) <= progress; \
583 for (int p = 0; p < s->nb_planes; p++) { \
584 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
585 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
586 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
587 dst[x] = mix(mix(xf0[x], xf1[x], dist), xf1[x], progress); \
593 DISTANCE_TRANSITION(8, uint8_t, 1)
594 DISTANCE_TRANSITION(16, uint16_t, 2)
596 #define FADEBLACK_TRANSITION(name, type, div) \
597 static void fadeblack##name##_transition(AVFilterContext *ctx, \
598 const AVFrame *a, const AVFrame *b, AVFrame *out, \
600 int slice_start, int slice_end, int jobnr) \
602 XFadeContext *s = ctx->priv; \
603 const int height = slice_end - slice_start; \
604 const float phase = 0.2f; \
606 for (int p = 0; p < s->nb_planes; p++) { \
607 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
608 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
609 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
610 const int bg = s->black[p]; \
612 for (int y = 0; y < height; y++) { \
613 for (int x = 0; x < out->width; x++) { \
614 dst[x] = mix(mix(xf0[x], bg, smoothstep(1.f-phase, 1.f, progress)), \
615 mix(bg, xf1[x], smoothstep(phase, 1.f, progress)), \
619 dst += out->linesize[p] / div; \
620 xf0 += a->linesize[p] / div; \
621 xf1 += b->linesize[p] / div; \
626 FADEBLACK_TRANSITION(8, uint8_t, 1)
627 FADEBLACK_TRANSITION(16, uint16_t, 2)
629 #define FADEWHITE_TRANSITION(name, type, div) \
630 static void fadewhite##name##_transition(AVFilterContext *ctx, \
631 const AVFrame *a, const AVFrame *b, AVFrame *out, \
633 int slice_start, int slice_end, int jobnr) \
635 XFadeContext *s = ctx->priv; \
636 const int height = slice_end - slice_start; \
637 const float phase = 0.2f; \
639 for (int p = 0; p < s->nb_planes; p++) { \
640 const type *xf0 = (const type *)(a->data[p] + slice_start * a->linesize[p]); \
641 const type *xf1 = (const type *)(b->data[p] + slice_start * b->linesize[p]); \
642 type *dst = (type *)(out->data[p] + slice_start * out->linesize[p]); \
643 const int bg = s->white[p]; \
645 for (int y = 0; y < height; y++) { \
646 for (int x = 0; x < out->width; x++) { \
647 dst[x] = mix(mix(xf0[x], bg, smoothstep(1.f-phase, 1.f, progress)), \
648 mix(bg, xf1[x], smoothstep(phase, 1.f, progress)), \
652 dst += out->linesize[p] / div; \
653 xf0 += a->linesize[p] / div; \
654 xf1 += b->linesize[p] / div; \
659 FADEWHITE_TRANSITION(8, uint8_t, 1)
660 FADEWHITE_TRANSITION(16, uint16_t, 2)
662 #define RADIAL_TRANSITION(name, type, div) \
663 static void radial##name##_transition(AVFilterContext *ctx, \
664 const AVFrame *a, const AVFrame *b, AVFrame *out, \
666 int slice_start, int slice_end, int jobnr) \
668 XFadeContext *s = ctx->priv; \
669 const int width = out->width; \
670 const int height = out->height; \
672 for (int y = slice_start; y < slice_end; y++) { \
673 for (int x = 0; x < width; x++) { \
674 const float smooth = atan2f(x - width / 2, y - height / 2) - \
675 (progress - 0.5f) * (M_PI * 2.5f); \
676 for (int p = 0; p < s->nb_planes; p++) { \
677 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
678 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
679 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
681 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
687 RADIAL_TRANSITION(8, uint8_t, 1)
688 RADIAL_TRANSITION(16, uint16_t, 2)
690 #define SMOOTHLEFT_TRANSITION(name, type, div) \
691 static void smoothleft##name##_transition(AVFilterContext *ctx, \
692 const AVFrame *a, const AVFrame *b, AVFrame *out, \
694 int slice_start, int slice_end, int jobnr) \
696 XFadeContext *s = ctx->priv; \
697 const int width = out->width; \
698 const float w = width; \
700 for (int y = slice_start; y < slice_end; y++) { \
701 for (int x = 0; x < width; x++) { \
702 const float smooth = 1.f + x / w - progress * 2.f; \
704 for (int p = 0; p < s->nb_planes; p++) { \
705 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
706 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
707 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
709 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
715 SMOOTHLEFT_TRANSITION(8, uint8_t, 1)
716 SMOOTHLEFT_TRANSITION(16, uint16_t, 2)
718 #define SMOOTHRIGHT_TRANSITION(name, type, div) \
719 static void smoothright##name##_transition(AVFilterContext *ctx, \
720 const AVFrame *a, const AVFrame *b, AVFrame *out, \
722 int slice_start, int slice_end, int jobnr) \
724 XFadeContext *s = ctx->priv; \
725 const int width = out->width; \
726 const float w = width; \
728 for (int y = slice_start; y < slice_end; y++) { \
729 for (int x = 0; x < width; x++) { \
730 const float smooth = 1.f + (w - 1 - x) / w - progress * 2.f; \
732 for (int p = 0; p < s->nb_planes; p++) { \
733 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
734 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
735 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
737 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
743 SMOOTHRIGHT_TRANSITION(8, uint8_t, 1)
744 SMOOTHRIGHT_TRANSITION(16, uint16_t, 2)
746 #define SMOOTHUP_TRANSITION(name, type, div) \
747 static void smoothup##name##_transition(AVFilterContext *ctx, \
748 const AVFrame *a, const AVFrame *b, AVFrame *out, \
750 int slice_start, int slice_end, int jobnr) \
752 XFadeContext *s = ctx->priv; \
753 const int width = out->width; \
754 const float h = out->height; \
756 for (int y = slice_start; y < slice_end; y++) { \
757 const float smooth = 1.f + y / h - progress * 2.f; \
758 for (int x = 0; x < width; x++) { \
759 for (int p = 0; p < s->nb_planes; p++) { \
760 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
761 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
762 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
764 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
770 SMOOTHUP_TRANSITION(8, uint8_t, 1)
771 SMOOTHUP_TRANSITION(16, uint16_t, 2)
773 #define SMOOTHDOWN_TRANSITION(name, type, div) \
774 static void smoothdown##name##_transition(AVFilterContext *ctx, \
775 const AVFrame *a, const AVFrame *b, AVFrame *out, \
777 int slice_start, int slice_end, int jobnr) \
779 XFadeContext *s = ctx->priv; \
780 const int width = out->width; \
781 const float h = out->height; \
783 for (int y = slice_start; y < slice_end; y++) { \
784 const float smooth = 1.f + (h - 1 - y) / h - progress * 2.f; \
785 for (int x = 0; x < width; x++) { \
786 for (int p = 0; p < s->nb_planes; p++) { \
787 const type *xf0 = (const type *)(a->data[p] + y * a->linesize[p]); \
788 const type *xf1 = (const type *)(b->data[p] + y * b->linesize[p]); \
789 type *dst = (type *)(out->data[p] + y * out->linesize[p]); \
791 dst[x] = mix(xf1[x], xf0[x], smoothstep(0.f, 1.f, smooth)); \
797 SMOOTHDOWN_TRANSITION(8, uint8_t, 1)
798 SMOOTHDOWN_TRANSITION(16, uint16_t, 2)
800 static inline double getpix(void *priv, double x, double y, int plane, int nb)
802 XFadeContext *s = priv;
803 AVFrame *in = s->xf[nb];
804 const uint8_t *src = in->data[FFMIN(plane, s->nb_planes - 1)];
805 int linesize = in->linesize[FFMIN(plane, s->nb_planes - 1)];
806 const int w = in->width;
807 const int h = in->height;
811 xi = av_clipd(x, 0, w - 1);
812 yi = av_clipd(y, 0, h - 1);
815 const uint16_t *src16 = (const uint16_t*)src;
818 return src16[xi + yi * linesize];
820 return src[xi + yi * linesize];
824 static double a0(void *priv, double x, double y) { return getpix(priv, x, y, 0, 0); }
825 static double a1(void *priv, double x, double y) { return getpix(priv, x, y, 1, 0); }
826 static double a2(void *priv, double x, double y) { return getpix(priv, x, y, 2, 0); }
827 static double a3(void *priv, double x, double y) { return getpix(priv, x, y, 3, 0); }
829 static double b0(void *priv, double x, double y) { return getpix(priv, x, y, 0, 1); }
830 static double b1(void *priv, double x, double y) { return getpix(priv, x, y, 1, 1); }
831 static double b2(void *priv, double x, double y) { return getpix(priv, x, y, 2, 1); }
832 static double b3(void *priv, double x, double y) { return getpix(priv, x, y, 3, 1); }
834 static int config_output(AVFilterLink *outlink)
836 AVFilterContext *ctx = outlink->src;
837 AVFilterLink *inlink0 = ctx->inputs[0];
838 AVFilterLink *inlink1 = ctx->inputs[1];
839 XFadeContext *s = ctx->priv;
840 const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(inlink0->format);
843 if (inlink0->format != inlink1->format) {
844 av_log(ctx, AV_LOG_ERROR, "inputs must be of same pixel format\n");
845 return AVERROR(EINVAL);
847 if (inlink0->w != inlink1->w || inlink0->h != inlink1->h) {
848 av_log(ctx, AV_LOG_ERROR, "First input link %s parameters "
849 "(size %dx%d) do not match the corresponding "
850 "second input link %s parameters (size %dx%d)\n",
851 ctx->input_pads[0].name, inlink0->w, inlink0->h,
852 ctx->input_pads[1].name, inlink1->w, inlink1->h);
853 return AVERROR(EINVAL);
856 if (inlink0->time_base.num != inlink1->time_base.num ||
857 inlink0->time_base.den != inlink1->time_base.den) {
858 av_log(ctx, AV_LOG_ERROR, "First input link %s timebase "
859 "(%d/%d) do not match the corresponding "
860 "second input link %s timebase (%d/%d)\n",
861 ctx->input_pads[0].name, inlink0->time_base.num, inlink0->time_base.den,
862 ctx->input_pads[1].name, inlink1->time_base.num, inlink1->time_base.den);
863 return AVERROR(EINVAL);
866 outlink->w = inlink0->w;
867 outlink->h = inlink0->h;
868 outlink->time_base = inlink0->time_base;
869 outlink->sample_aspect_ratio = inlink0->sample_aspect_ratio;
870 outlink->frame_rate = inlink0->frame_rate;
872 s->depth = pix_desc->comp[0].depth;
873 is_rgb = !!(pix_desc->flags & AV_PIX_FMT_FLAG_RGB);
874 s->nb_planes = av_pix_fmt_count_planes(inlink0->format);
875 s->max_value = (1 << s->depth) - 1;
877 s->black[1] = s->black[2] = is_rgb ? 0 : s->max_value / 2;
878 s->black[3] = s->max_value;
879 s->white[0] = s->white[3] = s->max_value;
880 s->white[1] = s->white[2] = is_rgb ? s->max_value : s->max_value / 2;
882 s->first_pts = s->last_pts = s->pts = AV_NOPTS_VALUE;
885 s->duration_pts = av_rescale_q(s->duration, AV_TIME_BASE_Q, outlink->time_base);
887 s->offset_pts = av_rescale_q(s->offset, AV_TIME_BASE_Q, outlink->time_base);
889 switch (s->transition) {
890 case CUSTOM: s->transitionf = s->depth <= 8 ? custom8_transition : custom16_transition; break;
891 case FADE: s->transitionf = s->depth <= 8 ? fade8_transition : fade16_transition; break;
892 case WIPELEFT: s->transitionf = s->depth <= 8 ? wipeleft8_transition : wipeleft16_transition; break;
893 case WIPERIGHT: s->transitionf = s->depth <= 8 ? wiperight8_transition : wiperight16_transition; break;
894 case WIPEUP: s->transitionf = s->depth <= 8 ? wipeup8_transition : wipeup16_transition; break;
895 case WIPEDOWN: s->transitionf = s->depth <= 8 ? wipedown8_transition : wipedown16_transition; break;
896 case SLIDELEFT: s->transitionf = s->depth <= 8 ? slideleft8_transition : slideleft16_transition; break;
897 case SLIDERIGHT: s->transitionf = s->depth <= 8 ? slideright8_transition : slideright16_transition; break;
898 case SLIDEUP: s->transitionf = s->depth <= 8 ? slideup8_transition : slideup16_transition; break;
899 case SLIDEDOWN: s->transitionf = s->depth <= 8 ? slidedown8_transition : slidedown16_transition; break;
900 case CIRCLECROP: s->transitionf = s->depth <= 8 ? circlecrop8_transition : circlecrop16_transition; break;
901 case RECTCROP: s->transitionf = s->depth <= 8 ? rectcrop8_transition : rectcrop16_transition; break;
902 case DISTANCE: s->transitionf = s->depth <= 8 ? distance8_transition : distance16_transition; break;
903 case FADEBLACK: s->transitionf = s->depth <= 8 ? fadeblack8_transition : fadeblack16_transition; break;
904 case FADEWHITE: s->transitionf = s->depth <= 8 ? fadewhite8_transition : fadewhite16_transition; break;
905 case RADIAL: s->transitionf = s->depth <= 8 ? radial8_transition : radial16_transition; break;
906 case SMOOTHLEFT: s->transitionf = s->depth <= 8 ? smoothleft8_transition : smoothleft16_transition; break;
907 case SMOOTHRIGHT:s->transitionf = s->depth <= 8 ? smoothright8_transition: smoothright16_transition;break;
908 case SMOOTHUP: s->transitionf = s->depth <= 8 ? smoothup8_transition : smoothup16_transition; break;
909 case SMOOTHDOWN: s->transitionf = s->depth <= 8 ? smoothdown8_transition : smoothdown16_transition; break;
912 if (s->transition == CUSTOM) {
913 static const char *const func2_names[] = {
914 "a0", "a1", "a2", "a3",
915 "b0", "b1", "b2", "b3",
918 double (*func2[])(void *, double, double) = {
925 return AVERROR(EINVAL);
926 ret = av_expr_parse(&s->e, s->custom_str, var_names,
927 NULL, NULL, func2_names, func2, 0, ctx);
935 static int xfade_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
937 XFadeContext *s = ctx->priv;
938 AVFilterLink *outlink = ctx->outputs[0];
939 ThreadData *td = arg;
940 int slice_start = (outlink->h * jobnr ) / nb_jobs;
941 int slice_end = (outlink->h * (jobnr+1)) / nb_jobs;
943 s->transitionf(ctx, td->xf[0], td->xf[1], td->out, td->progress, slice_start, slice_end, jobnr);
948 static int xfade_frame(AVFilterContext *ctx, AVFrame *a, AVFrame *b)
950 XFadeContext *s = ctx->priv;
951 AVFilterLink *outlink = ctx->outputs[0];
952 float progress = av_clipf(1.f - ((float)(s->pts - s->first_pts - s->offset_pts) / s->duration_pts), 0.f, 1.f);
956 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
958 return AVERROR(ENOMEM);
960 td.xf[0] = a, td.xf[1] = b, td.out = out, td.progress = progress;
961 ctx->internal->execute(ctx, xfade_slice, &td, NULL, FFMIN(outlink->h, ff_filter_get_nb_threads(ctx)));
965 return ff_filter_frame(outlink, out);
968 static int xfade_activate(AVFilterContext *ctx)
970 XFadeContext *s = ctx->priv;
971 AVFilterLink *outlink = ctx->outputs[0];
976 FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, ctx);
978 if (s->xfade_is_over) {
979 ret = ff_inlink_consume_frame(ctx->inputs[1], &in);
982 } else if (ff_inlink_acknowledge_status(ctx->inputs[1], &status, &pts)) {
983 ff_outlink_set_status(outlink, status, s->pts);
986 if (ff_outlink_frame_wanted(outlink)) {
987 ff_inlink_request_frame(ctx->inputs[1]);
991 in->pts = (in->pts - s->last_pts) + s->pts;
992 return ff_filter_frame(outlink, in);
996 if (ff_inlink_queued_frames(ctx->inputs[0]) > 0) {
997 s->xf[0] = ff_inlink_peek_frame(ctx->inputs[0], 0);
999 if (s->first_pts == AV_NOPTS_VALUE) {
1000 s->first_pts = s->xf[0]->pts;
1002 s->pts = s->xf[0]->pts;
1003 if (s->first_pts + s->offset_pts > s->xf[0]->pts) {
1006 ff_inlink_consume_frame(ctx->inputs[0], &in);
1007 return ff_filter_frame(outlink, in);
1014 if (s->xf[0] && ff_inlink_queued_frames(ctx->inputs[1]) > 0) {
1015 ff_inlink_consume_frame(ctx->inputs[0], &s->xf[0]);
1016 ff_inlink_consume_frame(ctx->inputs[1], &s->xf[1]);
1018 s->last_pts = s->xf[1]->pts;
1019 s->pts = s->xf[0]->pts;
1020 if (s->xf[0]->pts - (s->first_pts + s->offset_pts) > s->duration_pts)
1021 s->xfade_is_over = 1;
1022 ret = xfade_frame(ctx, s->xf[0], s->xf[1]);
1023 av_frame_free(&s->xf[0]);
1024 av_frame_free(&s->xf[1]);
1028 if (ff_inlink_queued_frames(ctx->inputs[0]) > 0 &&
1029 ff_inlink_queued_frames(ctx->inputs[1]) > 0) {
1030 ff_filter_set_ready(ctx, 100);
1034 if (ff_outlink_frame_wanted(outlink)) {
1035 if (!s->eof[0] && ff_outlink_get_status(ctx->inputs[0])) {
1037 s->xfade_is_over = 1;
1039 if (!s->eof[1] && ff_outlink_get_status(ctx->inputs[1])) {
1042 if (!s->eof[0] && !s->xf[0])
1043 ff_inlink_request_frame(ctx->inputs[0]);
1044 if (!s->eof[1] && (s->need_second || s->eof[0]))
1045 ff_inlink_request_frame(ctx->inputs[1]);
1046 if (s->eof[0] && s->eof[1] && (
1047 ff_inlink_queued_frames(ctx->inputs[0]) <= 0 ||
1048 ff_inlink_queued_frames(ctx->inputs[1]) <= 0))
1049 ff_outlink_set_status(outlink, AVERROR_EOF, AV_NOPTS_VALUE);
1053 return FFERROR_NOT_READY;
1056 static const AVFilterPad xfade_inputs[] = {
1059 .type = AVMEDIA_TYPE_VIDEO,
1063 .type = AVMEDIA_TYPE_VIDEO,
1068 static const AVFilterPad xfade_outputs[] = {
1071 .type = AVMEDIA_TYPE_VIDEO,
1072 .config_props = config_output,
1077 AVFilter ff_vf_xfade = {
1079 .description = NULL_IF_CONFIG_SMALL("Cross fade one video with another video."),
1080 .priv_size = sizeof(XFadeContext),
1081 .priv_class = &xfade_class,
1082 .query_formats = query_formats,
1083 .activate = xfade_activate,
1085 .inputs = xfade_inputs,
1086 .outputs = xfade_outputs,
1087 .flags = AVFILTER_FLAG_SLICE_THREADS,