#include #include "audio/aframe.h" #include "audio/format.h" #include "common/common.h" #include "common/msg.h" #include "options/m_config.h" #include "options/options.h" #include "video/filter/refqueue.h" #include "video/mp_image.h" #include "video/mp_image_pool.h" #include "f_auto_filters.h" #include "f_autoconvert.h" #include "f_hwtransfer.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; bool deinterlace_active; 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; } struct mp_image *img = frame.data; bool interlaced = img->fields & MP_IMGFIELD_INTERLACED; m_config_cache_update(p->opts); struct filter_opts *opts = p->opts->opts; bool should_deinterlace = (opts->deinterlace == -1 && interlaced) || opts->deinterlace == 1; if (!should_deinterlace) mp_subfilter_destroy(&p->sub); if (img->imgfmt == p->prev_imgfmt && p->deinterlace_active == should_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->deinterlace_active = should_deinterlace; if (!p->deinterlace_active) { mp_subfilter_continue(&p->sub); return; } char *field_parity; switch (opts->field_parity) { case MP_FIELD_PARITY_TFF: field_parity = "tff"; break; case MP_FIELD_PARITY_BFF: field_parity = "bff"; break; default: field_parity = "auto"; } bool has_filter = true; if (img->imgfmt == IMGFMT_VDPAU) { char *args[] = {"deint", "yes", "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vdpaupp", args); } else if (img->imgfmt == IMGFMT_D3D11) { char *args[] = {"parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", args); } else if (img->imgfmt == IMGFMT_CUDA) { char *args[] = {"mode", "send_field", "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_cuda", args); } else if (img->imgfmt == IMGFMT_VULKAN) { char *args[] = {"mode", "send_field", "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "bwdif_vulkan", args); } else if (img->imgfmt == IMGFMT_VAAPI) { char *args[] = {"deint", "motion-adaptive", "interlaced-only", "yes", "parity", field_parity, NULL}; p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vavpp", args); } else { has_filter = false; } if (!p->sub.filter) { if (has_filter) MP_ERR(f, "creating deinterlacer failed\n"); struct mp_filter *subf = mp_bidir_dummy_filter_create(f); struct mp_filter *filters[2] = {0}; struct mp_autoconvert *ac = mp_autoconvert_create(subf); if (ac) { filters[0] = ac->f; // We know vf_bwdif does not support hw inputs. mp_autoconvert_add_all_sw_imgfmts(ac); if (!mp_autoconvert_probe_input_video(ac, img)) { MP_ERR(f, "no deinterlace filter available for format %s\n", mp_imgfmt_to_name(img->imgfmt)); talloc_free(subf); mp_subfilter_continue(&p->sub); return; } } char *args[] = {"mode", "send_field", "parity", field_parity, NULL}; filters[1] = mp_create_user_filter(subf, MP_OUTPUT_CHAIN_VIDEO, "bwdif", args); mp_chain_filters(subf->ppins[0], subf->ppins[1], filters, 2); p->sub.filter = subf; } 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 bool deint_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct deint_priv *p = f->priv; if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) { cmd->is_active = !!p->sub.filter; return true; } return false; } static const struct mp_filter_info deint_filter = { .name = "deint", .priv_size = sizeof(struct deint_priv), .command = deint_command, .process = deint_process, .reset = deint_reset, .destroy = deint_destroy, }; bool mp_deint_active(struct mp_filter *f) { struct deint_priv *p = f->priv; return p->deinterlace_active; } 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 bool rotate_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct rotate_priv *p = f->priv; if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) { cmd->is_active = !!p->sub.filter; return true; } return false; } static const struct mp_filter_info rotate_filter = { .name = "autorotate", .priv_size = sizeof(struct rotate_priv), .command = rotate_command, .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; } struct aspeed_priv { struct mp_subfilter sub; double cur_speed, cur_speed_drop; int current_filter; }; static void aspeed_process(struct mp_filter *f) { struct aspeed_priv *p = f->priv; if (!mp_subfilter_read(&p->sub)) return; if (!p->sub.filter) p->current_filter = 0; double speed = p->cur_speed * p->cur_speed_drop; int req_filter = 0; if (fabs(speed - 1.0) >= 1e-8) { req_filter = p->cur_speed_drop == 1.0 ? 1 : 2; if (p->sub.frame.type == MP_FRAME_AUDIO && !af_fmt_is_pcm(mp_aframe_get_format(p->sub.frame.data))) req_filter = 2; } if (req_filter != p->current_filter) { if (p->sub.filter) MP_VERBOSE(f, "removing audio speed filter\n"); if (!mp_subfilter_drain_destroy(&p->sub)) return; if (req_filter) { if (req_filter == 1) { MP_VERBOSE(f, "adding scaletempo2\n"); p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, "scaletempo2", NULL); } else if (req_filter == 2) { MP_VERBOSE(f, "adding drop\n"); p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, "drop", NULL); } if (!p->sub.filter) { MP_ERR(f, "could not create filter\n"); mp_subfilter_continue(&p->sub); return; } p->current_filter = req_filter; } } if (p->sub.filter) { struct mp_filter_command cmd = { .type = MP_FILTER_COMMAND_SET_SPEED, .speed = speed, }; mp_filter_command(p->sub.filter, &cmd); } mp_subfilter_continue(&p->sub); } static bool aspeed_command(struct mp_filter *f, struct mp_filter_command *cmd) { struct aspeed_priv *p = f->priv; if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) { p->cur_speed = cmd->speed; return true; } if (cmd->type == MP_FILTER_COMMAND_SET_SPEED_DROP) { p->cur_speed_drop = cmd->speed; return true; } if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) { cmd->is_active = !!p->sub.filter; return true; } return false; } static void aspeed_reset(struct mp_filter *f) { struct aspeed_priv *p = f->priv; mp_subfilter_reset(&p->sub); } static void aspeed_destroy(struct mp_filter *f) { struct aspeed_priv *p = f->priv; mp_subfilter_reset(&p->sub); TA_FREEP(&p->sub.filter); } static const struct mp_filter_info aspeed_filter = { .name = "autoaspeed", .priv_size = sizeof(struct aspeed_priv), .command = aspeed_command, .process = aspeed_process, .reset = aspeed_reset, .destroy = aspeed_destroy, }; struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent) { struct mp_filter *f = mp_filter_create(parent, &aspeed_filter); if (!f) return NULL; struct aspeed_priv *p = f->priv; p->cur_speed = 1.0; p->cur_speed_drop = 1.0; 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; }