PR #23217 opened by StachowiakDawid URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23217 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23217.patch
This video filter is based on `mpdecimate`, but instead of removing duplicated frames, it also removes all frames that aren't duplicates and keeps the first occurrence of every duplicated frame. Sample usage: `ffmpeg -i INPUT -an -c:v CODEC -g 1 -vf mpdupfirst=min=20,setpts=N/TB OUTPUT` E.g. deployment scenario: archiving a TV station with static advertisements displayed for ~20 seconds occasionally mixed with video advertisements. >From 150b4b71ddd9c888b60e253a1cc19c589d1479e4 Mon Sep 17 00:00:00 2001 From: StachowiakDawid <[email protected]> Date: Thu, 2 Jan 2025 14:54:03 +0100 Subject: [PATCH] Add mpdupfirst video filter --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_mpdupfirst.c | 235 ++++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 libavfilter/vf_mpdupfirst.c diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 03bf51d3fd..a73ea8b50a 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -404,6 +404,7 @@ OBJS-$(CONFIG_MIX_FILTER) += vf_mix.o framesync.o OBJS-$(CONFIG_MONOCHROME_FILTER) += vf_monochrome.o OBJS-$(CONFIG_MORPHO_FILTER) += vf_morpho.o framesync.o OBJS-$(CONFIG_MPDECIMATE_FILTER) += vf_mpdecimate.o +OBJS-$(CONFIG_MPDUPFIRST_FILTER) += vf_mpdupfirst.o OBJS-$(CONFIG_MSAD_FILTER) += vf_identity.o framesync.o OBJS-$(CONFIG_MULTIPLY_FILTER) += vf_multiply.o framesync.o OBJS-$(CONFIG_NEGATE_FILTER) += vf_negate.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 66c49d453b..9b2b5a08a6 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -378,6 +378,7 @@ extern const FFFilter ff_vf_mix; extern const FFFilter ff_vf_monochrome; extern const FFFilter ff_vf_morpho; extern const FFFilter ff_vf_mpdecimate; +extern const FFFilter ff_vf_mpdupfirst; extern const FFFilter ff_vf_msad; extern const FFFilter ff_vf_multiply; extern const FFFilter ff_vf_negate; diff --git a/libavfilter/vf_mpdupfirst.c b/libavfilter/vf_mpdupfirst.c new file mode 100644 index 0000000000..f293cd3cd9 --- /dev/null +++ b/libavfilter/vf_mpdupfirst.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2003 Rich Felker + * Copyright (c) 2012 Stefano Sabatini + * Copyright (c) 2026 Dawid Stachowiak + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with FFmpeg; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * @file mpdupfirst filter, based on vf_mpdecimate.c + */ + +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/pixelutils.h" +#include "libavutil/timestamp.h" +#include "avfilter.h" +#include "filters.h" +#include "video.h" + +typedef struct DecimateContext { + const AVClass *class; + 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 min_dup_count; ///< minimum number of previous frames that need to be duplicated to keep frame + + int dup_count; ///< number of duplicated frames + + int hsub, vsub; ///< chroma subsampling values + AVFrame *ref; ///< reference picture + av_pixelutils_sad_fn sad; ///< sum of absolute difference function +} DecimateContext; + +#define OFFSET(x) offsetof(DecimateContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption mpdupfirst_options[] = { + { "min", + "set minimum number of previous frames that need to be duplicated to keep current frame", + OFFSET(min_dup_count), AV_OPT_TYPE_INT, {.i64 = + 10}, 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 }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(mpdupfirst); + +/** + * Return 1 if the two planes are different, 0 otherwise. + */ +static int diff_planes(AVFilterContext *ctx, + uint8_t *cur, int cur_linesize, + uint8_t *ref, int ref_linesize, int w, int h) +{ + DecimateContext *decimate = ctx->priv; + + int x, y; + int d, c = 0; + int t = (w / 16) * (h / 16) * decimate->frac; + + /* compute difference for blocks of 8x8 bytes */ + for (y = 0; y < h - 7; y += 4) { + for (x = 8; x < w - 7; x += 4) { + d = decimate->sad(cur + y * cur_linesize + x, cur_linesize, + ref + y * ref_linesize + x, ref_linesize); + if (d > decimate->hi) { + av_log(ctx, AV_LOG_DEBUG, "%d>=hi ", d); + return 1; + } + if (d > decimate->lo) { + c++; + if (c > t) { + av_log(ctx, AV_LOG_DEBUG, "lo:%d>=%d ", c, t); + return 1; + } + } + } + } + + av_log(ctx, AV_LOG_DEBUG, "lo:%d<%d ", c, t); + return 0; +} + +/** + * Tell if the frame is different with respect to the reference frame ref. + */ +static int is_frame_different(AVFilterContext *ctx, + AVFrame *cur, AVFrame *ref) +{ + DecimateContext *decimate = ctx->priv; + int plane; + + for (plane = 0; ref->data[plane] && ref->linesize[plane]; plane++) { + /* use 8x8 SAD even on subsampled planes. The blocks won't match up with + * luma blocks, but hopefully nobody is depending on this to catch + * localized chroma changes that wouldn't exceed the thresholds when + * diluted by using what's effectively a larger block size. + */ + int vsub = plane == 1 || plane == 2 ? decimate->vsub : 0; + int hsub = plane == 1 || plane == 2 ? decimate->hsub : 0; + 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))) + return 1; + } + + return 0; +} + +static av_cold int init(AVFilterContext *ctx) +{ + DecimateContext *decimate = ctx->priv; + + decimate->sad = av_pixelutils_get_sad_fn(3, 3, 0, ctx); // 8x8, not aligned on blocksize + if (!decimate->sad) + return AVERROR(EINVAL); + + 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); + + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + DecimateContext *decimate = ctx->priv; + av_frame_free(&decimate->ref); +} + +static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, + AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV440P, + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ422P, + AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ440P, + AV_PIX_FMT_YUVA420P, + + AV_PIX_FMT_GBRP, + + AV_PIX_FMT_YUVA444P, + AV_PIX_FMT_YUVA422P, + + AV_PIX_FMT_NONE +}; + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + DecimateContext *decimate = ctx->priv; + const AVPixFmtDescriptor *pix_desc = + av_pix_fmt_desc_get(inlink->format); + decimate->hsub = pix_desc->log2_chroma_w; + decimate->vsub = pix_desc->log2_chroma_h; + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *cur) +{ + DecimateContext *decimate = inlink->dst->priv; + AVFilterLink *outlink = inlink->dst->outputs[0]; + int ret; + + if (decimate->ref + && is_frame_different(inlink->dst, cur, decimate->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 + && (ret = ff_filter_frame(outlink, av_frame_clone(cur))) < 0) + return ret; + + av_frame_free(&decimate->ref); + decimate->ref = av_frame_clone(cur); + av_frame_free(&cur); + + return 0; +} + +static const AVFilterPad mpdupfirst_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_input, + .filter_frame = filter_frame, + }, +}; + +const FFFilter ff_vf_mpdupfirst = { + .p.name = "mpdupfirst", + .p.description = + NULL_IF_CONFIG_SMALL + ("Remove non-duplicate frames and duplicate frames after the first one."), + .p.priv_class = &mpdupfirst_class, + .init = init, + .uninit = uninit, + .priv_size = sizeof(DecimateContext), + FILTER_INPUTS(mpdupfirst_inputs), + FILTER_OUTPUTS(ff_video_default_filterpad), + FILTER_PIXFMTS_ARRAY(pix_fmts), +}; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
