summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xconfigure3
-rw-r--r--libavfilter/Makefile1
-rw-r--r--libavfilter/allfilters.c1
-rw-r--r--libavfilter/vf_libplacebo.c717
4 files changed, 722 insertions, 0 deletions
diff --git a/configure b/configure
index eb451d2782..891824757b 100755
--- a/configure
+++ b/configure
@@ -1827,6 +1827,7 @@ EXTERNAL_LIBRARY_LIST="
libopenmpt
libopenvino
libopus
+ libplacebo
libpulse
librabbitmq
librav1e
@@ -3618,6 +3619,7 @@ interlace_filter_deps="gpl"
kerndeint_filter_deps="gpl"
ladspa_filter_deps="ladspa libdl"
lensfun_filter_deps="liblensfun version3"
+libplacebo_filter_deps="libplacebo vulkan libglslang"
lv2_filter_deps="lv2"
mcdeint_filter_deps="avcodec gpl"
metadata_filter_deps="avformat"
@@ -6493,6 +6495,7 @@ enabled libopus && {
require_pkg_config libopus opus opus_multistream.h opus_multistream_surround_encoder_create
}
}
+enabled libplacebo && require_pkg_config libplacebo "libplacebo >= 4.173.0" libplacebo/vulkan.h pl_vulkan_create
enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new
enabled librabbitmq && require_pkg_config librabbitmq "librabbitmq >= 0.7.1" amqp.h amqp_new_connection
enabled librav1e && require_pkg_config librav1e "rav1e >= 0.4.0" rav1e.h rav1e_context_new
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 552bd4e286..e2059766b0 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -323,6 +323,7 @@ OBJS-$(CONFIG_LAGFUN_FILTER) += vf_lagfun.o
OBJS-$(CONFIG_LATENCY_FILTER) += f_latency.o
OBJS-$(CONFIG_LENSCORRECTION_FILTER) += vf_lenscorrection.o
OBJS-$(CONFIG_LENSFUN_FILTER) += vf_lensfun.o
+OBJS-$(CONFIG_LIBPLACEBO_FILTER) += vf_libplacebo.o vulkan.o
OBJS-$(CONFIG_LIBVMAF_FILTER) += vf_libvmaf.o framesync.o
OBJS-$(CONFIG_LIMITDIFF_FILTER) += vf_limitdiff.o framesync.o
OBJS-$(CONFIG_LIMITER_FILTER) += vf_limiter.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 667b6fc246..be94249024 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -308,6 +308,7 @@ extern const AVFilter ff_vf_lagfun;
extern const AVFilter ff_vf_latency;
extern const AVFilter ff_vf_lenscorrection;
extern const AVFilter ff_vf_lensfun;
+extern const AVFilter ff_vf_libplacebo;
extern const AVFilter ff_vf_libvmaf;
extern const AVFilter ff_vf_limitdiff;
extern const AVFilter ff_vf_limiter;
diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c
new file mode 100644
index 0000000000..0590e99093
--- /dev/null
+++ b/libavfilter/vf_libplacebo.c
@@ -0,0 +1,717 @@
+/*
+ * 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
+ */
+
+#include "libavutil/file.h"
+#include "libavutil/opt.h"
+#include "internal.h"
+#include "vulkan.h"
+#include "scale_eval.h"
+
+#include <libplacebo/renderer.h>
+#include <libplacebo/utils/libav.h>
+#include <libplacebo/vulkan.h>
+
+typedef struct LibplaceboContext {
+ /* lavfi vulkan*/
+ FFVulkanContext vkctx;
+ int initialized;
+
+ /* libplacebo */
+ pl_log log;
+ pl_vulkan vulkan;
+ pl_gpu gpu;
+ pl_renderer renderer;
+
+ /* settings */
+ char *out_format_string;
+ char *w_expr;
+ char *h_expr;
+ AVRational target_sar;
+ float pad_crop_ratio;
+ int force_original_aspect_ratio;
+ int force_divisible_by;
+ int normalize_sar;
+ int apply_filmgrain;
+ int colorspace;
+ int color_range;
+ int color_primaries;
+ int color_trc;
+
+ /* pl_render_params */
+ char *upscaler;
+ char *downscaler;
+ int lut_entries;
+ float antiringing;
+ int sigmoid;
+ int skip_aa;
+ float polar_cutoff;
+ int disable_linear;
+ int disable_builtin;
+ int force_3dlut;
+ int force_dither;
+ int disable_fbos;
+
+ /* pl_deband_params */
+ int deband;
+ int deband_iterations;
+ float deband_threshold;
+ float deband_radius;
+ float deband_grain;
+
+ /* pl_color_adjustment */
+ float brightness;
+ float contrast;
+ float saturation;
+ float hue;
+ float gamma;
+
+ /* pl_peak_detect_params */
+ int peakdetect;
+ float smoothing;
+ float min_peak;
+ float scene_low;
+ float scene_high;
+ float overshoot;
+
+ /* pl_color_map_params */
+ int intent;
+ int tonemapping;
+ float tonemapping_param;
+ float desat_str;
+ float desat_exp;
+ float desat_base;
+ float max_boost;
+ int gamut_warning;
+ int gamut_clipping;
+
+ /* pl_dither_params */
+ int dithering;
+ int dither_lut_size;
+ int dither_temporal;
+
+ /* pl_cone_params */
+ int cones;
+ float cone_str;
+
+ /* custom shaders */
+ char *shader_path;
+ void *shader_bin;
+ int shader_bin_len;
+ const struct pl_hook *hooks[2];
+ int num_hooks;
+} LibplaceboContext;
+
+static void pl_av_log(void *log_ctx, enum pl_log_level level, const char *msg)
+{
+ int av_lev;
+
+ switch (level) {
+ case PL_LOG_FATAL: av_lev = AV_LOG_FATAL; break;
+ case PL_LOG_ERR: av_lev = AV_LOG_ERROR; break;
+ case PL_LOG_WARN: av_lev = AV_LOG_WARNING; break;
+ case PL_LOG_INFO: av_lev = AV_LOG_VERBOSE; break;
+ case PL_LOG_DEBUG: av_lev = AV_LOG_DEBUG; break;
+ case PL_LOG_TRACE: av_lev = AV_LOG_TRACE; break;
+ default: return;
+ }
+
+ av_log(log_ctx, av_lev, "%s\n", msg);
+}
+
+static int parse_shader(AVFilterContext *avctx, const void *shader, size_t len)
+{
+ LibplaceboContext *s = avctx->priv;
+ const struct pl_hook *hook;
+
+ hook = pl_mpv_user_shader_parse(s->gpu, shader, len);
+ if (!hook) {
+ av_log(s, AV_LOG_ERROR, "Failed parsing custom shader!\n");
+ return AVERROR(EINVAL);
+ }
+
+ s->hooks[s->num_hooks++] = hook;
+ return 0;
+}
+
+static int find_scaler(AVFilterContext *avctx,
+ const struct pl_filter_config **opt,
+ const char *name)
+{
+ const struct pl_filter_preset *preset;
+ if (!strcmp(name, "help")) {
+ av_log(avctx, AV_LOG_INFO, "Available scaler presets:\n");
+ for (preset = pl_scale_filters; preset->name; preset++)
+ av_log(avctx, AV_LOG_INFO, " %s\n", preset->name);
+ return AVERROR_EXIT;
+ }
+
+ for (preset = pl_scale_filters; preset->name; preset++) {
+ if (!strcmp(name, preset->name)) {
+ *opt = preset->filter;
+ return 0;
+ }
+ }
+
+ av_log(avctx, AV_LOG_ERROR, "No such scaler preset '%s'.\n", name);
+ return AVERROR(EINVAL);
+}
+
+static int libplacebo_init(AVFilterContext *avctx)
+{
+ LibplaceboContext *s = avctx->priv;
+
+ /* Create libplacebo log context */
+ s->log = pl_log_create(PL_API_VER, pl_log_params(
+ .log_level = PL_LOG_DEBUG,
+ .log_cb = pl_av_log,
+ .log_priv = s,
+ ));
+
+ if (!s->log)
+ return AVERROR(ENOMEM);
+
+ /* Note: s->vulkan etc. are initialized later, when hwctx is available */
+ return 0;
+}
+
+static int init_vulkan(AVFilterContext *avctx)
+{
+ int err = 0;
+ LibplaceboContext *s = avctx->priv;
+ const AVVulkanDeviceContext *hwctx = s->vkctx.hwctx;
+ uint8_t *buf = NULL;
+ size_t buf_len;
+
+ /* Import libavfilter vulkan context into libplacebo */
+ s->vulkan = pl_vulkan_import(s->log, pl_vulkan_import_params(
+ .instance = hwctx->inst,
+ .get_proc_addr = hwctx->get_proc_addr,
+ .phys_device = hwctx->phys_dev,
+ .device = hwctx->act_dev,
+ .extensions = hwctx->enabled_dev_extensions,
+ .num_extensions = hwctx->nb_enabled_dev_extensions,
+ .features = &hwctx->device_features,
+ .queue_graphics = {
+ .index = hwctx->queue_family_index,
+ .count = hwctx->nb_graphics_queues,
+ },
+ .queue_compute = {
+ .index = hwctx->queue_family_comp_index,
+ .count = hwctx->nb_comp_queues,
+ },
+ .queue_transfer = {
+ .index = hwctx->queue_family_tx_index,
+ .count = hwctx->nb_tx_queues,
+ },
+ /* This is the highest version created by hwcontext_vulkan.c */
+ .max_api_version = VK_API_VERSION_1_2,
+ ));
+
+ if (!s->vulkan) {
+ av_log(s, AV_LOG_ERROR, "Failed importing vulkan device to libplacebo!\n");
+ err = AVERROR_EXTERNAL;
+ goto fail;
+ }
+
+ /* Create the renderer */
+ s->gpu = s->vulkan->gpu;
+ s->renderer = pl_renderer_create(s->log, s->gpu);
+
+ /* Parse the user shaders, if requested */
+ if (s->shader_bin_len)
+ RET(parse_shader(avctx, s->shader_bin, s->shader_bin_len));
+
+ if (s->shader_path && s->shader_path[0]) {
+ RET(av_file_map(s->shader_path, &buf, &buf_len, 0, s));
+ RET(parse_shader(avctx, buf, buf_len));
+ }
+
+ /* fall through */
+fail:
+ if (buf)
+ av_file_unmap(buf, buf_len);
+ s->initialized = 1;
+ return err;
+}
+
+static void libplacebo_uninit(AVFilterContext *avctx)
+{
+ LibplaceboContext *s = avctx->priv;
+
+ for (int i = 0; i < s->num_hooks; i++)
+ pl_mpv_user_shader_destroy(&s->hooks[i]);
+ pl_renderer_destroy(&s->renderer);
+ pl_vulkan_destroy(&s->vulkan);
+ pl_log_destroy(&s->log);
+ ff_vk_filter_uninit(avctx);
+ s->initialized = 0;
+ s->gpu = NULL;
+}
+
+static int wrap_vkframe(pl_gpu gpu, const AVFrame *frame, int plane, pl_tex *tex)
+{
+ AVVkFrame *vkf = (AVVkFrame *) frame->data[0];
+ const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
+ const AVVulkanFramesContext *vkfc = hwfc->hwctx;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format);
+ const VkFormat *vk_fmt = av_vkfmt_from_pixfmt(hwfc->sw_format);
+ const int chroma = plane == 1 || plane == 2;
+
+ *tex = pl_vulkan_wrap(gpu, pl_vulkan_wrap_params(
+ .image = vkf->img[plane],
+ .format = vk_fmt[plane],
+ .width = AV_CEIL_RSHIFT(frame->width, chroma ? desc->log2_chroma_w : 0),
+ .height = AV_CEIL_RSHIFT(frame->height, chroma ? desc->log2_chroma_h : 0),
+ .usage = vkfc->usage,
+ ));
+
+ if (!*tex)
+ return AVERROR(ENOMEM);
+
+ pl_vulkan_release(gpu, *tex, vkf->layout[plane], (pl_vulkan_sem) {
+ .sem = vkf->sem[plane],
+ .value = vkf->sem_value[plane]
+ });
+ return 0;
+}
+
+static int unwrap_vkframe(pl_gpu gpu, AVFrame *frame, int plane, pl_tex *tex)
+{
+ AVVkFrame *vkf = (AVVkFrame *) frame->data[0];
+ int ok = pl_vulkan_hold_raw(gpu, *tex, &vkf->layout[plane],
+ (pl_vulkan_sem) { vkf->sem[plane], vkf->sem_value[plane] + 1 });
+ vkf->access[plane] = 0;
+ vkf->sem_value[plane] += !!ok;
+ return ok ? 0 : AVERROR_EXTERNAL;
+}
+
+static void set_sample_depth(struct pl_frame *out_frame, const AVFrame *frame)
+{
+ const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
+ pl_fmt fmt = out_frame->planes[0].texture->params.format;
+ struct pl_bit_encoding *bits = &out_frame->repr.bits;
+ bits->sample_depth = fmt->component_depth[0];
+
+ switch (hwfc->sw_format) {
+ case AV_PIX_FMT_P010: bits->bit_shift = 6; break;
+ default: break;
+ }
+}
+
+static int process_frames(AVFilterContext *avctx, AVFrame *out, AVFrame *in)
+{
+ int err = 0;
+ LibplaceboContext *s = avctx->priv;
+ struct pl_render_params params;
+ struct pl_frame image, target;
+ pl_frame_from_avframe(&image, in);
+ pl_frame_from_avframe(&target, out);
+
+ if (!s->apply_filmgrain)
+ image.film_grain.type = PL_FILM_GRAIN_NONE;
+
+ if (s->target_sar.num) {
+ float aspect = pl_rect2df_aspect(&target.crop) * av_q2d(s->target_sar);
+ pl_rect2df_aspect_set(&target.crop, aspect, s->pad_crop_ratio);
+ }
+
+ /* Update render params */
+ params = (struct pl_render_params) {
+ PL_RENDER_DEFAULTS
+ .lut_entries = s->lut_entries,
+ .antiringing_strength = s->antiringing,
+
+ .deband_params = !s->deband ? NULL : pl_deband_params(
+ .iterations = s->deband_iterations,
+ .threshold = s->deband_threshold,
+ .radius = s->deband_radius,
+ .grain = s->deband_grain,
+ ),
+
+ .sigmoid_params = s->sigmoid ? &pl_sigmoid_default_params : NULL,
+
+ .color_adjustment = &(struct pl_color_adjustment) {
+ .brightness = s->brightness,
+ .contrast = s->contrast,
+ .saturation = s->saturation,
+ .hue = s->hue,
+ .gamma = s->gamma,
+ },
+
+ .peak_detect_params = !s->peakdetect ? NULL : pl_peak_detect_params(
+ .smoothing_period = s->smoothing,
+ .minimum_peak = s->min_peak,
+ .scene_threshold_low = s->scene_low,
+ .scene_threshold_high = s->scene_high,
+ .overshoot_margin = s->overshoot,
+ ),
+
+ .color_map_params = pl_color_map_params(
+ .intent = s->intent,
+ .tone_mapping_algo = s->tonemapping,
+ .tone_mapping_param = s->tonemapping_param,
+ .desaturation_strength = s->desat_str,
+ .desaturation_exponent = s->desat_exp,
+ .desaturation_base = s->desat_base,
+ .max_boost = s->max_boost,
+ .gamut_warning = s->gamut_warning,
+ .gamut_clipping = s->gamut_clipping,
+ ),
+
+ .dither_params = s->dithering < 0 ? NULL : pl_dither_params(
+ .method = s->dithering,
+ .lut_size = s->dither_lut_size,
+ .temporal = s->dither_temporal,
+ ),
+
+ .cone_params = !s->cones ? NULL : pl_cone_params(
+ .cones = s->cones,
+ .strength = s->cone_str,
+ ),
+
+ .hooks = s->hooks,
+ .num_hooks = s->num_hooks,
+
+ .skip_anti_aliasing = s->skip_aa,
+ .polar_cutoff = s->polar_cutoff,
+ .disable_linear_scaling = s->disable_linear,
+ .disable_builtin_scalers = s->disable_builtin,
+ .force_3dlut = s->force_3dlut,
+ .force_dither = s->force_dither,
+ .disable_fbos = s->disable_fbos,
+ };
+
+ RET(find_scaler(avctx, &params.upscaler, s->upscaler));
+ RET(find_scaler(avctx, &params.downscaler, s->downscaler));
+
+ /* Ideally, we would persistently wrap all of these AVVkFrames into pl_tex
+ * objects, but for now we'll just create and destroy a wrapper per frame.
+ * Note that doing it this way is suboptimal, since it results in the
+ * creation and destruction of a VkSampler and VkFramebuffer per frame.
+ *
+ * FIXME: Can we do better? */
+ for (int i = 0; i < image.num_planes; i++)
+ RET(wrap_vkframe(s->gpu, in, i, &image.planes[i].texture));
+ for (int i = 0; i < target.num_planes; i++)
+ RET(wrap_vkframe(s->gpu, out, i, &target.planes[i].texture));
+
+ /* Since we-re mapping vkframes manually, the pl_frame helpers don't know
+ * about the mismatch between the sample format and the color depth. */
+ set_sample_depth(&image, in);
+ set_sample_depth(&target, out);
+
+ pl_render_image(s->renderer, &image, &target, &params);
+
+ for (int i = 0; i < image.num_planes; i++)
+ RET(unwrap_vkframe(s->gpu, in, i, &image.planes[i].texture));
+ for (int i = 0; i < target.num_planes; i++)
+ RET(unwrap_vkframe(s->gpu, out, i, &target.planes[i].texture));
+
+ /* Flush the command queues for performance */
+ pl_gpu_flush(s->gpu);
+
+ /* fall through */
+fail:
+ for (int i = 0; i < image.num_planes; i++)
+ pl_tex_destroy(s->gpu, &image.planes[i].texture);
+ for (int i = 0; i < target.num_planes; i++)
+ pl_tex_destroy(s->gpu, &target.planes[i].texture);
+ return err;
+}
+
+static int filter_frame(AVFilterLink *link, AVFrame *in)
+{
+ int err;
+ AVFilterContext *ctx = link->dst;
+ LibplaceboContext *s = ctx->priv;
+ AVFilterLink *outlink = ctx->outputs[0];
+
+ AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+ if (!out) {
+ err = AVERROR(ENOMEM);
+ goto fail;
+ }
+
+ if (!s->initialized)
+ RET(init_vulkan(ctx));
+
+ RET(av_frame_copy_props(out, in));
+ out->width = outlink->w;
+ out->height = outlink->h;
+
+ if (s->colorspace >= 0)
+ out->colorspace = s->colorspace;
+ if (s->color_range >= 0)
+ out->color_range = s->color_range;
+ if (s->color_trc >= 0)
+ out->color_trc = s->color_trc;
+ if (s->color_primaries >= 0)
+ out->color_primaries = s->color_primaries;
+
+ RET(process_frames(ctx, out, in));
+
+ if (s->apply_filmgrain)
+ av_frame_remove_side_data(out, AV_FRAME_DATA_FILM_GRAIN_PARAMS);
+
+ av_frame_free(&in);
+
+ return ff_filter_frame(outlink, out);
+
+fail:
+ av_frame_free(&in);
+ av_frame_free(&out);
+ return err;
+}
+
+static int libplacebo_config_output(AVFilterLink *outlink)
+{
+ int err;
+ AVFilterContext *avctx = outlink->src;
+ LibplaceboContext *s = avctx->priv;
+ AVFilterLink *inlink = outlink->src->inputs[0];
+ AVHWFramesContext *hwfc;
+ AVVulkanFramesContext *vkfc;
+ AVRational scale_sar;
+ int *out_w = &s->vkctx.output_width;
+ int *out_h = &s->vkctx.output_height;
+
+ RET(ff_scale_eval_dimensions(s, s->w_expr, s->h_expr, inlink, outlink,
+ out_w, out_h));
+
+ ff_scale_adjust_dimensions(inlink, out_w, out_h,
+ s->force_original_aspect_ratio,
+ s->force_divisible_by);
+
+ scale_sar = (AVRational){outlink->h * inlink->w, *out_w * *out_h};
+ if (inlink->sample_aspect_ratio.num)
+ scale_sar = av_mul_q(scale_sar, inlink->sample_aspect_ratio);
+
+ if (s->normalize_sar) {
+ /* Apply all SAR during scaling, so we don't need to set the out SAR */
+ s->target_sar = scale_sar;
+ } else {
+ /* This is consistent with other scale_* filters, which only
+ * set the outlink SAR to be equal to the scale SAR iff the input SAR
+ * was set to something nonzero */
+ if (inlink->sample_aspect_ratio.num)
+ outlink->sample_aspect_ratio = scale_sar;
+ }
+
+ if (s->out_format_string) {
+ s->vkctx.output_format = av_get_pix_fmt(s->out_format_string);
+ if (s->vkctx.output_format == AV_PIX_FMT_NONE) {
+ av_log(avctx, AV_LOG_ERROR, "Invalid output format.\n");
+ return AVERROR(EINVAL);
+ }
+ } else {
+ /* Default to re-using the input format */
+ s->vkctx.output_format = s->vkctx.input_format;
+ }
+
+ RET(ff_vk_filter_config_output(outlink));
+ hwfc = (AVHWFramesContext *) outlink->hw_frames_ctx->data;
+ vkfc = hwfc->hwctx;
+ vkfc->usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+
+ return 0;
+
+fail:
+ return err;
+}
+
+#define OFFSET(x) offsetof(LibplaceboContext, x)
+#define STATIC (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
+#define DYNAMIC (STATIC | AV_OPT_FLAG_RUNTIME_PARAM)
+
+static const AVOption libplacebo_options[] = {
+ { "w", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, {.str = "iw"}, .flags = STATIC },
+ { "h", "Output video height", OFFSET(h_expr), AV_OPT_TYPE_STRING, {.str = "ih"}, .flags = STATIC },
+ { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, .flags = STATIC },
+ { "force_original_aspect_ratio", "decrease or increase w/h if necessary to keep the original AR", OFFSET(force_original_aspect_ratio), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, STATIC, "force_oar" },
+ { "disable", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, 0, 0, STATIC, "force_oar" },
+ { "decrease", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1 }, 0, 0, STATIC, "force_oar" },
+ { "increase", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 2 }, 0, 0, STATIC, "force_oar" },
+ { "force_divisible_by", "enforce that the output resolution is divisible by a defined integer when force_original_aspect_ratio is used", OFFSET(force_divisible_by), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, 256, STATIC },
+ { "normalize_sar", "force SAR normalization to 1:1", OFFSET(normalize_sar), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, STATIC },
+ { "pad_crop_ratio", "ratio between padding and cropping when normalizing SAR (0=pad, 1=crop)", OFFSET(pad_crop_ratio), AV_OPT_TYPE_FLOAT, {.dbl=0.0}, 0.0, 1.0, DYNAMIC },
+
+ {"colorspace", "select colorspace", OFFSET(colorspace), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_SPC_NB-1, DYNAMIC, "colorspace"},
+ {"auto", "keep the same colorspace", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"gbr", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_RGB}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT709}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT470BG}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"ycgco", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_YCGCO}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"bt2020nc", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_NCL}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"bt2020c", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_CL}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+ {"ictcp", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_ICTCP}, INT_MIN, INT_MAX, STATIC, "colorspace"},
+
+ {"range", "select color range", OFFSET(color_range), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_RANGE_NB-1, DYNAMIC, "range"},
+ {"auto", "keep the same color range", 0, AV_OPT_TYPE_CONST, {.i64=-1}, 0, 0, STATIC, "range"},
+ {"unspecified", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, 0, STATIC, "range"},
+ {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, 0, STATIC, "range"},
+ {"limited", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
+ {"tv", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
+ {"mpeg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
+ {"full", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
+ {"pc", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
+ {"jpeg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
+
+ {"color_primaries", "select color primaries", OFFSET(color_primaries), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_PRI_NB-1, DYNAMIC, "color_primaries"},
+ {"auto", "keep the same color primaries", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT709}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470BG}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"film", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_FILM}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"bt2020", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT2020}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"smpte428", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE428}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"smpte431", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE431}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"smpte432", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE432}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"jedec-p22", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_JEDEC_P22}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+ {"ebu3213", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_EBU3213}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
+
+ {"color_trc", "select color transfer", OFFSET(color_trc), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_TRC_NB-1, DYNAMIC, "color_trc"},
+ {"auto", "keep the same color transfer", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT709}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA22}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA28}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"linear", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LINEAR}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"iec61966-2-4", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_4}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"bt1361e", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT1361_ECG}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"iec61966-2-1", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_1}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"bt2020-10", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_10}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"bt2020-12", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_12}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"smpte2084", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE2084}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+ {"arib-std-b67", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_ARIB_STD_B67}, INT_MIN, INT_MAX, STATIC, "color_trc"},
+
+ { "upscaler", "Upscaler function", OFFSET(upscaler), AV_OPT_TYPE_STRING, {.str = "spline36"}, .flags = DYNAMIC },
+ { "downscaler", "Downscaler function", OFFSET(downscaler), AV_OPT_TYPE_STRING, {.str = "mitchell"}, .flags = DYNAMIC },
+ { "lut_entries", "Number of scaler LUT entries", OFFSET(lut_entries), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 256, DYNAMIC },
+ { "antiringing", "Antiringing strength (for non-EWA filters)", OFFSET(antiringing), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 1.0, DYNAMIC },
+ { "sigmoid", "Enable sigmoid upscaling", OFFSET(sigmoid), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
+ { "apply_filmgrain", "Apply film grain metadata", OFFSET(apply_filmgrain), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
+
+ { "deband", "Enable debanding", OFFSET(deband), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
+ { "deband_iterations", "Deband iterations", OFFSET(deband_iterations), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 16, DYNAMIC },
+ { "deband_threshold", "Deband threshold", OFFSET(deband_threshold), AV_OPT_TYPE_FLOAT, {.dbl = 4.0}, 0.0, 1024.0, DYNAMIC },
+ { "deband_radius", "Deband radius", OFFSET(deband_radius), AV_OPT_TYPE_FLOAT, {.dbl = 16.0}, 0.0, 1024.0, DYNAMIC },
+ { "deband_grain", "Deband grain", OFFSET(deband_grain), AV_OPT_TYPE_FLOAT, {.dbl = 6.0}, 0.0, 1024.0, DYNAMIC },
+
+ { "brightness", "Brightness boost", OFFSET(brightness), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -1.0, 1.0, DYNAMIC },
+ { "contrast", "Contrast gain", OFFSET(contrast), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
+ { "saturation", "Saturation gain", OFFSET(saturation), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
+ { "hue", "Hue shift", OFFSET(hue), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -M_PI, M_PI, DYNAMIC },
+ { "gamma", "Gamma adjustment", OFFSET(gamma), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
+
+ { "peak_detect", "Enable dynamic peak detection for HDR tone-mapping", OFFSET(peakdetect), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
+ { "smoothing_period", "Peak detection smoothing period", OFFSET(smoothing), AV_OPT_TYPE_FLOAT, {.dbl = 100.0}, 0.0, 1000.0, DYNAMIC },
+ { "minimum_peak", "Peak detection minimum peak", OFFSET(min_peak), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 100.0, DYNAMIC },
+ { "scene_threshold_low", "Scene change low threshold", OFFSET(scene_low), AV_OPT_TYPE_FLOAT, {.dbl = 5.5}, -1.0, 100.0, DYNAMIC },
+ { "scene_threshold_high", "Scene change high threshold", OFFSET(scene_high), AV_OPT_TYPE_FLOAT, {.dbl = 10.0}, -1.0, 100.0, DYNAMIC },
+ { "overshoot", "Tone-mapping overshoot margin", OFFSET(overshoot), AV_OPT_TYPE_FLOAT, {.dbl = 0.05}, 0.0, 1.0, DYNAMIC },
+
+ { "intent", "Rendering intent", OFFSET(intent), AV_OPT_TYPE_INT, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 3, DYNAMIC, "intent" },
+ { "perceptual", "Perceptual", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_PERCEPTUAL}, 0, 0, STATIC, "intent" },
+ { "relative", "Relative colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
+ { "absolute", "Absolute colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_ABSOLUTE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
+ { "saturation", "Saturation mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_SATURATION}, 0, 0, STATIC, "intent" },
+ { "tonemapping", "Tone-mapping algorithm", OFFSET(tonemapping), AV_OPT_TYPE_INT, {.i64 = PL_TONE_MAPPING_BT_2390}, 0, PL_TONE_MAPPING_ALGORITHM_COUNT - 1, DYNAMIC, "tonemap" },
+ { "clip", "Hard-clipping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_CLIP}, 0, 0, STATIC, "tonemap" },
+ { "mobius", "Mobius tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_MOBIUS}, 0, 0, STATIC, "tonemap" },
+ { "reinhard", "Reinhard tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_REINHARD}, 0, 0, STATIC, "tonemap" },
+ { "hable", "Hable/Filmic tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_HABLE}, 0, 0, STATIC, "tonemap" },
+ { "gamma", "Gamma tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_GAMMA}, 0, 0, STATIC, "tonemap" },
+ { "linear", "Linear tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_LINEAR}, 0, 0, STATIC, "tonemap" },
+ { "bt.2390", "ITU-R BT.2390 tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_BT_2390}, 0, 0, STATIC, "tonemap" },
+ { "tonemapping_param", "Tunable parameter for some tone-mapping functions", OFFSET(tonemapping_param), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 100.0, .flags = DYNAMIC },
+ { "desaturation_strength", "Desaturation strength", OFFSET(desat_str), AV_OPT_TYPE_FLOAT, {.dbl = 0.90}, 0.0, 1.0, DYNAMIC },
+ { "desaturation_exponent", "Desaturation exponent", OFFSET(desat_exp), AV_OPT_TYPE_FLOAT, {.dbl = 0.2}, 0.0, 10.0, DYNAMIC },
+ { "desaturation_base", "Desaturation base", OFFSET(desat_base), AV_OPT_TYPE_FLOAT, {.dbl = 0.18}, 0.0, 10.0, DYNAMIC },
+ { "max_boost", "Tone-mapping maximum boost", OFFSET(max_boost), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 1.0, 10.0, DYNAMIC },
+ { "gamut_warning", "Highlight out-of-gamut colors", OFFSET(gamut_warning), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
+ { "gamut_clipping", "Enable colorimetric gamut clipping", OFFSET(gamut_clipping), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
+
+ { "dithering", "Dither method to use", OFFSET(dithering), AV_OPT_TYPE_INT, {.i64 = PL_DITHER_BLUE_NOISE}, -1, PL_DITHER_METHOD_COUNT - 1, DYNAMIC, "dither" },
+ { "none", "Disable dithering", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, STATIC, "dither" },
+ { "blue", "Blue noise", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_BLUE_NOISE}, 0, 0, STATIC, "dither" },
+ { "ordered", "Ordered LUT", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_ORDERED_LUT}, 0, 0, STATIC, "dither" },
+ { "ordered_fixed", "Fixed function ordered", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_ORDERED_FIXED}, 0, 0, STATIC, "dither" },
+ { "white", "White noise", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_WHITE_NOISE}, 0, 0, STATIC, "dither" },
+ { "dither_lut_size", "Dithering LUT size", OFFSET(dither_lut_size), AV_OPT_TYPE_INT, {.i64 = 6}, 1, 8, STATIC },
+ { "dither_temporal", "Enable temporal dithering", OFFSET(dither_temporal), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
+
+ { "cones", "Colorblindness adaptation model", OFFSET(cones), AV_OPT_TYPE_FLAGS, {.i64 = 0}, 0, PL_CONE_LMS, DYNAMIC, "cone" },
+ { "l", "L cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_L}, 0, 0, STATIC, "cone" },
+ { "m", "M cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_M}, 0, 0, STATIC, "cone" },
+ { "s", "S cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_S}, 0, 0, STATIC, "cone" },
+ { "cone-strength", "Colorblindness adaptation strength", OFFSET(cone_str), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 10.0, DYNAMIC },
+
+ { "custom_shader_path", "Path to custom user shader (mpv .hook format)", OFFSET(shader_path), AV_OPT_TYPE_STRING, .flags = STATIC },
+ { "custom_shader_bin", "Custom user shader as binary (mpv .hook format)", OFFSET(shader_bin), AV_OPT_TYPE_BINARY, .flags = STATIC },
+
+ /* Performance/quality tradeoff options */
+ { "skip_aa", "Skip anti-aliasing", OFFSET(skip_aa), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 0, DYNAMIC },
+ { "polar_cutoff", "Polar LUT cutoff", OFFSET(polar_cutoff), AV_OPT_TYPE_FLOAT, {.i64 = 0}, 0.0, 1.0, DYNAMIC },
+ { "disable_linear", "Disable linear scaling", OFFSET(disable_linear), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
+ { "disable_builtin", "Disable built-in scalers", OFFSET(disable_builtin), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
+ { "force_3dlut", "Force the use of a full 3DLUT", OFFSET(force_3dlut), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
+ { "force_dither", "Force dithering", OFFSET(force_dither), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
+ { "disable_fbos", "Force-disable FBOs", OFFSET(disable_fbos), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
+ { NULL },
+};
+
+AVFILTER_DEFINE_CLASS(libplacebo);
+
+static const AVFilterPad libplacebo_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = &filter_frame,
+ .config_props = &ff_vk_filter_config_input,
+ },
+};
+
+static const AVFilterPad libplacebo_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .config_props = &libplacebo_config_output,
+ },
+};
+
+AVFilter ff_vf_libplacebo = {
+ .name = "libplacebo",
+ .description = NULL_IF_CONFIG_SMALL("Apply various GPU filters from libplacebo"),
+ .priv_size = sizeof(LibplaceboContext),
+ .init = &libplacebo_init,
+ .uninit = &libplacebo_uninit,
+ .process_command = &ff_filter_process_command,
+ FILTER_INPUTS(libplacebo_inputs),
+ FILTER_OUTPUTS(libplacebo_outputs),
+ FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
+ .priv_class = &libplacebo_class,
+ .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
+};