summaryrefslogtreecommitdiffstats
path: root/filters/f_autoconvert.c
diff options
context:
space:
mode:
Diffstat (limited to 'filters/f_autoconvert.c')
-rw-r--r--filters/f_autoconvert.c288
1 files changed, 288 insertions, 0 deletions
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;
+}