FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_mpdecimate.c
Date: 2026-08-30 18:45:47
Exec Total Coverage
Lines: 118 130 90.8%
Functions: 9 9 100.0%
Branches: 65 74 87.8%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2003 Rich Felker
3 * Copyright (c) 2012 Stefano Sabatini
4 * Copyright (c) 2026 Dawid Stachowiak
5 *
6 * This file is part of FFmpeg.
7 *
8 * FFmpeg is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 /**
24 * @file mpdecimate filter, ported from libmpcodecs/vf_decimate.c by
25 * Rich Felker.
26 */
27
28 #include "libavutil/opt.h"
29 #include "libavutil/pixdesc.h"
30 #include "libavutil/pixelutils.h"
31 #include "libavutil/timestamp.h"
32 #include "avfilter.h"
33 #include "filters.h"
34 #include "video.h"
35
36 typedef enum {
37 DECIMATE_DROP, ///< similar frame, past keep threshold — drop it
38 DECIMATE_KEEP_UPDATE, ///< keep frame and update reference (frame is different, or first frame)
39 DECIMATE_KEEP_NO_UPDATE,///< keep frame without updating reference (similar frame under keep threshold, or forced keep due to max_drop_count)
40 } DecimateResult;
41
42 typedef struct DecimateContext {
43 const AVClass *class;
44 int mode; ///< 0: drop similar frames, 1: drop similar and unique frames
45 int lo, hi; ///< lower and higher threshold number of differences
46 ///< values for 8x8 blocks
47
48 float frac; ///< threshold of changed pixels over the total fraction
49
50 int max_drop_count; ///< for mode 0: if positive: maximum number of sequential frames to drop
51 ///< for mode 0: if negative: minimum number of frames between two drops
52
53 int drop_count; ///< if positive: number of frames sequentially dropped
54 ///< if negative: number of sequential frames which were not dropped
55
56 int max_keep_count; ///< for mode 0: number of similar frames to ignore before to start dropping them
57 int keep_count; ///< for mode 0: number of similar frames already ignored
58
59 int min_dup_count; ///< for mode 1: minimum number of previous frames that need to be duplicated to keep frame
60 int dup_count; ///< for mode 1: number of duplicated frames
61
62 int hsub, vsub; ///< chroma subsampling values
63 AVFrame *ref; ///< reference picture
64 av_pixelutils_sad_fn sad; ///< sum of absolute difference function
65 } DecimateContext;
66
67 #define OFFSET(x) offsetof(DecimateContext, x)
68 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
69
70 static const AVOption mpdecimate_options[] = {
71 { "max", "for mode 0: set the maximum number of consecutive dropped frames (positive), or the minimum interval between dropped frames (negative)",
72 OFFSET(max_drop_count), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS },
73 { "keep", "for mode 0: set the number of similar consecutive frames to be kept before starting to drop similar frames",
74 OFFSET(max_keep_count), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
75 { "hi", "set high dropping threshold", OFFSET(hi), AV_OPT_TYPE_INT, {.i64=64*12}, INT_MIN, INT_MAX, FLAGS },
76 { "lo", "set low dropping threshold", OFFSET(lo), AV_OPT_TYPE_INT, {.i64=64*5}, INT_MIN, INT_MAX, FLAGS },
77 { "frac", "set fraction dropping threshold", OFFSET(frac), AV_OPT_TYPE_FLOAT, {.dbl=0.33}, 0, 1, FLAGS },
78 { "mode", "0: drop similar frames, 1: drop similar and unique frames", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS },
79 { "min", "for mode 1: set minimum number of previous frames that need to be duplicated to keep current frame",
80 OFFSET(min_dup_count), AV_OPT_TYPE_INT, {.i64=1}, 1, INT_MAX, FLAGS },
81 { NULL }
82 };
83
84 AVFILTER_DEFINE_CLASS(mpdecimate);
85
86 /**
87 * Return 1 if the two planes are different, 0 otherwise.
88 */
89 617 static int diff_planes(AVFilterContext *ctx,
90 uint8_t *cur, int cur_linesize,
91 uint8_t *ref, int ref_linesize,
92 int w, int h)
93 {
94 617 DecimateContext *decimate = ctx->priv;
95
96 int x, y;
97 617 int d, c = 0;
98 617 int t = (w/16)*(h/16)*decimate->frac;
99
100 /* compute difference for blocks of 8x8 bytes */
101
2/2
✓ Branch 0 taken 21181 times.
✓ Branch 1 taken 540 times.
21721 for (y = 0; y < h-7; y += 4) {
102
2/2
✓ Branch 0 taken 1209144 times.
✓ Branch 1 taken 21104 times.
1230248 for (x = 8; x < w-7; x += 4) {
103 1209144 d = decimate->sad(cur + y*cur_linesize + x, cur_linesize,
104 1209144 ref + y*ref_linesize + x, ref_linesize);
105
2/2
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 1209067 times.
1209144 if (d > decimate->hi) {
106 77 av_log(ctx, AV_LOG_DEBUG, "%d>=hi ", d);
107 77 return 1;
108 }
109
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 1208887 times.
1209067 if (d > decimate->lo) {
110 180 c++;
111
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 180 times.
180 if (c > t) {
112 av_log(ctx, AV_LOG_DEBUG, "lo:%d>=%d ", c, t);
113 return 1;
114 }
115 }
116 }
117 }
118
119 540 av_log(ctx, AV_LOG_DEBUG, "lo:%d<%d ", c, t);
120 540 return 0;
121 }
122
123 /**
124 * Tell if the frame is different with respect to the reference frame ref.
125 */
126 257 static int is_frame_different(AVFilterContext *ctx,
127 AVFrame *cur, AVFrame *ref)
128 {
129 257 DecimateContext *decimate = ctx->priv;
130 int plane;
131
132
3/4
✓ Branch 0 taken 617 times.
✓ Branch 1 taken 180 times.
✓ Branch 2 taken 617 times.
✗ Branch 3 not taken.
797 for (plane = 0; ref->data[plane] && ref->linesize[plane]; plane++) {
133 /* use 8x8 SAD even on subsampled planes. The blocks won't match up with
134 * luma blocks, but hopefully nobody is depending on this to catch
135 * localized chroma changes that wouldn't exceed the thresholds when
136 * diluted by using what's effectively a larger block size.
137 */
138
4/4
✓ Branch 0 taken 437 times.
✓ Branch 1 taken 180 times.
✓ Branch 2 taken 180 times.
✓ Branch 3 taken 257 times.
617 int vsub = plane == 1 || plane == 2 ? decimate->vsub : 0;
139
4/4
✓ Branch 0 taken 437 times.
✓ Branch 1 taken 180 times.
✓ Branch 2 taken 180 times.
✓ Branch 3 taken 257 times.
617 int hsub = plane == 1 || plane == 2 ? decimate->hsub : 0;
140
2/2
✓ Branch 0 taken 77 times.
✓ Branch 1 taken 540 times.
617 if (diff_planes(ctx,
141 cur->data[plane], cur->linesize[plane],
142 ref->data[plane], ref->linesize[plane],
143 617 AV_CEIL_RSHIFT(ref->width, hsub),
144 617 AV_CEIL_RSHIFT(ref->height, vsub)))
145 77 return 1;
146 }
147
148 180 return 0;
149 }
150
151 /**
152 * Tell if the frame should be decimated, for example if it is no much
153 * different with respect to the reference frame ref.
154 */
155 185 static DecimateResult decimate_frame(AVFilterContext *ctx, AVFrame *cur, AVFrame *ref)
156 {
157 185 DecimateContext *decimate = ctx->priv;
158 185 int is_similar = is_frame_different(ctx, cur, ref) == 0;
159
160
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 140 times.
185 if (!is_similar) {
161 45 return DECIMATE_KEEP_UPDATE;
162 }
163 /* Frame is similar - check if we must keep it due to drop limits */
164
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 95 times.
140 if (decimate->max_drop_count > 0 &&
165
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 35 times.
45 decimate->drop_count >= decimate->max_drop_count)
166 10 return DECIMATE_KEEP_NO_UPDATE;
167
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 85 times.
130 if (decimate->max_drop_count < 0 &&
168
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 15 times.
45 (decimate->drop_count - 1) > decimate->max_drop_count)
169 30 return DECIMATE_KEEP_NO_UPDATE;
170
171 /* Frame is similar - check if we must keep it due to keep option */
172
4/4
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 10 times.
100 if (decimate->max_keep_count > 0 && decimate->keep_count > -1 &&
173
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 10 times.
30 decimate->keep_count < decimate->max_keep_count) {
174 20 decimate->keep_count++;
175 20 return DECIMATE_KEEP_NO_UPDATE;
176 }
177 80 return DECIMATE_DROP;
178 }
179
180 14 static av_cold int init(AVFilterContext *ctx)
181 {
182 14 DecimateContext *decimate = ctx->priv;
183
184 14 decimate->sad = av_pixelutils_get_sad_fn(3, 3, 0, ctx); // 8x8, not aligned on blocksize
185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (!decimate->sad)
186 return AVERROR(EINVAL);
187
188
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
14 if (decimate->mode == 1) {
189 4 av_log(ctx, AV_LOG_VERBOSE, "min_dup_count:%d hi:%d lo:%d frac:%f\n",
190 decimate->min_dup_count, decimate->hi, decimate->lo,
191 4 decimate->frac);
192 } else {
193 10 av_log(ctx, AV_LOG_VERBOSE, "max_drop_count:%d hi:%d lo:%d frac:%f\n",
194 10 decimate->max_drop_count, decimate->hi, decimate->lo, decimate->frac);
195 }
196 14 return 0;
197 }
198
199 14 static av_cold void uninit(AVFilterContext *ctx)
200 {
201 14 DecimateContext *decimate = ctx->priv;
202 14 av_frame_free(&decimate->ref);
203 14 }
204
205 static const enum AVPixelFormat pix_fmts[] = {
206 AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P,
207 AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P,
208 AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV440P,
209 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ422P,
210 AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ440P,
211 AV_PIX_FMT_YUVA420P,
212
213 AV_PIX_FMT_GBRP,
214
215 AV_PIX_FMT_YUVA444P,
216 AV_PIX_FMT_YUVA422P,
217
218 AV_PIX_FMT_NONE
219 };
220
221 7 static int config_input(AVFilterLink *inlink)
222 {
223 7 AVFilterContext *ctx = inlink->dst;
224 7 DecimateContext *decimate = ctx->priv;
225 7 const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(inlink->format);
226 7 decimate->hsub = pix_desc->log2_chroma_w;
227 7 decimate->vsub = pix_desc->log2_chroma_h;
228
229 7 return 0;
230 }
231
232 190 static int filter_frame_mode_0(AVFilterLink *inlink, AVFrame *cur)
233 {
234 190 DecimateContext *decimate = inlink->dst->priv;
235 190 AVFilterLink *outlink = inlink->dst->outputs[0];
236 190 AVFrame *out = NULL;
237 int ret;
238
2/2
✓ Branch 0 taken 185 times.
✓ Branch 1 taken 5 times.
190 DecimateResult result = decimate->ref ? decimate_frame(inlink->dst, cur, decimate->ref) : DECIMATE_KEEP_UPDATE;
239
240
3/4
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 50 times.
✗ Branch 3 not taken.
190 switch (result) {
241 80 case DECIMATE_DROP:
242 80 decimate->drop_count = FFMAX(1, decimate->drop_count+1);
243 80 decimate->keep_count = -1;
244 80 break;
245 60 case DECIMATE_KEEP_NO_UPDATE:
246 60 decimate->drop_count = FFMIN(-1, decimate->drop_count-1);
247 60 out = cur;
248 60 break;
249 50 case DECIMATE_KEEP_UPDATE:
250 50 out = av_frame_clone(cur);
251
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 50 times.
50 if (!out) {
252 av_frame_free(&cur);
253 return AVERROR(ENOMEM);
254 }
255 50 av_frame_free(&decimate->ref);
256 50 decimate->ref = cur;
257 50 decimate->drop_count = FFMIN(-1, decimate->drop_count-1);
258 50 decimate->keep_count = 0;
259 50 break;
260 }
261
262
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 110 times.
190 av_log(inlink->dst, AV_LOG_DEBUG,
263 "%s pts:%s pts_time:%s drop_count:%d keep_count:%d\n",
264 result == DECIMATE_DROP ? "drop" : "keep",
265 190 av_ts2str(cur->pts), av_ts2timestr(cur->pts, &inlink->time_base),
266 decimate->drop_count,
267 decimate->keep_count);
268
269
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 110 times.
190 if (result == DECIMATE_DROP) {
270 80 av_frame_free(&cur);
271 80 return 0;
272 }
273
274 110 ret = ff_filter_frame(outlink, out);
275
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 110 times.
110 if (ret < 0)
276 return ret;
277
278 110 return 0;
279 }
280
281 74 static int filter_frame_mode_1(AVFilterLink *inlink, AVFrame *cur)
282 {
283 74 DecimateContext *decimate = inlink->dst->priv;
284 74 AVFilterLink *outlink = inlink->dst->outputs[0];
285 int ret;
286 74 AVFrame *out = NULL;
287
288
4/4
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 32 times.
✓ Branch 4 taken 40 times.
74 if (!decimate->ref || is_frame_different(inlink->dst, cur, decimate->ref)) {
289 34 AVFrame *ref = av_frame_clone(cur);
290
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (!ref) {
291 av_frame_free(&cur);
292 return AVERROR(ENOMEM);
293 }
294 34 av_frame_free(&decimate->ref);
295 34 decimate->ref = ref;
296 34 decimate->dup_count = 0;
297 } else {
298 40 decimate->dup_count++;
299 }
300
301 74 av_log(inlink->dst, AV_LOG_DEBUG,
302 "%s pts:%s pts_time:%s dup_count:%d\n",
303 74 decimate->dup_count ==
304
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 62 times.
74 decimate->min_dup_count ? "keep" : "drop", av_ts2str(cur->pts),
305 74 av_ts2timestr(cur->pts, &inlink->time_base),
306 decimate->dup_count);
307
308
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 62 times.
74 if (decimate->dup_count == decimate->min_dup_count) {
309 12 out = av_frame_clone(decimate->ref);
310
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (!out) {
311 av_frame_free(&cur);
312 return AVERROR(ENOMEM);
313 }
314 12 ret = ff_filter_frame(outlink, out);
315
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (ret < 0) {
316 av_frame_free(&cur);
317 return ret;
318 }
319 }
320 74 av_frame_free(&cur);
321
322 74 return 0;
323 }
324
325 264 static int filter_frame(AVFilterLink *inlink, AVFrame *cur)
326 {
327 264 DecimateContext *decimate = inlink->dst->priv;
328
2/2
✓ Branch 0 taken 190 times.
✓ Branch 1 taken 74 times.
264 return decimate->mode == 0 ? filter_frame_mode_0(inlink, cur) : filter_frame_mode_1(inlink, cur);
329 }
330
331 static const AVFilterPad mpdecimate_inputs[] = {
332 {
333 .name = "default",
334 .type = AVMEDIA_TYPE_VIDEO,
335 .config_props = config_input,
336 .filter_frame = filter_frame,
337 },
338 };
339
340 const FFFilter ff_vf_mpdecimate = {
341 .p.name = "mpdecimate",
342 .p.description = NULL_IF_CONFIG_SMALL("Remove near-duplicate frames."),
343 .p.priv_class = &mpdecimate_class,
344 .init = init,
345 .uninit = uninit,
346 .priv_size = sizeof(DecimateContext),
347 FILTER_INPUTS(mpdecimate_inputs),
348 FILTER_OUTPUTS(ff_video_default_filterpad),
349 FILTER_PIXFMTS_ARRAY(pix_fmts),
350 };
351