FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_blackframe.c
Date: 2026-04-24 19:58:39
Exec Total Coverage
Lines: 0 37 0.0%
Functions: 0 2 0.0%
Branches: 0 10 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2010 Stefano Sabatini
3 * Copyright (c) 2006 Ivo van Poorten
4 * Copyright (c) 2006 Julian Hall
5 * Copyright (c) 2002-2003 Brian J. Murrell
6 *
7 * This file is part of FFmpeg.
8 *
9 * FFmpeg is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * FFmpeg is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24 /**
25 * @file
26 * Search for black frames to detect scene transitions.
27 * Ported from MPlayer libmpcodecs/vf_blackframe.c.
28 */
29
30 #include <stdio.h>
31 #include <inttypes.h>
32 #include <stdatomic.h>
33
34 #include "libavutil/internal.h"
35 #include "libavutil/opt.h"
36 #include "avfilter.h"
37 #include "filters.h"
38 #include "video.h"
39
40 typedef struct BlackFrameContext {
41 const AVClass *class;
42 int bamount; ///< black amount
43 int bthresh; ///< black threshold
44 unsigned int frame; ///< frame number
45 atomic_uint nblack; ///< number of black pixels counted so far
46 unsigned int last_keyframe; ///< frame number of the last received key-frame
47 } BlackFrameContext;
48
49 typedef struct ThreadData {
50 const uint8_t *data;
51 int linesize;
52 int bthresh;
53 int width;
54 int height;
55 BlackFrameContext *s;
56 } ThreadData;
57
58 static const enum AVPixelFormat pix_fmts[] = {
59 AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_GRAY8, AV_PIX_FMT_NV12,
60 AV_PIX_FMT_NV21, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV411P,
61 AV_PIX_FMT_NONE
62 };
63
64 #define SET_META(key, format, value) \
65 snprintf(buf, sizeof(buf), format, value); \
66 av_dict_set(metadata, key, buf, 0)
67
68 static int blackframe_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
69 {
70 ThreadData *td = arg;
71 int slice_start = (td->height * jobnr) / nb_jobs;
72 int slice_end = (td->height * (jobnr+1)) / nb_jobs;
73 const uint8_t *p;
74 unsigned int black_pixels_count = 0;
75
76 p = td->data + slice_start * td->linesize;
77
78 for (int y = slice_start; y < slice_end; y++) {
79 for (int x = 0; x < td->width; x++)
80 black_pixels_count += p[x] < td->bthresh;
81 p += td->linesize;
82 }
83
84 atomic_fetch_add_explicit(&td->s->nblack, black_pixels_count, memory_order_relaxed);
85 return 0;
86 }
87
88 static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
89 {
90 AVFilterContext *ctx = inlink->dst;
91 BlackFrameContext *s = ctx->priv;
92 ThreadData td;
93 int pblack = 0;
94 int nb_threads = ff_filter_get_nb_threads(ctx);
95 int nb_jobs = FFMIN(inlink->h, nb_threads);
96 AVDictionary **metadata;
97 char buf[32];
98
99 atomic_init(&s->nblack, 0);
100
101 td.data = frame->data[0];
102 td.linesize = frame->linesize[0];
103 td.width = inlink->w;
104 td.height = inlink->h;
105 td.bthresh = s->bthresh;
106 td.s = s;
107
108 ff_filter_execute(ctx, blackframe_slice, &td, NULL, nb_jobs);
109
110 if (frame->flags & AV_FRAME_FLAG_KEY)
111 s->last_keyframe = s->frame;
112
113 pblack = atomic_load_explicit(&s->nblack, memory_order_relaxed) * 100 / (inlink->w * inlink->h);
114 if (pblack >= s->bamount) {
115 metadata = &frame->metadata;
116
117 av_log(ctx, AV_LOG_INFO, "frame:%u pblack:%u pts:%"PRId64" t:%f "
118 "type:%c last_keyframe:%d\n",
119 s->frame, pblack, frame->pts,
120 frame->pts == AV_NOPTS_VALUE ? -1 : frame->pts * av_q2d(inlink->time_base),
121 av_get_picture_type_char(frame->pict_type), s->last_keyframe);
122
123 SET_META("lavfi.blackframe.pblack", "%u", pblack);
124 }
125
126 s->frame++;
127 return ff_filter_frame(inlink->dst->outputs[0], frame);
128 }
129
130 #define OFFSET(x) offsetof(BlackFrameContext, x)
131 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
132 static const AVOption blackframe_options[] = {
133 { "amount", "percentage of the pixels that have to be below the threshold "
134 "for the frame to be considered black", OFFSET(bamount), AV_OPT_TYPE_INT, { .i64 = 98 }, 0, 100, FLAGS },
135 { "threshold", "threshold below which a pixel value is considered black",
136 OFFSET(bthresh), AV_OPT_TYPE_INT, { .i64 = 32 }, 0, 255, FLAGS },
137 { "thresh", "threshold below which a pixel value is considered black",
138 OFFSET(bthresh), AV_OPT_TYPE_INT, { .i64 = 32 }, 0, 255, FLAGS },
139 { NULL }
140 };
141
142 AVFILTER_DEFINE_CLASS(blackframe);
143
144 static const AVFilterPad avfilter_vf_blackframe_inputs[] = {
145 {
146 .name = "default",
147 .type = AVMEDIA_TYPE_VIDEO,
148 .filter_frame = filter_frame,
149 },
150 };
151
152 const FFFilter ff_vf_blackframe = {
153 .p.name = "blackframe",
154 .p.description = NULL_IF_CONFIG_SMALL("Detect frames that are (almost) black."),
155 .p.priv_class = &blackframe_class,
156 .p.flags = AVFILTER_FLAG_METADATA_ONLY | AVFILTER_FLAG_SLICE_THREADS,
157 .priv_size = sizeof(BlackFrameContext),
158 FILTER_INPUTS(avfilter_vf_blackframe_inputs),
159 FILTER_OUTPUTS(ff_video_default_filterpad),
160 FILTER_PIXFMTS_ARRAY(pix_fmts),
161 };
162