Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DOCS/interface-changes/add-deinterlace-algorithm.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add `--deinterlace-algorithm` option
14 changes: 11 additions & 3 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1815,9 +1815,11 @@ Video
``--deinterlace=<yes|no|auto>``
Enable or disable deinterlacing (default: no).
Interlaced video shows ugly comb-like artifacts, which are visible on
fast movement. Enabling this typically inserts the bwdif video filter in
order to deinterlace the video, or lets the video output apply deinterlacing
if supported.
fast movement. Enabling this inserts the hardware deinterlacing filter if
``--hwdec`` is used, otherwise the video output applies deinterlacing. If
neither is possible, a software bwdif filter is used instead. The
``--deinterlace-algorithm`` option can be used to select the deinterlacing
algorithm used by the video output (``gpu-next`` only).

When using ``auto``, mpv will insert a deinterlacing filter if ffmpeg
detects that the video frame is interlaced. Be aware that there can be false
Expand Down Expand Up @@ -6266,6 +6268,12 @@ them.
Among these kernels, ``burkes`` achieves a good balance between performance
and quality, and probably is the one you want to try first.

``--deinterlace-algorithm=<weave|bob|yadif|bwdif>``
Sets the deinterlacing algorithm to use when the VO is doing deinterlacing.
Currently only ``gpu-next`` supports this.

Default: ``bwdif``.

``--gpu-debug``
Enables GPU debugging. What this means depends on the API type. For OpenGL,
it calls ``glGetError()``, and requests a debug context. For Vulkan, it
Expand Down
24 changes: 18 additions & 6 deletions filters/f_auto_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
struct deint_priv {
struct mp_subfilter sub;
int prev_imgfmt;
int prev_deint_opt;
bool interlaced_frame;
struct m_config_cache *opts;
};
Expand Down Expand Up @@ -52,10 +53,6 @@ static void deint_process(struct mp_filter *f)
m_config_cache_update(p->opts);
struct filter_opts *opts = p->opts->opts;

// Handle deinterlace=0 case by destroying any existing filter immediately.
if (!opts->deinterlace)
mp_subfilter_destroy(&p->sub);

// Insert a deinterlace filter in the chain if:
// 1) deinterlace=1 (always), or
// 2) deinterlace=-1 (auto) and the frame is interlaced.
Expand All @@ -66,9 +63,17 @@ static void deint_process(struct mp_filter *f)
// If the image format changed, destroy any existing filter immediately since
// it may not support the new format. If we no longer need a filter, drain
// and destroy it gracefully.
if (img->imgfmt != p->prev_imgfmt) {
bool imgfmt_changed = img->imgfmt != p->prev_imgfmt;
// If we already have a deinterlace filter, and the option value changed
// (i.e. we switched from auto to yes or vice versa), we need to recreate it
// immediately to reflect the new value.
bool deint_opt_changed = p->sub.filter && opts->deinterlace != p->prev_deint_opt;
// Handle deinterlace=0 case by destroying any existing filter immediately.
bool deint_off = opts->deinterlace == 0;
if (imgfmt_changed || deint_opt_changed || deint_off) {
mp_subfilter_destroy(&p->sub);
p->prev_imgfmt = img->imgfmt;
if (imgfmt_changed)
p->prev_imgfmt = img->imgfmt;
} else if (p->sub.filter && !filter_needed) {
if (!mp_subfilter_drain_destroy(&p->sub))
return;
Expand All @@ -95,6 +100,7 @@ static void deint_process(struct mp_filter *f)
field_parity = "auto";
}

struct mp_stream_info *info = mp_filter_find_stream_info(f);
bool has_filter = true;
if (img->imgfmt == IMGFMT_VDPAU) {
char *args[] = {"deint", "yes",
Expand Down Expand Up @@ -126,6 +132,11 @@ static void deint_process(struct mp_filter *f)
"parity", field_parity, NULL};
p->sub.filter =
mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vavpp", args);
} else if (info && info->deinterlace && !IMGFMT_IS_HWACCEL(img->imgfmt)) {
char *args[] = {"interlaced-only", opts->deinterlace == 1 ? "no" : "yes",
"parity", field_parity, NULL};
p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO,
"fieldrate", args);
} else {
has_filter = false;
}
Expand Down Expand Up @@ -161,6 +172,7 @@ static void deint_process(struct mp_filter *f)
mp_chain_filters(subf->ppins[0], subf->ppins[1], filters, 2);
p->sub.filter = subf;
}
p->prev_deint_opt = opts->deinterlace;

mp_subfilter_continue(&p->sub);
}
Expand Down
1 change: 1 addition & 0 deletions filters/f_output_chain.c
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ void mp_output_chain_set_vo(struct mp_output_chain *c, struct vo *vo)
p->stream_info.osd = vo ? vo->osd : NULL;
p->stream_info.vflip = vo ? vo->driver->caps & VO_CAP_VFLIP : false;
p->stream_info.rotate90 = vo ? vo->driver->caps & VO_CAP_ROTATE90 : false;
p->stream_info.deinterlace = vo ? vo->driver->caps & VO_CAP_DEINTERLACE : false;
p->stream_info.dr_vo = vo;
p->vo = vo;
update_output_caps(p);
Expand Down
1 change: 1 addition & 0 deletions filters/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ struct mp_stream_info {
bool vflip;
bool rotate90;
bool force_swdec;
bool deinterlace;
struct vo *dr_vo; // for calling vo_get_image()
};

Expand Down
1 change: 1 addition & 0 deletions filters/user_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const struct mp_user_filter_entry *vf_list[] = {
#if (HAVE_GL && HAVE_EGL) || HAVE_VULKAN
&vf_gpu,
#endif
&vf_fieldrate,
};

static bool get_vf_desc(struct m_obj_desc *dst, int index)
Expand Down
1 change: 1 addition & 0 deletions filters/user_filters.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ extern const struct mp_user_filter_entry vf_d3d11vpp;
extern const struct mp_user_filter_entry vf_amf_frc;
extern const struct mp_user_filter_entry vf_fingerprint;
extern const struct mp_user_filter_entry vf_gpu;
extern const struct mp_user_filter_entry vf_fieldrate;
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ sources = files(
'video/filter/refqueue.c',
'video/filter/vf_format.c',
'video/filter/vf_sub.c',
'video/filter/vf_fieldrate.c',
'video/fmt-conversion.c',
'video/hwdec.c',
'video/image_loader.c',
Expand Down
5 changes: 4 additions & 1 deletion player/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,10 @@ typedef struct MPContext {
// The +1 is for adding 1 additional frame in backstep mode.
struct mp_image *next_frames[VO_MAX_REQ_FRAMES + 1];
int num_next_frames;
struct mp_image *saved_frame; // for hrseek_lastframe and hrseek_backstep

// saved_frames[0] is the last frame, saved_frames[1] the one before that.
struct mp_image *saved_frames[VO_MAX_REQ_FRAMES + 1];
int num_saved_frames;

enum playback_status video_status, audio_status;
bool restart_complete;
Expand Down
87 changes: 74 additions & 13 deletions player/video.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ static void vo_chain_reset_state(struct vo_chain *vo_c)
vo_c->underrun_signaled = false;
}

static void trim_saved_frames(struct MPContext *mpctx, int max_frames)
{
mp_assert(max_frames >= 0);
mp_assert(max_frames <= MP_ARRAY_SIZE(mpctx->saved_frames));

while (mpctx->num_saved_frames > max_frames) {
mpctx->num_saved_frames--;
mp_image_unrefp(&mpctx->saved_frames[mpctx->num_saved_frames]);
}
}

void reset_video_state(struct MPContext *mpctx)
{
if (mpctx->vo_chain) {
Expand All @@ -107,7 +118,7 @@ void reset_video_state(struct MPContext *mpctx)
for (int n = 0; n < mpctx->num_next_frames; n++)
mp_image_unrefp(&mpctx->next_frames[n]);
mpctx->num_next_frames = 0;
mp_image_unrefp(&mpctx->saved_frame);
trim_saved_frames(mpctx, 0);

mpctx->delay = 0;
mpctx->time_frame = 0;
Expand Down Expand Up @@ -401,15 +412,59 @@ static void handle_new_frame(struct MPContext *mpctx)
MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time);
}

static void add_saved_frame(struct MPContext *mpctx, struct mp_image *frame,
int max_frames)
{
mp_assert(frame);
mp_assert(max_frames >= 0);
mp_assert(max_frames <= MP_ARRAY_SIZE(mpctx->saved_frames));

if (!max_frames) {
trim_saved_frames(mpctx, 0);
talloc_free(frame);
return;
}

// Make room for the new newest frame.
trim_saved_frames(mpctx, max_frames - 1);

for (int n = mpctx->num_saved_frames; n > 0; n--)
mpctx->saved_frames[n] = mpctx->saved_frames[n - 1];

mpctx->saved_frames[0] = frame;
mpctx->num_saved_frames++;
}

static struct mp_image *take_saved_frame(struct MPContext *mpctx)
{
if (!mpctx->num_saved_frames)
return NULL;

struct mp_image *frame = mpctx->saved_frames[0];

for (int n = 1; n < mpctx->num_saved_frames; n++)
mpctx->saved_frames[n - 1] = mpctx->saved_frames[n];

mpctx->num_saved_frames--;
mpctx->saved_frames[mpctx->num_saved_frames] = NULL;

return frame;
}

// Remove the first frame in mpctx->next_frames
static void shift_frames(struct MPContext *mpctx)
{
if (mpctx->num_next_frames < 1)
return;
talloc_free(mpctx->next_frames[0]);

struct mp_image *frame = mpctx->next_frames[0];

for (int n = 0; n < mpctx->num_next_frames - 1; n++)
mpctx->next_frames[n] = mpctx->next_frames[n + 1];
mpctx->num_next_frames -= 1;
mpctx->next_frames[mpctx->num_next_frames] = NULL;

add_saved_frame(mpctx, frame, vo_get_num_req_past_frames(mpctx->video_out));
}

static bool use_video_lookahead(struct MPContext *mpctx)
Expand Down Expand Up @@ -528,33 +583,33 @@ static int video_output_image(struct MPContext *mpctx, bool *logical_eof)
mpctx->hrseek_lastframe))
{
/* just skip - but save in case it was the last frame */
mp_image_setrefp(&mpctx->saved_frame, img);
add_saved_frame(mpctx, img,
vo_get_num_req_past_frames(mpctx->video_out) + 1);
img = NULL;
} else {
if (hrseek && mpctx->hrseek_backstep) {
if (mpctx->saved_frame) {
add_new_frame(mpctx, mpctx->saved_frame);
mpctx->saved_frame = NULL;
struct mp_image *saved = take_saved_frame(mpctx);
if (saved) {
add_new_frame(mpctx, saved);
} else {
MP_WARN(mpctx, "Backstep failed.\n");
}
mpctx->hrseek_backstep = false;
}
mp_image_unrefp(&mpctx->saved_frame);
add_new_frame(mpctx, img);
img = NULL;
}
talloc_free(img);
}
}

if (!hrseek)
mp_image_unrefp(&mpctx->saved_frame);

if (r == VD_EOF) {
// If hr-seek went past EOF, use the last frame.
if (mpctx->saved_frame)
add_new_frame(mpctx, mpctx->saved_frame);
mpctx->saved_frame = NULL;
if (hrseek) {
struct mp_image *saved = take_saved_frame(mpctx);
if (saved)
add_new_frame(mpctx, saved);
}
*logical_eof = true;
}

Expand Down Expand Up @@ -1225,16 +1280,22 @@ void write_video(struct MPContext *mpctx)
};
calculate_frame_duration(mpctx);

int req_past = vo_get_num_req_past_frames(mpctx->video_out);
int req = vo_get_num_req_frames(mpctx->video_out);
mp_assert(req_past >= 0 && req_past <= VO_MAX_REQ_FRAMES);
mp_assert(req >= 1 && req <= VO_MAX_REQ_FRAMES);
trim_saved_frames(mpctx, req_past);
struct vo_frame dummy = {
.pts = pts,
.duration = -1,
.still = mpctx->step_frames > 0,
.can_drop = opts->frame_dropping & 1,
.num_past_frames = mpctx->num_saved_frames,
.num_frames = MPMIN(mpctx->num_next_frames, req),
.num_vsyncs = 1,
};
for (int n = 0; n < dummy.num_past_frames; n++)
dummy.past_frames[n] = mpctx->saved_frames[n];
for (int n = 0; n < dummy.num_frames; n++)
dummy.frames[n] = mpctx->next_frames[n];
struct vo_frame *frame = vo_frame_ref(&dummy);
Expand Down
Loading
Loading