FFmpeg
Loading...
Searching...
No Matches
vf_colordetect.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2025 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/**
22 * @file
23 * Video color space detector, tries to auto-detect YUV range and alpha mode.
24 */
25
26#include <stdbool.h>
27#include <stdatomic.h>
28
29#include "config.h"
30
31#include "libavutil/mem.h"
32#include "libavutil/opt.h"
33#include "libavutil/pixdesc.h"
34
35#include "avfilter.h"
36#include "filters.h"
37#include "formats.h"
38#include "video.h"
39
40#include "vf_colordetectdsp.h"
41
46
47typedef struct ColorDetectContext {
48 const AVClass *class;
50 unsigned mode;
51 float threshold;
52
55 int depth;
56 int range;
57 int idx_a;
58
59 /* for color range detection only */
62
63 /* for alpha detection only */
65 int offset;
66
67 atomic_int detected_range; // enum AVColorRange
68 atomic_int detected_alpha; // enum FFAlphaDetect
70
71#define OFFSET(x) offsetof(ColorDetectContext, x)
72#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
73
74static const AVOption colordetect_options[] = {
75 { "mode", "Image properties to detect", OFFSET(mode), AV_OPT_TYPE_FLAGS, {.i64 = -1}, 0, UINT_MAX, FLAGS, .unit = "mode" },
76 { "color_range", "Detect (YUV) color range", 0, AV_OPT_TYPE_CONST, {.i64 = COLOR_DETECT_COLOR_RANGE}, 0, 0, FLAGS, .unit = "mode" },
77 { "alpha_mode", "Detect alpha mode", 0, AV_OPT_TYPE_CONST, {.i64 = COLOR_DETECT_ALPHA_MODE }, 0, 0, FLAGS, .unit = "mode" },
78 { "all", "Detect all supported properties", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, FLAGS, .unit = "mode" },
79
80 /* Note: threshold should not be increased past ~0.4 as it overflows 8-bit SIMD otherwise */
81 { "threshold", "Detection threshold, as a fraction of the full range", OFFSET(threshold), AV_OPT_TYPE_FLOAT, {.dbl = 0.001}, 0.0, 0.05, FLAGS },
82 { NULL }
83};
84
86
88 AVFilterFormatsConfig **cfg_in,
89 AVFilterFormatsConfig **cfg_out)
90{
91 int want_flags = AV_PIX_FMT_FLAG_PLANAR;
92 int reject_flags = AV_PIX_FMT_FLAG_PAL | AV_PIX_FMT_FLAG_HWACCEL |
95
96 if (HAVE_BIGENDIAN) {
97 want_flags |= AV_PIX_FMT_FLAG_BE;
98 } else {
99 reject_flags |= AV_PIX_FMT_FLAG_BE;
100 }
101
102 AVFilterFormats *formats = ff_formats_pixdesc_filter(want_flags, reject_flags);
103 return ff_set_common_formats2(ctx, cfg_in, cfg_out, formats);
104}
105
106static int config_input(AVFilterLink *inlink)
107{
108 AVFilterContext *ctx = inlink->dst;
109 ColorDetectContext *s = ctx->priv;
111 const int depth = desc->comp[0].depth;
112 const int range = (1 << depth) - 1;
113 const int threshold = lrintf(s->threshold * range);
114 const int mpeg_min = 16 << (depth - 8);
115 const int mpeg_max = 235 << (depth - 8);
116 if (depth > 16) /* not currently possible; prevent future bugs */
117 return AVERROR(ENOTSUP);
118
119 s->desc = desc;
120 s->depth = depth;
121 s->range = range;
122 s->nb_threads = ff_filter_get_nb_threads(ctx);
123
124 /* Color range detection: */
125 s->mpeg_min = av_clip_uintp2(mpeg_min - threshold, depth);
126 s->mpeg_max = av_clip_uintp2(mpeg_max + threshold, depth);
127
128 /**
129 * Alpha mode detection:
130 *
131 * To check if a value is out of range, we need to compare the color value
132 * against the maximum possible color for a given alpha value.
133 * x > ((mpeg_max - mpeg_min) / pixel_max) * a + mpeg_min + threshold
134 *
135 * This simplifies to:
136 * (x - mpeg_min - threshold) * pixel_max > (mpeg_max - mpeg_min) * a
137 * = range * x - offset > mpeg_range * a in the below formula.
138 *
139 * We subtract an additional offset of (1 << (depth - 1)) to account for
140 * rounding errors in the value of `x`.
141 *
142 * For full range input this degenerates to `x > a + threshold`, so the
143 * threshold is passed through directly, without the `range` scaling.
144 */
145 if (inlink->color_range == AVCOL_RANGE_JPEG) {
146 s->mpeg_range = range;
147 s->offset = threshold;
148 } else {
149 s->mpeg_range = mpeg_max - mpeg_min;
150 s->offset = range * (mpeg_min + threshold) + (1 << (depth - 1));
151 }
152
153 if (desc->flags & AV_PIX_FMT_FLAG_RGB) {
154 atomic_init(&s->detected_range, AVCOL_RANGE_JPEG);
155 } else {
156 atomic_init(&s->detected_range, AVCOL_RANGE_UNSPECIFIED);
157 }
158
159 if (desc->flags & AV_PIX_FMT_FLAG_ALPHA) {
160 s->idx_a = desc->comp[desc->nb_components - 1].plane;
161 atomic_init(&s->detected_alpha, FF_ALPHA_UNDETERMINED);
162 } else {
163 atomic_init(&s->detected_alpha, FF_ALPHA_NONE);
164 }
165
166 ff_color_detect_dsp_init(&s->dsp, depth, s->offset, inlink->color_range);
167 return 0;
168}
169
171 int jobnr, int nb_jobs)
172{
173 ColorDetectContext *s = ctx->priv;
174 const AVFrame *in = arg;
175 const ptrdiff_t stride = in->linesize[0];
176 const int y_start = ff_slice_pos(in->height, jobnr, nb_jobs);
177 const int y_end = ff_slice_pos(in->height, jobnr + 1, nb_jobs);
178 const int h_slice = y_end - y_start;
179
180 if (s->dsp.detect_range(in->data[0] + y_start * stride, stride,
181 in->width, h_slice, s->mpeg_min, s->mpeg_max))
182 atomic_store_explicit(&s->detected_range, AVCOL_RANGE_JPEG,
184
185 return 0;
186}
187
189 int jobnr, int nb_jobs)
190{
191 ColorDetectContext *s = ctx->priv;
192 const AVFrame *in = arg;
193 const int w = in->width;
194 const int h = in->height;
195 const int y_start = ff_slice_pos(h, jobnr, nb_jobs);
196 const int y_end = ff_slice_pos(h, jobnr + 1, nb_jobs);
197 const int h_slice = y_end - y_start;
198
199 const int nb_planes = (s->desc->flags & AV_PIX_FMT_FLAG_RGB) ? 3 : 1;
200 const ptrdiff_t alpha_stride = in->linesize[s->idx_a];
201 const uint8_t *alpha = in->data[s->idx_a] + y_start * alpha_stride;
202
203 int ret = 0;
204 for (int i = 0; i < nb_planes; i++) {
205 const ptrdiff_t stride = in->linesize[i];
206 ret = s->dsp.detect_alpha(in->data[i] + y_start * stride, stride,
207 alpha, alpha_stride, w, h_slice, s->range,
208 s->mpeg_range, s->offset);
209 ret |= atomic_fetch_or_explicit(&s->detected_alpha, ret, memory_order_relaxed);
210 if (ret == FF_ALPHA_STRAIGHT)
211 break;
212 }
213
214 return 0;
215}
216
217static int filter_frame(AVFilterLink *inlink, AVFrame *in)
218{
219 AVFilterContext *ctx = inlink->dst;
220 ColorDetectContext *s = ctx->priv;
221 const int nb_threads = FFMIN(inlink->h, s->nb_threads);
222
223 enum AVColorRange detected_range = atomic_load_explicit(&s->detected_range, memory_order_relaxed);
224 if (s->mode & COLOR_DETECT_COLOR_RANGE && detected_range == AVCOL_RANGE_UNSPECIFIED)
225 ff_filter_execute(ctx, detect_range, in, NULL, nb_threads);
226
227 enum FFAlphaDetect detected_alpha = atomic_load_explicit(&s->detected_alpha, memory_order_relaxed);
228 if (s->mode & COLOR_DETECT_ALPHA_MODE && detected_alpha != FF_ALPHA_NONE &&
229 detected_alpha != FF_ALPHA_STRAIGHT)
230 ff_filter_execute(ctx, detect_alpha, in, NULL, nb_threads);
231
232 return ff_filter_frame(inlink->dst->outputs[0], in);
233}
234
236{
237 ColorDetectContext *s = ctx->priv;
238 if (!s->mode)
239 return;
240
241 av_log(ctx, AV_LOG_INFO, "Detected color properties:\n");
242 if (s->mode & COLOR_DETECT_COLOR_RANGE) {
243 enum AVColorRange detected_range = atomic_load_explicit(&s->detected_range,
245 av_log(ctx, AV_LOG_INFO, " Color range: %s\n",
246 detected_range == AVCOL_RANGE_JPEG ? "JPEG / full range"
247 : "undetermined");
248 }
249
250 if (s->mode & COLOR_DETECT_ALPHA_MODE) {
251 enum FFAlphaDetect detected_alpha = atomic_load_explicit(&s->detected_alpha,
253 av_log(ctx, AV_LOG_INFO, " Alpha mode: %s\n",
254 detected_alpha == FF_ALPHA_NONE ? "none" :
255 detected_alpha == FF_ALPHA_STRAIGHT ? "straight" :
256 detected_alpha == FF_ALPHA_TRANSPARENT ? "undetermined"
257 : "opaque");
258 }
259}
260
262{
263 AVFilterLink *inlink = ctx->inputs[0];
264 AVFilterLink *outlink = ctx->outputs[0];
265 AVFrame *frame;
266 int64_t pts;
267 int ret;
268
269 ret = ff_outlink_get_status(outlink);
270 if (ret) {
271 ff_inlink_set_status(inlink, ret);
273 return 0;
274 }
275
276 ret = ff_inlink_consume_frame(inlink, &frame);
277 if (ret < 0) {
278 return ret;
279 } else if (ret) {
280 return filter_frame(inlink, frame);
281 }
282
283 if (ff_inlink_acknowledge_status(inlink, &ret, &pts)) {
284 ff_outlink_set_status(outlink, ret, pts);
286 return 0;
287 }
288
289 FF_FILTER_FORWARD_WANTED(outlink, inlink);
290 return FFERROR_NOT_READY;
291}
292
294 {
295 .name = "default",
296 .type = AVMEDIA_TYPE_VIDEO,
297 .config_props = config_input,
298 },
299};
300
302 .p.name = "colordetect",
303 .p.description = NULL_IF_CONFIG_SMALL("Detect video color properties."),
304 .p.priv_class = &colordetect_class,
306 .priv_size = sizeof(ColorDetectContext),
310 .activate = activate,
311};
static int config_input(AVFilterLink *inlink)
const FFFilter ff_vf_colordetect
static AVFormatContext * ctx
void ff_inlink_set_status(AVFilterLink *link, int status)
Set the status on an input link.
Definition avfilter.c:1637
int ff_outlink_get_status(AVFilterLink *link)
Get the status on an output link.
Definition avfilter.c:1653
int ff_inlink_acknowledge_status(AVFilterLink *link, int *rstatus, int64_t *rpts)
Test and acknowledge the change of status on the link.
Definition avfilter.c:1472
int ff_filter_frame(AVFilterLink *link, AVFrame *frame)
Send a frame of data to the next filter.
Definition avfilter.c:1073
int ff_filter_execute(AVFilterContext *ctx, avfilter_action_func *func, void *arg, int *ret, int nb_jobs)
Definition avfilter.c:1701
int ff_filter_get_nb_threads(AVFilterContext *ctx)
Get number of threads for current filter instance.
Definition avfilter.c:851
int ff_inlink_consume_frame(AVFilterLink *link, AVFrame **rframe)
Take a frame from the link's FIFO and update the link's stats.
Definition avfilter.c:1525
Main libavfilter public API header.
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
#define s(width, name)
Definition cbs_vp9.c:198
#define FLAGS
Definition cmdutils.c:596
#define av_clip_uintp2
Definition common.h:124
#define NULL
Definition coverity.c:32
long long int64_t
Definition coverity.c:34
static AVFrame * frame
int ff_set_common_formats2(const AVFilterContext *ctx, AVFilterFormatsConfig **cfg_in, AVFilterFormatsConfig **cfg_out, AVFilterFormats *formats)
Definition formats.c:1200
AVFilterFormats * ff_formats_pixdesc_filter(unsigned want, unsigned rej)
Construct a formats list containing all pixel formats with certain properties.
Definition formats.c:629
@ AV_OPT_TYPE_CONST
Special option type for declaring named constants.
Definition opt.h:298
@ AV_OPT_TYPE_FLAGS
Underlying C type is unsigned int.
Definition opt.h:254
@ AV_OPT_TYPE_FLOAT
Underlying C type is float.
Definition opt.h:270
#define AVFILTER_FLAG_SLICE_THREADS
The filter supports multithreading by splitting frames into multiple parts and processing them concur...
Definition avfilter.h:172
#define AVFILTER_FLAG_METADATA_ONLY
The filter is a "metadata" filter - it does not modify the frame data in any way.
Definition avfilter.h:188
#define AVERROR(e)
Definition error.h:45
#define AV_LOG_INFO
Standard information.
Definition log.h:221
@ AVMEDIA_TYPE_VIDEO
Definition avutil.h:200
static const int16_t alpha[]
Definition ilbcdata.h:55
static int activate(AVBitStreamFilterContext *ctx)
const char * arg
Definition jacosubdec.c:65
#define FILTER_INPUTS(array)
Definition filters.h:264
#define FILTER_OUTPUTS(array)
Definition filters.h:265
static int ff_slice_pos(int total, int jobnr, int nb_jobs)
Compute the boundary index for a slice when work of size total is split into nb_jobs slices.
Definition filters.h:763
#define FF_FILTER_FORWARD_WANTED(outlink, inlink)
Forward the frame_wanted_out flag from an output link to an input link.
Definition filters.h:694
static void ff_outlink_set_status(AVFilterLink *link, int status, int64_t pts)
Set the status field of a link from the source filter.
Definition filters.h:629
#define FFERROR_NOT_READY
Filters implementation helper functions and internal structures.
Definition filters.h:34
#define AVFILTER_DEFINE_CLASS(fname)
Definition filters.h:478
#define FILTER_QUERY_FUNC2(func)
Definition filters.h:241
static int query_format(const AVFilterContext *ctx, AVFilterFormatsConfig **cfg_in, AVFilterFormatsConfig **cfg_out)
static const AVFilterPad colordetect_inputs[]
static av_cold void report_detected_props(AVFilterContext *ctx)
static int detect_range(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
static int detect_alpha(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
static int config_input(AVFilterLink *inlink)
static const AVOption colordetect_options[]
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
ColorDetectMode
@ COLOR_DETECT_COLOR_RANGE
@ COLOR_DETECT_ALPHA_MODE
static int activate(AVFilterContext *ctx)
static int query_format(const AVFilterContext *ctx, AVFilterFormatsConfig **cfg_in, AVFilterFormatsConfig **cfg_out)
#define OFFSET(x)
#define av_cold
Definition attributes.h:117
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification.
Definition internal.h:97
#define lrintf(x)
Definition libm_mips.h:74
const char * desc
Definition libsvtav1.c:83
uint8_t w
Definition llvidencdsp.c:39
#define FFMIN(a, b)
Definition macros.h:49
enum AVColorRange range
Memory handling functions.
AVOptions.
const AVPixFmtDescriptor * av_pix_fmt_desc_get(enum AVPixelFormat pix_fmt)
Definition pixdesc.c:3484
#define AV_PIX_FMT_FLAG_ALPHA
The pixel format has an alpha channel.
Definition pixdesc.h:147
#define AV_PIX_FMT_FLAG_BITSTREAM
All values of a component are bit-wise packed end to end.
Definition pixdesc.h:124
#define AV_PIX_FMT_FLAG_RGB
The pixel format contains RGB-like data (as opposed to YUV/grayscale).
Definition pixdesc.h:136
#define AV_PIX_FMT_FLAG_FLOAT
The pixel format contains IEEE-754 floating point values.
Definition pixdesc.h:158
#define AV_PIX_FMT_FLAG_HWACCEL
Pixel format is an HW accelerated format.
Definition pixdesc.h:128
#define AV_PIX_FMT_FLAG_PLANAR
At least one pixel component is not in the first data plane.
Definition pixdesc.h:132
#define AV_PIX_FMT_FLAG_BE
Pixel format is big-endian.
Definition pixdesc.h:116
#define AV_PIX_FMT_FLAG_XYZ
The pixel format contains XYZ-like data (as opposed to YUV/RGB/grayscale).
Definition pixdesc.h:163
#define AV_PIX_FMT_FLAG_BAYER
The pixel format is following a Bayer pattern.
Definition pixdesc.h:152
#define AV_PIX_FMT_FLAG_PAL
Pixel format has a palette in data[1], values are indexes in this palette.
Definition pixdesc.h:120
AVColorRange
Visual content value range.
Definition pixfmt.h:753
@ AVCOL_RANGE_UNSPECIFIED
Definition pixfmt.h:754
@ AVCOL_RANGE_JPEG
Full range content.
Definition pixfmt.h:788
formats
Definition signature.h:47
@ memory_order_relaxed
Definition stdatomic.h:29
int atomic_int
Definition stdatomic.h:63
#define atomic_load_explicit(object, order)
Definition stdatomic.h:247
#define atomic_fetch_or_explicit(object, operand, order)
Definition stdatomic.h:303
#define atomic_store_explicit(object, desired, order)
Definition stdatomic.h:253
#define atomic_init(obj, value)
Definition stdatomic.h:119
Describe the class of an AVClass context structure.
Definition log.h:76
An instance of a filter.
Definition avfilter.h:279
AVFilterLink ** outputs
array of pointers to output links
Definition avfilter.h:291
Lists of formats / etc.
Definition avfilter.h:120
A list of supported formats for one end of a filter link.
Definition formats.h:64
A filter pad used for either input or output.
Definition filters.h:40
This structure describes decoded (raw) audio or video data.
Definition frame.h:479
uint8_t * data[AV_NUM_DATA_POINTERS]
pointer to the picture/channel planes.
Definition frame.h:500
int width
Definition frame.h:551
int height
Definition frame.h:551
int linesize[AV_NUM_DATA_POINTERS]
For video, a positive or negative value, which is typically indicating the size in bytes of each pict...
Definition frame.h:524
AVOption.
Definition opt.h:428
Descriptor that unambiguously describes how the bits of a pixel are stored in the up to 4 data planes...
Definition pixdesc.h:69
FFColorDetectDSPContext dsp
atomic_int detected_alpha
atomic_int detected_range
const AVPixFmtDescriptor * desc
Definition swscale.c:71
#define stride
#define av_log(a,...)
static int64_t pts
FFAlphaDetect
@ FF_ALPHA_TRANSPARENT
alpha < alpha_max
@ FF_ALPHA_STRAIGHT
alpha < pixel
@ FF_ALPHA_UNDETERMINED
@ FF_ALPHA_NONE
static av_cold void ff_color_detect_dsp_init(FFColorDetectDSPContext *dsp, int depth, int offset, enum AVColorRange color_range)
const AVFilterPad ff_video_default_filterpad[1]
An AVFilterPad array whose only entry has name "default" and is of type AVMEDIA_TYPE_VIDEO.
Definition video.c:37