mirror of https://git.ffmpeg.org/ffmpeg.git
721 lines
32 KiB
C
721 lines
32 KiB
C
/*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "libavutil/avassert.h"
|
|
#include "libavutil/random_seed.h"
|
|
#include "libavutil/opt.h"
|
|
#include "vulkan_filter.h"
|
|
#include "vulkan_spirv.h"
|
|
#include "filters.h"
|
|
#include "internal.h"
|
|
#include "video.h"
|
|
|
|
#define IN_A 0
|
|
#define IN_B 1
|
|
#define IN_NB 2
|
|
|
|
typedef struct XFadeParameters {
|
|
float progress;
|
|
} XFadeParameters;
|
|
|
|
typedef struct XFadeVulkanContext {
|
|
FFVulkanContext vkctx;
|
|
|
|
int transition;
|
|
int64_t duration;
|
|
int64_t offset;
|
|
|
|
int initialized;
|
|
FFVulkanPipeline pl;
|
|
FFVkExecPool e;
|
|
FFVkQueueFamilyCtx qf;
|
|
FFVkSPIRVShader shd;
|
|
VkSampler sampler;
|
|
|
|
// PTS when the fade should start (in IN_A timebase)
|
|
int64_t start_pts;
|
|
|
|
// PTS offset between IN_A and IN_B
|
|
int64_t inputs_offset_pts;
|
|
|
|
// Duration of the transition
|
|
int64_t duration_pts;
|
|
|
|
// Current PTS of the first input (IN_A)
|
|
int64_t pts;
|
|
|
|
// If frames are currently just passed through
|
|
// unmodified, like before and after the actual
|
|
// transition.
|
|
int passthrough;
|
|
|
|
int status[IN_NB];
|
|
} XFadeVulkanContext;
|
|
|
|
enum XFadeTransitions {
|
|
FADE,
|
|
WIPELEFT,
|
|
WIPERIGHT,
|
|
WIPEUP,
|
|
WIPEDOWN,
|
|
SLIDEDOWN,
|
|
SLIDEUP,
|
|
SLIDELEFT,
|
|
SLIDERIGHT,
|
|
CIRCLEOPEN,
|
|
CIRCLECLOSE,
|
|
DISSOLVE,
|
|
PIXELIZE,
|
|
WIPETL,
|
|
WIPETR,
|
|
WIPEBL,
|
|
WIPEBR,
|
|
NB_TRANSITIONS,
|
|
};
|
|
|
|
static const char transition_fade[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, mix(a, b, progress)); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_wipeleft[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, int s = int(size.x * (1.0 - progress)); )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, pos.x > s ? b : a); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_wiperight[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, int s = int(size.x * progress); )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, pos.x > s ? a : b); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_wipeup[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, int s = int(size.y * (1.0 - progress)); )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, pos.y > s ? b : a); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_wipedown[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, int s = int(size.y * progress); )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, pos.y > s ? a : b); )
|
|
C(0, } )
|
|
};
|
|
|
|
#define SHADER_SLIDE_COMMON \
|
|
C(0, void slide(int idx, ivec2 pos, float progress, ivec2 direction) ) \
|
|
C(0, { ) \
|
|
C(1, ivec2 size = imageSize(output_images[idx]); ) \
|
|
C(1, ivec2 pi = ivec2(progress * size); ) \
|
|
C(1, ivec2 p = pos + pi * direction; ) \
|
|
C(1, ivec2 f = p % size; ) \
|
|
C(1, f = f + size * ivec2(f.x < 0, f.y < 0); ) \
|
|
C(1, vec4 a = texture(a_images[idx], f); ) \
|
|
C(1, vec4 b = texture(b_images[idx], f); ) \
|
|
C(1, vec4 r = (p.y >= 0 && p.x >= 0 && size.y > p.y && size.x > p.x) ? a : b; ) \
|
|
C(1, imageStore(output_images[idx], pos, r); ) \
|
|
C(0, } )
|
|
|
|
static const char transition_slidedown[] = {
|
|
SHADER_SLIDE_COMMON
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, slide(idx, pos, progress, ivec2(0, -1)); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_slideup[] = {
|
|
SHADER_SLIDE_COMMON
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, slide(idx, pos, progress, ivec2(0, +1)); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_slideleft[] = {
|
|
SHADER_SLIDE_COMMON
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, slide(idx, pos, progress, ivec2(+1, 0)); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_slideright[] = {
|
|
SHADER_SLIDE_COMMON
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, slide(idx, pos, progress, ivec2(-1, 0)); )
|
|
C(0, } )
|
|
};
|
|
|
|
#define SHADER_CIRCLE_COMMON \
|
|
C(0, void circle(int idx, ivec2 pos, float progress, bool open) ) \
|
|
C(0, { ) \
|
|
C(1, const ivec2 half_size = imageSize(output_images[idx]) / 2; ) \
|
|
C(1, const float z = dot(half_size, half_size); ) \
|
|
C(1, float p = ((open ? (1.0 - progress) : progress) - 0.5) * 3.0; ) \
|
|
C(1, ivec2 dsize = pos - half_size; ) \
|
|
C(1, float sm = dot(dsize, dsize) / z + p; ) \
|
|
C(1, vec4 a = texture(a_images[idx], pos); ) \
|
|
C(1, vec4 b = texture(b_images[idx], pos); ) \
|
|
C(1, imageStore(output_images[idx], pos, \
|
|
mix(open ? b : a, open ? a : b, \
|
|
smoothstep(0.f, 1.f, sm))); ) \
|
|
C(0, } )
|
|
|
|
static const char transition_circleopen[] = {
|
|
SHADER_CIRCLE_COMMON
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, circle(idx, pos, progress, true); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_circleclose[] = {
|
|
SHADER_CIRCLE_COMMON
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, circle(idx, pos, progress, false); )
|
|
C(0, } )
|
|
};
|
|
|
|
#define SHADER_FRAND_FUNC \
|
|
C(0, float frand(vec2 v) ) \
|
|
C(0, { ) \
|
|
C(1, return fract(sin(dot(v, vec2(12.9898, 78.233))) * 43758.545); ) \
|
|
C(0, } )
|
|
|
|
static const char transition_dissolve[] = {
|
|
SHADER_FRAND_FUNC
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, float sm = frand(pos) * 2.0 + (1.0 - progress) * 2.0 - 1.5; )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, sm >= 0.5 ? a : b); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_pixelize[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, float d = min(progress, 1.0 - progress); )
|
|
C(1, float dist = ceil(d * 50.0) / 50.0; )
|
|
C(1, float sq = 2.0 * dist * min(size.x, size.y) / 20.0; )
|
|
C(1, float sx = dist > 0.0 ? min((floor(pos.x / sq) + 0.5) * sq, size.x - 1) : pos.x; )
|
|
C(1, float sy = dist > 0.0 ? min((floor(pos.y / sq) + 0.5) * sq, size.y - 1) : pos.y; )
|
|
C(1, vec4 a = texture(a_images[idx], vec2(sx, sy)); )
|
|
C(1, vec4 b = texture(b_images[idx], vec2(sx, sy)); )
|
|
C(1, imageStore(output_images[idx], pos, mix(a, b, progress)); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_wipetl[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, float zw = size.x * (1.0 - progress); )
|
|
C(1, float zh = size.y * (1.0 - progress); )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, (pos.y <= zh && pos.x <= zw) ? a : b); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_wipetr[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, float zw = size.x * (progress); )
|
|
C(1, float zh = size.y * (1.0 - progress); )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, (pos.y <= zh && pos.x > zw) ? a : b); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_wipebl[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, float zw = size.x * (1.0 - progress); )
|
|
C(1, float zh = size.y * (progress); )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, (pos.y > zh && pos.x <= zw) ? a : b); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char transition_wipebr[] = {
|
|
C(0, void transition(int idx, ivec2 pos, float progress) )
|
|
C(0, { )
|
|
C(1, ivec2 size = imageSize(output_images[idx]); )
|
|
C(1, float zw = size.x * (progress); )
|
|
C(1, float zh = size.y * (progress); )
|
|
C(1, vec4 a = texture(a_images[idx], pos); )
|
|
C(1, vec4 b = texture(b_images[idx], pos); )
|
|
C(1, imageStore(output_images[idx], pos, (pos.y > zh && pos.x > zw) ? a : b); )
|
|
C(0, } )
|
|
};
|
|
|
|
static const char* transitions_map[NB_TRANSITIONS] = {
|
|
[FADE] = transition_fade,
|
|
[WIPELEFT] = transition_wipeleft,
|
|
[WIPERIGHT] = transition_wiperight,
|
|
[WIPEUP] = transition_wipeup,
|
|
[WIPEDOWN] = transition_wipedown,
|
|
[SLIDEDOWN] = transition_slidedown,
|
|
[SLIDEUP] = transition_slideup,
|
|
[SLIDELEFT] = transition_slideleft,
|
|
[SLIDERIGHT] = transition_slideright,
|
|
[CIRCLEOPEN] = transition_circleopen,
|
|
[CIRCLECLOSE] = transition_circleclose,
|
|
[DISSOLVE] = transition_dissolve,
|
|
[PIXELIZE] = transition_pixelize,
|
|
[WIPETL] = transition_wipetl,
|
|
[WIPETR] = transition_wipetr,
|
|
[WIPEBL] = transition_wipebl,
|
|
[WIPEBR] = transition_wipebr,
|
|
};
|
|
|
|
static av_cold int init_vulkan(AVFilterContext *avctx)
|
|
{
|
|
int err = 0;
|
|
uint8_t *spv_data;
|
|
size_t spv_len;
|
|
void *spv_opaque = NULL;
|
|
XFadeVulkanContext *s = avctx->priv;
|
|
FFVulkanContext *vkctx = &s->vkctx;
|
|
const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
|
|
FFVkSPIRVShader *shd = &s->shd;
|
|
FFVkSPIRVCompiler *spv;
|
|
FFVulkanDescriptorSetBinding *desc;
|
|
|
|
spv = ff_vk_spirv_init();
|
|
if (!spv) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n");
|
|
return AVERROR_EXTERNAL;
|
|
}
|
|
|
|
ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
|
|
RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
|
|
RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST));
|
|
RET(ff_vk_shader_init(&s->pl, &s->shd, "xfade_compute",
|
|
VK_SHADER_STAGE_COMPUTE_BIT, 0));
|
|
|
|
ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1);
|
|
|
|
desc = (FFVulkanDescriptorSetBinding []) {
|
|
{
|
|
.name = "a_images",
|
|
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
.dimensions = 2,
|
|
.elems = planes,
|
|
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
|
|
.samplers = DUP_SAMPLER(s->sampler),
|
|
},
|
|
{
|
|
.name = "b_images",
|
|
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
.dimensions = 2,
|
|
.elems = planes,
|
|
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
|
|
.samplers = DUP_SAMPLER(s->sampler),
|
|
},
|
|
{
|
|
.name = "output_images",
|
|
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
|
.mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format),
|
|
.mem_quali = "writeonly",
|
|
.dimensions = 2,
|
|
.elems = planes,
|
|
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
|
|
},
|
|
};
|
|
|
|
RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 3, 0, 0));
|
|
|
|
GLSLC(0, layout(push_constant, std430) uniform pushConstants { );
|
|
GLSLC(1, float progress; );
|
|
GLSLC(0, }; );
|
|
|
|
ff_vk_add_push_constant(&s->pl, 0, sizeof(XFadeParameters),
|
|
VK_SHADER_STAGE_COMPUTE_BIT);
|
|
|
|
// Add the right transition type function to the shader
|
|
GLSLD(transitions_map[s->transition]);
|
|
|
|
GLSLC(0, void main() );
|
|
GLSLC(0, { );
|
|
GLSLC(1, ivec2 pos = ivec2(gl_GlobalInvocationID.xy); );
|
|
GLSLF(1, int planes = %i; ,planes);
|
|
GLSLC(1, for (int i = 0; i < planes; i++) { );
|
|
GLSLC(2, transition(i, pos, progress); );
|
|
GLSLC(1, } );
|
|
GLSLC(0, } );
|
|
|
|
RET(spv->compile_shader(spv, avctx, shd, &spv_data, &spv_len, "main",
|
|
&spv_opaque));
|
|
RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
|
|
|
|
RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
|
|
RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
|
|
|
|
s->initialized = 1;
|
|
|
|
fail:
|
|
if (spv_opaque)
|
|
spv->free_shader(spv, &spv_opaque);
|
|
if (spv)
|
|
spv->uninit(&spv);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int xfade_frame(AVFilterContext *avctx, AVFrame *frame_a, AVFrame *frame_b)
|
|
{
|
|
int err;
|
|
AVFilterLink *outlink = avctx->outputs[0];
|
|
XFadeVulkanContext *s = avctx->priv;
|
|
float progress;
|
|
|
|
AVFrame *output = ff_get_video_buffer(outlink, outlink->w, outlink->h);
|
|
if (!output) {
|
|
err = AVERROR(ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
if (!s->initialized) {
|
|
AVHWFramesContext *a_fc = (AVHWFramesContext*)frame_a->hw_frames_ctx->data;
|
|
AVHWFramesContext *b_fc = (AVHWFramesContext*)frame_b->hw_frames_ctx->data;
|
|
if (a_fc->sw_format != b_fc->sw_format) {
|
|
av_log(avctx, AV_LOG_ERROR,
|
|
"Currently the sw format of the first input needs to match the second!\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
RET(init_vulkan(avctx));
|
|
}
|
|
|
|
RET(av_frame_copy_props(output, frame_a));
|
|
output->pts = s->pts;
|
|
|
|
progress = av_clipf((float)(s->pts - s->start_pts) / s->duration_pts,
|
|
0.f, 1.f);
|
|
|
|
RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl, output,
|
|
(AVFrame *[]){ frame_a, frame_b }, 2, s->sampler,
|
|
&(XFadeParameters){ progress }, sizeof(XFadeParameters)));
|
|
|
|
return ff_filter_frame(outlink, output);
|
|
|
|
fail:
|
|
av_frame_free(&output);
|
|
return err;
|
|
}
|
|
|
|
static int config_props_output(AVFilterLink *outlink)
|
|
{
|
|
int err;
|
|
AVFilterContext *avctx = outlink->src;
|
|
XFadeVulkanContext *s = avctx->priv;
|
|
AVFilterLink *inlink_a = avctx->inputs[IN_A];
|
|
AVFilterLink *inlink_b = avctx->inputs[IN_B];
|
|
|
|
if (inlink_a->w != inlink_b->w || inlink_a->h != inlink_b->h) {
|
|
av_log(avctx, AV_LOG_ERROR, "First input link %s parameters "
|
|
"(size %dx%d) do not match the corresponding "
|
|
"second input link %s parameters (size %dx%d)\n",
|
|
avctx->input_pads[IN_A].name, inlink_a->w, inlink_a->h,
|
|
avctx->input_pads[IN_B].name, inlink_b->w, inlink_b->h);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
if (inlink_a->time_base.num != inlink_b->time_base.num ||
|
|
inlink_a->time_base.den != inlink_b->time_base.den) {
|
|
av_log(avctx, AV_LOG_ERROR, "First input link %s timebase "
|
|
"(%d/%d) does not match the corresponding "
|
|
"second input link %s timebase (%d/%d)\n",
|
|
avctx->input_pads[IN_A].name, inlink_a->time_base.num, inlink_a->time_base.den,
|
|
avctx->input_pads[IN_B].name, inlink_b->time_base.num, inlink_b->time_base.den);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
s->start_pts = s->inputs_offset_pts = AV_NOPTS_VALUE;
|
|
|
|
outlink->time_base = inlink_a->time_base;
|
|
outlink->frame_rate = inlink_a->frame_rate;
|
|
outlink->sample_aspect_ratio = inlink_a->sample_aspect_ratio;
|
|
|
|
if (s->duration)
|
|
s->duration_pts = av_rescale_q(s->duration, AV_TIME_BASE_Q, inlink_a->time_base);
|
|
RET(ff_vk_filter_config_output(outlink));
|
|
|
|
fail:
|
|
return err;
|
|
}
|
|
|
|
static int forward_frame(XFadeVulkanContext *s,
|
|
AVFilterLink *inlink, AVFilterLink *outlink)
|
|
{
|
|
int64_t status_pts;
|
|
int ret = 0, status;
|
|
AVFrame *frame = NULL;
|
|
|
|
ret = ff_inlink_consume_frame(inlink, &frame);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret > 0) {
|
|
// If we do not have an offset yet, it's because we
|
|
// never got a first input. Just offset to 0
|
|
if (s->inputs_offset_pts == AV_NOPTS_VALUE)
|
|
s->inputs_offset_pts = -frame->pts;
|
|
|
|
// We got a frame, nothing to do other than adjusting the timestamp
|
|
frame->pts += s->inputs_offset_pts;
|
|
return ff_filter_frame(outlink, frame);
|
|
}
|
|
|
|
// Forward status with our timestamp
|
|
if (ff_inlink_acknowledge_status(inlink, &status, &status_pts)) {
|
|
if (s->inputs_offset_pts == AV_NOPTS_VALUE)
|
|
s->inputs_offset_pts = -status_pts;
|
|
|
|
ff_outlink_set_status(outlink, status, status_pts + s->inputs_offset_pts);
|
|
return 0;
|
|
}
|
|
|
|
// No frame available, request one if needed
|
|
if (ff_outlink_frame_wanted(outlink))
|
|
ff_inlink_request_frame(inlink);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int activate(AVFilterContext *avctx)
|
|
{
|
|
XFadeVulkanContext *s = avctx->priv;
|
|
AVFilterLink *in_a = avctx->inputs[IN_A];
|
|
AVFilterLink *in_b = avctx->inputs[IN_B];
|
|
AVFilterLink *outlink = avctx->outputs[0];
|
|
int64_t status_pts;
|
|
|
|
FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, avctx);
|
|
|
|
// Check if we already transitioned or IN_A ended prematurely,
|
|
// in which case just forward the frames from IN_B with adjusted
|
|
// timestamps until EOF.
|
|
if (s->status[IN_A] && !s->status[IN_B])
|
|
return forward_frame(s, in_b, outlink);
|
|
|
|
// We did not finish transitioning yet and the first stream
|
|
// did not end either, so check if there are more frames to consume.
|
|
if (ff_inlink_check_available_frame(in_a)) {
|
|
AVFrame *peeked_frame = ff_inlink_peek_frame(in_a, 0);
|
|
s->pts = peeked_frame->pts;
|
|
|
|
if (s->start_pts == AV_NOPTS_VALUE)
|
|
s->start_pts =
|
|
s->pts + av_rescale_q(s->offset, AV_TIME_BASE_Q, in_a->time_base);
|
|
|
|
// Check if we are not yet transitioning, in which case
|
|
// just request and forward the input frame.
|
|
if (s->start_pts > s->pts) {
|
|
AVFrame *frame_a = NULL;
|
|
s->passthrough = 1;
|
|
ff_inlink_consume_frame(in_a, &frame_a);
|
|
return ff_filter_frame(outlink, frame_a);
|
|
}
|
|
s->passthrough = 0;
|
|
|
|
// We are transitioning, so we need a frame from IN_B
|
|
if (ff_inlink_check_available_frame(in_b)) {
|
|
int ret;
|
|
AVFrame *frame_a = NULL, *frame_b = NULL;
|
|
ff_inlink_consume_frame(avctx->inputs[IN_A], &frame_a);
|
|
ff_inlink_consume_frame(avctx->inputs[IN_B], &frame_b);
|
|
|
|
// Calculate PTS offset to first input
|
|
if (s->inputs_offset_pts == AV_NOPTS_VALUE)
|
|
s->inputs_offset_pts = s->pts - frame_b->pts;
|
|
|
|
// Check if we finished transitioning, in which case we
|
|
// report back EOF to IN_A as it is no longer needed.
|
|
if (s->pts - s->start_pts > s->duration_pts) {
|
|
s->status[IN_A] = AVERROR_EOF;
|
|
ff_inlink_set_status(in_a, AVERROR_EOF);
|
|
s->passthrough = 1;
|
|
}
|
|
ret = xfade_frame(avctx, frame_a, frame_b);
|
|
av_frame_free(&frame_a);
|
|
av_frame_free(&frame_b);
|
|
return ret;
|
|
}
|
|
|
|
// We did not get a frame from IN_B, check its status.
|
|
if (ff_inlink_acknowledge_status(in_b, &s->status[IN_B], &status_pts)) {
|
|
// We should transition, but IN_B is EOF so just report EOF output now.
|
|
ff_outlink_set_status(outlink, s->status[IN_B], s->pts);
|
|
return 0;
|
|
}
|
|
|
|
// We did not get a frame for IN_B but no EOF either, so just request more.
|
|
if (ff_outlink_frame_wanted(outlink)) {
|
|
ff_inlink_request_frame(in_b);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// We did not get a frame from IN_A, check its status.
|
|
if (ff_inlink_acknowledge_status(in_a, &s->status[IN_A], &status_pts)) {
|
|
// No more frames from IN_A, do not report EOF though, we will just
|
|
// forward the IN_B frames in the next activate calls.
|
|
s->passthrough = 1;
|
|
ff_filter_set_ready(avctx, 100);
|
|
return 0;
|
|
}
|
|
|
|
// We have no frames yet from IN_A and no EOF, so request some.
|
|
if (ff_outlink_frame_wanted(outlink)) {
|
|
ff_inlink_request_frame(in_a);
|
|
return 0;
|
|
}
|
|
|
|
return FFERROR_NOT_READY;
|
|
}
|
|
|
|
static av_cold void uninit(AVFilterContext *avctx)
|
|
{
|
|
XFadeVulkanContext *s = avctx->priv;
|
|
FFVulkanContext *vkctx = &s->vkctx;
|
|
FFVulkanFunctions *vk = &vkctx->vkfn;
|
|
|
|
ff_vk_exec_pool_free(vkctx, &s->e);
|
|
ff_vk_pipeline_free(vkctx, &s->pl);
|
|
ff_vk_shader_free(vkctx, &s->shd);
|
|
|
|
if (s->sampler)
|
|
vk->DestroySampler(vkctx->hwctx->act_dev, s->sampler,
|
|
vkctx->hwctx->alloc);
|
|
|
|
ff_vk_uninit(&s->vkctx);
|
|
|
|
s->initialized = 0;
|
|
}
|
|
|
|
static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h)
|
|
{
|
|
XFadeVulkanContext *s = inlink->dst->priv;
|
|
|
|
return s->passthrough ?
|
|
ff_null_get_video_buffer (inlink, w, h) :
|
|
ff_default_get_video_buffer(inlink, w, h);
|
|
}
|
|
|
|
#define OFFSET(x) offsetof(XFadeVulkanContext, x)
|
|
#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
|
|
|
|
static const AVOption xfade_vulkan_options[] = {
|
|
{ "transition", "set cross fade transition", OFFSET(transition), AV_OPT_TYPE_INT, {.i64=FADE}, 0, NB_TRANSITIONS-1, FLAGS, .unit = "transition" },
|
|
{ "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "wipeup", "wipe up transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEUP}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "wipedown", "wipe down transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEDOWN}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "slidedown", "slide down transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEDOWN}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "slideup", "slide up transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEUP}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "slideleft", "slide left transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDELEFT}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "slideright", "slide right transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDERIGHT}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "circleopen", "circleopen transition", 0, AV_OPT_TYPE_CONST, {.i64=CIRCLEOPEN}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "circleclose", "circleclose transition", 0, AV_OPT_TYPE_CONST, {.i64=CIRCLECLOSE}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "dissolve", "dissolve transition", 0, AV_OPT_TYPE_CONST, {.i64=DISSOLVE}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "pixelize", "pixelize transition", 0, AV_OPT_TYPE_CONST, {.i64=PIXELIZE}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "wipetl", "wipe top left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPETL}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "wipetr", "wipe top right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPETR}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "wipebl", "wipe bottom left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEBL}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "wipebr", "wipe bottom right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEBR}, 0, 0, FLAGS, .unit = "transition" },
|
|
{ "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS },
|
|
{ "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS },
|
|
{ NULL }
|
|
};
|
|
|
|
AVFILTER_DEFINE_CLASS(xfade_vulkan);
|
|
|
|
static const AVFilterPad xfade_vulkan_inputs[] = {
|
|
{
|
|
.name = "main",
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
.get_buffer.video = &get_video_buffer,
|
|
.config_props = &ff_vk_filter_config_input,
|
|
},
|
|
{
|
|
.name = "xfade",
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
.get_buffer.video = &get_video_buffer,
|
|
.config_props = &ff_vk_filter_config_input,
|
|
},
|
|
};
|
|
|
|
static const AVFilterPad xfade_vulkan_outputs[] = {
|
|
{
|
|
.name = "default",
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
.config_props = &config_props_output,
|
|
},
|
|
};
|
|
|
|
const AVFilter ff_vf_xfade_vulkan = {
|
|
.name = "xfade_vulkan",
|
|
.description = NULL_IF_CONFIG_SMALL("Cross fade one video with another video."),
|
|
.priv_size = sizeof(XFadeVulkanContext),
|
|
.init = &ff_vk_filter_init,
|
|
.uninit = &uninit,
|
|
.activate = &activate,
|
|
FILTER_INPUTS(xfade_vulkan_inputs),
|
|
FILTER_OUTPUTS(xfade_vulkan_outputs),
|
|
FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
|
|
.priv_class = &xfade_vulkan_class,
|
|
.flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
|
|
.flags = AVFILTER_FLAG_HWDEVICE,
|
|
};
|