summaryrefslogtreecommitdiffstats
path: root/filters
diff options
context:
space:
mode:
Diffstat (limited to 'filters')
-rw-r--r--filters/f_auto_filters.c244
-rw-r--r--filters/f_auto_filters.h10
-rw-r--r--filters/f_autoconvert.c288
-rw-r--r--filters/f_autoconvert.h39
-rw-r--r--filters/f_hwtransfer.c299
-rw-r--r--filters/f_hwtransfer.h32
-rw-r--r--filters/f_lavfi.c952
-rw-r--r--filters/f_lavfi.h30
-rw-r--r--filters/f_output_chain.c564
-rw-r--r--filters/f_output_chain.h59
-rw-r--r--filters/f_swscale.c148
-rw-r--r--filters/f_swscale.h25
-rw-r--r--filters/f_utils.c175
-rw-r--r--filters/f_utils.h72
-rw-r--r--filters/filter.c790
-rw-r--r--filters/filter.h379
-rw-r--r--filters/filter_internal.h144
-rw-r--r--filters/frame.c179
-rw-r--r--filters/frame.h55
-rw-r--r--filters/user_filters.c119
-rw-r--r--filters/user_filters.h29
21 files changed, 4632 insertions, 0 deletions
diff --git a/filters/f_auto_filters.c b/filters/f_auto_filters.c
new file mode 100644
index 0000000000..eac6f745ca
--- /dev/null
+++ b/filters/f_auto_filters.c
@@ -0,0 +1,244 @@
+#include <math.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+#include "options/m_config.h"
+#include "options/options.h"
+#include "video/mp_image.h"
+
+#include "f_auto_filters.h"
+#include "f_swscale.h"
+#include "f_utils.h"
+#include "filter.h"
+#include "filter_internal.h"
+#include "user_filters.h"
+
+struct deint_priv {
+ struct mp_subfilter sub;
+ int prev_imgfmt;
+ int prev_setting;
+ struct m_config_cache *opts;
+};
+
+static void deint_process(struct mp_filter *f)
+{
+ struct deint_priv *p = f->priv;
+
+ if (!mp_subfilter_read(&p->sub))
+ return;
+
+ struct mp_frame frame = p->sub.frame;
+
+ if (mp_frame_is_signaling(frame)) {
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ if (frame.type != MP_FRAME_VIDEO) {
+ MP_ERR(f, "video input required!\n");
+ mp_filter_internal_mark_failed(f);
+ return;
+ }
+
+ m_config_cache_update(p->opts);
+ struct filter_opts *opts = p->opts->opts;
+
+ if (!opts->deinterlace)
+ mp_subfilter_destroy(&p->sub);
+
+ struct mp_image *img = frame.data;
+
+ if (img->imgfmt == p->prev_imgfmt && p->prev_setting == opts->deinterlace) {
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ if (!mp_subfilter_drain_destroy(&p->sub))
+ return;
+
+ assert(!p->sub.filter);
+
+ p->prev_imgfmt = img->imgfmt;
+ p->prev_setting = opts->deinterlace;
+ if (!p->prev_setting) {
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ if (img->imgfmt == IMGFMT_VDPAU) {
+ char *args[] = {"deint", "yes", NULL};
+ p->sub.filter =
+ mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vdpaupp", args);
+ } else if (img->imgfmt == IMGFMT_VAAPI) {
+ p->sub.filter =
+ mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vavpp", NULL);
+ } else if (img->imgfmt == IMGFMT_D3D11) {
+ p->sub.filter =
+ mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", NULL);
+ } else if (mp_sws_supports_input(img->imgfmt)) {
+ char *args[] = {"mode", "send_field", "deint", "interlaced", NULL};
+ p->sub.filter =
+ mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "yadif", args);
+ } else {
+ MP_ERR(f, "no deinterlace filter available for this format\n");
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ if (!p->sub.filter)
+ MP_ERR(f, "creating deinterlacer failed\n");
+
+ mp_subfilter_continue(&p->sub);
+}
+
+static void deint_reset(struct mp_filter *f)
+{
+ struct deint_priv *p = f->priv;
+
+ mp_subfilter_reset(&p->sub);
+}
+
+static void deint_destroy(struct mp_filter *f)
+{
+ struct deint_priv *p = f->priv;
+
+ mp_subfilter_reset(&p->sub);
+ TA_FREEP(&p->sub.filter);
+}
+
+static const struct mp_filter_info deint_filter = {
+ .name = "deint",
+ .priv_size = sizeof(struct deint_priv),
+ .process = deint_process,
+ .reset = deint_reset,
+ .destroy = deint_destroy,
+};
+
+struct mp_filter *mp_deint_create(struct mp_filter *parent)
+{
+ struct mp_filter *f = mp_filter_create(parent, &deint_filter);
+ if (!f)
+ return NULL;
+
+ struct deint_priv *p = f->priv;
+
+ p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
+ p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");
+
+ p->opts = m_config_cache_alloc(f, f->global, &filter_conf);
+
+ return f;
+}
+
+struct rotate_priv {
+ struct mp_subfilter sub;
+ int prev_rotate;
+ int prev_imgfmt;
+ int target_rotate;
+};
+
+static void rotate_process(struct mp_filter *f)
+{
+ struct rotate_priv *p = f->priv;
+
+ if (!mp_subfilter_read(&p->sub))
+ return;
+
+ struct mp_frame frame = p->sub.frame;
+
+ if (mp_frame_is_signaling(frame)) {
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ if (frame.type != MP_FRAME_VIDEO) {
+ MP_ERR(f, "video input required!\n");
+ return;
+ }
+
+ struct mp_image *img = frame.data;
+
+ if (img->params.rotate == p->prev_rotate &&
+ img->imgfmt == p->prev_imgfmt)
+ {
+ img->params.rotate = p->target_rotate;
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ if (!mp_subfilter_drain_destroy(&p->sub))
+ return;
+
+ assert(!p->sub.filter);
+
+ int rotate = p->prev_rotate = img->params.rotate;
+ p->target_rotate = rotate;
+ p->prev_imgfmt = img->imgfmt;
+
+ struct mp_stream_info *info = mp_filter_find_stream_info(f);
+ if (rotate == 0 || (info && info->rotate90 && !(rotate % 90))) {
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ if (mp_sws_supports_input(img->imgfmt)) {
+ MP_ERR(f, "Video rotation with this format not supported\n");
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ double angle = rotate / 360.0 * M_PI * 2;
+ char *args[] = {"angle", mp_tprintf(30, "%f", angle),
+ "ow", mp_tprintf(30, "rotw(%f)", angle),
+ "oh", mp_tprintf(30, "roth(%f)", angle),
+ NULL};
+ p->sub.filter =
+ mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "rotate", args);
+
+ if (p->sub.filter) {
+ MP_INFO(f, "Inserting rotation filter.\n");
+ p->target_rotate = 0;
+ } else {
+ MP_ERR(f, "could not create rotation filter\n");
+ }
+
+ mp_subfilter_continue(&p->sub);
+}
+
+static void rotate_reset(struct mp_filter *f)
+{
+ struct rotate_priv *p = f->priv;
+
+ mp_subfilter_reset(&p->sub);
+}
+
+static void rotate_destroy(struct mp_filter *f)
+{
+ struct rotate_priv *p = f->priv;
+
+ mp_subfilter_reset(&p->sub);
+ TA_FREEP(&p->sub.filter);
+}
+
+static const struct mp_filter_info rotate_filter = {
+ .name = "autorotate",
+ .priv_size = sizeof(struct rotate_priv),
+ .process = rotate_process,
+ .reset = rotate_reset,
+ .destroy = rotate_destroy,
+};
+
+struct mp_filter *mp_autorotate_create(struct mp_filter *parent)
+{
+ struct mp_filter *f = mp_filter_create(parent, &rotate_filter);
+ if (!f)
+ return NULL;
+
+ struct rotate_priv *p = f->priv;
+ p->prev_rotate = -1;
+
+ p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
+ p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");
+
+ return f;
+}
diff --git a/filters/f_auto_filters.h b/filters/f_auto_filters.h
new file mode 100644
index 0000000000..5f1a99f636
--- /dev/null
+++ b/filters/f_auto_filters.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "filter.h"
+
+// A filter which inserts the required deinterlacing filter based on the
+// hardware decode mode and the deinterlace user option.
+struct mp_filter *mp_deint_create(struct mp_filter *parent);
+
+// Rotate according to mp_image.rotate and VO capabilities.
+struct mp_filter *mp_autorotate_create(struct mp_filter *parent);
diff --git a/filters/f_autoconvert.c b/filters/f_autoconvert.c
new file mode 100644
index 0000000000..687a846ae5
--- /dev/null
+++ b/filters/f_autoconvert.c
@@ -0,0 +1,288 @@
+#include "config.h"
+
+#include "common/common.h"
+#include "common/msg.h"
+#include "video/hwdec.h"
+#include "video/mp_image.h"
+
+#include "f_autoconvert.h"
+#include "f_hwtransfer.h"
+#include "f_swscale.h"
+#include "f_utils.h"
+#include "filter.h"
+#include "filter_internal.h"
+
+struct priv {
+ struct mp_log *log;
+
+ struct mp_subfilter sub;
+
+ bool force_update;
+
+ int *imgfmts;
+ int *subfmts;
+ int num_imgfmts;
+
+ // Enable special conversion for the final stage before the VO.
+ bool vo_convert;
+
+ // sws state
+ int in_imgfmt, in_subfmt;
+
+ struct mp_autoconvert public;
+};
+
+// Dummy filter for bundling sub-conversion filters.
+static const struct mp_filter_info convert_filter = {
+ .name = "convert",
+};
+
+// For hw decoding: thing which can convert between underlying surface formats.
+// The filter detects the needed target format from struct mp_hwdec_ctx.
+struct subfmt_conv {
+ int hw_imgfmt;
+ struct mp_filter *(*create)(struct mp_filter *parent);
+};
+
+static const struct subfmt_conv subfmt_converters[] = {
+#if HAVE_D3D_HWACCEL
+ {IMGFMT_D3D11, vf_d3d11_create_outconv},
+#endif
+ {0}
+};
+
+void mp_autoconvert_clear(struct mp_autoconvert *c)
+{
+ struct priv *p = c->f->priv;
+
+ p->num_imgfmts = 0;
+}
+
+void mp_autoconvert_add_imgfmt(struct mp_autoconvert *c, int imgfmt, int subfmt)
+{
+ struct priv *p = c->f->priv;
+
+ MP_TARRAY_GROW(p, p->imgfmts, p->num_imgfmts);
+ MP_TARRAY_GROW(p, p->subfmts, p->num_imgfmts);
+
+ p->imgfmts[p->num_imgfmts] = imgfmt;
+ p->subfmts[p->num_imgfmts] = subfmt;
+
+ p->num_imgfmts += 1;
+ p->force_update = true;
+}
+
+void mp_autoconvert_add_vo_hwdec_subfmts(struct mp_autoconvert *c,
+ struct mp_hwdec_devices *devs)
+{
+ struct priv *p = c->f->priv;
+ assert(devs);
+
+ int prev_format = 0;
+
+ for (int n = 0; ; n++) {
+ struct mp_hwdec_ctx *ctx = hwdec_devices_get_n(devs, n);
+ if (!ctx)
+ break;
+ if (!ctx->hw_imgfmt || !ctx->supported_formats)
+ continue;
+ // Very hacky: don't let d3d11-egl-rgb overwrite d3d11-egl
+ if (ctx->hw_imgfmt == prev_format)
+ continue;
+ prev_format = ctx->hw_imgfmt;
+ // Stupidity: VOs export imgfmt only, so subfmt is always 0. Remove it
+ // to fix it up.
+ for (int i = 0; i < p->num_imgfmts; i++) {
+ if (p->imgfmts[i] != ctx->hw_imgfmt)
+ continue;
+
+ int count = p->num_imgfmts;
+ MP_TARRAY_REMOVE_AT(p->imgfmts, count, i);
+ count = p->num_imgfmts;
+ MP_TARRAY_REMOVE_AT(p->subfmts, count, i);
+ p->num_imgfmts -= 1;
+ break;
+ }
+ for (int i = 0; ctx->supported_formats[i]; i++)
+ mp_autoconvert_add_imgfmt(c, ctx->hw_imgfmt, ctx->supported_formats[i]);
+ }
+
+ p->vo_convert = true;
+}
+
+static void handle_video_frame(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ struct mp_frame frame = p->sub.frame;
+ if (frame.type != MP_FRAME_VIDEO) {
+ MP_ERR(p, "video input required!\n");
+ mp_filter_internal_mark_failed(f);
+ return;
+ }
+
+ struct mp_image *img = frame.data;
+
+ if (p->force_update)
+ p->in_imgfmt = p->in_subfmt = 0;
+
+ if (img->imgfmt == p->in_imgfmt && img->params.hw_subfmt == p->in_subfmt) {
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+
+ if (!mp_subfilter_drain_destroy(&p->sub)) {
+ p->in_imgfmt = p->in_subfmt = 0;
+ return;
+ }
+
+ p->in_imgfmt = img->params.imgfmt;
+ p->in_subfmt = img->params.hw_subfmt;
+ p->force_update = false;
+
+ bool different_subfmt = false;
+
+ for (int n = 0; n < p->num_imgfmts; n++) {
+ bool samefmt = img->params.imgfmt == p->imgfmts[n];
+ bool samesubffmt = img->params.hw_subfmt == p->subfmts[n];
+ if (samefmt && !samesubffmt)
+ different_subfmt = true;
+ if (samefmt && (samesubffmt || !p->subfmts[n])) {
+ mp_subfilter_continue(&p->sub);
+ return;
+ }
+ }
+
+ struct mp_stream_info *info = mp_filter_find_stream_info(f);
+
+ struct mp_filter *conv = mp_filter_create(f, &convert_filter);
+ mp_filter_add_pin(conv, MP_PIN_IN, "in");
+ mp_filter_add_pin(conv, MP_PIN_OUT, "out");
+
+ struct mp_filter *filters[2] = {0};
+ bool need_sws = true;
+
+ int *fmts = p->imgfmts;
+ int num_fmts = p->num_imgfmts;
+
+ // Source is sw, all targets are hw -> try to upload.
+ bool sw_to_hw = !IMGFMT_IS_HWACCEL(img->imgfmt);
+ for (int n = 0; n < num_fmts; n++)
+ sw_to_hw &= IMGFMT_IS_HWACCEL(fmts[n]);
+
+ if (sw_to_hw && num_fmts > 0) {
+ // We can probably use this! Very lazy and very approximate.
+ struct mp_hwupload *upload = mp_hwupload_create(conv, fmts[0]);
+ if (upload) {
+ MP_INFO(p, "HW-uploading to %s\n", mp_imgfmt_to_name(fmts[0]));
+ filters[1] = upload->f;
+ fmts = upload->upload_fmts;
+ num_fmts = upload->num_upload_fmts;
+ }
+ } else if (p->vo_convert && different_subfmt && info && info->hwdec_devs) {
+ for (int n = 0; subfmt_converters[n].hw_imgfmt; n++) {
+ if (subfmt_converters[n].hw_imgfmt == img->imgfmt) {
+ MP_INFO(p, "Using HW sub-conversion.\n");
+ filters[1] = subfmt_converters[n].create(conv);
+ if (filters[1]) {
+ need_sws = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (need_sws) {
+ // Create a new conversion filter.
+ struct mp_sws_filter *sws = mp_sws_filter_create(conv);
+ if (!sws) {
+ MP_ERR(p, "error creating conversion filter\n");
+ return;
+ }
+
+ int out = mp_sws_find_best_out_format(img->imgfmt, fmts, num_fmts);
+ if (!out) {
+ MP_ERR(p, "can't find video conversion for %s/%s\n",
+ mp_imgfmt_to_name(img->imgfmt),
+ mp_imgfmt_to_name(img->params.hw_subfmt));
+ talloc_free(conv);
+ mp_filter_internal_mark_failed(f);
+ return;
+ }
+
+ if (out == img->imgfmt) {
+ // Can happen if hwupload goes to same format.
+ talloc_free(sws->f);
+ } else {
+ sws->out_format = out;
+ MP_INFO(p, "Converting %s -> %s\n", mp_imgfmt_to_name(img->imgfmt),
+ mp_imgfmt_to_name(sws->out_format));
+ filters[0] = sws->f;
+ }
+ }
+
+ mp_chain_filters(conv->ppins[0], conv->ppins[1], filters, 2);
+
+ p->sub.filter = conv;
+ mp_subfilter_continue(&p->sub);
+}
+
+static void process(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ if (!mp_subfilter_read(&p->sub))
+ return;
+
+ struct mp_frame frame = p->sub.frame;
+
+ if (!mp_frame_is_signaling(frame)) {
+ if (p->num_imgfmts) {
+ handle_video_frame(f);
+ return;
+ }
+ }
+
+ mp_subfilter_continue(&p->sub);
+}
+
+static void reset(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ mp_subfilter_reset(&p->sub);
+}
+
+static void destroy(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ mp_subfilter_reset(&p->sub);
+ TA_FREEP(&p->sub.filter);
+}
+
+static const struct mp_filter_info autoconvert_filter = {
+ .name = "autoconvert",
+ .priv_size = sizeof(struct priv),
+ .process = process,
+ .reset = reset,
+ .destroy = destroy,
+};
+
+struct mp_autoconvert *mp_autoconvert_create(struct mp_filter *parent)
+{
+ struct mp_filter *f = mp_filter_create(parent, &autoconvert_filter);
+ if (!f)
+ return NULL;
+
+ mp_filter_add_pin(f, MP_PIN_IN, "in");
+ mp_filter_add_pin(f, MP_PIN_OUT, "out");
+
+ struct priv *p = f->priv;
+ p->public.f = f;
+ p->log = f->log;
+ p->sub.in = f->ppins[0];
+ p->sub.out = f->ppins[1];
+
+ return &p->public;
+}
diff --git a/filters/f_autoconvert.h b/filters/f_autoconvert.h
new file mode 100644
index 0000000000..72af21a0df
--- /dev/null
+++ b/filters/f_autoconvert.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "filter.h"
+
+// A filter which automatically creates and uses a conversion filter based on
+// the filter settings, or passes through data unchanged if no conversion is
+// required.
+struct mp_autoconvert {
+ // f->pins[0] is input, f->pins[1] is output
+ struct mp_filter *f;
+};
+
+// (to free this, free the filter itself, mp_autoconvert.f)
+struct mp_autoconvert *mp_autoconvert_create(struct mp_filter *parent);
+
+// Add the imgfmt as allowed video image format, and error on non-video frames.
+// Each call adds to the list of allowed formats. Before the first call, all
+// formats are allowed (even non-video).
+// subfmt can be used to specify underlying surface formats for hardware formats,
+// otherwise must be 0.
+void mp_autoconvert_add_imgfmt(struct mp_autoconvert *c, int imgfmt, int subfmt);
+
+// Add the formats supported by the hwdec interops (or essentially refine them),
+// and trigger conversion if hw_subfmts mismatch. This is mostly a hack for
+// D3D11/ANGLE (which supports NV12 only).
+// Must be called mp_autoconvert_add_imgfmt(), and overrides them where formats
+// collide.
+struct mp_hwdec_devices;
+void mp_autoconvert_add_vo_hwdec_subfmts(struct mp_autoconvert *c,
+ struct mp_hwdec_devices *devs);
+
+// Reset set of allowed formats back to initial state. (This does not flush
+// any frames or remove currently active filters, although to get reasonable
+// behavior, you need to readd all previously allowed formats, or reset the
+// filter.)
+void mp_autoconvert_clear(struct mp_autoconvert *c);
+
+// vf_d3d11vpp.c
+struct mp_filter *vf_d3d11_create_outconv(struct mp_filter *parent);
diff --git a/filters/f_hwtransfer.c b/filters/f_hwtransfer.c
new file mode 100644
index 0000000000..6ffda567ae
--- /dev/null
+++ b/filters/f_hwtransfer.c
@@ -0,0 +1,299 @@
+#include <libavutil/buffer.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/mem.h>
+
+#include "video/fmt-conversion.h"
+#include "video/hwdec.h"
+#include "video/mp_image.h"
+#include "video/mp_image_pool.h"
+
+#include "f_hwtransfer.h"
+#include "filter_internal.h"
+
+struct priv {
+ AVBufferRef *av_device_ctx;
+
+ AVBufferRef *hw_pool;
+
+ int last_input_fmt;
+ int last_upload_fmt;
+ int last_sw_fmt;
+
+ struct mp_hwupload public;
+};
+
+static bool update_format_decision(struct priv *p, int input_fmt)
+{
+ struct mp_hwupload *u = &p->public;
+
+ if (!input_fmt)
+ return false;
+
+ if (input_fmt == p->last_input_fmt)
+ return true;
+
+ p->last_input_fmt = 0;
+
+ int res = mp_imgfmt_select_best_list(u->upload_fmts, u->num_upload_fmts,
+ input_fmt);
+
+ if (!res)
+ return false;
+
+ // Find which sw format we should use.
+ // NOTE: if there are ever any hw APIs that actually do expensive
+ // conversions on mismatching format uploads, we should probably first look
+ // which sw format is preferred?
+ int index = -1;
+ for (int n = 0; n < u->num_upload_fmts; n++) {
+ if (u->upload_fmts[n] == res)
+ index = n;
+ }
+
+ if (index < 0)
+ return false;
+
+ for (int n = 0; n < u->num_fmts; n++) {
+ if (u->fmt_upload_index[n] >= index &&
+ index < u->fmt_upload_index[n] + u->fmt_upload_num[n])
+ {
+ p->last_input_fmt = input_fmt;
+ p->last_upload_fmt = u->upload_fmts[index];
+ p->last_sw_fmt = u->fmts[n];
+ MP_INFO(u->f, "upload %s -> %s\n",
+ mp_imgfmt_to_name(p->last_sw_fmt),
+ mp_imgfmt_to_name(p->last_input_fmt));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int mp_hwupload_find_upload_format(struct mp_hwupload *u, int imgfmt)
+{
+ struct priv *p = u->f->priv;
+
+ if (!update_format_decision(p, imgfmt))
+ return 0;
+ return p->last_upload_fmt;
+}
+
+static void process(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ if (!mp_pin_can_transfer_data(f->ppins[1], f->ppins[0]))
+ return;
+
+ struct mp_frame frame = mp_pin_out_read(f->ppins[0]);
+ if (mp_frame_is_signaling(frame)) {
+ mp_pin_in_write(f->ppins[1], frame);
+ return;
+ }
+ if (frame.type != MP_FRAME_VIDEO) {
+ MP_ERR(f, "unsupported frame type\n");
+ goto error;
+ }
+ struct mp_image *src = frame.data;
+
+ // As documented, just pass though HW frames.
+ if (IMGFMT_IS_HWACCEL(src->imgfmt)) {
+ mp_pin_in_write(f->ppins[1], frame);
+ return;
+ }
+
+ if (src->w % 2 || src->h % 2) {
+ MP_ERR(f, "non-mod 2 input frames unsupported\n");
+ goto error;
+ }
+
+ if (!update_format_decision(p, src->imgfmt)) {
+ MP_ERR(f, "no hw upload format found\n");
+ goto error;
+ }
+
+ if (!mp_update_av_hw_frames_pool(&p->hw_pool, p->av_device_ctx,
+ p->public.hw_imgfmt, p->last_sw_fmt,
+ src->w, src->h))
+ {
+ MP_ERR(f, "failed to create frame pool\n");
+ goto error;
+ }
+
+ struct mp_image *dst = mp_av_pool_image_hw_upload(p->hw_pool, src);
+ if (!dst)
+ goto error;
+
+ mp_frame_unref(&frame);
+ mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_VIDEO, dst));
+
+ return;
+
+error:
+ mp_frame_unref(&frame);
+ MP_ERR(f, "failed to upload frame\n");
+ mp_filter_internal_mark_failed(f);
+}
+
+static void destroy(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ av_buffer_unref(&p->hw_pool);
+ av_buffer_unref(&p->av_device_ctx);
+}
+
+static const struct mp_filter_info hwupload_filter = {
+ .name = "hwupload",
+ .priv_size = sizeof(struct priv),
+ .process = process,
+ .destroy = destroy,
+};
+
+// The VO layer might have restricted format support. It might actually
+// work if this is input to a conversion filter anyway, but our format
+// negotiation is too stupid and non-existent to detect this.
+// So filter out all not explicitly supported formats.
+static bool vo_supports(struct mp_hwdec_ctx *ctx, int hw_fmt, int sw_fmt)
+{
+ if (!ctx->hw_imgfmt)
+ return true; // if unset, all formats are allowed
+ if (ctx->hw_imgfmt != hw_fmt)
+ return false;
+
+ for (int i = 0; ctx->supported_formats && ctx->supported_formats[i]; i++) {
+ if (ctx->supported_formats[i] == sw_fmt)
+ return true;
+ }
+
+ return false;
+}
+
+static bool probe_formats(struct mp_hwupload *u, int hw_imgfmt)
+{
+ struct priv *p = u->f->priv;
+
+ u->hw_imgfmt = hw_imgfmt;
+ u->num_fmts = 0;
+ u->num_upload_fmts = 0;
+
+ struct mp_stream_info *info = mp_filter_find_stream_info(u->f);
+ if (!info || !info->hwdec_devs) {
+ MP_ERR(u->f, "no hw context\n");
+ return false;
+ }
+
+ struct mp_hwdec_ctx *ctx = NULL;
+ AVHWFramesConstraints *cstr = NULL;
+
+ for (int n = 0; ; n++) {
+ struct mp_hwdec_ctx *cur = hwdec_devices_get_n(info->hwdec_devs, n);
+ if (!cur)
+ break;
+ if (!cur->av_device_ref)
+ continue;
+ cstr = av_hwdevice_get_hwframe_constraints(cur->av_device_ref, NULL);
+ if (!cstr)
+ continue;
+ bool found = false;
+ for (int i = 0; cstr->valid_hw_formats &&
+ cstr->valid_hw_formats[i] != AV_PIX_FMT_NONE; i++)
+ {
+ found |= cstr->valid_hw_formats[i] == imgfmt2pixfmt(hw_imgfmt);
+ }
+ if (found && (!cur->hw_imgfmt || cur->hw_imgfmt == hw_imgfmt)) {
+ ctx = cur;
+ break;
+ }
+ av_hwframe_constraints_free(&cstr);
+ }
+
+ if (!ctx) {
+ MP_ERR(u->f, "no support for this hw format\n");
+ return false;
+ }
+
+ // Probe for supported formats. This is very roundabout, because the
+ // hwcontext API does not give us this information directly. We resort to
+ // creating temporary AVHWFramesContexts in order to retrieve the list of
+ // supported formats. This should be relatively cheap as we don't create
+ // any real frames (although some backends do for probing info).
+
+ for (int n = 0; cstr->valid_sw_formats &&
+ cstr->valid_sw_formats[n] != AV_PIX_FMT_NONE; n++)
+ {
+ int imgfmt = pixfmt2imgfmt(cstr->valid_sw_formats[n]);
+ if (!imgfmt)
+ continue;
+
+ MP_VERBOSE(u->f, "looking at format %s\n", mp_imgfmt_to_name(imgfmt));
+
+ // Creates an AVHWFramesContexts with the given parameters.
+ AVBufferRef *frames = NULL;
+ if (!mp_update_av_hw_frames_pool(&frames, ctx->av_device_ref,
+ hw_imgfmt, imgfmt, 128, 128))
+ {
+ MP_WARN(u->f, "failed to allocate pool\n");
+ continue;
+ }
+
+ enum AVPixelFormat *fmts;
+ if (av_hwframe_transfer_get_formats(frames,
+ AV_HWFRAME_TRANSFER_DIRECTION_TO, &fmts, 0) >= 0)
+ {
+ int index = u->num_fmts;
+ MP_TARRAY_APPEND(p, u->fmts, u->num_fmts, imgfmt);
+ MP_TARRAY_GROW(p, u->fmt_upload_index, index);
+ MP_TARRAY_GROW(p, u->fmt_upload_num, index);
+
+ u->fmt_upload_index[index] = u->num_upload_fmts;
+
+ for (int i = 0; fmts[i] != AV_PIX_FMT_NONE; i++) {
+ int fmt = pixfmt2imgfmt(fmts[i]);
+ if (!fmt)
+ continue;
+ MP_VERBOSE(u->f, "supports %s\n", mp_imgfmt_to_name(fmt));
+ if (vo_supports(ctx, hw_imgfmt, fmt))
+ MP_TARRAY_APPEND(p, u->upload_fmts, u->num_upload_fmts, fmt);
+ }
+
+ u->fmt_upload_num[index] =
+ u->num_upload_fmts - u->fmt_upload_index[index];
+
+ av_free(fmts);
+ }
+
+ av_buffer_unref(&frames);
+ }
+
+ p->av_device_ctx = av_buffer_ref(ctx->av_device_ref);
+ if (!p->av_device_ctx)
+ return false;
+
+ return u->num_upload_fmts > 0;
+}
+
+struct mp_hwupload *mp_hwupload_create(struct mp_filter *parent, int hw_imgfmt)
+{
+ struct mp_filter *f = mp_filter_create(parent, &hwupload_filter);
+ if (!f)
+ return NULL;
+
+ struct priv *p = f->priv;
+ struct mp_hwupload *u = &p->public;
+ u->f = f;
+
+ mp_filter_add_pin(f, MP_PIN_IN, "in");
+ mp_filter_add_pin(f, MP_PIN_OUT, "out");
+
+ if (!probe_formats(u, hw_imgfmt)) {
+ MP_ERR(f, "hardware format not supported\n");
+ goto error;
+ }
+
+ return u;
+error:
+ talloc_free(f);
+ return NULL;
+}
diff --git a/filters/f_hwtransfer.h b/filters/f_hwtransfer.h
new file mode 100644
index 0000000000..4595cb393d
--- /dev/null
+++ b/filters/f_hwtransfer.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "filter.h"
+
+// A filter which uploads sw frames to hw. Ignores hw frames.
+struct mp_hwupload {
+ struct mp_filter *f;
+
+ // Hardware wrapper format, e.g. IMGFMT_VAAPI.
+ int hw_imgfmt;
+
+ // List of supported underlying surface formats.
+ int *fmts;
+ int num_fmts;
+ // List of supported upload image formats. May contain duplicate entries
+ // (which should be ignored).
+ int *upload_fmts;
+ int num_upload_fmts;
+ // For fmts[n], fmt_upload_index[n] gives the index of the first supported
+ // upload format in upload_fmts[], and fmt_upload_num[n] gives the number
+ // of formats at this position.
+ int *fmt_upload_index;
+ int *fmt_upload_num;
+};
+
+struct mp_hwupload *mp_hwupload_create(struct mp_filter *parent, int hw_imgfmt);
+
+// Return the best format suited for upload that is supported for a given input
+// imgfmt. This returns the same as imgfmt if the format is natively supported,
+// and otherwise a format that likely results in the least loss.
+// Returns 0 if completely unsupported.
+int mp_hwupload_find_upload_format(struct mp_hwupload *u, int imgfmt);
diff --git a/filters/f_lavfi.c b/filters/f_lavfi.c
new file mode 100644
index 0000000000..a97f126efb
--- /dev/null
+++ b/filters/f_lavfi.c
@@ -0,0 +1,952 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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
+ *