This is an automated email from the git hooks/post-receive script.
Git pushed a commit to branch master
in repository ffmpeg.
The following commit(s) were added to refs/heads/master by this push:
new c182b1c09c Add new mode to mpdecimate video filter
c182b1c09c is described below
commit c182b1c09c086deb62545393cc7b4a829d541508
Author: StachowiakDawid <dawidos234(a)gmail.com>
AuthorDate: Thu Jan 2 14:54:03 2025 +0100
Commit: StachowiakDawid <email(a)dawidstachowiak.pl>
CommitDate: Mon Jul 20 17:49:47 2026 +0000
Add new mode to mpdecimate video filter
---
doc/filters.texi | 16 +++-
libavfilter/vf_mpdecimate.c | 111 +++++++++++++++++++++-----
tests/fate/filter-video.mak | 6 ++
tests/ref/fate/filter-mpdecimate-mode-1 | 10 +++
tests/ref/fate/filter-mpdecimate-mode-1-min-3 | 12 +++
5 files changed, 133 insertions(+), 22 deletions(-)
diff --git a/doc/filters.texi b/doc/filters.texi
index 002979ba08..985a1615a9 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -18721,16 +18721,28 @@ fixing movies that were inverse-telecined incorrectly.
A description of the accepted options follows.
@table @option
+@item mode
+Set the mode. If the value is 0, the filter drops similar frames.
+If the value is 1, the filter keeps only frames that are one representative of sufficiently long duplicate runs.
+
+Default value is 0.
+
@item max
-Set the maximum number of consecutive frames which can be dropped (if
+For mode 0: Set the maximum number of consecutive frames which can be dropped (if
positive), or the minimum interval between dropped frames (if
negative). If the value is 0, the frame is dropped disregarding the
number of previous sequentially dropped frames.
Default value is 0.
+@item min
+For mode 1: Set the minimum number of frame duplicates to mark the frame as non-unique
+and keep the first occurrence of it.
+
+Default value is 1.
+
@item keep
-Set the maximum number of consecutive similar frames to ignore before to start dropping them.
+For mode 0: Set the maximum number of consecutive similar frames to ignore before to start dropping them.
If the value is 0, the frame is dropped disregarding the
number of previous sequentially similar frames.
diff --git a/libavfilter/vf_mpdecimate.c b/libavfilter/vf_mpdecimate.c
index 00bc6699c3..7523af2e86 100644
--- a/libavfilter/vf_mpdecimate.c
+++ b/libavfilter/vf_mpdecimate.c
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2003 Rich Felker
* Copyright (c) 2012 Stefano Sabatini
+ * Copyright (c) 2026 Dawid Stachowiak
*
* This file is part of FFmpeg.
*
@@ -40,19 +41,23 @@ typedef enum {
typedef struct DecimateContext {
const AVClass *class;
+ int mode; ///< 0: drop similar frames, 1: drop similar and unique frames
int lo, hi; ///< lower and higher threshold number of differences
///< values for 8x8 blocks
float frac; ///< threshold of changed pixels over the total fraction
- int max_drop_count; ///< if positive: maximum number of sequential frames to drop
- ///< if negative: minimum number of frames between two drops
+ int max_drop_count; ///< for mode 0: if positive: maximum number of sequential frames to drop
+ ///< for mode 0: if negative: minimum number of frames between two drops
int drop_count; ///< if positive: number of frames sequentially dropped
///< if negative: number of sequential frames which were not dropped
- int max_keep_count; ///< number of similar frames to ignore before to start dropping them
- int keep_count; ///< number of similar frames already ignored
+ int max_keep_count; ///< for mode 0: number of similar frames to ignore before to start dropping them
+ int keep_count; ///< for mode 0: number of similar frames already ignored
+
+ int min_dup_count; ///< for mode 1: minimum number of previous frames that need to be duplicated to keep frame
+ int dup_count; ///< for mode 1: number of duplicated frames
int hsub, vsub; ///< chroma subsampling values
AVFrame *ref; ///< reference picture
@@ -63,13 +68,16 @@ typedef struct DecimateContext {
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
static const AVOption mpdecimate_options[] = {
- { "max", "set the maximum number of consecutive dropped frames (positive), or the minimum interval between dropped frames (negative)",
+ { "max", "for mode 0: set the maximum number of consecutive dropped frames (positive), or the minimum interval between dropped frames (negative)",
OFFSET(max_drop_count), AV_OPT_TYPE_INT, {.i64=0}, INT_MIN, INT_MAX, FLAGS },
- { "keep", "set the number of similar consecutive frames to be kept before starting to drop similar frames",
+ { "keep", "for mode 0: set the number of similar consecutive frames to be kept before starting to drop similar frames",
OFFSET(max_keep_count), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
{ "hi", "set high dropping threshold", OFFSET(hi), AV_OPT_TYPE_INT, {.i64=64*12}, INT_MIN, INT_MAX, FLAGS },
{ "lo", "set low dropping threshold", OFFSET(lo), AV_OPT_TYPE_INT, {.i64=64*5}, INT_MIN, INT_MAX, FLAGS },
{ "frac", "set fraction dropping threshold", OFFSET(frac), AV_OPT_TYPE_FLOAT, {.dbl=0.33}, 0, 1, FLAGS },
+ { "mode", "0: drop similar frames, 1: drop similar and unique frames", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS },
+ { "min", "for mode 1: set minimum number of previous frames that need to be duplicated to keep current frame",
+ OFFSET(min_dup_count), AV_OPT_TYPE_INT, {.i64=1}, 1, INT_MAX, FLAGS },
{ NULL }
};
@@ -113,16 +121,13 @@ static int diff_planes(AVFilterContext *ctx,
}
/**
- * Tell if the frame should be decimated, for example if it is no much
- * different with respect to the reference frame ref.
+ * Tell if the frame is different with respect to the reference frame ref.
*/
-static DecimateResult decimate_frame(AVFilterContext *ctx, AVFrame *cur, AVFrame *ref)
+static int is_frame_different(AVFilterContext *ctx,
+ AVFrame *cur, AVFrame *ref)
{
DecimateContext *decimate = ctx->priv;
int plane;
- int is_similar;
-
- is_similar = 1;
for (plane = 0; ref->data[plane] && ref->linesize[plane]; plane++) {
/* use 8x8 SAD even on subsampled planes. The blocks won't match up with
@@ -135,12 +140,23 @@ static DecimateResult decimate_frame(AVFilterContext *ctx, AVFrame *cur, AVFrame
if (diff_planes(ctx,
cur->data[plane], cur->linesize[plane],
ref->data[plane], ref->linesize[plane],
- AV_CEIL_RSHIFT(ref->width, hsub),
- AV_CEIL_RSHIFT(ref->height, vsub))) {
- is_similar = 0;
- break;
- }
+ AV_CEIL_RSHIFT(ref->width, hsub),
+ AV_CEIL_RSHIFT(ref->height, vsub)))
+ return 1;
}
+
+ return 0;
+}
+
+/**
+ * Tell if the frame should be decimated, for example if it is no much
+ * different with respect to the reference frame ref.
+ */
+static DecimateResult decimate_frame(AVFilterContext *ctx, AVFrame *cur, AVFrame *ref)
+{
+ DecimateContext *decimate = ctx->priv;
+ int is_similar = is_frame_different(ctx, cur, ref) == 0;
+
if (!is_similar) {
return DECIMATE_KEEP_UPDATE;
}
@@ -169,9 +185,14 @@ static av_cold int init(AVFilterContext *ctx)
if (!decimate->sad)
return AVERROR(EINVAL);
- av_log(ctx, AV_LOG_VERBOSE, "max_drop_count:%d hi:%d lo:%d frac:%f\n",
+ if (decimate->mode == 1) {
+ av_log(ctx, AV_LOG_VERBOSE, "min_dup_count:%d hi:%d lo:%d frac:%f\n",
+ decimate->min_dup_count, decimate->hi, decimate->lo,
+ decimate->frac);
+ } else {
+ av_log(ctx, AV_LOG_VERBOSE, "max_drop_count:%d hi:%d lo:%d frac:%f\n",
decimate->max_drop_count, decimate->hi, decimate->lo, decimate->frac);
-
+ }
return 0;
}
@@ -208,7 +229,7 @@ static int config_input(AVFilterLink *inlink)
return 0;
}
-static int filter_frame(AVFilterLink *inlink, AVFrame *cur)
+static int filter_frame_mode_0(AVFilterLink *inlink, AVFrame *cur)
{
DecimateContext *decimate = inlink->dst->priv;
AVFilterLink *outlink = inlink->dst->outputs[0];
@@ -257,6 +278,56 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *cur)
return 0;
}
+static int filter_frame_mode_1(AVFilterLink *inlink, AVFrame *cur)
+{
+ DecimateContext *decimate = inlink->dst->priv;
+ AVFilterLink *outlink = inlink->dst->outputs[0];
+ int ret;
+ AVFrame *out = NULL;
+
+ if (!decimate->ref || is_frame_different(inlink->dst, cur, decimate->ref)) {
+ AVFrame *ref = av_frame_clone(cur);
+ if (!ref) {
+ av_frame_free(&cur);
+ return AVERROR(ENOMEM);
+ }
+ av_frame_free(&decimate->ref);
+ decimate->ref = ref;
+ decimate->dup_count = 0;
+ } else {
+ decimate->dup_count++;
+ }
+
+ av_log(inlink->dst, AV_LOG_DEBUG,
+ "%s pts:%s pts_time:%s dup_count:%d\n",
+ decimate->dup_count ==
+ decimate->min_dup_count ? "keep" : "drop", av_ts2str(cur->pts),
+ av_ts2timestr(cur->pts, &inlink->time_base),
+ decimate->dup_count);
+
+ if (decimate->dup_count == decimate->min_dup_count) {
+ out = av_frame_clone(decimate->ref);
+ if (!out) {
+ av_frame_free(&cur);
+ return AVERROR(ENOMEM);
+ }
+ ret = ff_filter_frame(outlink, out);
+ if (ret < 0) {
+ av_frame_free(&cur);
+ return ret;
+ }
+ }
+ av_frame_free(&cur);
+
+ return 0;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *cur)
+{
+ DecimateContext *decimate = inlink->dst->priv;
+ return decimate->mode == 0 ? filter_frame_mode_0(inlink, cur) : filter_frame_mode_1(inlink, cur);
+}
+
static const AVFilterPad mpdecimate_inputs[] = {
{
.name = "default",
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index f5f5096f2b..0eb2e7076c 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -475,6 +475,12 @@ fate-filter-mpdecimate-maxdrop-pos: CMD = framecrc -lavfi testsrc2=r=1:d=5,fps=1
FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC2 FPS MPDECIMATE) += fate-filter-mpdecimate-maxdrop-neg
fate-filter-mpdecimate-maxdrop-neg: CMD = framecrc -lavfi testsrc2=r=1:d=5,fps=10,mpdecimate=max=-3 -pix_fmt yuv420p
+FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC2 FPS MPDECIMATE) += fate-filter-mpdecimate-mode-1
+fate-filter-mpdecimate-mode-1: CMD = framecrc -lavfi testsrc2=r=4:d=5,fps=5,mpdecimate=mode=1 -pix_fmt yuv420p
+
+FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC2 FPS MPDECIMATE) += fate-filter-mpdecimate-mode-1-min-3
+fate-filter-mpdecimate-mode-1-min-3: CMD = framecrc -lavfi testsrc2=r=2:d=7,fps=7,mpdecimate=mode=1:min=3 -pix_fmt yuv420p
+
FATE_FILTER-$(call FILTERFRAMECRC, FPS TESTSRC2) += $(addprefix fate-filter-fps-, up up-round-down up-round-up down down-round-down down-round-up down-eof-pass start-drop start-fill)
fate-filter-fps-up: CMD = framecrc -lavfi testsrc2=r=3:d=2,fps=7
fate-filter-fps-up-round-down: CMD = framecrc -lavfi testsrc2=r=3:d=2,fps=7:round=down
diff --git a/tests/ref/fate/filter-mpdecimate-mode-1 b/tests/ref/fate/filter-mpdecimate-mode-1
new file mode 100644
index 0000000000..f55c09e4be
--- /dev/null
+++ b/tests/ref/fate/filter-mpdecimate-mode-1
@@ -0,0 +1,10 @@
+#tb 0: 1/5
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 320x240
+#sar 0: 1/1
+0, 1, 1, 1, 115200, 0xbd87f5f6
+0, 6, 6, 1, 115200, 0xa0e1f3d6
+0, 11, 11, 1, 115200, 0xc0c5fe56
+0, 16, 16, 1, 115200, 0xba2fccf6
+0, 21, 21, 1, 115200, 0xccefb2df
diff --git a/tests/ref/fate/filter-mpdecimate-mode-1-min-3 b/tests/ref/fate/filter-mpdecimate-mode-1-min-3
new file mode 100644
index 0000000000..3897f6834e
--- /dev/null
+++ b/tests/ref/fate/filter-mpdecimate-mode-1-min-3
@@ -0,0 +1,12 @@
+#tb 0: 1/7
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 320x240
+#sar 0: 1/1
+0, 0, 0, 1, 115200, 0xeba70ff3
+0, 7, 7, 1, 115200, 0x1dadb3a3
+0, 14, 14, 1, 115200, 0xb8cdb7ac
+0, 21, 21, 1, 115200, 0xb789c3ba
+0, 28, 28, 1, 115200, 0xd1084e35
+0, 35, 35, 1, 115200, 0x7bbb8ede
+0, 42, 42, 1, 115200, 0x939ba4bd