Skip to content

Instantly share code, notes, and snippets.

@0x09
Last active August 3, 2019 01:50
Show Gist options
  • Save 0x09/5417ddeb1c80ad52acb69691aca3353a to your computer and use it in GitHub Desktop.
Save 0x09/5417ddeb1c80ad52acb69691aca3353a to your computer and use it in GitHub Desktop.
resdetect filter for FFmpeg
diff --git a/configure b/configure
index 5a4f507246..6cc110d7e7 100755
--- a/configure
+++ b/configure
@@ -255,6 +255,7 @@ External library support:
--enable-libopus enable Opus de/encoding via libopus [no]
--enable-libpulse enable Pulseaudio input via libpulse [no]
--enable-librsvg enable SVG rasterization via librsvg [no]
+ --enable-libresdet enable resdetect filter [no]
--enable-librubberband enable rubberband needed for rubberband filter [no]
--enable-librtmp enable RTMP[E] support via librtmp [no]
--enable-libshine enable fixed-point MP3 encoding via libshine [no]
@@ -1779,6 +1780,7 @@ EXTERNAL_LIBRARY_LIST="
libopenmpt
libopus
libpulse
+ libresdet
librsvg
librtmp
libshine
@@ -3548,6 +3550,7 @@ scale_vaapi_filter_deps="vaapi"
vpp_qsv_filter_deps="libmfx"
vpp_qsv_filter_select="qsvvpp"
yadif_cuda_filter_deps="ffnvcodec cuda_nvcc"
+resdetect_filter_deps="libresdet"
# examples
avio_dir_cmd_deps="avformat avutil"
@@ -6333,6 +6336,7 @@ enabled rkmpp && { require_pkg_config rkmpp rockchip_mpp rockchip/r
die "ERROR: rkmpp requires --enable-libdrm"; }
}
enabled vapoursynth && require_pkg_config vapoursynth "vapoursynth-script >= 42" VSScript.h vsscript_init
+enabled libresdet && require_pkg_config libresdet resdet resdet.h resdet_methods
if enabled gcrypt; then
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index efc7bbb153..53cfc0d181 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -341,6 +341,7 @@ OBJS-$(CONFIG_REMAP_FILTER) += vf_remap.o framesync.o
OBJS-$(CONFIG_REMOVEGRAIN_FILTER) += vf_removegrain.o
OBJS-$(CONFIG_REMOVELOGO_FILTER) += bbox.o lswsutils.o lavfutils.o vf_removelogo.o
OBJS-$(CONFIG_REPEATFIELDS_FILTER) += vf_repeatfields.o
+OBJS-$(CONFIG_RESDETECT_FILTER) += vf_resdetect.o
OBJS-$(CONFIG_REVERSE_FILTER) += f_reverse.o
OBJS-$(CONFIG_RGBASHIFT_FILTER) += vf_chromashift.o
OBJS-$(CONFIG_ROBERTS_FILTER) += vf_convolution.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index abd726d616..beb6104609 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -325,6 +325,7 @@ extern AVFilter ff_vf_remap;
extern AVFilter ff_vf_removegrain;
extern AVFilter ff_vf_removelogo;
extern AVFilter ff_vf_repeatfields;
+extern AVFilter ff_vf_resdetect;
extern AVFilter ff_vf_reverse;
extern AVFilter ff_vf_rgbashift;
extern AVFilter ff_vf_roberts;
diff --git a/libavfilter/vf_resdetect.c b/libavfilter/vf_resdetect.c
new file mode 100644
index 0000000000..f2d63226c1
--- /dev/null
+++ b/libavfilter/vf_resdetect.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2019 0x09.net
+ * 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
+ * resolution detection filter using resdet
+ */
+
+#include "libavutil/opt.h"
+#include "libavutil/imgutils.h"
+#include "avfilter.h"
+#include "internal.h"
+
+#include <resdet.h>
+
+typedef struct ResDetectContext {
+ const AVClass *class;
+ const char *method_name;
+ RDMethod *method;
+ size_t range;
+ float threshold;
+} ResDetectContext;
+
+static int query_formats(AVFilterContext *ctx)
+{
+ // Any planar 8-bit format where the first plane represents the primary intensity data will work
+ static const enum AVPixelFormat pix_fmts[] = {
+ AV_PIX_FMT_GRAY8,
+ AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_YUV422P,
+ AV_PIX_FMT_YUV444P,
+ AV_PIX_FMT_YUV410P,
+ AV_PIX_FMT_YUV411P,
+ AV_PIX_FMT_YUVJ420P,
+ AV_PIX_FMT_YUVJ422P,
+ AV_PIX_FMT_YUVJ444P,
+ AV_PIX_FMT_NV12,
+ AV_PIX_FMT_NONE
+ };
+
+ AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
+ if (!fmts_list)
+ return AVERROR(ENOMEM);
+
+ return ff_set_common_formats(ctx, fmts_list);
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+ ResDetectContext *s = ctx->priv;
+
+ s->method = resdet_get_method(s->method_name);
+ if(!s->method)
+ {
+ av_log(ctx, AV_LOG_ERROR, "Invalid resdetect method '%s'.\n", s->method_name);
+ return AVERROR(EINVAL);
+ }
+
+ if(!s->range)
+ s->range = resdet_default_range();
+
+ if(s->threshold < 0)
+ s->threshold = s->method->threshold;
+
+ return 0;
+}
+
+static int sortres(const void *left, const void *right)
+{
+ return ((const RDResolution*)right)->confidence*10000 - ((const RDResolution*)left)->confidence*10000;
+}
+
+/*
+ * resdet expects a single non-aligned Y plane. If the frame data is already suitable for that (usually the case)
+ * then just return it, otherwise copy into a new buffer which can be freed by `free_plane_unaligned`
+ * some mild extensions to resdet's api could make this unnecessary as the actual internal detect functions
+ * do accept stride and dist parameters
+*/
+static uint8_t *get_plane_unaligned(AVFrame *frame)
+{
+ if(frame->linesize[0] == frame->width)
+ return frame->data[0];
+
+ uint8_t *buf = av_malloc(frame->width * frame->height);
+ if(buf)
+ av_image_copy_plane(buf, frame->width, frame->data[0], frame->linesize[0], 1, frame->height);
+
+ return buf;
+}
+
+static void free_plane_unaligned(AVFrame *frame, uint8_t *buf)
+{
+ if(frame->linesize[0] != frame->width)
+ av_free(buf);
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
+{
+ AVFilterContext *ctx = inlink->dst;
+ ResDetectContext *s = ctx->priv;
+ RDResolution *rw, *rh;
+ size_t cw, ch;
+
+ uint8_t *buf = get_plane_unaligned(frame);
+ if(!buf)
+ return AVERROR(ENOMEM);
+
+ resdetect_with_params(buf, 1, frame->width, frame->height,
+ &rw, &cw, &rh, &ch,
+ s->method, s->range, s->threshold);
+
+ free_plane_unaligned(frame, buf);
+
+ qsort(rw, cw, sizeof(*rw), sortres);
+ qsort(rh, ch, sizeof(*rh), sortres);
+
+ av_log(ctx, AV_LOG_INFO, "w: %zu, h: %zu\n", rw[0].index, rh[0].index);
+
+ return ff_filter_frame(inlink->dst->outputs[0], frame);
+}
+
+#define OFFSET(x) offsetof(ResDetectContext, x)
+#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption resdetect_options[] = {
+ { "method", "Detection method to use", OFFSET(method_name), AV_OPT_TYPE_STRING, { .str = NULL }, CHAR_MIN, CHAR_MAX, FLAGS },
+ { "range", "Number of neighboring values to search", OFFSET(range), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
+ { "threshold", "Consider results above this confidence", OFFSET(threshold), AV_OPT_TYPE_FLOAT, { .dbl = -1 }, -1.0, 1.0, FLAGS },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(resdetect);
+
+static const AVFilterPad avfilter_vf_resdetect_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = filter_frame,
+ },
+ { NULL }
+};
+
+static const AVFilterPad avfilter_vf_resdetect_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO
+ },
+ { NULL }
+};
+
+AVFilter ff_vf_resdetect = {
+ .name = "resdetect",
+ .description = NULL_IF_CONFIG_SMALL("Detect source resolution of upscaled video."),
+ .priv_size = sizeof(ResDetectContext),
+ .priv_class = &resdetect_class,
+ .init = init,
+ .query_formats = query_formats,
+ .inputs = avfilter_vf_resdetect_inputs,
+ .outputs = avfilter_vf_resdetect_outputs,
+ .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
+};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment