History

avfilter: add limitdiff video filter

Paul B Mahol committed 5 years ago

Commit 32eaf401 parent 73fe19f6 files · +405 −1Browse files

Unified · Split

6 files
  1. Changelog +1 −0
  2. doc/filters.texi +31 −0
  3. libavfilter/Makefile +1 −0
  4. libavfilter/allfilters.c +1 −0
  5. libavfilter/version.h +1 −1
  6. libavfilter/vf_limitdiff.c +370 −0
Changelog+1−0
@@ -26,6 +26,7 @@ version <next>: · 25 unmodified lines
26- GEM Raster image decoder26- GEM Raster image decoder
27- asdr audio filter27- asdr audio filter
28- speex decoder28- speex decoder
29- limitdiff video filter
29 30
30 31
31version 4.4:32version 4.4:
doc/filters.texi+31−0
@@ -14488,6 +14488,37 @@ ffmpeg -i main.mpg -i ref.mkv -lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[main]; · 14487 unmodified lines
14488@end example14488@end example
14489@end itemize14489@end itemize
14490 14490
14491@section limitdiff
14492Apply limited difference filter using second and optionally third video stream.
14493
14494The filter accepts the following options:
14495
14496@table @option
14497@item threshold
14498Set the threshold to use when allowing certain differences between video streams.
14499Any absolute difference value lower or exact than this threshold will pick pixel components from
14500first video stream.
14501
14502@item elasticity
14503Set the elasticity of soft thresholding when processing video streams.
14504This value multiplied with first one sets second threshold.
14505Any absolute difference value greater or exact than second threshold will pick pixel components
14506from second video stream. For values between those two threshold
14507linear interpolation between first and second video stream will be used.
14508
14509@item reference
14510Enable the reference (third) video stream processing. By default is disabled.
14511If set, this video stream will be used for calculating absolute difference with first video
14512stream.
14513
14514@item planes
14515Specify which planes will be processed. Defaults to all available.
14516@end table
14517
14518@subsection Commands
14519
14520This filter supports the all above options as @ref{commands} except option @samp{reference}.
14521
14491@section limiter14522@section limiter
14492 14523
14493Limits the pixel components values to the specified range [min, max].14524Limits the pixel components values to the specified range [min, max].
libavfilter/Makefile+1−0
@@ -323,6 +323,7 @@ OBJS-$(CONFIG_LATENCY_FILTER) += f_latency.o · 322 unmodified lines
323OBJS-$(CONFIG_LENSCORRECTION_FILTER) += vf_lenscorrection.o323OBJS-$(CONFIG_LENSCORRECTION_FILTER) += vf_lenscorrection.o
324OBJS-$(CONFIG_LENSFUN_FILTER) += vf_lensfun.o324OBJS-$(CONFIG_LENSFUN_FILTER) += vf_lensfun.o
325OBJS-$(CONFIG_LIBVMAF_FILTER) += vf_libvmaf.o framesync.o325OBJS-$(CONFIG_LIBVMAF_FILTER) += vf_libvmaf.o framesync.o
326OBJS-$(CONFIG_LIMITDIFF_FILTER) += vf_limitdiff.o framesync.o
326OBJS-$(CONFIG_LIMITER_FILTER) += vf_limiter.o327OBJS-$(CONFIG_LIMITER_FILTER) += vf_limiter.o
327OBJS-$(CONFIG_LOOP_FILTER) += f_loop.o328OBJS-$(CONFIG_LOOP_FILTER) += f_loop.o
328OBJS-$(CONFIG_LUMAKEY_FILTER) += vf_lumakey.o329OBJS-$(CONFIG_LUMAKEY_FILTER) += vf_lumakey.o
libavfilter/allfilters.c+1−0
@@ -308,6 +308,7 @@ extern const AVFilter ff_vf_latency; · 307 unmodified lines
308extern const AVFilter ff_vf_lenscorrection;308extern const AVFilter ff_vf_lenscorrection;
309extern const AVFilter ff_vf_lensfun;309extern const AVFilter ff_vf_lensfun;
310extern const AVFilter ff_vf_libvmaf;310extern const AVFilter ff_vf_libvmaf;
311extern const AVFilter ff_vf_limitdiff;
311extern const AVFilter ff_vf_limiter;312extern const AVFilter ff_vf_limiter;
312extern const AVFilter ff_vf_loop;313extern const AVFilter ff_vf_loop;
313extern const AVFilter ff_vf_lumakey;314extern const AVFilter ff_vf_lumakey;
libavfilter/version.h+1−1
@@ -30,7 +30,7 @@ · 29 unmodified lines
30#include "libavutil/version.h"30#include "libavutil/version.h"
31 31
32#define LIBAVFILTER_VERSION_MAJOR 832#define LIBAVFILTER_VERSION_MAJOR 8
33#define LIBAVFILTER_VERSION_MINOR 1233#define LIBAVFILTER_VERSION_MINOR 13
34#define LIBAVFILTER_VERSION_MICRO 10034#define LIBAVFILTER_VERSION_MICRO 100
35 35
36 36
libavfilter/vf_limitdiff.c+370−0
@@ -0,0 +1,370 @@
1/*
2 * Copyright (c) 2021 Paul B Mahol
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/imgutils.h"
22#include "libavutil/pixdesc.h"
23#include "libavutil/opt.h"
24#include "avfilter.h"
25#include "formats.h"
26#include "internal.h"
27#include "video.h"
28#include "framesync.h"
29
30typedef struct ThreadData {
31 AVFrame *filtered, *source, *reference, *dst;
32} ThreadData;
33
34typedef struct LimitDiffContext {
35 const AVClass *class;
36
37 float threshold;
38 float elasticity;
39 int reference;
40 int planes;
41
42 int thr1, thr2;
43
44 int linesize[4];
45 int planewidth[4], planeheight[4];
46 int nb_planes;
47 int depth;
48 FFFrameSync fs;
49
50 void (*limitdiff)(const uint8_t *filtered, uint8_t *dst,
51 const uint8_t *source, const uint8_t *reference,
52 int thr1, int thr2, int w, int depth);
53} LimitDiffContext;
54
55#define OFFSET(x) offsetof(LimitDiffContext, x)
56#define TFLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
57#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
58
59static const AVOption limitdiff_options[] = {
60 { "threshold", "set the threshold", OFFSET(threshold), AV_OPT_TYPE_FLOAT, {.dbl=1/255.f}, 0, 1, TFLAGS },
61 { "elasticity", "set the elasticity", OFFSET(elasticity), AV_OPT_TYPE_FLOAT, {.dbl=2.f}, 0, 10, TFLAGS },
62 { "reference", "enable reference stream", OFFSET(reference), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS },
63 { "planes", "set the planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=0xF}, 0, 0xF, TFLAGS },
64 { NULL }
65};
66
67static const enum AVPixelFormat pix_fmts[] = {
68 AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
69 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
70 AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P,
71 AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
72 AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
73 AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9,
74 AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
75 AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12,
76 AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
77 AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
78 AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
79 AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
80 AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
81 AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
82 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRP9, AV_PIX_FMT_GBRP10,
83 AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
84 AV_PIX_FMT_GBRAP, AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
85 AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
86 AV_PIX_FMT_NONE
87};
88
89static void limitdiff8(const uint8_t *filtered, uint8_t *dst,
90 const uint8_t *source, const uint8_t *reference,
91 int thr1, int thr2, int w, int depth)
92{
93 for (int x = 0; x < w; x++) {
94 const int diff = filtered[x] - source[x];
95 const int diff_ref = FFABS(filtered[x] - reference[x]);
96
97 if (diff_ref <= thr1)
98 dst[x] = filtered[x];
99 else if (diff_ref >= thr2)
100 dst[x] = source[x];
101 else
102 dst[x] = av_clip_uint8(source[x] + diff * (thr2 - diff_ref) / (thr2 - thr1));
103 }
104}
105
106static void limitdiff16(const uint8_t *ffiltered, uint8_t *ddst,
107 const uint8_t *ssource, const uint8_t *rreference,
108 int thr1, int thr2, int w, int depth)
109{
110 const uint16_t *source = (const uint16_t *)ssource;
111 const uint16_t *filtered = (const uint16_t *)ffiltered;
112 const uint16_t *reference = (const uint16_t *)rreference;
113 uint16_t *dst = (uint16_t *)ddst;
114
115 for (int x = 0; x < w; x++) {
116 const int diff = filtered[x] - source[x];
117 const int diff_ref = FFABS(filtered[x] - reference[x]);
118
119 if (diff_ref <= thr1)
120 dst[x] = filtered[x];
121 else if (diff_ref >= thr2)
122 dst[x] = source[x];
123 else
124 dst[x] = av_clip_uintp2_c(source[x] + diff * (thr2 - diff_ref) / (thr2 - thr1), depth);
125 }
126}
127
128static int config_input(AVFilterLink *inlink)
129{
130 AVFilterContext *ctx = inlink->dst;
131 LimitDiffContext *s = ctx->priv;
132 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
133 int vsub, hsub, ret;
134
135 s->nb_planes = av_pix_fmt_count_planes(inlink->format);
136
137 if ((ret = av_image_fill_linesizes(s->linesize, inlink->format, inlink->w)) < 0)
138 return ret;
139
140 hsub = desc->log2_chroma_w;
141 vsub = desc->log2_chroma_h;
142 s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, vsub);
143 s->planeheight[0] = s->planeheight[3] = inlink->h;
144 s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, hsub);
145 s->planewidth[0] = s->planewidth[3] = inlink->w;
146
147 s->depth = desc->comp[0].depth;
148 s->thr1 = s->threshold * ((1 << s->depth) - 1);
149 s->thr2 = s->thr1 * s->elasticity;
150
151 if (desc->comp[0].depth == 8)
152 s->limitdiff = limitdiff8;
153 else
154 s->limitdiff = limitdiff16;
155
156 return 0;
157}
158
159static int limitdiff_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
160{
161 LimitDiffContext *s = ctx->priv;
162 const int depth = s->depth;
163 ThreadData *td = arg;
164
165 for (int p = 0; p < s->nb_planes; p++) {
166 const ptrdiff_t filtered_linesize = td->filtered->linesize[p];
167 const ptrdiff_t source_linesize = td->source->linesize[p];
168 const ptrdiff_t reference_linesize = td->reference->linesize[p];
169 const ptrdiff_t dst_linesize = td->dst->linesize[p];
170 const int thr1 = s->thr1;
171 const int thr2 = s->thr2;
172 const int w = s->planewidth[p];
173 const int h = s->planeheight[p];
174 const int slice_start = (h * jobnr) / nb_jobs;
175 const int slice_end = (h * (jobnr+1)) / nb_jobs;
176 const uint8_t *filtered = td->filtered->data[p] + slice_start * filtered_linesize;
177 const uint8_t *source = td->source->data[p] + slice_start * source_linesize;
178 const uint8_t *reference = td->reference->data[p] + slice_start * reference_linesize;
179 uint8_t *dst = td->dst->data[p] + slice_start * dst_linesize;
180
181 if (!((1 << p) & s->planes)) {
182 av_image_copy_plane(dst, dst_linesize, filtered, filtered_linesize,
183 s->linesize[p], slice_end - slice_start);
184 continue;
185 }
186
187 for (int y = slice_start; y < slice_end; y++) {
188 s->limitdiff(filtered, dst, source, reference, thr1, thr2, w, depth);
189
190 dst += dst_linesize;
191 filtered += filtered_linesize;
192 source += source_linesize;
193 reference += reference_linesize;
194 }
195 }
196
197 return 0;
198}
199
200static int process_frame(FFFrameSync *fs)
201{
202 AVFilterContext *ctx = fs->parent;
203 LimitDiffContext *s = fs->opaque;
204 AVFilterLink *outlink = ctx->outputs[0];
205 AVFrame *out, *filtered, *source, *reference = NULL;
206 int ret;
207
208 if ((ret = ff_framesync_get_frame(&s->fs, 0, &filtered, 0)) < 0 ||
209 (ret = ff_framesync_get_frame(&s->fs, 1, &source, 0)) < 0)
210 return ret;
211 if (s->reference) {
212 if ((ret = ff_framesync_get_frame(&s->fs, 2, &reference, 0)) < 0)
213 return ret;
214 }
215
216 if (ctx->is_disabled) {
217 out = av_frame_clone(filtered);
218 if (!out)
219 return AVERROR(ENOMEM);
220 } else {
221 ThreadData td;
222
223 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
224 if (!out)
225 return AVERROR(ENOMEM);
226 av_frame_copy_props(out, filtered);
227
228 td.filtered = filtered;
229 td.source = source;
230 td.reference = reference ? reference : source;
231 td.dst = out;
232
233 ff_filter_execute(ctx, limitdiff_slice, &td, NULL,
234 FFMIN(s->planeheight[0], ff_filter_get_nb_threads(ctx)));
235 }
236 out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
237
238 return ff_filter_frame(outlink, out);
239}
240
241static int config_output(AVFilterLink *outlink)
242{
243 AVFilterContext *ctx = outlink->src;
244 LimitDiffContext *s = ctx->priv;
245 AVFilterLink *filtered = ctx->inputs[0];
246 AVFilterLink *source = ctx->inputs[1];
247 FFFrameSyncIn *in;
248 int ret;
249
250 if (filtered->w != source->w || filtered->h != source->h) {
251 av_log(ctx, AV_LOG_ERROR, "First input link %s parameters "
252 "(size %dx%d) do not match the corresponding "
253 "second input link %s parameters (%dx%d)\n",
254 ctx->input_pads[0].name, filtered->w, filtered->h,
255 ctx->input_pads[1].name, source->w, source->h);
256 return AVERROR(EINVAL);
257 }
258
259 if (s->reference) {
260 AVFilterLink *reference = ctx->inputs[2];
261
262 if (filtered->w != reference->w || filtered->h != reference->h) {
263 av_log(ctx, AV_LOG_ERROR, "First input link %s parameters "
264 "(size %dx%d) do not match the corresponding "
265 "third input link %s parameters (%dx%d)\n",
266 ctx->input_pads[0].name, filtered->w, filtered->h,
267 ctx->input_pads[1].name, reference->w, reference->h);
268 return AVERROR(EINVAL);
269 }
270 }
271
272 outlink->w = filtered->w;
273 outlink->h = filtered->h;
274 outlink->sample_aspect_ratio = filtered->sample_aspect_ratio;
275 outlink->frame_rate = filtered->frame_rate;
276
277 if ((ret = ff_framesync_init(&s->fs, ctx, 2 + !!s->reference)) < 0)
278 return ret;
279
280 in = s->fs.in;
281 in[0].time_base = filtered->time_base;
282 in[1].time_base = source->time_base;
283 if (s->reference)
284 in[2].time_base = ctx->inputs[2]->time_base;
285 in[0].sync = 1;
286 in[0].before = EXT_STOP;
287 in[0].after = EXT_INFINITY;
288 in[1].sync = 1;
289 in[1].before = EXT_STOP;
290 in[1].after = EXT_INFINITY;
291 if (s->reference) {
292 in[2].sync = 1;
293 in[2].before = EXT_STOP;
294 in[2].after = EXT_INFINITY;
295 }
296 s->fs.opaque = s;
297 s->fs.on_event = process_frame;
298
299 ret = ff_framesync_configure(&s->fs);
300 outlink->time_base = s->fs.time_base;
301
302 return ret;
303}
304
305static int activate(AVFilterContext *ctx)
306{
307 LimitDiffContext *s = ctx->priv;
308 return ff_framesync_activate(&s->fs);
309}
310
311static av_cold int init(AVFilterContext *ctx)
312{
313 const LimitDiffContext *s = ctx->priv;
314 AVFilterPad pad = {
315 .name = "filtered",
316 .type = AVMEDIA_TYPE_VIDEO,
317 .config_props = config_input,
318 };
319 int ret;
320
321 if ((ret = ff_append_inpad(ctx, &pad)) < 0)
322 return ret;
323
324 pad.name = "source";
325 pad.config_props = NULL;
326 if ((ret = ff_append_inpad(ctx, &pad)) < 0)
327 return ret;
328
329 if (s->reference) {
330 pad.name = "reference";
331 pad.config_props = NULL;
332 if ((ret = ff_append_inpad(ctx, &pad)) < 0)
333 return ret;
334 }
335
336 return 0;
337}
338
339static av_cold void uninit(AVFilterContext *ctx)
340{
341 LimitDiffContext *s = ctx->priv;
342
343 ff_framesync_uninit(&s->fs);
344}
345
346static const AVFilterPad limitdiff_outputs[] = {
347 {
348 .name = "default",
349 .type = AVMEDIA_TYPE_VIDEO,
350 .config_props = config_output,
351 },
352};
353
354AVFILTER_DEFINE_CLASS(limitdiff);
355
356const AVFilter ff_vf_limitdiff = {
357 .name = "limitdiff",
358 .description = NULL_IF_CONFIG_SMALL("Apply filtering with limiting difference."),
359 .priv_class = &limitdiff_class,
360 .priv_size = sizeof(LimitDiffContext),
361 .init = init,
362 .uninit = uninit,
363 .activate = activate,
364 FILTER_OUTPUTS(limitdiff_outputs),
365 FILTER_PIXFMTS_ARRAY(pix_fmts),
366 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL |
367 AVFILTER_FLAG_SLICE_THREADS |
368 AVFILTER_FLAG_DYNAMIC_INPUTS,
369 .process_command = ff_filter_process_command,
370};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t