PR #25748 opened by Corneliu68 URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25748 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25748.patch
This adds `hbnlmeans`, a wrapper for libhbnlmeans (https://github.com/WebcasterLTD/hbnlmeans, release v1.0.0), which packages HandBrake's NLMeans denoiser as a standalone library, the way zscale uses zimg and vidstab uses vid.stab. Why a second NLMeans filter: the existing `nlmeans` is spatial only. HandBrake's version also searches the frames after the current one (1 to 32), weights each pixel's own patch separately (origin-tune), and has prefilters, presets and tunes that a lot of encodes are made with. With this filter, a HandBrake encode's denoise settings can be reproduced in FFmpeg exactly, from the same option string: ffmpeg -i in.mkv -vf hbnlmeans=preset=medium:tune=film out.mkv ffmpeg -i in.mkv -vf hbnlmeans=y-strength=6:y-origin-tune=0.8:cb-strength=8 out.mkv The output is byte for byte identical to HandBrake's own code for every preset and tune, at 8 and 10 bit and at odd frame sizes. The library's `test/compare-handbrake.run` builds HandBrake's unmodified nlmeans sources and compares them with `ffmpeg -vf hbnlmeans`. Licensing: HandBrake's code is GPL version 2 only, so libhbnlmeans is too. It is added to EXTERNAL_LIBRARY_GPL_LIST, and configure refuses it with `--enable-version3`. The wrapper itself is LGPL 2.1 or later, like the rest of libavfilter. Tested: - `make fate` passes (3168 tests, no samples), including the two new tests `fate-filter-hbnlmeans` (8-bit 4:2:0, preset and tune, 97x65) and `fate-filter-hbnlmeans-prefilter-10bit` (10-bit 4:2:2, prefilter, 3 frames, 97x65). Like the zscale tests, they run only when FFmpeg is configured with the library. - The same checksums at 1, 3 and 16 filter threads. - Builds standalone with `--disable-everything --enable-filter=hbnlmeans` (plus testsrc2, format, crop for the test). - AddressSanitizer: no leaks or invalid accesses, including a 32-frame search on a 10-frame input (flush with fewer frames than the search depth). - `configure --enable-version3 --enable-libhbnlmeans` and `configure --enable-libhbnlmeans` without `--enable-gpl` both stop with a clear message. Corneliu Ticu >From 9fd2abff616fb6916ed31731de9d03d6f7477976 Mon Sep 17 00:00:00 2001 From: Corneliu Ticu <[email protected]> Date: Wed, 4 Nov 2026 11:15:41 +0200 Subject: [PATCH] avfilter: add hbnlmeans filter Add a wrapper for libhbnlmeans, which packages HandBrake's NLMeans denoiser (libhb/nlmeans.c, by Dirk Farin and the HandBrake Team) as a standalone library: https://github.com/WebcasterLTD/hbnlmeans It differs from the existing nlmeans filter in three ways: - it also searches the frames that follow the current one (temporal NLM, 1 to 32 frames), not only the current frame; - each patch's own weight is tunable (origin-tune), which keeps detail on noisy sources and flat areas in animation; - it has HandBrake's prefilters (mean, median, CSM, reduce, edge boost) and HandBrake's presets and tunes. The output is identical, byte for byte, to HandBrake's own NLMeans code for every preset and tune, at 8 and 10 bit and at odd frame sizes. The library's test/compare-handbrake.run builds HandBrake's unmodified nlmeans sources and checks this. So a HandBrake encode's denoise settings can be reproduced in FFmpeg exactly, using the same option string: ffmpeg -i in.mkv -vf hbnlmeans=preset=medium:tune=film out.mkv ffmpeg -i in.mkv -vf hbnlmeans=y-strength=6:y-origin-tune=0.8:cb-strength=8 out.mkv The filter holds back frame-count - 1 frames and flushes them at EOF, uses slice threading, and supports planar YUV from 8 to 16 bit. libhbnlmeans is GPL version 2 only, like HandBrake. It is added to the GPL external libraries, and configure refuses it together with --enable-version3. Signed-off-by: Corneliu Ticu <[email protected]> --- Changelog | 1 + configure | 7 + doc/filters.texi | 135 +++++++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/version.h | 2 +- libavfilter/vf_hbnlmeans.c | 286 ++++++++++++++++++ tests/fate/filter-video.mak | 4 + tests/ref/fate/filter-hbnlmeans | 15 + .../ref/fate/filter-hbnlmeans-prefilter-10bit | 15 + 10 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 libavfilter/vf_hbnlmeans.c create mode 100644 tests/ref/fate/filter-hbnlmeans create mode 100644 tests/ref/fate/filter-hbnlmeans-prefilter-10bit diff --git a/Changelog b/Changelog index 4c5e631d0ad..b89a3fbcc03 100644 --- a/Changelog +++ b/Changelog @@ -5,6 +5,7 @@ version <next>: - DTS:X height channels in the DCA decoder - LCEVC raw demuxer and muxer - AVS+ (AVS1-P16, Broadcasting profile) support in the Chinese AVS decoder +- hbnlmeans filter (HandBrake NLMeans denoiser via libhbnlmeans) version 9.1: - ASTC image encoding and decoding via libastcenc, with .astc and KTX 1.0 muxers and demuxers diff --git a/configure b/configure index 2bbc7812eb3..0ddc80f2afe 100755 --- a/configure +++ b/configure @@ -239,6 +239,7 @@ External library support: --enable-libfreetype enable libfreetype, needed for drawtext filter [no] --enable-libfribidi enable libfribidi, improves drawtext filter [no] --enable-libharfbuzz enable libharfbuzz, needed for drawtext filter [no] + --enable-libhbnlmeans enable HandBrake NLMeans denoiser via libhbnlmeans [no] --enable-libgme enable Game Music Emu via libgme [no] --enable-libgsm enable GSM de/encoding via libgsm [no] --enable-libiec61883 enable iec61883 via libiec61883 [no] @@ -2009,6 +2010,7 @@ EXTERNAL_LIBRARY_GPL_LIST=" libdavs2 libdvdnav libdvdread + libhbnlmeans librubberband libvidstab libx264 @@ -4180,6 +4182,7 @@ fruc_vulkan_filter_deps="vulkan vulkan_1_4 spirv_compiler" fspp_filter_deps="gpl" fsync_filter_deps="avformat" gblur_vulkan_filter_deps="vulkan spirv_compiler" +hbnlmeans_filter_deps="libhbnlmeans" hflip_vulkan_filter_deps="vulkan spirv_compiler" histeq_filter_deps="gpl" hqdn3d_filter_deps="gpl" @@ -4823,6 +4826,9 @@ map "die_license_disabled version3" $EXTERNAL_LIBRARY_VERSION3_LIST $EXTERNAL_LI enabled gpl && map "die_license_disabled_gpl nonfree" $EXTERNAL_LIBRARY_NONFREE_LIST map "die_license_disabled nonfree" $HWACCEL_LIBRARY_NONFREE_LIST +# GPL version 2 only, cannot be combined with (L)GPL version 3 code +enabled version3 && enabled libhbnlmeans && die "libhbnlmeans is GPL version 2 only and incompatible with --enable-version3." + enabled version3 && { enabled gpl && enable gplv3 || enable lgplv3; } enabled gpl && enable lgpl_gpl # Files that are marked as LGPL but some developers prefer only building them with --enable-gpl @@ -7261,6 +7267,7 @@ enabled libfontconfig && require_pkg_config libfontconfig fontconfig "fontco enabled libfreetype && require_pkg_config libfreetype freetype2 "ft2build.h FT_FREETYPE_H" FT_Init_FreeType enabled libfribidi && require_pkg_config libfribidi fribidi fribidi.h fribidi_version_info enabled libharfbuzz && require_pkg_config libharfbuzz harfbuzz hb.h hb_buffer_create +enabled libhbnlmeans && require_pkg_config libhbnlmeans "hbnlmeans >= 1.0.0" hbnlmeans.h hbnlm_create enabled libgme && { check_pkg_config libgme libgme gme/gme.h gme_new_emu || require libgme gme/gme.h gme_new_emu -lgme -lstdc++; } enabled libgsm && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do diff --git a/doc/filters.texi b/doc/filters.texi index b57a5e8a008..2bf64f49b10 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -15882,6 +15882,141 @@ Then, the effect of this Hald CLUT can be visualized with: ffplay input.mkv -vf "movie=clut.png, [in] haldclut" @end example +@section hbnlmeans + +Denoise frames using HandBrake's Non-Local Means algorithm, through the +libhbnlmeans library. + +Unlike @ref{nlmeans}, this filter also searches for similar patches in the +frames that follow the current one, and it offers HandBrake's presets, tunes +and prefilters. Its output is identical to HandBrake's own NLMeans denoiser +with the same settings. + +Each frame is compared with the frames after it, so the filter holds back +@option{frame-count} - 1 frames before it outputs the first one. + +To enable compilation of this filter you need to configure FFmpeg with +@code{--enable-gpl --enable-libhbnlmeans}. libhbnlmeans is licensed under the +GPL version 2 only, so it cannot be combined with @code{--enable-version3}. + +The option names are the ones HandBrake uses, so a HandBrake custom denoise +string can be passed as is. Values set explicitly override the preset. +Values left unset cascade: Cr takes the value of Cb, Cb takes the value of Y, +and Y falls back to the defaults given below. + +The filter accepts the following options: + +@table @option +@item preset +Set the HandBrake preset. Default is @code{custom}. +@table @samp +@item custom +No preset, only the values set explicitly. +@item ultralight +@item light +@item medium +@item strong +@end table + +@item tune +Set the HandBrake tune. Only used together with a @option{preset}. +Default is @code{none}. +@table @samp +@item none +@item film +Most content, live action. +@item grain +Like @code{film}, but preserves luma grain. +@item highmotion +Like @code{film}, but avoids color smearing. +@item animation +Cel animation such as cartoons and anime. +@item tape +Analog tape sources such as VHS. +@item sprite +1-, 4-, 8- and 16-bit two-dimensional games. +@end table + +@item y-strength +@item cb-strength +@item cr-strength +Set the averaging weight decay. Larger values smooth more. Range is +[0, 63], default for Y is 6. + +@item y-origin-tune +@item cb-origin-tune +@item cr-origin-tune +Set the weight of the pixel's own patch. Lower values suit noisier sources +and animation. Range is [0.01, 1], default for Y is 1. + +@item y-patch-size +@item cb-patch-size +@item cr-patch-size +Set the patch width in pixels. Must be odd, range is [1, 15], default for Y +is 7. + +@item y-range +@item cb-range +@item cr-range +Set the width of the spatial search window in pixels. Must be odd. Larger +values give better results and are much slower. Default for Y is 3. + +@item y-frame-count +@item cb-frame-count +@item cr-frame-count +Set how many frames are searched, the current one included. Range is +[1, 32], default for Y is 2. Too many frames smear motion. + +@item y-prefilter +@item cb-prefilter +@item cr-prefilter +Set the prefilter applied before the patches are compared, as the sum of +the following values. Default for Y is 0, no prefilter. +@table @samp +@item 1 +mean 3x3 +@item 2 +mean 5x5 +@item 4 +median 3x3 +@item 8 +median 5x5 +@item 16 +CSM 3x3 +@item 32 +CSM 5x5 +@item 256 +reduce the prefilter by 25% +@item 512 +reduce the prefilter by 50% +@item 1024 +edge boost +@item 2048 +passthru: output the prefiltered image, without NLMeans +@end table +@end table + +@subsection Examples +@itemize +@item +Use HandBrake's medium preset tuned for film: +@example +hbnlmeans=preset=medium:tune=film +@end example + +@item +Use a HandBrake custom string: +@example +hbnlmeans=y-strength=6:y-origin-tune=0.8:cb-strength=8 +@end example + +@item +Denoise luma with a 3x3 mean prefilter reduced by 25%, with edge boost: +@example +hbnlmeans=y-strength=8:y-prefilter=1281 +@end example +@end itemize + @section hflip Flip the input video horizontally. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 6d052562e8f..0392efbca77 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -344,6 +344,7 @@ OBJS-$(CONFIG_GRAYWORLD_FILTER) += vf_grayworld.o OBJS-$(CONFIG_GREYEDGE_FILTER) += vf_colorconstancy.o OBJS-$(CONFIG_GUIDED_FILTER) += vf_guided.o framesync.o OBJS-$(CONFIG_HALDCLUT_FILTER) += vf_lut3d.o framesync.o +OBJS-$(CONFIG_HBNLMEANS_FILTER) += vf_hbnlmeans.o OBJS-$(CONFIG_HFLIP_FILTER) += vf_hflip.o OBJS-$(CONFIG_HFLIP_VULKAN_FILTER) += vf_flip_vulkan.o vulkan.o OBJS-$(CONFIG_HISTEQ_FILTER) += vf_histeq.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 9f07bf83dce..e0cbc739742 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -317,6 +317,7 @@ extern const FFFilter ff_vf_grayworld; extern const FFFilter ff_vf_greyedge; extern const FFFilter ff_vf_guided; extern const FFFilter ff_vf_haldclut; +extern const FFFilter ff_vf_hbnlmeans; extern const FFFilter ff_vf_hflip; extern const FFFilter ff_vf_hflip_vulkan; extern const FFFilter ff_vf_histeq; diff --git a/libavfilter/version.h b/libavfilter/version.h index 64cd692ab67..537df129cd3 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -31,7 +31,7 @@ #include "version_major.h" -#define LIBAVFILTER_VERSION_MINOR 12 +#define LIBAVFILTER_VERSION_MINOR 13 #define LIBAVFILTER_VERSION_MICRO 100 diff --git a/libavfilter/vf_hbnlmeans.c b/libavfilter/vf_hbnlmeans.c new file mode 100644 index 00000000000..2e01ae21bfc --- /dev/null +++ b/libavfilter/vf_hbnlmeans.c @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2026 Corneliu Ticu + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + * HandBrake's NLMeans denoiser, through libhbnlmeans. + * + * Options use HandBrake's own names, so a HandBrake custom string works as is: + * hbnlmeans=y-strength=6:y-origin-tune=0.8:cb-strength=8 + * or a HandBrake preset and tune: + * hbnlmeans=preset=medium:tune=film + * Explicit values override the preset. + */ + +#include <string.h> + +#include <hbnlmeans.h> + +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" +#include "filters.h" +#include "video.h" + +static const char *const preset_names[] = { "custom", "ultralight", "light", "medium", "strong" }; +static const char *const tune_names[] = { "none", "film", "grain", "highmotion", "animation", "tape", "sprite" }; + +#define MAX_QUEUE 32 + +typedef struct HBNLMeansFilterContext { + const AVClass *class; + + int preset; + int tune; + double strength[3]; + double origin_tune[3]; + int patch_size[3]; + int range[3]; + int frame_count[3]; + int prefilter[3]; + + HBNLMeans *nlm; + AVFrame *props[MAX_QUEUE]; // queued frames' properties, no data + int nb_queued; + int eof; + int64_t eof_pts; +} HBNLMeansFilterContext; + +#define OFFSET(x) offsetof(HBNLMeansFilterContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM + +#define CHANNEL_OPTS(prefix, i) \ + { prefix "-strength", "averaging weight decay, larger is smoother", OFFSET(strength[i]), AV_OPT_TYPE_DOUBLE, {.dbl=-1}, -1, 63, FLAGS }, \ + { prefix "-origin-tune", "weight of the origin patch, 0.01..1", OFFSET(origin_tune[i]), AV_OPT_TYPE_DOUBLE, {.dbl=-1}, -1, 1, FLAGS }, \ + { prefix "-patch-size", "patch width in pixels (odd)", OFFSET(patch_size[i]), AV_OPT_TYPE_INT, {.i64=-1}, -1, 15, FLAGS }, \ + { prefix "-range", "spatial search window width (odd)", OFFSET(range[i]), AV_OPT_TYPE_INT, {.i64=-1}, -1, 99, FLAGS }, \ + { prefix "-frame-count", "temporal search depth in frames", OFFSET(frame_count[i]), AV_OPT_TYPE_INT, {.i64=-1}, -1, 32, FLAGS }, \ + { prefix "-prefilter", "prefilter mode (sum of mode values)", OFFSET(prefilter[i]), AV_OPT_TYPE_INT, {.i64=-1}, -1, 4095, FLAGS } + +static const AVOption hbnlmeans_options[] = { + { "preset", "HandBrake preset", OFFSET(preset), AV_OPT_TYPE_INT, {.i64=0}, 0, 4, FLAGS, .unit = "preset" }, + { "custom", "no preset, only the explicit values", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, .unit = "preset" }, + { "ultralight", NULL, 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, .unit = "preset" }, + { "light", NULL, 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, .unit = "preset" }, + { "medium", NULL, 0, AV_OPT_TYPE_CONST, {.i64=3}, 0, 0, FLAGS, .unit = "preset" }, + { "strong", NULL, 0, AV_OPT_TYPE_CONST, {.i64=4}, 0, 0, FLAGS, .unit = "preset" }, + { "tune", "HandBrake tune (with a preset)", OFFSET(tune), AV_OPT_TYPE_INT, {.i64=0}, 0, 6, FLAGS, .unit = "tune" }, + { "none", NULL, 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, .unit = "tune" }, + { "film", "most content, live action", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, .unit = "tune" }, + { "grain", "like film but preserves luma grain", 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, .unit = "tune" }, + { "highmotion", "like film but avoids color smearing", 0, AV_OPT_TYPE_CONST, {.i64=3}, 0, 0, FLAGS, .unit = "tune" }, + { "animation", "cel animation such as cartoons, anime", 0, AV_OPT_TYPE_CONST, {.i64=4}, 0, 0, FLAGS, .unit = "tune" }, + { "tape", "analog tape sources such as VHS", 0, AV_OPT_TYPE_CONST, {.i64=5}, 0, 0, FLAGS, .unit = "tune" }, + { "sprite", "1-/4-/8-/16-bit 2-dimensional games", 0, AV_OPT_TYPE_CONST, {.i64=6}, 0, 0, FLAGS, .unit = "tune" }, + CHANNEL_OPTS("y", 0), + CHANNEL_OPTS("cb", 1), + CHANNEL_OPTS("cr", 2), + { NULL } +}; + +AVFILTER_DEFINE_CLASS(hbnlmeans); + +static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P, + AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV440P10, AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV440P12, AV_PIX_FMT_YUV444P12, + AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, + AV_PIX_FMT_NONE +}; + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + HBNLMeansFilterContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + HBNLMParams p, used; + + for (int c = 0; c < 3; c++) { + p.strength[c] = s->strength[c]; + p.origin_tune[c] = s->origin_tune[c]; + p.patch_size[c] = s->patch_size[c]; + p.range[c] = s->range[c]; + p.frame_count[c] = s->frame_count[c]; + p.prefilter[c] = s->prefilter[c]; + } + if (s->preset) + hbnlm_params_preset(&p, preset_names[s->preset], tune_names[s->tune]); + else if (s->tune) + av_log(ctx, AV_LOG_WARNING, "tune only applies with a preset; ignoring it.\n"); + + for (int i = 0; i < s->nb_queued; i++) + av_frame_free(&s->props[i]); + s->nb_queued = 0; + hbnlm_free(&s->nlm); + s->nlm = hbnlm_create(&p, inlink->w, inlink->h, desc->log2_chroma_w, + desc->log2_chroma_h, desc->comp[0].depth); + if (!s->nlm) + return AVERROR(ENOMEM); + + hbnlm_get_params(s->nlm, &used); + for (int c = 0; c < 3; c++) + av_log(ctx, AV_LOG_VERBOSE, "%s: strength %g origin-tune %g patch %d range %d frames %d prefilter %d\n", + c == 0 ? "y" : c == 1 ? "cb" : "cr", used.strength[c], used.origin_tune[c], + used.patch_size[c], used.range[c], used.frame_count[c], used.prefilter[c]); + return 0; +} + +static int filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + HBNLMeansFilterContext *s = ctx->priv; + AVFrame *out = arg; + uint8_t *dst[3] = { out->data[0], out->data[1], out->data[2] }; + const ptrdiff_t stride[3] = { out->linesize[0], out->linesize[1], out->linesize[2] }; + int ret = hbnlm_filter_slice(s->nlm, dst, stride, jobnr, nb_jobs); + return ret < 0 ? AVERROR(-ret) : 0; +} + +// Denoise the head of the queue, send it, and drop it +static int output_frame(AVFilterContext *ctx) +{ + HBNLMeansFilterContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + int ret; + + if (!out) + return AVERROR(ENOMEM); + ret = av_frame_copy_props(out, s->props[0]); + if (ret >= 0) + ret = ff_filter_execute(ctx, filter_slice, out, NULL, + FFMIN(AV_CEIL_RSHIFT(outlink->h, 2), ff_filter_get_nb_threads(ctx))); + if (ret < 0) { + av_frame_free(&out); + return ret; + } + + hbnlm_pop(s->nlm); + av_frame_free(&s->props[0]); + memmove(&s->props[0], &s->props[1], (s->nb_queued - 1) * sizeof(*s->props)); + s->props[--s->nb_queued] = NULL; + + return ff_filter_frame(outlink, out); +} + +static int add_frame(AVFilterContext *ctx, AVFrame *in) +{ + HBNLMeansFilterContext *s = ctx->priv; + const uint8_t *src[3] = { in->data[0], in->data[1], in->data[2] }; + const ptrdiff_t stride[3] = { in->linesize[0], in->linesize[1], in->linesize[2] }; + AVFrame *props = av_frame_alloc(); + int ret; + + if (!props) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + ret = av_frame_copy_props(props, in); + if (ret >= 0) { + ret = hbnlm_push(s->nlm, src, stride); + ret = ret < 0 ? AVERROR(-ret) : 0; + } + av_frame_free(&in); + if (ret < 0) { + av_frame_free(&props); + return ret; + } + s->props[s->nb_queued++] = props; + return 0; +} + +static int activate(AVFilterContext *ctx) +{ + HBNLMeansFilterContext *s = ctx->priv; + AVFilterLink *inlink = ctx->inputs[0]; + AVFilterLink *outlink = ctx->outputs[0]; + AVFrame *in; + int64_t pts; + int status, ret; + + FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink); + + if (!s->eof) { + ret = ff_inlink_consume_frame(inlink, &in); + if (ret < 0) + return ret; + if (ret > 0) { + ret = add_frame(ctx, in); + if (ret < 0) + return ret; + // Each frame is compared with the frames after it, so wait for them + if (s->nb_queued >= hbnlm_frames_needed(s->nlm)) + ret = output_frame(ctx); + ff_filter_set_ready(ctx, 100); + return ret; + } + if (ff_inlink_acknowledge_status(inlink, &status, &pts)) { + s->eof = 1; + s->eof_pts = pts; + } + } + + if (s->eof) { + // Flush: the last frames use only the frames that remain + if (s->nb_queued) { + ret = output_frame(ctx); + ff_filter_set_ready(ctx, 100); + return ret; + } + ff_outlink_set_status(outlink, AVERROR_EOF, s->eof_pts); + return 0; + } + + FF_FILTER_FORWARD_WANTED(outlink, inlink); + return FFERROR_NOT_READY; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + HBNLMeansFilterContext *s = ctx->priv; + for (int i = 0; i < s->nb_queued; i++) + av_frame_free(&s->props[i]); + s->nb_queued = 0; + hbnlm_free(&s->nlm); +} + +static const AVFilterPad hbnlmeans_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_input, + }, +}; + +const FFFilter ff_vf_hbnlmeans = { + .p.name = "hbnlmeans", + .p.description = NULL_IF_CONFIG_SMALL("HandBrake's NLMeans denoiser (libhbnlmeans)."), + .p.priv_class = &hbnlmeans_class, + .p.flags = AVFILTER_FLAG_SLICE_THREADS, + .priv_size = sizeof(HBNLMeansFilterContext), + .activate = activate, + .uninit = uninit, + FILTER_INPUTS(hbnlmeans_inputs), + FILTER_OUTPUTS(ff_video_default_filterpad), + FILTER_PIXFMTS_ARRAY(pix_fmts), +}; diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 95254ff3fcf..efe058d7d03 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -250,6 +250,10 @@ fate-filter-negate: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf perms=random,negate FATE_FILTER_VSYNTH_PGMYUV-$(CONFIG_HISTOGRAM_FILTER) += fate-filter-histogram-levels fate-filter-histogram-levels: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf histogram -flags +bitexact -sws_flags +accurate_rnd+bitexact +FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC2 FORMAT CROP HBNLMEANS) += fate-filter-hbnlmeans fate-filter-hbnlmeans-prefilter-10bit +fate-filter-hbnlmeans: CMD = framecrc -lavfi testsrc2=s=98x66:r=5:d=2,format=yuv420p,crop=97:65:0:0:exact=1,hbnlmeans=preset=medium:tune=film -flags +bitexact +fate-filter-hbnlmeans-prefilter-10bit: CMD = framecrc -lavfi testsrc2=s=98x66:r=5:d=2,format=yuv422p10,crop=97:65:0:0:exact=1,hbnlmeans=y-strength=8:y-prefilter=1281:y-frame-count=3:cb-strength=4:cb-range=5 -flags +bitexact + FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC2 FORMAT THISTOGRAM) += fate-filter-thistogram-scroll fate-filter-thistogram-scroll: CMD = framecrc -lavfi testsrc2=s=65x49:r=1:d=1,format=yuv420p,thistogram=display_mode=parade:components=7:slide=scroll -flags +bitexact diff --git a/tests/ref/fate/filter-hbnlmeans b/tests/ref/fate/filter-hbnlmeans new file mode 100644 index 00000000000..013267a7a33 --- /dev/null +++ b/tests/ref/fate/filter-hbnlmeans @@ -0,0 +1,15 @@ +#tb 0: 1/5 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 97x65 +#sar 0: 1/1 +0, 0, 0, 1, 9539, 0xf9f39cb7 +0, 1, 1, 1, 9539, 0xa81ea0cf +0, 2, 2, 1, 9539, 0x93a19389 +0, 3, 3, 1, 9539, 0x77198ade +0, 4, 4, 1, 9539, 0xc22a9563 +0, 5, 5, 1, 9539, 0xd01f8527 +0, 6, 6, 1, 9539, 0xa1e496c0 +0, 7, 7, 1, 9539, 0x1732ae3c +0, 8, 8, 1, 9539, 0x011bafbc +0, 9, 9, 1, 9539, 0x1047ae82 diff --git a/tests/ref/fate/filter-hbnlmeans-prefilter-10bit b/tests/ref/fate/filter-hbnlmeans-prefilter-10bit new file mode 100644 index 00000000000..4ed2aa76d74 --- /dev/null +++ b/tests/ref/fate/filter-hbnlmeans-prefilter-10bit @@ -0,0 +1,15 @@ +#tb 0: 1/5 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 97x65 +#sar 0: 1/1 +0, 0, 0, 1, 25350, 0xb4d281e6 +0, 1, 1, 1, 25350, 0x5ba9957f +0, 2, 2, 1, 25350, 0xedae7c39 +0, 3, 3, 1, 25350, 0x19ee3108 +0, 4, 4, 1, 25350, 0xa3c98cce +0, 5, 5, 1, 25350, 0x615c58b6 +0, 6, 6, 1, 25350, 0xc8c37aa1 +0, 7, 7, 1, 25350, 0x510d5e47 +0, 8, 8, 1, 25350, 0x5cc388b7 +0, 9, 9, 1, 25350, 0x2c1984bc -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
