| @@ -0,0 +1,412 @@ |
| 1 | +/* |
| 2 | + * Copyright 2025 (c) Niklas Haas |
| 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 "libavutil/avassert.h" |
| 22 | +#include "libavutil/vulkan_spirv.h" |
| 23 | +#include "libavutil/opt.h" |
| 24 | +#include "libavutil/timestamp.h" |
| 25 | +#include "vulkan_filter.h" |
| 26 | + |
| 27 | +#include "filters.h" |
| 28 | + |
| 29 | +typedef struct SceneDetectVulkanContext { |
| 30 | + FFVulkanContext vkctx; |
| 31 | + |
| 32 | + int initialized; |
| 33 | + FFVkExecPool e; |
| 34 | + AVVulkanDeviceQueueFamily *qf; |
| 35 | + FFVulkanShader shd; |
| 36 | + AVBufferPool *det_buf_pool; |
| 37 | + |
| 38 | + double threshold; |
| 39 | + int sc_pass; |
| 40 | + |
| 41 | + int nb_planes; |
| 42 | + double prev_mafd; |
| 43 | + AVFrame *prev; |
| 44 | + AVFrame *cur; |
| 45 | +} SceneDetectVulkanContext; |
| 46 | + |
| 47 | +typedef struct SceneDetectBuf { |
| 48 | +#define SLICES 16 |
| 49 | + uint32_t frame_sad[SLICES]; |
| 50 | +} SceneDetectBuf; |
| 51 | + |
| 52 | +static av_cold int init_filter(AVFilterContext *ctx) |
| 53 | +{ |
| 54 | + int err; |
| 55 | + uint8_t *spv_data; |
| 56 | + size_t spv_len; |
| 57 | + void *spv_opaque = NULL; |
| 58 | + SceneDetectVulkanContext *s = ctx->priv; |
| 59 | + FFVulkanContext *vkctx = &s->vkctx; |
| 60 | + FFVulkanShader *shd; |
| 61 | + FFVkSPIRVCompiler *spv; |
| 62 | + FFVulkanDescriptorSetBinding *desc; |
| 63 | + |
| 64 | + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->vkctx.input_format); |
| 65 | + const int lumaonly = !(pixdesc->flags & AV_PIX_FMT_FLAG_RGB) && |
| 66 | + (pixdesc->flags & AV_PIX_FMT_FLAG_PLANAR); |
| 67 | + s->nb_planes = lumaonly ? 1 : av_pix_fmt_count_planes(s->vkctx.input_format); |
| 68 | + |
| 69 | + spv = ff_vk_spirv_init(); |
| 70 | + if (!spv) { |
| 71 | + av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n"); |
| 72 | + return AVERROR_EXTERNAL; |
| 73 | + } |
| 74 | + |
| 75 | + s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0); |
| 76 | + if (!s->qf) { |
| 77 | + av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n"); |
| 78 | + err = AVERROR(ENOTSUP); |
| 79 | + goto fail; |
| 80 | + } |
| 81 | + |
| 82 | + RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL)); |
| 83 | + RET(ff_vk_shader_init(vkctx, &s->shd, "scdet", |
| 84 | + VK_SHADER_STAGE_COMPUTE_BIT, |
| 85 | + (const char *[]) { "GL_KHR_shader_subgroup_arithmetic" }, 1, |
| 86 | + 32, 32, 1, |
| 87 | + 0)); |
| 88 | + shd = &s->shd; |
| 89 | + |
| 90 | + desc = (FFVulkanDescriptorSetBinding []) { |
| 91 | + { |
| 92 | + .name = "prev_img", |
| 93 | + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, |
| 94 | + .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_UINT), |
| 95 | + .mem_quali = "readonly", |
| 96 | + .dimensions = 2, |
| 97 | + .elems = av_pix_fmt_count_planes(s->vkctx.input_format), |
| 98 | + .stages = VK_SHADER_STAGE_COMPUTE_BIT, |
| 99 | + }, { |
| 100 | + .name = "cur_img", |
| 101 | + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, |
| 102 | + .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_UINT), |
| 103 | + .mem_quali = "readonly", |
| 104 | + .dimensions = 2, |
| 105 | + .elems = av_pix_fmt_count_planes(s->vkctx.input_format), |
| 106 | + .stages = VK_SHADER_STAGE_COMPUTE_BIT, |
| 107 | + }, { |
| 108 | + .name = "sad_buffer", |
| 109 | + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, |
| 110 | + .stages = VK_SHADER_STAGE_COMPUTE_BIT, |
| 111 | + .buf_content = "uint frame_sad[];", |
| 112 | + } |
| 113 | + }; |
| 114 | + |
| 115 | + RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0)); |
| 116 | + |
| 117 | + GLSLC(0, shared uint wg_sum; ); |
| 118 | + GLSLC(0, void main() ); |
| 119 | + GLSLC(0, { ); |
| 120 | + GLSLF(1, const uint slice = gl_WorkGroupID.x %% %u; ,SLICES); |
| 121 | + GLSLC(1, const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ); |
| 122 | + GLSLC(1, wg_sum = 0; ); |
| 123 | + GLSLC(1, barrier(); ); |
| 124 | + for (int i = 0; i < s->nb_planes; i++) { |
| 125 | + GLSLF(1, if (IS_WITHIN(pos, imageSize(cur_img[%d]))) { ,i); |
| 126 | + GLSLF(2, uvec4 prev = imageLoad(prev_img[%d], pos); ,i); |
| 127 | + GLSLF(2, uvec4 cur = imageLoad(cur_img[%d], pos); ,i); |
| 128 | + GLSLC(2, uvec4 sad = abs(ivec4(cur) - ivec4(prev)); ); |
| 129 | + GLSLC(2, uint sum = subgroupAdd(sad.x + sad.y + sad.z); ); |
| 130 | + GLSLC(2, if (subgroupElect()) ); |
| 131 | + GLSLC(3, atomicAdd(wg_sum, sum); ); |
| 132 | + GLSLC(1, } ); |
| 133 | + } |
| 134 | + GLSLC(1, barrier(); ); |
| 135 | + GLSLC(1, if (gl_LocalInvocationIndex == 0) ); |
| 136 | + GLSLC(2, atomicAdd(frame_sad[slice], wg_sum); ); |
| 137 | + GLSLC(0, } ); |
| 138 | + |
| 139 | + RET(spv->compile_shader(vkctx, spv, &s->shd, &spv_data, &spv_len, "main", |
| 140 | + &spv_opaque)); |
| 141 | + RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main")); |
| 142 | + |
| 143 | + RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd)); |
| 144 | + |
| 145 | + s->initialized = 1; |
| 146 | + |
| 147 | +fail: |
| 148 | + if (spv_opaque) |
| 149 | + spv->free_shader(spv, &spv_opaque); |
| 150 | + if (spv) |
| 151 | + spv->uninit(&spv); |
| 152 | + |
| 153 | + return err; |
| 154 | +} |
| 155 | + |
| 156 | +static double evaluate(AVFilterContext *ctx, const SceneDetectBuf *buf) |
| 157 | +{ |
| 158 | + SceneDetectVulkanContext *s = ctx->priv; |
| 159 | + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(s->vkctx.input_format); |
| 160 | + const AVFilterLink *inlink = ctx->inputs[0]; |
| 161 | + uint64_t count; |
| 162 | + double mafd, diff; |
| 163 | + |
| 164 | + uint64_t sad = 0; |
| 165 | + for (int i = 0; i < SLICES; i++) |
| 166 | + sad += buf->frame_sad[i]; |
| 167 | + |
| 168 | + av_assert2(s->nb_planes == 1 || !(desc->log2_chroma_w || desc->log2_chroma_h)); |
| 169 | + count = s->nb_planes * inlink->w * inlink->h; |
| 170 | + mafd = (double) sad * 100.0 / count / (1ULL << desc->comp[0].depth); |
| 171 | + diff = fabs(mafd - s->prev_mafd); |
| 172 | + s->prev_mafd = mafd; |
| 173 | + |
| 174 | + return av_clipf(FFMIN(mafd, diff), 0.0, 100.0); |
| 175 | +} |
| 176 | + |
| 177 | +static int scdet_vulkan_filter_frame(AVFilterLink *link, AVFrame *in) |
| 178 | +{ |
| 179 | + int err; |
| 180 | + AVFilterContext *ctx = link->dst; |
| 181 | + SceneDetectVulkanContext *s = ctx->priv; |
| 182 | + AVFilterLink *outlink = ctx->outputs[0]; |
| 183 | + |
| 184 | + VkImageView prev_views[AV_NUM_DATA_POINTERS]; |
| 185 | + VkImageView cur_views[AV_NUM_DATA_POINTERS]; |
| 186 | + VkImageMemoryBarrier2 img_bar[8]; |
| 187 | + int nb_img_bar = 0; |
| 188 | + |
| 189 | + FFVulkanContext *vkctx = &s->vkctx; |
| 190 | + FFVulkanFunctions *vk = &vkctx->vkfn; |
| 191 | + FFVkExecContext *exec = NULL; |
| 192 | + AVBufferRef *buf = NULL; |
| 193 | + FFVkBuffer *buf_vk; |
| 194 | + |
| 195 | + SceneDetectBuf *sad; |
| 196 | + double score = 0.0; |
| 197 | + char str[64]; |
| 198 | + |
| 199 | + if (!s->initialized) |
| 200 | + RET(init_filter(ctx)); |
| 201 | + |
| 202 | + av_frame_free(&s->prev); |
| 203 | + s->prev = s->cur; |
| 204 | + s->cur = av_frame_clone(in); |
| 205 | + if (!s->prev) |
| 206 | + goto done; |
| 207 | + |
| 208 | + RET(ff_vk_get_pooled_buffer(vkctx, &s->det_buf_pool, &buf, |
| 209 | + VK_BUFFER_USAGE_TRANSFER_DST_BIT | |
| 210 | + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, |
| 211 | + NULL, |
| 212 | + sizeof(SceneDetectBuf), |
| 213 | + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | |
| 214 | + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | |
| 215 | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)); |
| 216 | + buf_vk = (FFVkBuffer *)buf->data; |
| 217 | + sad = (SceneDetectBuf *) buf_vk->mapped_mem; |
| 218 | + |
| 219 | + exec = ff_vk_exec_get(vkctx, &s->e); |
| 220 | + ff_vk_exec_start(vkctx, exec); |
| 221 | + |
| 222 | + RET(ff_vk_exec_add_dep_frame(vkctx, exec, s->prev, |
| 223 | + VK_PIPELINE_STAGE_2_NONE, |
| 224 | + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT)); |
| 225 | + RET(ff_vk_create_imageviews(vkctx, exec, prev_views, s->prev, FF_VK_REP_UINT)); |
| 226 | + |
| 227 | + ff_vk_shader_update_img_array(vkctx, exec, &s->shd, s->prev, prev_views, 0, 0, |
| 228 | + VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE); |
| 229 | + |
| 230 | + ff_vk_frame_barrier(vkctx, exec, s->prev, img_bar, &nb_img_bar, |
| 231 | + VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, |
| 232 | + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, |
| 233 | + VK_ACCESS_SHADER_READ_BIT, |
| 234 | + VK_IMAGE_LAYOUT_GENERAL, |
| 235 | + VK_QUEUE_FAMILY_IGNORED); |
| 236 | + |
| 237 | + RET(ff_vk_exec_add_dep_frame(vkctx, exec, s->cur, |
| 238 | + VK_PIPELINE_STAGE_2_NONE, |
| 239 | + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT)); |
| 240 | + RET(ff_vk_create_imageviews(vkctx, exec, cur_views, s->cur, FF_VK_REP_UINT)); |
| 241 | + |
| 242 | + ff_vk_shader_update_img_array(vkctx, exec, &s->shd, s->cur, cur_views, 0, 1, |
| 243 | + VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE); |
| 244 | + |
| 245 | + ff_vk_frame_barrier(vkctx, exec, s->cur, img_bar, &nb_img_bar, |
| 246 | + VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, |
| 247 | + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, |
| 248 | + VK_ACCESS_SHADER_READ_BIT, |
| 249 | + VK_IMAGE_LAYOUT_GENERAL, |
| 250 | + VK_QUEUE_FAMILY_IGNORED); |
| 251 | + |
| 252 | + /* zero buffer */ |
| 253 | + vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) { |
| 254 | + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, |
| 255 | + .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) { |
| 256 | + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, |
| 257 | + .srcStageMask = VK_PIPELINE_STAGE_2_NONE, |
| 258 | + .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, |
| 259 | + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, |
| 260 | + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| 261 | + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| 262 | + .buffer = buf_vk->buf, |
| 263 | + .size = buf_vk->size, |
| 264 | + .offset = 0, |
| 265 | + }, |
| 266 | + .bufferMemoryBarrierCount = 1, |
| 267 | + }); |
| 268 | + |
| 269 | + vk->CmdFillBuffer(exec->buf, buf_vk->buf, 0, buf_vk->size, 0x0); |
| 270 | + |
| 271 | + vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) { |
| 272 | + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, |
| 273 | + .pImageMemoryBarriers = img_bar, |
| 274 | + .imageMemoryBarrierCount = nb_img_bar, |
| 275 | + .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) { |
| 276 | + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, |
| 277 | + .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, |
| 278 | + .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, |
| 279 | + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, |
| 280 | + .dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT | |
| 281 | + VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, |
| 282 | + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| 283 | + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| 284 | + .buffer = buf_vk->buf, |
| 285 | + .size = buf_vk->size, |
| 286 | + .offset = 0, |
| 287 | + }, |
| 288 | + .bufferMemoryBarrierCount = 1, |
| 289 | + }); |
| 290 | + |
| 291 | + RET(ff_vk_shader_update_desc_buffer(&s->vkctx, exec, &s->shd, 0, 2, 0, |
| 292 | + buf_vk, 0, buf_vk->size, |
| 293 | + VK_FORMAT_UNDEFINED)); |
| 294 | + |
| 295 | + ff_vk_exec_bind_shader(vkctx, exec, &s->shd); |
| 296 | + |
| 297 | + vk->CmdDispatch(exec->buf, |
| 298 | + FFALIGN(in->width, s->shd.lg_size[0]) / s->shd.lg_size[0], |
| 299 | + FFALIGN(in->height, s->shd.lg_size[1]) / s->shd.lg_size[1], |
| 300 | + s->shd.lg_size[2]); |
| 301 | + |
| 302 | + vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) { |
| 303 | + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, |
| 304 | + .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) { |
| 305 | + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, |
| 306 | + .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, |
| 307 | + .dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT, |
| 308 | + .srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT | |
| 309 | + VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, |
| 310 | + .dstAccessMask = VK_ACCESS_HOST_READ_BIT, |
| 311 | + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| 312 | + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, |
| 313 | + .buffer = buf_vk->buf, |
| 314 | + .size = buf_vk->size, |
| 315 | + .offset = 0, |
| 316 | + }, |
| 317 | + .bufferMemoryBarrierCount = 1, |
| 318 | + }); |
| 319 | + |
| 320 | + RET(ff_vk_exec_submit(vkctx, exec)); |
| 321 | + ff_vk_exec_wait(vkctx, exec); |
| 322 | + score = evaluate(ctx, sad); |
| 323 | + |
| 324 | +done: |
| 325 | + snprintf(str, sizeof(str), "%0.3f", s->prev_mafd); |
| 326 | + av_dict_set(&in->metadata, "lavfi.scd.mafd", str, 0); |
| 327 | + snprintf(str, sizeof(str), "%0.3f", score); |
| 328 | + av_dict_set(&in->metadata, "lavfi.scd.score", str, 0); |
| 329 | + |
| 330 | + if (score >= s->threshold) { |
| 331 | + const char *pts = av_ts2timestr(in->pts, &link->time_base); |
| 332 | + av_dict_set(&in->metadata, "lavfi.scd.time", pts, 0); |
| 333 | + av_log(s, AV_LOG_INFO, "lavfi.scd.score: %.3f, lavfi.scd.time: %s\n", |
| 334 | + score, pts); |
| 335 | + } |
| 336 | + |
| 337 | + av_buffer_unref(&buf); |
| 338 | + if (!s->sc_pass || score >= s->threshold) |
| 339 | + return ff_filter_frame(outlink, in); |
| 340 | + else { |
| 341 | + av_frame_free(&in); |
| 342 | + return 0; |
| 343 | + } |
| 344 | + |
| 345 | +fail: |
| 346 | + if (exec) |
| 347 | + ff_vk_exec_discard_deps(&s->vkctx, exec); |
| 348 | + av_frame_free(&in); |
| 349 | + av_buffer_unref(&buf); |
| 350 | + return err; |
| 351 | +} |
| 352 | + |
| 353 | +static void scdet_vulkan_uninit(AVFilterContext *avctx) |
| 354 | +{ |
| 355 | + SceneDetectVulkanContext *s = avctx->priv; |
| 356 | + FFVulkanContext *vkctx = &s->vkctx; |
| 357 | + |
| 358 | + av_frame_free(&s->prev); |
| 359 | + av_frame_free(&s->cur); |
| 360 | + |
| 361 | + ff_vk_exec_pool_free(vkctx, &s->e); |
| 362 | + ff_vk_shader_free(vkctx, &s->shd); |
| 363 | + |
| 364 | + av_buffer_pool_uninit(&s->det_buf_pool); |
| 365 | + |
| 366 | + ff_vk_uninit(&s->vkctx); |
| 367 | + |
| 368 | + s->initialized = 0; |
| 369 | +} |
| 370 | + |
| 371 | +#define OFFSET(x) offsetof(SceneDetectVulkanContext, x) |
| 372 | +#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM) |
| 373 | +static const AVOption scdet_vulkan_options[] = { |
| 374 | + { "threshold", "set scene change detect threshold", OFFSET(threshold), AV_OPT_TYPE_DOUBLE, {.dbl = 10.}, 0, 100., FLAGS }, |
| 375 | + { "t", "set scene change detect threshold", OFFSET(threshold), AV_OPT_TYPE_DOUBLE, {.dbl = 10.}, 0, 100., FLAGS }, |
| 376 | + { "sc_pass", "Set the flag to pass scene change frames", OFFSET(sc_pass), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, FLAGS }, |
| 377 | + { "s", "Set the flag to pass scene change frames", OFFSET(sc_pass), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, FLAGS }, |
| 378 | + { NULL } |
| 379 | +}; |
| 380 | + |
| 381 | +AVFILTER_DEFINE_CLASS(scdet_vulkan); |
| 382 | + |
| 383 | +static const AVFilterPad scdet_vulkan_inputs[] = { |
| 384 | + { |
| 385 | + .name = "default", |
| 386 | + .type = AVMEDIA_TYPE_VIDEO, |
| 387 | + .filter_frame = &scdet_vulkan_filter_frame, |
| 388 | + .config_props = &ff_vk_filter_config_input, |
| 389 | + }, |
| 390 | +}; |
| 391 | + |
| 392 | +static const AVFilterPad scdet_vulkan_outputs[] = { |
| 393 | + { |
| 394 | + .name = "default", |
| 395 | + .type = AVMEDIA_TYPE_VIDEO, |
| 396 | + .config_props = &ff_vk_filter_config_output, |
| 397 | + }, |
| 398 | +}; |
| 399 | + |
| 400 | +const FFFilter ff_vf_scdet_vulkan = { |
| 401 | + .p.name = "scdet_vulkan", |
| 402 | + .p.description = NULL_IF_CONFIG_SMALL("Detect video scene change"), |
| 403 | + .p.priv_class = &scdet_vulkan_class, |
| 404 | + .p.flags = AVFILTER_FLAG_HWDEVICE, |
| 405 | + .priv_size = sizeof(SceneDetectVulkanContext), |
| 406 | + .init = &ff_vk_filter_init, |
| 407 | + .uninit = &scdet_vulkan_uninit, |
| 408 | + FILTER_INPUTS(scdet_vulkan_inputs), |
| 409 | + FILTER_OUTPUTS(scdet_vulkan_outputs), |
| 410 | + FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN), |
| 411 | + .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, |
| 412 | +}; |