* [FFmpeg-devel] [PATCH 1/5] avutil/vulkan: add YUVA pixel formats support
@ 2025-05-16 14:30 Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 2/5] avfilter/vf_blackdetect: add alpha option Niklas Haas
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Niklas Haas @ 2025-05-16 14:30 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Niklas Haas
From: Niklas Haas <git@haasn.dev>
Signed-off-by: Niklas Haas <git@haasn.dev>
Sponsored-by: nxtedition
---
libavutil/hwcontext_vulkan.c | 17 +++++++++++++++++
libavutil/vulkan.c | 15 +++++++++++++--
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index 978d7e29d3..1a714c0663 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -406,6 +406,23 @@ static const struct FFVkFormatEntry {
{ VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16, AV_PIX_FMT_Y212, VK_IMAGE_ASPECT_COLOR_BIT, 1, 1, 1, { VK_FORMAT_R16G16B16A16_UNORM } },
{ VK_FORMAT_G16B16G16R16_422_UNORM, AV_PIX_FMT_Y216, VK_IMAGE_ASPECT_COLOR_BIT, 1, 1, 1, { VK_FORMAT_R16G16B16A16_UNORM } },
+ /* Planar YUVA 420 at 8, 10 and 16 bits */
+ { VK_FORMAT_R8_UNORM, AV_PIX_FMT_YUVA420P, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM } },
+ { VK_FORMAT_R16_UNORM, AV_PIX_FMT_YUVA420P10, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM } },
+ { VK_FORMAT_R16_UNORM, AV_PIX_FMT_YUVA420P16, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM } },
+
+ /* Planar YUVA 422 at 8, 10, 12 and 16 bits */
+ { VK_FORMAT_R8_UNORM, AV_PIX_FMT_YUVA422P, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM } },
+ { VK_FORMAT_R16_UNORM, AV_PIX_FMT_YUVA422P10, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM } },
+ { VK_FORMAT_R16_UNORM, AV_PIX_FMT_YUVA422P12, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM } },
+ { VK_FORMAT_R16_UNORM, AV_PIX_FMT_YUVA422P16, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM } },
+
+ /* Planar YUVA 444 at 8, 10, 12 and 16 bits */
+ { VK_FORMAT_R8_UNORM, AV_PIX_FMT_YUVA444P, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM } },
+ { VK_FORMAT_R16_UNORM, AV_PIX_FMT_YUVA444P10, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM } },
+ { VK_FORMAT_R16_UNORM, AV_PIX_FMT_YUVA444P12, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM } },
+ { VK_FORMAT_R16_UNORM, AV_PIX_FMT_YUVA444P16, VK_IMAGE_ASPECT_COLOR_BIT, 4, 4, 4, { VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_UNORM } },
+
/* Single plane 444 at 8, 10, 12 and 16 bits */
{ VK_FORMAT_B8G8R8A8_UNORM, AV_PIX_FMT_UYVA, VK_IMAGE_ASPECT_COLOR_BIT, 1, 1, 1, { VK_FORMAT_B8G8R8A8_UNORM } },
{ VK_FORMAT_A2R10G10B10_UNORM_PACK32, AV_PIX_FMT_XV30, VK_IMAGE_ASPECT_COLOR_BIT, 1, 1, 1, { VK_FORMAT_R16G16B16A16_UNORM } },
diff --git a/libavutil/vulkan.c b/libavutil/vulkan.c
index 2cc8ec110e..5f2ac6267d 100644
--- a/libavutil/vulkan.c
+++ b/libavutil/vulkan.c
@@ -1611,7 +1611,10 @@ const char *ff_vk_shader_rep_fmt(enum AVPixelFormat pix_fmt,
case AV_PIX_FMT_GBRAP:
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUV422P:
- case AV_PIX_FMT_YUV444P: {
+ case AV_PIX_FMT_YUV444P:
+ case AV_PIX_FMT_YUVA420P:
+ case AV_PIX_FMT_YUVA422P:
+ case AV_PIX_FMT_YUVA444P: {
const char *rep_tab[] = {
[FF_VK_REP_NATIVE] = "r8ui",
[FF_VK_REP_FLOAT] = "r8",
@@ -1640,7 +1643,15 @@ const char *ff_vk_shader_rep_fmt(enum AVPixelFormat pix_fmt,
case AV_PIX_FMT_YUV422P16:
case AV_PIX_FMT_YUV444P10:
case AV_PIX_FMT_YUV444P12:
- case AV_PIX_FMT_YUV444P16: {
+ case AV_PIX_FMT_YUV444P16:
+ case AV_PIX_FMT_YUVA420P10:
+ case AV_PIX_FMT_YUVA420P16:
+ case AV_PIX_FMT_YUVA422P10:
+ case AV_PIX_FMT_YUVA422P12:
+ case AV_PIX_FMT_YUVA422P16:
+ case AV_PIX_FMT_YUVA444P10:
+ case AV_PIX_FMT_YUVA444P12:
+ case AV_PIX_FMT_YUVA444P16: {
const char *rep_tab[] = {
[FF_VK_REP_NATIVE] = "r16ui",
[FF_VK_REP_FLOAT] = "r16f",
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 5+ messages in thread
* [FFmpeg-devel] [PATCH 2/5] avfilter/vf_blackdetect: add alpha option
2025-05-16 14:30 [FFmpeg-devel] [PATCH 1/5] avutil/vulkan: add YUVA pixel formats support Niklas Haas
@ 2025-05-16 14:30 ` Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 3/5] avfilter/blackdetect_vulkan: add hw accelerated blackdetect filter Niklas Haas
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Niklas Haas @ 2025-05-16 14:30 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Niklas Haas
From: Niklas Haas <git@haasn.dev>
Check the alpha plane for (almost) transparent frames, instead of checking
the luma channel for almost black frames.
Signed-off-by: Niklas Haas <git@haasn.dev>
Sponsored-by: nxtedition
---
doc/filters.texi | 6 +++++
libavfilter/vf_blackdetect.c | 51 +++++++++++++++++++++++++++---------
2 files changed, 44 insertions(+), 13 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index 434fdec109..a90322e242 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -8687,6 +8687,12 @@ the input video format, the range is [0-255] for YUV full-range
formats and [16-235] for YUV non full-range formats.
Default value is 0.10.
+
+@item alpha
+If true, check the alpha channel instead of the luma channel. Detects frames
+which are (almost) transparent, instead of frames which are almost black.
+
+Default value is disabled.
@end table
The following example sets the maximum pixel threshold to the minimum
diff --git a/libavfilter/vf_blackdetect.c b/libavfilter/vf_blackdetect.c
index 21f35f705d..8be33a814d 100644
--- a/libavfilter/vf_blackdetect.c
+++ b/libavfilter/vf_blackdetect.c
@@ -31,6 +31,7 @@
#include "libavutil/timestamp.h"
#include "avfilter.h"
#include "filters.h"
+#include "formats.h"
#include "video.h"
typedef struct BlackDetectContext {
@@ -45,6 +46,7 @@ typedef struct BlackDetectContext {
double picture_black_ratio_th;
double pixel_black_th;
unsigned int pixel_black_th_i;
+ int alpha;
unsigned int nb_black_pixels; ///< number of black pixels counted so far
AVRational time_base;
@@ -63,6 +65,7 @@ static const AVOption blackdetect_options[] = {
{ "pic_th", "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1, FLAGS },
{ "pixel_black_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1, FLAGS },
{ "pix_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1, FLAGS },
+ { "alpha", "check alpha instead of luma", OFFSET(alpha), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS },
{ NULL }
};
@@ -71,11 +74,21 @@ AVFILTER_DEFINE_CLASS(blackdetect);
#define YUVJ_FORMATS \
AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P
+#define YUVA_FORMATS \
+ AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P, \
+ AV_PIX_FMT_YUVA444P9, AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12, AV_PIX_FMT_YUVA444P16, \
+ AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA422P16, \
+ AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16
+
static const enum AVPixelFormat yuvj_formats[] = {
YUVJ_FORMATS, AV_PIX_FMT_NONE
};
-static const enum AVPixelFormat pix_fmts[] = {
+static const enum AVPixelFormat yuva_formats[] = {
+ YUVA_FORMATS, AV_PIX_FMT_NONE
+};
+
+static const enum AVPixelFormat yuv_formats[] = {
AV_PIX_FMT_GRAY8,
AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P,
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P,
@@ -91,13 +104,23 @@ static const enum AVPixelFormat pix_fmts[] = {
AV_PIX_FMT_YUV440P12,
AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14,
AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
- AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P,
- AV_PIX_FMT_YUVA444P9, AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12, AV_PIX_FMT_YUVA444P16,
- AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA422P16,
- AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16,
- AV_PIX_FMT_NONE
+ YUVA_FORMATS, AV_PIX_FMT_NONE
};
+static int query_format(const AVFilterContext *ctx,
+ AVFilterFormatsConfig **cfg_in,
+ AVFilterFormatsConfig **cfg_out)
+{
+ const BlackDetectContext *s = ctx->priv;
+ AVFilterFormats *formats;
+ if (s->alpha)
+ formats = ff_make_format_list(yuva_formats);
+ else
+ formats = ff_make_format_list(yuv_formats);
+
+ return ff_set_common_formats2(ctx, cfg_in, cfg_out, formats);
+}
+
static int config_input(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
@@ -114,9 +137,9 @@ static int config_input(AVFilterLink *inlink)
return AVERROR(ENOMEM);
av_log(s, AV_LOG_VERBOSE,
- "black_min_duration:%s pixel_black_th:%f picture_black_ratio_th:%f\n",
+ "black_min_duration:%s pixel_black_th:%f picture_black_ratio_th:%f alpha:%d\n",
av_ts2timestr(s->black_min_duration, &s->time_base),
- s->pixel_black_th, s->picture_black_ratio_th);
+ s->pixel_black_th, s->picture_black_ratio_th, s->alpha);
return 0;
}
@@ -140,7 +163,8 @@ static int black_counter(AVFilterContext *ctx, void *arg,
const unsigned int threshold = s->pixel_black_th_i;
unsigned int *counterp = &s->counter[jobnr];
AVFrame *in = arg;
- const int linesize = in->linesize[0];
+ const int plane = s->alpha ? 3 : 0;
+ const int linesize = in->linesize[plane];
const int w = in->width;
const int h = in->height;
const int start = (h * jobnr) / nb_jobs;
@@ -149,7 +173,7 @@ static int black_counter(AVFilterContext *ctx, void *arg,
unsigned int counter = 0;
if (s->depth == 8) {
- const uint8_t *p = in->data[0] + start * linesize;
+ const uint8_t *p = in->data[plane] + start * linesize;
for (int i = 0; i < size; i++) {
for (int x = 0; x < w; x++)
@@ -157,7 +181,7 @@ static int black_counter(AVFilterContext *ctx, void *arg,
p += linesize;
}
} else {
- const uint16_t *p = (const uint16_t *)(in->data[0] + start * linesize);
+ const uint16_t *p = (const uint16_t *)(in->data[plane] + start * linesize);
for (int i = 0; i < size; i++) {
for (int x = 0; x < w; x++)
@@ -180,7 +204,8 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *picref)
const int max = (1 << s->depth) - 1;
const int factor = (1 << (s->depth - 8));
const int full = picref->color_range == AVCOL_RANGE_JPEG ||
- ff_fmt_is_in(picref->format, yuvj_formats);
+ ff_fmt_is_in(picref->format, yuvj_formats) ||
+ s->alpha;
s->pixel_black_th_i = full ? s->pixel_black_th * max :
// luminance_minimum_value + pixel_black_th * luminance_range_size
@@ -252,6 +277,6 @@ const FFFilter ff_vf_blackdetect = {
.priv_size = sizeof(BlackDetectContext),
FILTER_INPUTS(blackdetect_inputs),
FILTER_OUTPUTS(ff_video_default_filterpad),
- FILTER_PIXFMTS_ARRAY(pix_fmts),
+ FILTER_QUERY_FUNC2(query_format),
.uninit = uninit,
};
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 5+ messages in thread
* [FFmpeg-devel] [PATCH 3/5] avfilter/blackdetect_vulkan: add hw accelerated blackdetect filter
2025-05-16 14:30 [FFmpeg-devel] [PATCH 1/5] avutil/vulkan: add YUVA pixel formats support Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 2/5] avfilter/vf_blackdetect: add alpha option Niklas Haas
@ 2025-05-16 14:30 ` Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 4/5] avfilter/alphadetect_vulkan: add alpha type detection filter Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 5/5] avutil/vf_scdet_vulkan: add new filter Niklas Haas
3 siblings, 0 replies; 5+ messages in thread
From: Niklas Haas @ 2025-05-16 14:30 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Niklas Haas
From: Niklas Haas <git@haasn.dev>
Like vf_blackdetect but better, faster, stronger, harder.
Signed-off-by: Niklas Haas <git@haasn.dev>
Sponsored-by: nxtedition
---
configure | 1 +
doc/filters.texi | 2 +-
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/vf_blackdetect_vulkan.c | 431 ++++++++++++++++++++++++++++
5 files changed, 435 insertions(+), 1 deletion(-)
create mode 100644 libavfilter/vf_blackdetect_vulkan.c
diff --git a/configure b/configure
index 2e69b3c56c..2e5f9fea49 100755
--- a/configure
+++ b/configure
@@ -3877,6 +3877,7 @@ ass_filter_deps="libass"
avgblur_opencl_filter_deps="opencl"
avgblur_vulkan_filter_deps="vulkan spirv_compiler"
azmq_filter_deps="libzmq"
+blackdetect_vulkan_filter_deps="vulkan spirv_compiler"
blackframe_filter_deps="gpl"
blend_vulkan_filter_deps="vulkan spirv_compiler"
boxblur_filter_deps="gpl"
diff --git a/doc/filters.texi b/doc/filters.texi
index a90322e242..105adda0db 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -8634,7 +8634,7 @@ Filter out noisy pixels from @code{bitplane} set above.
Default is disabled.
@end table
-@section blackdetect
+@section blackdetect, blackdetect_vulkan
Detect video intervals that are (almost) completely black. Can be
useful to detect chapter transitions, commercials, or invalid
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 0effe4127f..e9d4566a2a 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -208,6 +208,7 @@ OBJS-$(CONFIG_BILATERAL_FILTER) += vf_bilateral.o
OBJS-$(CONFIG_BILATERAL_CUDA_FILTER) += vf_bilateral_cuda.o vf_bilateral_cuda.ptx.o
OBJS-$(CONFIG_BITPLANENOISE_FILTER) += vf_bitplanenoise.o
OBJS-$(CONFIG_BLACKDETECT_FILTER) += vf_blackdetect.o
+OBJS-$(CONFIG_BLACKDETECT_VULKAN_FILTER) += vf_blackdetect_vulkan.o
OBJS-$(CONFIG_BLACKFRAME_FILTER) += vf_blackframe.o
OBJS-$(CONFIG_BLEND_FILTER) += vf_blend.o framesync.o
OBJS-$(CONFIG_BLEND_VULKAN_FILTER) += vf_blend_vulkan.o framesync.o vulkan.o vulkan_filter.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 5ea33cdf01..92890575f1 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -192,6 +192,7 @@ extern const FFFilter ff_vf_bilateral;
extern const FFFilter ff_vf_bilateral_cuda;
extern const FFFilter ff_vf_bitplanenoise;
extern const FFFilter ff_vf_blackdetect;
+extern const FFFilter ff_vf_blackdetect_vulkan;
extern const FFFilter ff_vf_blackframe;
extern const FFFilter ff_vf_blend;
extern const FFFilter ff_vf_blend_vulkan;
diff --git a/libavfilter/vf_blackdetect_vulkan.c b/libavfilter/vf_blackdetect_vulkan.c
new file mode 100644
index 0000000000..4e977abe3d
--- /dev/null
+++ b/libavfilter/vf_blackdetect_vulkan.c
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2025 (c) Niklas Haas
+ *
+ * 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 <float.h>
+#include "libavutil/vulkan_spirv.h"
+#include "libavutil/opt.h"
+#include "libavutil/timestamp.h"
+#include "vulkan_filter.h"
+
+#include "filters.h"
+#include "video.h"
+
+typedef struct BlackDetectVulkanContext {
+ FFVulkanContext vkctx;
+
+ int initialized;
+ FFVkExecPool e;
+ AVVulkanDeviceQueueFamily *qf;
+ FFVulkanShader shd;
+ AVBufferPool *sum_buf_pool;
+
+ double black_min_duration_time;
+ double picture_black_ratio_th;
+ double pixel_black_th;
+ int alpha;
+
+ int64_t black_start;
+ int64_t black_end;
+} BlackDetectVulkanContext;
+
+typedef struct BlackDetectPushData {
+ float threshold;
+} BlackDetectPushData;
+
+typedef struct BlackDetectBuf {
+#define SLICES 16
+ uint32_t slice_sum[SLICES];
+} BlackDetectBuf;
+
+static av_cold int init_filter(AVFilterContext *ctx)
+{
+ int err;
+ uint8_t *spv_data;
+ size_t spv_len;
+ void *spv_opaque = NULL;
+ BlackDetectVulkanContext *s = ctx->priv;
+ FFVulkanContext *vkctx = &s->vkctx;
+ FFVulkanShader *shd;
+ FFVkSPIRVCompiler *spv;
+ FFVulkanDescriptorSetBinding *desc;
+ const int plane = s->alpha ? 3 : 0;
+
+ const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->vkctx.input_format);
+ if (pixdesc->flags & AV_PIX_FMT_FLAG_RGB) {
+ av_log(ctx, AV_LOG_ERROR, "RGB inputs are not supported\n");
+ return AVERROR(ENOTSUP);
+ }
+
+ spv = ff_vk_spirv_init();
+ if (!spv) {
+ av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0);
+ if (!s->qf) {
+ av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n");
+ err = AVERROR(ENOTSUP);
+ goto fail;
+ }
+
+ RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL));
+ RET(ff_vk_shader_init(vkctx, &s->shd, "blackdetect",
+ VK_SHADER_STAGE_COMPUTE_BIT,
+ (const char *[]) { "GL_KHR_shader_subgroup_ballot" }, 1,
+ 32, 32, 1,
+ 0));
+ shd = &s->shd;
+
+ GLSLC(0, layout(push_constant, std430) uniform pushConstants { );
+ GLSLC(1, float threshold; );
+ GLSLC(0, }; );
+
+ ff_vk_shader_add_push_const(shd, 0, sizeof(BlackDetectPushData),
+ VK_SHADER_STAGE_COMPUTE_BIT);
+
+ desc = (FFVulkanDescriptorSetBinding []) {
+ {
+ .name = "input_img",
+ .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_FLOAT),
+ .mem_quali = "readonly",
+ .dimensions = 2,
+ .elems = av_pix_fmt_count_planes(s->vkctx.input_format),
+ .stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ }, {
+ .name = "sum_buffer",
+ .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ .buf_content = "uint slice_sum[];",
+ }
+ };
+
+ RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0));
+
+ GLSLC(0, shared uint wg_sum; );
+ GLSLC(0, );
+ GLSLC(0, void main() );
+ GLSLC(0, { );
+ GLSLC(1, wg_sum = 0u; );
+ GLSLC(1, barrier(); );
+ GLSLC(0, );
+ GLSLC(1, const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); );
+ GLSLF(1, if (!IS_WITHIN(pos, imageSize(input_img[%d]))) ,plane);
+ GLSLC(2, return; );
+ GLSLF(1, float value = imageLoad(input_img[%d], pos).x; ,plane);
+ GLSLC(1, uvec4 isblack = subgroupBallot(value <= threshold); );
+ GLSLC(1, if (subgroupElect()) );
+ GLSLC(2, atomicAdd(wg_sum, subgroupBallotBitCount(isblack)); );
+ GLSLC(1, barrier(); );
+ GLSLC(1, if (gl_LocalInvocationIndex == 0u) );
+ GLSLF(2, atomicAdd(slice_sum[gl_WorkGroupID.x %% %du], wg_sum); ,SLICES);
+ GLSLC(0, } );
+
+ RET(spv->compile_shader(vkctx, spv, &s->shd, &spv_data, &spv_len, "main",
+ &spv_opaque));
+ RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main"));
+
+ RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
+
+ s->initialized = 1;
+
+fail:
+ if (spv_opaque)
+ spv->free_shader(spv, &spv_opaque);
+ if (spv)
+ spv->uninit(&spv);
+
+ return err;
+}
+
+static void evaluate(AVFilterLink *link, AVFrame *in,
+ const BlackDetectBuf *sum)
+{
+ AVFilterContext *ctx = link->dst;
+ BlackDetectVulkanContext *s = ctx->priv;
+ FilterLink *inl = ff_filter_link(link);
+ uint64_t nb_black_pixels = 0;
+ double ratio;
+
+ for (int i = 0; i < FF_ARRAY_ELEMS(sum->slice_sum); i++)
+ nb_black_pixels += sum->slice_sum[i];
+
+ ratio = (double) nb_black_pixels / (link->w * link->h);
+
+ av_log(ctx, AV_LOG_DEBUG,
+ "frame:%"PRId64" picture_black_ratio:%f pts:%s t:%s type:%c\n",
+ inl->frame_count_out, ratio,
+ av_ts2str(in->pts), av_ts2timestr(in->pts, &in->time_base),
+ av_get_picture_type_char(in->pict_type));
+
+ if (ratio >= s->picture_black_ratio_th) {
+ if (s->black_start == AV_NOPTS_VALUE) {
+ s->black_start = in->pts;
+ av_dict_set(&in->metadata, "lavfi.black_start",
+ av_ts2timestr(in->pts, &in->time_base), 0);
+ }
+ } else if (s->black_start != AV_NOPTS_VALUE) {
+ av_dict_set(&in->metadata, "lavfi.black_end",
+ av_ts2timestr(in->pts, &in->time_base), 0);
+ if ((in->pts - s->black_start) >= s->black_min_duration_time / av_q2d(in->time_base)) {
+ av_log(s, AV_LOG_INFO,
+ "black_start:%s black_end:%s black_duration:%s\n",
+ av_ts2timestr(s->black_start, &in->time_base),
+ av_ts2timestr(in->pts, &in->time_base),
+ av_ts2timestr(in->pts - s->black_start, &in->time_base));
+ }
+ s->black_start = AV_NOPTS_VALUE;
+ }
+}
+
+static int blackdetect_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
+{
+ int err;
+ AVFilterContext *ctx = link->dst;
+ BlackDetectVulkanContext *s = ctx->priv;
+ AVFilterLink *outlink = ctx->outputs[0];
+
+ VkImageView in_views[AV_NUM_DATA_POINTERS];
+ VkImageMemoryBarrier2 img_bar[4];
+ int nb_img_bar = 0;
+
+ FFVulkanContext *vkctx = &s->vkctx;
+ FFVulkanFunctions *vk = &vkctx->vkfn;
+ FFVkExecContext *exec = NULL;
+ AVBufferRef *sum_buf = NULL;
+ FFVkBuffer *sum_vk;
+
+ BlackDetectBuf *sum;
+ BlackDetectPushData push_data;
+
+ if (in->color_range == AVCOL_RANGE_JPEG || s->alpha) {
+ push_data.threshold = s->pixel_black_th;
+ } else {
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(vkctx->input_format);
+ const int depth = desc->comp[0].depth;
+ const int ymin = 16 << (depth - 8);
+ const int ymax = 235 << (depth - 8);
+ const int imax = (1 << depth) - 1;
+ push_data.threshold = (s->pixel_black_th * (ymax - ymin) + ymin) / imax;
+ }
+
+ if (!s->initialized)
+ RET(init_filter(ctx));
+
+ err = ff_vk_get_pooled_buffer(vkctx, &s->sum_buf_pool, &sum_buf,
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+ NULL,
+ sizeof(BlackDetectBuf),
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+ if (err < 0)
+ return err;
+ sum_vk = (FFVkBuffer *)sum_buf->data;
+ sum = (BlackDetectBuf *) sum_vk->mapped_mem;
+
+ exec = ff_vk_exec_get(vkctx, &s->e);
+ ff_vk_exec_start(vkctx, exec);
+
+ RET(ff_vk_exec_add_dep_frame(vkctx, exec, in,
+ VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT));
+ RET(ff_vk_create_imageviews(vkctx, exec, in_views, in, FF_VK_REP_FLOAT));
+
+ ff_vk_shader_update_img_array(vkctx, exec, &s->shd, in, in_views, 0, 0,
+ VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE);
+
+ ff_vk_frame_barrier(vkctx, exec, in, img_bar, &nb_img_bar,
+ VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ VK_ACCESS_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_GENERAL,
+ VK_QUEUE_FAMILY_IGNORED);
+
+ /* zero sum buffer */
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_NONE,
+ .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = sum_vk->buf,
+ .size = sum_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ vk->CmdFillBuffer(exec->buf, sum_vk->buf, 0, sum_vk->size, 0x0);
+
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pImageMemoryBarriers = img_bar,
+ .imageMemoryBarrierCount = nb_img_bar,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT |
+ VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = sum_vk->buf,
+ .size = sum_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ RET(ff_vk_shader_update_desc_buffer(&s->vkctx, exec, &s->shd, 0, 1, 0,
+ sum_vk, 0, sum_vk->size,
+ VK_FORMAT_UNDEFINED));
+
+ ff_vk_exec_bind_shader(vkctx, exec, &s->shd);
+ ff_vk_shader_update_push_const(vkctx, exec, &s->shd, VK_SHADER_STAGE_COMPUTE_BIT,
+ 0, sizeof(push_data), &push_data);
+
+ vk->CmdDispatch(exec->buf,
+ FFALIGN(in->width, s->shd.lg_size[0]) / s->shd.lg_size[0],
+ FFALIGN(in->height, s->shd.lg_size[1]) / s->shd.lg_size[1],
+ s->shd.lg_size[2]);
+
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
+ .srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT |
+ VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_HOST_READ_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = sum_vk->buf,
+ .size = sum_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ RET(ff_vk_exec_submit(vkctx, exec));
+ ff_vk_exec_wait(vkctx, exec);
+ evaluate(link, in, sum);
+
+ av_buffer_unref(&sum_buf);
+ return ff_filter_frame(outlink, in);
+
+fail:
+ if (exec)
+ ff_vk_exec_discard_deps(&s->vkctx, exec);
+ av_frame_free(&in);
+ av_buffer_unref(&sum_buf);
+ return err;
+}
+
+static void blackdetect_vulkan_uninit(AVFilterContext *avctx)
+{
+ BlackDetectVulkanContext *s = avctx->priv;
+ FFVulkanContext *vkctx = &s->vkctx;
+
+ ff_vk_exec_pool_free(vkctx, &s->e);
+ ff_vk_shader_free(vkctx, &s->shd);
+
+ av_buffer_pool_uninit(&s->sum_buf_pool);
+
+ ff_vk_uninit(&s->vkctx);
+
+ s->initialized = 0;
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+ AVFilterContext *ctx = outlink->src;
+ BlackDetectVulkanContext *s = ctx->priv;
+ FFVulkanContext *vkctx = &s->vkctx;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(vkctx->input_format);
+
+ if (s->alpha && !(desc->flags & AV_PIX_FMT_FLAG_ALPHA)) {
+ av_log(ctx, AV_LOG_ERROR, "Input format %s does not have an alpha channel\n",
+ av_get_pix_fmt_name(vkctx->input_format));
+ return AVERROR(EINVAL);
+ }
+
+ if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_XYZ) ||
+ !(desc->flags & AV_PIX_FMT_FLAG_PLANAR)) {
+ av_log(ctx, AV_LOG_ERROR, "Input format %s is not planar YUV\n",
+ av_get_pix_fmt_name(vkctx->input_format));
+ return AVERROR(EINVAL);
+ }
+
+ return ff_vk_filter_config_output(outlink);
+}
+
+#define OFFSET(x) offsetof(BlackDetectVulkanContext, x)
+#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
+static const AVOption blackdetect_vulkan_options[] = {
+ { "d", "set minimum detected black duration in seconds", OFFSET(black_min_duration_time), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 0, DBL_MAX, FLAGS },
+ { "black_min_duration", "set minimum detected black duration in seconds", OFFSET(black_min_duration_time), AV_OPT_TYPE_DOUBLE, {.dbl=2}, 0, DBL_MAX, FLAGS },
+ { "picture_black_ratio_th", "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1, FLAGS },
+ { "pic_th", "set the picture black ratio threshold", OFFSET(picture_black_ratio_th), AV_OPT_TYPE_DOUBLE, {.dbl=.98}, 0, 1, FLAGS },
+ { "pixel_black_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1, FLAGS },
+ { "pix_th", "set the pixel black threshold", OFFSET(pixel_black_th), AV_OPT_TYPE_DOUBLE, {.dbl=.10}, 0, 1, FLAGS },
+ { "alpha", "check alpha instead of luma", OFFSET(alpha), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(blackdetect_vulkan);
+
+static const AVFilterPad blackdetect_vulkan_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = &blackdetect_vulkan_filter_frame,
+ .config_props = &ff_vk_filter_config_input,
+ },
+};
+
+static const AVFilterPad blackdetect_vulkan_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .config_props = &config_output,
+ },
+};
+
+const FFFilter ff_vf_blackdetect_vulkan = {
+ .p.name = "blackdetect_vulkan",
+ .p.description = NULL_IF_CONFIG_SMALL("Detect video intervals that are (almost) black."),
+ .p.priv_class = &blackdetect_vulkan_class,
+ .p.flags = AVFILTER_FLAG_HWDEVICE,
+ .priv_size = sizeof(BlackDetectVulkanContext),
+ .init = &ff_vk_filter_init,
+ .uninit = &blackdetect_vulkan_uninit,
+ FILTER_INPUTS(blackdetect_vulkan_inputs),
+ FILTER_OUTPUTS(blackdetect_vulkan_outputs),
+ FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
+ .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
+};
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 5+ messages in thread
* [FFmpeg-devel] [PATCH 4/5] avfilter/alphadetect_vulkan: add alpha type detection filter
2025-05-16 14:30 [FFmpeg-devel] [PATCH 1/5] avutil/vulkan: add YUVA pixel formats support Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 2/5] avfilter/vf_blackdetect: add alpha option Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 3/5] avfilter/blackdetect_vulkan: add hw accelerated blackdetect filter Niklas Haas
@ 2025-05-16 14:30 ` Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 5/5] avutil/vf_scdet_vulkan: add new filter Niklas Haas
3 siblings, 0 replies; 5+ messages in thread
From: Niklas Haas @ 2025-05-16 14:30 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Niklas Haas
From: Niklas Haas <git@haasn.dev>
This determines whether the input has any pixel values exceeding the alpha
channel. In this case, we can be sure that the input is not premultiplied.
In all other cases, the result in indeterminate. As such, we can only have
false negatives, but no false positives.
Signed-off-by: Niklas Haas <git@haasn.dev>
Sponsored-by: nxtedition
---
configure | 1 +
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/vf_alphadetect_vulkan.c | 401 ++++++++++++++++++++++++++++
4 files changed, 404 insertions(+)
create mode 100644 libavfilter/vf_alphadetect_vulkan.c
diff --git a/configure b/configure
index 2e5f9fea49..9556d7c86f 100755
--- a/configure
+++ b/configure
@@ -3869,6 +3869,7 @@ libzmq_protocol_deps="libzmq"
libzmq_protocol_select="network"
# filters
+alphadetect_vulkan_filter_deps="vulkan spirv_compiler"
ametadata_filter_deps="avformat"
amovie_filter_deps="avcodec avformat"
aresample_filter_deps="swresample"
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index e9d4566a2a..76a4f53932 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -192,6 +192,7 @@ OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o
# video filters
OBJS-$(CONFIG_ADDROI_FILTER) += vf_addroi.o
+OBJS-$(CONFIG_ALPHADETECT_VULKAN_FILTER) += vf_alphadetect_vulkan.o
OBJS-$(CONFIG_ALPHAEXTRACT_FILTER) += vf_extractplanes.o
OBJS-$(CONFIG_ALPHAMERGE_FILTER) += vf_alphamerge.o framesync.o
OBJS-$(CONFIG_AMPLIFY_FILTER) += vf_amplify.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 92890575f1..88204e070c 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -177,6 +177,7 @@ extern const FFFilter ff_asrc_sine;
extern const FFFilter ff_asink_anullsink;
extern const FFFilter ff_vf_addroi;
+extern const FFFilter ff_vf_alphadetect_vulkan;
extern const FFFilter ff_vf_alphaextract;
extern const FFFilter ff_vf_alphamerge;
extern const FFFilter ff_vf_amplify;
diff --git a/libavfilter/vf_alphadetect_vulkan.c b/libavfilter/vf_alphadetect_vulkan.c
new file mode 100644
index 0000000000..0712e23cfb
--- /dev/null
+++ b/libavfilter/vf_alphadetect_vulkan.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2025 (c) Niklas Haas
+ *
+ * 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 <float.h>
+#include "libavutil/vulkan_spirv.h"
+#include "libavutil/opt.h"
+#include "libavutil/timestamp.h"
+#include "vulkan_filter.h"
+
+#include "drawutils.h"
+#include "filters.h"
+#include "video.h"
+
+enum AlphaType {
+ UNDETERMINED = 0,
+ STRAIGHT,
+ NONE,
+};
+
+static const char *type2str(enum AlphaType type)
+{
+ switch(type) {
+ case NONE : return "none";
+ case STRAIGHT : return "straight";
+ case UNDETERMINED : return "undetermined";
+ }
+ return NULL;
+}
+
+typedef struct AlphaDetectVulkanContext {
+ FFVulkanContext vkctx;
+
+ int initialized;
+ FFVkExecPool e;
+ AVVulkanDeviceQueueFamily *qf;
+ FFVulkanShader shd;
+ AVBufferPool *det_buf_pool;
+
+ enum AlphaType type;
+} AlphaDetectVulkanContext;
+
+typedef struct AlphaDetectPushData {
+ float yscale, yoff;
+} AlphaDetectPushData;
+
+typedef struct AlphaDetectBuf {
+ uint32_t frame_straight;
+} AlphaDetectBuf;
+
+static void fmt_swizzle(FFVulkanShader *shd, enum AVPixelFormat fmt)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+ uint8_t map[4];
+ char swiz[5] = {0};
+
+ if (desc->flags & AV_PIX_FMT_FLAG_RGB) {
+ ff_fill_rgba_map(map, fmt);
+ } else if (desc->nb_components == 2) { /* ya */
+ GLSLC(1, color.a = color.y;);
+ return;
+ } else {
+ ff_fill_ayuv_map(map, fmt);
+ }
+
+ for (int i = 0; i < 4; i++)
+ swiz[i] = "rgba"[map[i]];
+
+ GLSLF(1, color = color.%s; ,swiz);
+}
+
+static av_cold int init_filter(AVFilterContext *ctx)
+{
+ int err;
+ uint8_t *spv_data;
+ size_t spv_len;
+ void *spv_opaque = NULL;
+ AlphaDetectVulkanContext *s = ctx->priv;
+ FFVulkanContext *vkctx = &s->vkctx;
+ FFVulkanShader *shd;
+ FFVkSPIRVCompiler *spv;
+ FFVulkanDescriptorSetBinding *desc;
+
+ const int planes = av_pix_fmt_count_planes(s->vkctx.input_format);
+ const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->vkctx.input_format);
+ if (!(pixdesc->flags & AV_PIX_FMT_FLAG_ALPHA)) { /* nothing to do */
+ s->initialized = 1;
+ s->type = NONE;
+ return 0;
+ }
+
+ spv = ff_vk_spirv_init();
+ if (!spv) {
+ av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0);
+ if (!s->qf) {
+ av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n");
+ err = AVERROR(ENOTSUP);
+ goto fail;
+ }
+
+ RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL));
+ RET(ff_vk_shader_init(vkctx, &s->shd, "alphadetect",
+ VK_SHADER_STAGE_COMPUTE_BIT,
+ (const char *[]) { "GL_KHR_shader_subgroup_vote" }, 1,
+ 32, 32, 1,
+ 0));
+ shd = &s->shd;
+
+ GLSLC(0, layout(push_constant, std430) uniform pushConstants { );
+ GLSLC(1, float yscale; );
+ GLSLC(1, float yoff; );
+ GLSLC(0, }; );
+
+ ff_vk_shader_add_push_const(shd, 0, sizeof(AlphaDetectPushData),
+ VK_SHADER_STAGE_COMPUTE_BIT);
+
+ desc = (FFVulkanDescriptorSetBinding []) {
+ {
+ .name = "input_img",
+ .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_FLOAT),
+ .mem_quali = "readonly",
+ .dimensions = 2,
+ .elems = av_pix_fmt_count_planes(s->vkctx.input_format),
+ .stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ }, {
+ .name = "det_buffer",
+ .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ .buf_content = "uint frame_straight;",
+ }
+ };
+
+ RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0));
+
+ GLSLC(0, void main() );
+ GLSLC(0, { );
+ GLSLC(1, const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); );
+ GLSLC(1, if (!IS_WITHIN(pos, imageSize(input_img[0]))) );
+ GLSLC(2, return; );
+
+ GLSLC(1, vec4 color = imageLoad(input_img[0], pos); );
+ for (int i = 1; i < planes; i++) {
+ const int idx = planes == 2 ? 3 : i;
+ if (!(pixdesc->flags & AV_PIX_FMT_FLAG_RGB) && idx != 3)
+ continue; /* skip loading chroma */
+ GLSLF(1, color[%d] = imageLoad(input_img[%d], pos).x; ,idx,i);
+ }
+ fmt_swizzle(shd, s->vkctx.input_format);
+ if (pixdesc->flags & AV_PIX_FMT_FLAG_RGB)
+ GLSLC(1, bool straight = any(greaterThan(color.rgb, color.a)); );
+ else
+ GLSLC(1, bool straight = yscale * color.x + yoff > color.a; );
+ GLSLC(1, if (subgroupAny(straight) && subgroupElect()) );
+ GLSLC(2, frame_straight = 1u; );
+ GLSLC(0, } );
+
+ RET(spv->compile_shader(vkctx, spv, &s->shd, &spv_data, &spv_len, "main",
+ &spv_opaque));
+ RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main"));
+
+ RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
+
+ s->initialized = 1;
+
+fail:
+ if (spv_opaque)
+ spv->free_shader(spv, &spv_opaque);
+ if (spv)
+ spv->uninit(&spv);
+
+ return err;
+}
+
+static int alphadetect_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
+{
+ int err;
+ AVFilterContext *ctx = link->dst;
+ AlphaDetectVulkanContext *s = ctx->priv;
+ AVFilterLink *outlink = ctx->outputs[0];
+
+ VkImageView in_views[AV_NUM_DATA_POINTERS];
+ VkImageMemoryBarrier2 img_bar[4];
+ int nb_img_bar = 0;
+
+ FFVulkanContext *vkctx = &s->vkctx;
+ FFVulkanFunctions *vk = &vkctx->vkfn;
+ FFVkExecContext *exec = NULL;
+ AVBufferRef *sum_buf = NULL;
+ FFVkBuffer *sum_vk;
+
+ AlphaDetectBuf *det;
+ AlphaDetectPushData push_data;
+
+ if (in->color_range == AVCOL_RANGE_JPEG) {
+ push_data.yscale = 1.0f;
+ push_data.yoff = 0.0f;
+ } else {
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(vkctx->input_format);
+ const int depth = desc->comp[0].depth;
+ const int ymin = 16 << (depth - 8);
+ const int ymax = 235 << (depth - 8);
+ const int imax = (1 << depth) - 1;
+ push_data.yscale = (float) imax / (ymax - ymin);
+ push_data.yoff = push_data.yscale * -ymin / (float) imax;
+ }
+
+ if (!s->initialized)
+ RET(init_filter(ctx));
+
+ if (s->type != UNDETERMINED)
+ return ff_filter_frame(outlink, in);
+
+ RET(ff_vk_get_pooled_buffer(vkctx, &s->det_buf_pool, &sum_buf,
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
+ VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
+ NULL,
+ sizeof(AlphaDetectBuf),
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT));
+ sum_vk = (FFVkBuffer *)sum_buf->data;
+ det = (AlphaDetectBuf *) sum_vk->mapped_mem;
+
+ exec = ff_vk_exec_get(vkctx, &s->e);
+ ff_vk_exec_start(vkctx, exec);
+
+ RET(ff_vk_exec_add_dep_frame(vkctx, exec, in,
+ VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT));
+ RET(ff_vk_create_imageviews(vkctx, exec, in_views, in, FF_VK_REP_FLOAT));
+
+ ff_vk_shader_update_img_array(vkctx, exec, &s->shd, in, in_views, 0, 0,
+ VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE);
+
+ ff_vk_frame_barrier(vkctx, exec, in, img_bar, &nb_img_bar,
+ VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ VK_ACCESS_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_GENERAL,
+ VK_QUEUE_FAMILY_IGNORED);
+
+ /* zero det buffer */
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_NONE,
+ .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = sum_vk->buf,
+ .size = sum_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ vk->CmdFillBuffer(exec->buf, sum_vk->buf, 0, sum_vk->size, 0x0);
+
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pImageMemoryBarriers = img_bar,
+ .imageMemoryBarrierCount = nb_img_bar,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = sum_vk->buf,
+ .size = sum_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ RET(ff_vk_shader_update_desc_buffer(&s->vkctx, exec, &s->shd, 0, 1, 0,
+ sum_vk, 0, sum_vk->size,
+ VK_FORMAT_UNDEFINED));
+
+ ff_vk_exec_bind_shader(vkctx, exec, &s->shd);
+ ff_vk_shader_update_push_const(vkctx, exec, &s->shd, VK_SHADER_STAGE_COMPUTE_BIT,
+ 0, sizeof(push_data), &push_data);
+
+ vk->CmdDispatch(exec->buf,
+ FFALIGN(in->width, s->shd.lg_size[0]) / s->shd.lg_size[0],
+ FFALIGN(in->height, s->shd.lg_size[1]) / s->shd.lg_size[1],
+ s->shd.lg_size[2]);
+
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
+ .srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_HOST_READ_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = sum_vk->buf,
+ .size = sum_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ RET(ff_vk_exec_submit(vkctx, exec));
+ ff_vk_exec_wait(vkctx, exec);
+ if (s->type == UNDETERMINED && det->frame_straight)
+ s->type = STRAIGHT;
+
+ av_buffer_unref(&sum_buf);
+ return ff_filter_frame(outlink, in);
+
+fail:
+ if (exec)
+ ff_vk_exec_discard_deps(&s->vkctx, exec);
+ av_frame_free(&in);
+ av_buffer_unref(&sum_buf);
+ return err;
+}
+
+static void alphadetect_vulkan_uninit(AVFilterContext *avctx)
+{
+ AlphaDetectVulkanContext *s = avctx->priv;
+ FFVulkanContext *vkctx = &s->vkctx;
+
+ if (s->initialized)
+ av_log(avctx, AV_LOG_INFO, "Alpha detection: %s\n", type2str(s->type));
+
+ ff_vk_exec_pool_free(vkctx, &s->e);
+ ff_vk_shader_free(vkctx, &s->shd);
+
+ av_buffer_pool_uninit(&s->det_buf_pool);
+
+ ff_vk_uninit(&s->vkctx);
+
+ s->initialized = 0;
+}
+
+static const AVOption alphadetect_vulkan_options[] = {
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(alphadetect_vulkan);
+
+static const AVFilterPad alphadetect_vulkan_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = &alphadetect_vulkan_filter_frame,
+ .config_props = &ff_vk_filter_config_input,
+ },
+};
+
+static const AVFilterPad alphadetect_vulkan_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .config_props = &ff_vk_filter_config_output,
+ },
+};
+
+const FFFilter ff_vf_alphadetect_vulkan = {
+ .p.name = "alphadetect_vulkan",
+ .p.description = NULL_IF_CONFIG_SMALL("Detects if input is premultiplied or straight alpha."),
+ .p.priv_class = &alphadetect_vulkan_class,
+ .p.flags = AVFILTER_FLAG_HWDEVICE,
+ .priv_size = sizeof(AlphaDetectVulkanContext),
+ .init = &ff_vk_filter_init,
+ .uninit = &alphadetect_vulkan_uninit,
+ FILTER_INPUTS(alphadetect_vulkan_inputs),
+ FILTER_OUTPUTS(alphadetect_vulkan_outputs),
+ FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
+ .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
+};
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 5+ messages in thread
* [FFmpeg-devel] [PATCH 5/5] avutil/vf_scdet_vulkan: add new filter
2025-05-16 14:30 [FFmpeg-devel] [PATCH 1/5] avutil/vulkan: add YUVA pixel formats support Niklas Haas
` (2 preceding siblings ...)
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 4/5] avfilter/alphadetect_vulkan: add alpha type detection filter Niklas Haas
@ 2025-05-16 14:30 ` Niklas Haas
3 siblings, 0 replies; 5+ messages in thread
From: Niklas Haas @ 2025-05-16 14:30 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Niklas Haas
From: Niklas Haas <git@haasn.dev>
Carbon copy of vf_scdet.
Signed-off-by: Niklas Haas <git@haasn.dev>
Sponsored-by: nxtedition
---
configure | 1 +
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/vf_scdet_vulkan.c | 413 ++++++++++++++++++++++++++++++++++
4 files changed, 416 insertions(+)
create mode 100644 libavfilter/vf_scdet_vulkan.c
diff --git a/configure b/configure
index 9556d7c86f..e42ebbad06 100755
--- a/configure
+++ b/configure
@@ -3986,6 +3986,7 @@ vpp_amf_filter_deps="amf"
scale_qsv_filter_deps="libmfx"
scale_qsv_filter_select="qsvvpp"
scdet_filter_select="scene_sad"
+scdet_vulkan_filter_deps="vulkan spirv_compiler"
select_filter_select="scene_sad"
sharpness_vaapi_filter_deps="vaapi"
showcqt_filter_deps="avformat swscale"
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 76a4f53932..79113b3948 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -473,6 +473,7 @@ OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vf_scale_vulkan.o vulkan.o vulka
OBJS-$(CONFIG_SCALE2REF_FILTER) += vf_scale.o scale_eval.o framesync.o
OBJS-$(CONFIG_SCALE2REF_NPP_FILTER) += vf_scale_npp.o scale_eval.o
OBJS-$(CONFIG_SCDET_FILTER) += vf_scdet.o
+OBJS-$(CONFIG_SCDET_VULKAN_FILTER) += vf_scdet_vulkan.o
OBJS-$(CONFIG_SCHARR_FILTER) += vf_convolution.o
OBJS-$(CONFIG_SCROLL_FILTER) += vf_scroll.o
OBJS-$(CONFIG_SEGMENT_FILTER) += f_segment.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 88204e070c..ffa8397c62 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -445,6 +445,7 @@ extern const FFFilter ff_vf_scale_vulkan;
extern const FFFilter ff_vf_scale2ref;
extern const FFFilter ff_vf_scale2ref_npp;
extern const FFFilter ff_vf_scdet;
+extern const FFFilter ff_vf_scdet_vulkan;
extern const FFFilter ff_vf_scharr;
extern const FFFilter ff_vf_scroll;
extern const FFFilter ff_vf_segment;
diff --git a/libavfilter/vf_scdet_vulkan.c b/libavfilter/vf_scdet_vulkan.c
new file mode 100644
index 0000000000..a12c986bb8
--- /dev/null
+++ b/libavfilter/vf_scdet_vulkan.c
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2025 (c) Niklas Haas
+ *
+ * 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/vulkan_spirv.h"
+#include "libavutil/opt.h"
+#include "libavutil/timestamp.h"
+#include "vulkan_filter.h"
+
+#include "filters.h"
+
+typedef struct SceneDetectVulkanContext {
+ FFVulkanContext vkctx;
+
+ int initialized;
+ FFVkExecPool e;
+ AVVulkanDeviceQueueFamily *qf;
+ FFVulkanShader shd;
+ AVBufferPool *det_buf_pool;
+
+ double threshold;
+ int sc_pass;
+
+ int nb_planes;
+ double prev_mafd;
+ AVFrame *prev;
+ AVFrame *cur;
+} SceneDetectVulkanContext;
+
+typedef struct SceneDetectBuf {
+#define SLICES 16
+ uint32_t frame_sad[SLICES];
+} SceneDetectBuf;
+
+static av_cold int init_filter(AVFilterContext *ctx)
+{
+ int err;
+ uint8_t *spv_data;
+ size_t spv_len;
+ void *spv_opaque = NULL;
+ SceneDetectVulkanContext *s = ctx->priv;
+ FFVulkanContext *vkctx = &s->vkctx;
+ FFVulkanShader *shd;
+ FFVkSPIRVCompiler *spv;
+ FFVulkanDescriptorSetBinding *desc;
+
+ const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->vkctx.input_format);
+ const int lumaonly = !(pixdesc->flags & AV_PIX_FMT_FLAG_RGB) &&
+ (pixdesc->flags & AV_PIX_FMT_FLAG_PLANAR);
+ s->nb_planes = lumaonly ? 1 : av_pix_fmt_count_planes(s->vkctx.input_format);
+
+ spv = ff_vk_spirv_init();
+ if (!spv) {
+ av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n");
+ return AVERROR_EXTERNAL;
+ }
+
+ s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0);
+ if (!s->qf) {
+ av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n");
+ err = AVERROR(ENOTSUP);
+ goto fail;
+ }
+
+ RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL));
+ RET(ff_vk_shader_init(vkctx, &s->shd, "scdet",
+ VK_SHADER_STAGE_COMPUTE_BIT,
+ (const char *[]) { "GL_KHR_shader_subgroup_arithmetic" }, 1,
+ 32, 32, 1,
+ 0));
+ shd = &s->shd;
+
+ desc = (FFVulkanDescriptorSetBinding []) {
+ {
+ .name = "prev_img",
+ .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_UINT),
+ .mem_quali = "readonly",
+ .dimensions = 2,
+ .elems = av_pix_fmt_count_planes(s->vkctx.input_format),
+ .stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ }, {
+ .name = "cur_img",
+ .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_UINT),
+ .mem_quali = "readonly",
+ .dimensions = 2,
+ .elems = av_pix_fmt_count_planes(s->vkctx.input_format),
+ .stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ }, {
+ .name = "sad_buffer",
+ .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ .stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ .buf_content = "uint frame_sad[];",
+ }
+ };
+
+ RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0));
+
+ GLSLC(0, shared uint wg_sum; );
+ GLSLC(0, void main() );
+ GLSLC(0, { );
+ GLSLF(1, const uint slice = gl_WorkGroupID.x %% %u; ,SLICES);
+ GLSLC(1, const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); );
+ GLSLC(1, wg_sum = 0; );
+ GLSLC(1, barrier(); );
+ for (int i = 0; i < s->nb_planes; i++) {
+ GLSLF(1, if (IS_WITHIN(pos, imageSize(cur_img[%d]))) { ,i);
+ GLSLF(2, uvec4 prev = imageLoad(prev_img[%d], pos); ,i);
+ GLSLF(2, uvec4 cur = imageLoad(cur_img[%d], pos); ,i);
+ GLSLC(2, uvec4 sad = abs(ivec4(cur) - ivec4(prev)); );
+ GLSLC(2, uint sum = subgroupAdd(sad.x + sad.y + sad.z); );
+ GLSLC(2, if (subgroupElect()) );
+ GLSLC(3, atomicAdd(wg_sum, sum); );
+ GLSLC(1, } );
+ }
+ GLSLC(1, barrier(); );
+ GLSLC(1, if (gl_LocalInvocationIndex == 0) );
+ GLSLC(2, atomicAdd(frame_sad[slice], wg_sum); );
+ GLSLC(0, } );
+
+ RET(spv->compile_shader(vkctx, spv, &s->shd, &spv_data, &spv_len, "main",
+ &spv_opaque));
+ RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main"));
+
+ RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
+
+ s->initialized = 1;
+
+fail:
+ if (spv_opaque)
+ spv->free_shader(spv, &spv_opaque);
+ if (spv)
+ spv->uninit(&spv);
+
+ return err;
+}
+
+static double evaluate(AVFilterContext *ctx, const SceneDetectBuf *buf)
+{
+ SceneDetectVulkanContext *s = ctx->priv;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(s->vkctx.input_format);
+ const AVFilterLink *inlink = ctx->inputs[0];
+ uint64_t count;
+ double mafd, diff;
+
+ uint64_t sad = 0;
+ for (int i = 0; i < SLICES; i++)
+ sad += buf->frame_sad[i];
+
+ av_assert2(s->nb_planes == 1 || !(desc->log2_chroma_w || desc->log2_chroma_h));
+ count = s->nb_planes * inlink->w * inlink->h;
+ mafd = (double) sad * 100.0 / count / (1ULL << desc->comp[0].depth);
+ diff = fabs(mafd - s->prev_mafd);
+ s->prev_mafd = mafd;
+
+ return av_clipf(FFMIN(mafd, diff), 0.0, 100.0);
+}
+
+static int scdet_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
+{
+ int err;
+ AVFilterContext *ctx = link->dst;
+ SceneDetectVulkanContext *s = ctx->priv;
+ AVFilterLink *outlink = ctx->outputs[0];
+
+ VkImageView prev_views[AV_NUM_DATA_POINTERS];
+ VkImageView cur_views[AV_NUM_DATA_POINTERS];
+ VkImageMemoryBarrier2 img_bar[8];
+ int nb_img_bar = 0;
+
+ FFVulkanContext *vkctx = &s->vkctx;
+ FFVulkanFunctions *vk = &vkctx->vkfn;
+ FFVkExecContext *exec = NULL;
+ AVBufferRef *buf = NULL;
+ FFVkBuffer *buf_vk;
+
+ SceneDetectBuf *sad;
+ double score = 0.0;
+ char str[64];
+
+ if (!s->initialized)
+ RET(init_filter(ctx));
+
+ av_frame_free(&s->prev);
+ s->prev = s->cur;
+ s->cur = av_frame_clone(in);
+ if (!s->prev)
+ goto done;
+
+ RET(ff_vk_get_pooled_buffer(vkctx, &s->det_buf_pool, &buf,
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
+ VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
+ NULL,
+ sizeof(SceneDetectBuf),
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT));
+ buf_vk = (FFVkBuffer *)buf->data;
+ sad = (SceneDetectBuf *) buf_vk->mapped_mem;
+
+ exec = ff_vk_exec_get(vkctx, &s->e);
+ ff_vk_exec_start(vkctx, exec);
+
+ RET(ff_vk_exec_add_dep_frame(vkctx, exec, s->prev,
+ VK_PIPELINE_STAGE_2_NONE,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT));
+ RET(ff_vk_create_imageviews(vkctx, exec, prev_views, s->prev, FF_VK_REP_UINT));
+
+ ff_vk_shader_update_img_array(vkctx, exec, &s->shd, s->prev, prev_views, 0, 0,
+ VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE);
+
+ ff_vk_frame_barrier(vkctx, exec, s->prev, img_bar, &nb_img_bar,
+ VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ VK_ACCESS_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_GENERAL,
+ VK_QUEUE_FAMILY_IGNORED);
+
+ RET(ff_vk_exec_add_dep_frame(vkctx, exec, s->cur,
+ VK_PIPELINE_STAGE_2_NONE,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT));
+ RET(ff_vk_create_imageviews(vkctx, exec, cur_views, s->cur, FF_VK_REP_UINT));
+
+ ff_vk_shader_update_img_array(vkctx, exec, &s->shd, s->cur, cur_views, 0, 1,
+ VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE);
+
+ ff_vk_frame_barrier(vkctx, exec, s->cur, img_bar, &nb_img_bar,
+ VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ VK_ACCESS_SHADER_READ_BIT,
+ VK_IMAGE_LAYOUT_GENERAL,
+ VK_QUEUE_FAMILY_IGNORED);
+
+ /* zero buffer */
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_NONE,
+ .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = buf_vk->buf,
+ .size = buf_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ vk->CmdFillBuffer(exec->buf, buf_vk->buf, 0, buf_vk->size, 0x0);
+
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pImageMemoryBarriers = img_bar,
+ .imageMemoryBarrierCount = nb_img_bar,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT |
+ VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = buf_vk->buf,
+ .size = buf_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ RET(ff_vk_shader_update_desc_buffer(&s->vkctx, exec, &s->shd, 0, 2, 0,
+ buf_vk, 0, buf_vk->size,
+ VK_FORMAT_UNDEFINED));
+
+ ff_vk_exec_bind_shader(vkctx, exec, &s->shd);
+
+ vk->CmdDispatch(exec->buf,
+ FFALIGN(in->width, s->shd.lg_size[0]) / s->shd.lg_size[0],
+ FFALIGN(in->height, s->shd.lg_size[1]) / s->shd.lg_size[1],
+ s->shd.lg_size[2]);
+
+ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
+ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+ .pBufferMemoryBarriers = &(VkBufferMemoryBarrier2) {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
+ .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
+ .dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
+ .srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT |
+ VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_HOST_READ_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = buf_vk->buf,
+ .size = buf_vk->size,
+ .offset = 0,
+ },
+ .bufferMemoryBarrierCount = 1,
+ });
+
+ RET(ff_vk_exec_submit(vkctx, exec));
+ ff_vk_exec_wait(vkctx, exec);
+ score = evaluate(ctx, sad);
+
+done:
+ snprintf(str, sizeof(str), "%0.3f", s->prev_mafd);
+ av_dict_set(&in->metadata, "lavfi.scd.mafd", str, 0);
+ snprintf(str, sizeof(str), "%0.3f", score);
+ av_dict_set(&in->metadata, "lavfi.scd.score", str, 0);
+
+ if (score >= s->threshold) {
+ const char *pts = av_ts2timestr(in->pts, &link->time_base);
+ av_dict_set(&in->metadata, "lavfi.scd.time", pts, 0);
+ av_log(s, AV_LOG_INFO, "lavfi.scd.score: %.3f, lavfi.scd.time: %s\n",
+ score, pts);
+ }
+
+ av_buffer_unref(&buf);
+ if (!s->sc_pass || score >= s->threshold)
+ return ff_filter_frame(outlink, in);
+ else {
+ av_frame_free(&in);
+ return 0;
+ }
+
+fail:
+ if (exec)
+ ff_vk_exec_discard_deps(&s->vkctx, exec);
+ av_frame_free(&in);
+ av_buffer_unref(&buf);
+ return err;
+}
+
+static void scdet_vulkan_uninit(AVFilterContext *avctx)
+{
+ SceneDetectVulkanContext *s = avctx->priv;
+ FFVulkanContext *vkctx = &s->vkctx;
+
+ av_frame_free(&s->prev);
+ av_frame_free(&s->cur);
+
+ ff_vk_exec_pool_free(vkctx, &s->e);
+ ff_vk_shader_free(vkctx, &s->shd);
+
+ av_buffer_pool_uninit(&s->det_buf_pool);
+
+ ff_vk_uninit(&s->vkctx);
+
+ s->initialized = 0;
+}
+
+#define OFFSET(x) offsetof(SceneDetectVulkanContext, x)
+#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
+static const AVOption scdet_vulkan_options[] = {
+ { "threshold", "set scene change detect threshold", OFFSET(threshold), AV_OPT_TYPE_DOUBLE, {.dbl = 10.}, 0, 100., FLAGS },
+ { "t", "set scene change detect threshold", OFFSET(threshold), AV_OPT_TYPE_DOUBLE, {.dbl = 10.}, 0, 100., FLAGS },
+ { "sc_pass", "Set the flag to pass scene change frames", OFFSET(sc_pass), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, FLAGS },
+ { "s", "Set the flag to pass scene change frames", OFFSET(sc_pass), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, FLAGS },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(scdet_vulkan);
+
+static const AVFilterPad scdet_vulkan_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = &scdet_vulkan_filter_frame,
+ .config_props = &ff_vk_filter_config_input,
+ },
+};
+
+static const AVFilterPad scdet_vulkan_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .config_props = &ff_vk_filter_config_output,
+ },
+};
+
+const FFFilter ff_vf_scdet_vulkan = {
+ .p.name = "scdet_vulkan",
+ .p.description = NULL_IF_CONFIG_SMALL("Detect video scene change"),
+ .p.priv_class = &scdet_vulkan_class,
+ .p.flags = AVFILTER_FLAG_HWDEVICE,
+ .priv_size = sizeof(SceneDetectVulkanContext),
+ .init = &ff_vk_filter_init,
+ .uninit = &scdet_vulkan_uninit,
+ FILTER_INPUTS(scdet_vulkan_inputs),
+ FILTER_OUTPUTS(scdet_vulkan_outputs),
+ FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
+ .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
+};
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-05-16 14:31 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-05-16 14:30 [FFmpeg-devel] [PATCH 1/5] avutil/vulkan: add YUVA pixel formats support Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 2/5] avfilter/vf_blackdetect: add alpha option Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 3/5] avfilter/blackdetect_vulkan: add hw accelerated blackdetect filter Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 4/5] avfilter/alphadetect_vulkan: add alpha type detection filter Niklas Haas
2025-05-16 14:30 ` [FFmpeg-devel] [PATCH 5/5] avutil/vf_scdet_vulkan: add new filter Niklas Haas
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git