/*
* 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
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*
* Parts under HAVE_GPL are licensed under GNU General Public License.
*/
#include <stddef.h>
#include <stdbool.h>
#include <inttypes.h>
#include <math.h>
#include <assert.h>
#include "config.h"
#include "mpv_talloc.h"
#include "common/msg.h"
#include "options/options.h"
#include "options/m_config.h"
#include "options/m_option.h"
#include "common/common.h"
#include "common/encode.h"
#include "options/m_property.h"
#include "osdep/timer.h"
#include "audio/out/ao.h"
#include "demux/demux.h"
#include "stream/stream.h"
#include "sub/osd.h"
#include "video/hwdec.h"
#include "video/filter/vf.h"
#include "video/decode/dec_video.h"
#include "video/decode/vd.h"
#include "video/out/vo.h"
#include "audio/filter/af.h"
#include "audio/decode/dec_audio.h"
#include "core.h"
#include "command.h"
#include "screenshot.h"
#define VF_DEINTERLACE_LABEL "deinterlace"
enum {
// update_video() - code also uses: <0 error, 0 eof, >0 progress
VD_ERROR = -1,
VD_EOF = 0, // end of file - no new output
VD_PROGRESS = 1, // progress, but no output; repeat call with no waiting
VD_NEW_FRAME = 2, // the call produced a new frame
VD_WAIT = 3, // no EOF, but no output; wait until wakeup
VD_RECONFIG = 4,
};
static const char av_desync_help_text[] =
"\n"
"Audio/Video desynchronisation detected! Possible reasons include too slow\n"
"hardware, temporary CPU spikes, broken drivers, and broken files. Audio\n"
"position will not match to the video (see A-V status field).\n"
"\n";
// Send a VCTRL, or if it doesn't work, translate it to a VOCTRL and try the VO.
int video_vf_vo_control(struct vo_chain *vo_c, int vf_cmd, void *data)
{
if (vo_c->vf->initialized > 0) {
int r = vf_control_any(vo_c->vf, vf_cmd, data);
if (r != CONTROL_UNKNOWN)
return r;
}
return CONTROL_UNKNOWN;
}
static void set_allowed_vo_formats(struct vo_chain *vo_c)
{
vo_query_formats(vo_c->vo, vo_c->vf->allowed_output_formats);
}
static int try_filter(struct vo_chain *vo_c, char *name, char *label, char **args)
{
struct vf_instance *vf = vf_append_filter(vo_c->vf, name, args);
if (!vf)
return -1;
vf->label = talloc_strdup(vf, label);
if (vf_reconfig(vo_c->vf, &vo_c->input_format) < 0) {
vf_remove_filter(vo_c->vf, vf);
// restore
vf_reconfig(vo_c->vf, &vo_c->input_format);
return -1;
}
return 0;
}
static bool check_output_format(struct vo_chain *vo_c, int imgfmt)
{
return vo_c->vf->output_params.imgfmt == imgfmt;
}
static int probe_deint_filters(struct vo_chain *vo_c)
{
#if HAVE_GPL
// Usually, we prefer inserting/removing deint filters. But If there's VO
// support, or the user inserted a filter that supports swichting deint and
// that has no VF_DEINTERLACE_LABEL, or if the filter was auto-inserted
// for other reasons and supports switching deint (like vf_d3d11vpp), then
// use the runtime switching method.
if (video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){1}) == CONTROL_OK)
return 0;
#endif
if (check_output_format(vo_c, IMGFMT_VDPAU)) {
char *args[5] = {"deint", "yes"};
int pref = 0;
vo_control(vo_c->vo, VOCTRL_GET_PREF_DEINT, &pref);
pref = pref < 0 ? -pref : pref;
if (pref > 0 && pref <= 4) {
const char *types[] =
{"", "first-field", "bob", "temporal", "temporal-spatial"};
args[2] = "deint-mode";
args[3] = (char *)types[pref];
}
return try_filter(vo_c, "vdpaupp", VF_DEINTERLACE_LABEL, args);
}
if (check_output_format(vo_c, IMGFMT_VAAPI))
return try_filter(vo_c, "vavpp", VF_DEINTERLACE_LABEL, NULL);
if (check_output_format(vo_c, IMGFMT_D3D11VA) ||
check_output_format(vo_c, IMGFMT_D3D11NV12))
return try_filter(vo_c, "d3d11vpp", VF_DEINTERLACE_LABEL, NULL);
char *args[] = {"warn", "no", NULL};
return try_filter(vo_c, "yadif", VF_DEINTERLACE_LABEL, args);
}
// Reconfigure the filter chain according to the new input format.
static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
{
struct mp_image_params params = vo_c->input_format;
if (!params.imgfmt)
return;
set_allowed_vo_formats(vo_c);
char *filters[] = {"autorotate", "autostereo3d", "deinterlace", NULL};
for (int n = 0; filters[n]; n++) {
struct vf_instance *vf = vf_find_by_label(vo_c->vf, filters[n]);
if (vf)
vf_remove_filter(vo_c->vf, vf);
}
if (vo_c->vf->initialized < 1) {
if (vf_reconfig(vo_c->vf, ¶ms) < 0)
return;
}
#if HAVE_GPL
// Make sure to reset this even if runtime deint switching is used.
if (mpctx->opts->deinterlace >= 0)
video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){0});
#endif
if (params.rotate) {
if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90) || params.rotate % 90) {
// Try to insert a rotation filter.
char *args[] = {"angle", "auto", "warn", "no", NULL};
if (try_filter(vo_c, "rotate", "autorotate", args) < 0)
MP_ERR(vo_c, "Can't insert rotation filter.\n");
}
}
if (params.stereo_in != params.stereo_out &&
params.stereo_in > 0 && params.stereo_out >= 0)
{
char *to = (char *)MP_STEREO3D_NAME(params.stereo_out);
if (to) {
char *args[] = {"in", "auto", "out", to, "warn", "no", NULL, NULL};
if (try_filter(vo_c, "stereo3d", "autostereo3d", args) < 0)
MP_ERR(vo_c, "Can't insert 3D conversion filter.\n");
}
}
if (mpctx->opts->deinterlace == 1)
probe_deint_filters(vo_c);
}
static void recreate_auto_filters(struct MPContext *mpctx)
{
filter_reconfig(mpctx, mpctx->vo_chain);
mp_force_video_refresh(mpctx);
mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL);
}
int get_deinterlacing(struct MPContext *mpctx)
{
struct vo_chain *vo_c = mpctx->vo_chain;
int enabled = 0;
#if HAVE_GPL
if (video_vf_vo_control(vo_c, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
enabled = -1;
#endif
if (enabled < 0) {
// vf_lavfi doesn't support VFCTRL_GET_DEINTERLACE
if (vf_find_by_label(vo_c->vf, VF_DEINTERLACE_LABEL))
enabled = 1;
}
return enabled;
}
void set_deinterlacing(struct MPContext *mpctx, int opt_val)
{
if ((opt_val < 0 && mpctx->opts->deinterlace == opt_val) ||
(opt_val == (get_deinterlacing(mpctx) > 0)))
return;
mpctx->opts->deinterlace = opt_val;
recreate_auto_filters(mpctx);
if (opt_val >= 0)
mpctx->opts->deinterlace = get_deinterlacing(mpctx) > 0;
}
static void recreate_video_filters(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct vo_chain *vo_c = mpctx->vo_chain;
assert(vo_c);
vf_destroy(vo_c->vf);
vo_c->vf = vf_new(mpctx->global);
vo_c->vf->hwdec_devs = vo_c->hwdec_devs;
vo_c->vf->wakeup_callback = mp_wakeup_core_cb;
vo_c->vf->wakeup_callback_ctx = mpctx;
vo_c->vf->container_fps = vo_c->container_fps;
vo_control(vo_c->vo, VOCTRL_GET_DISPLAY_FPS, &vo_c->vf->display_fps);
vf_append_filter_list(vo_c->vf, opts->vf_settings);
// for vf_sub
osd_set_render_subs_in_filter(mpctx->osd,
vf_control_any(vo_c->vf, VFCTRL_INIT_OSD, mpctx->osd) > 0);
set_allowed_vo_formats(vo_c
|