FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_mpdecimate.c
Date: 2026-04-24 10:13:59
Exec Total Coverage
Lines: 83 88 94.3%
Functions: 6 6 100.0%
Branches: 50 56 89.3%

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