mirror of https://github.com/mpv-player/mpv
458 lines
12 KiB
C
458 lines
12 KiB
C
#include <math.h>
|
|
|
|
#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;
|
|
}
|