1
0
mirror of https://github.com/mpv-player/mpv synced 2025-02-16 20:27:23 +00:00

Implement vsync-aware frame timing for VDPAU

Main things added are custom frame dropping for VDPAU to work around
the display FPS limit, frame timing adjustment to avoid jitter when
video frame times keep falling near vsyncs, and use of VDPAU's timing
feature to keep one future frame queued in advance.

NVIDIA's VDPAU implementation refuses to change the displayed frame
more than once per vsync. This set a limit on how much video could be
sped up, and caused problems for nearly all videos on low-FPS video
projectors (playing 24 FPS video on a 24 FPS projector would not work
reliably as MPlayer may need to slightly speed up the video for AV
sync). This commit adds a framedrop mechanism that drops some frames
so that no more than one is sent for display per vsync. The code
tries to select the dropped frames smartly, selecting the best one to
show for each vsync. Because of the timing features needed the drop
functionality currently does not work if the correct-pts option is
disabled.

The code also adjusts frame timing slightly to avoid jitter. If you
for example play 24 FPS video content on a 72 FPS display then
normally a frame would be shown for 3 vsyncs, but if the frame times
happen to fall near vsyncs and change between just before and just
after then there could be frames alternating between 2 and 4
vsyncs. The code changes frame timing by up to one quarter vsync
interval to avoid this.

The above functionality depends on having reliable vsync timing
information available. The display refresh rate is not directly
provided by the VDPAU API. The current code uses information from the
XF86VidMode extension if available; I'm not sure how common cases
where that is inaccurate are. The refresh rate can be specified
manually if necessary.

After the changes in this commit MPlayer now always tries to keep one
frame queued for future display using VDPAU's internal timing
mechanism (however no more than 50 ms to the future). This should make
video playback somewhat more robust against timing inaccuracies caused
by system load.
This commit is contained in:
Uoti Urpala 2009-11-15 04:39:22 +02:00
parent b87ce8bc96
commit 201bef7ee1
11 changed files with 276 additions and 20 deletions

View File

@ -3442,6 +3442,11 @@ Use nochroma\-deint to solely use luma and speed up advanced deinterlacing.
Useful with slow video memory.
.IPs pullup
Try to apply inverse telecine, needs motion adaptive temporal deinterlacing.
.IPs fps=<number>
Override autodetected display refresh rate value (the value is needed for framedrop to allow video playback rates higher than display refresh rate, and for vsync-aware frame timing adjustments).
Default 0 means use autodetected value.
A positive value is interpreted as a refresh rate in Hz and overrides the autodetected value.
A negative value disables all timing adjustment and framedrop logic.
.RE
.PD 1
.

View File

@ -25,6 +25,8 @@ presentation_queue_block_until_surface_idle
presentation_queue_create
presentation_queue_destroy
presentation_queue_display
presentation_queue_get_time
presentation_queue_query_surface_status
presentation_queue_target_create_x11
presentation_queue_target_destroy
video_mixer_create

View File

@ -62,7 +62,7 @@ void vf_menu_pause_update(struct vf_instance* vf) {
put_image(vf,pause_mpi, MP_NOPTS_VALUE);
// Don't draw the osd atm
//vf->control(vf,VFCTRL_DRAW_OSD,NULL);
vo_flip_page(video_out);
vo_flip_page(video_out, 0, -1);
}
}

View File

@ -27,6 +27,8 @@ VDP_FUNCTION(VdpPresentationQueueBlockUntilSurfaceIdle, VDP_FUNC_ID_PRESENTATION
VDP_FUNCTION(VdpPresentationQueueCreate, VDP_FUNC_ID_PRESENTATION_QUEUE_CREATE, presentation_queue_create)
VDP_FUNCTION(VdpPresentationQueueDestroy, VDP_FUNC_ID_PRESENTATION_QUEUE_DESTROY, presentation_queue_destroy)
VDP_FUNCTION(VdpPresentationQueueDisplay, VDP_FUNC_ID_PRESENTATION_QUEUE_DISPLAY, presentation_queue_display)
VDP_FUNCTION(VdpPresentationQueueGetTime, VDP_FUNC_ID_PRESENTATION_QUEUE_GET_TIME, presentation_queue_get_time)
VDP_FUNCTION(VdpPresentationQueueQuerySurfaceStatus, VDP_FUNC_ID_PRESENTATION_QUEUE_QUERY_SURFACE_STATUS, presentation_queue_query_surface_status)
VDP_FUNCTION(VdpPresentationQueueTargetCreateX11, VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11, presentation_queue_target_create_x11)
VDP_FUNCTION(VdpPresentationQueueTargetDestroy, VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_DESTROY, presentation_queue_target_destroy)
VDP_FUNCTION(VdpVideoMixerCreate, VDP_FUNC_ID_VIDEO_MIXER_CREATE, video_mixer_create)

View File

@ -328,13 +328,16 @@ void vo_draw_osd(struct vo *vo, struct osd_state *osd)
vo->driver->draw_osd(vo, osd);
}
void vo_flip_page(struct vo *vo)
void vo_flip_page(struct vo *vo, unsigned int pts_us, int duration)
{
if (!vo->config_ok)
return;
vo->frame_loaded = false;
vo->next_pts = MP_NOPTS_VALUE;
vo->driver->flip_page(vo);
if (vo->driver->flip_page_timed)
vo->driver->flip_page_timed(vo, pts_us, duration);
else
vo->driver->flip_page(vo);
}
void vo_check_events(struct vo *vo)

View File

@ -200,6 +200,7 @@ struct vo_driver {
* Blit/Flip buffer to the screen. Must be called after each frame!
*/
void (*flip_page)(struct vo *vo);
void (*flip_page_timed)(struct vo *vo, unsigned int pts_us, int duration);
/*
* This func is called after every frames to handle keyboard and
@ -277,7 +278,7 @@ int vo_get_buffered_frame(struct vo *vo, bool eof);
int vo_draw_frame(struct vo *vo, uint8_t *src[]);
int vo_draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, int x, int y);
void vo_draw_osd(struct vo *vo, struct osd_state *osd);
void vo_flip_page(struct vo *vo);
void vo_flip_page(struct vo *vo, unsigned int pts_us, int duration);
void vo_check_events(struct vo *vo);
void vo_seek_reset(struct vo *vo);
void vo_destroy(struct vo *vo);

View File

@ -2,6 +2,7 @@
* VDPAU video output driver
*
* Copyright (C) 2008 NVIDIA
* Copyright (C) 2009 Uoti Urpala
*
* This file is part of MPlayer.
*
@ -32,6 +33,7 @@
#include <dlfcn.h>
#include <stdint.h>
#include <stdbool.h>
#include <limits.h>
#include "config.h"
#include "mp_msg.h"
@ -72,7 +74,7 @@
} while (0)
/* number of video and output surfaces */
#define NUM_OUTPUT_SURFACES 2
#define NUM_OUTPUT_SURFACES 3
#define MAX_VIDEO_SURFACES 50
#define NUM_BUFFERED_VIDEO 4
@ -105,6 +107,8 @@ struct vdpctx {
VdpPresentationQueueTarget flip_target;
VdpPresentationQueue flip_queue;
uint64_t last_vdp_time;
unsigned int last_sync_update;
void *vdpau_lib_handle;
@ -138,6 +142,13 @@ struct vdpctx {
struct vdpau_render_state surface_render[MAX_VIDEO_SURFACES];
int surface_num;
VdpTime recent_vsync_time;
float user_fps;
unsigned int vsync_interval;
uint64_t last_queue_time;
uint64_t last_ideal_time;
bool dropped_frame;
uint64_t dropped_time;
uint32_t vid_width, vid_height;
uint32_t image_format;
VdpChromaType vdp_chroma_type;
@ -172,15 +183,57 @@ struct vdpctx {
// Video equalizer
VdpProcamp procamp;
bool visible_buf;
int num_shown_frames;
bool paused;
// These tell what's been initialized and uninit() should free/uninitialize
bool mode_switched;
};
static int change_vdptime_sync(struct vdpctx *vc, unsigned int *t)
{
struct vdp_functions *vdp = vc->vdp;
VdpStatus vdp_st;
VdpTime vdp_time;
vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time);
CHECK_ST_ERROR("Error when calling vdp_presentation_queue_get_time");
unsigned int t1 = *t;
unsigned int t2 = GetTimer();
uint64_t old = vc->last_vdp_time + (t1 - vc->last_sync_update) * 1000ULL;
if (vdp_time > old)
if (vdp_time > old + (t2 - t1) * 1000ULL)
vdp_time -= (t2 - t1) * 1000ULL;
else
vdp_time = old;
mp_msg(MSGT_VO, MSGL_V, "[vdpau] adjusting VdpTime offset by %f µs\n",
(int64_t)(vdp_time - old) / 1000.);
vc->last_vdp_time = vdp_time;
vc->last_sync_update = t1;
*t = t2;
return 0;
}
static uint64_t sync_vdptime(struct vo *vo)
{
struct vdpctx *vc = vo->priv;
unsigned int t = GetTimer();
if (t - vc->last_sync_update > 5000000)
change_vdptime_sync(vc, &t);
uint64_t now = (t - vc->last_sync_update) * 1000ULL + vc->last_vdp_time;
// Make sure nanosecond inaccuracies don't make things inconsistent
now = FFMAX(now, vc->recent_vsync_time);
return now;
}
static uint64_t convert_to_vdptime(struct vo *vo, unsigned int t)
{
struct vdpctx *vc = vo->priv;
return (int)(t - vc->last_sync_update) * 1000LL + vc->last_vdp_time;
}
static void flip_page_timed(struct vo *vo, unsigned int pts_us, int duration);
static void flip_page(struct vo *vo);
static int video_to_output_surface(struct vo *vo)
{
struct vdpctx *vc = vo->priv;
@ -292,6 +345,7 @@ static void forget_frames(struct vo *vo)
struct vdpctx *vc = vo->priv;
vc->deint_queue_pos = -1001;
vc->dropped_frame = false;
for (int i = 0; i < NUM_BUFFERED_VIDEO; i++) {
struct buffered_video_surface *p = vc->buffered_video + i;
if (p->mpi)
@ -329,6 +383,7 @@ static void resize(struct vo *vo)
#endif
vo_osd_changed(OSDTYPE_OSD);
bool had_frames = vc->num_shown_frames;
if (vc->output_surface_width < vo->dwidth
|| vc->output_surface_height < vo->dheight) {
if (vc->output_surface_width < vo->dwidth) {
@ -354,10 +409,11 @@ static void resize(struct vo *vo)
mp_msg(MSGT_VO, MSGL_DBG2, "OUT CREATE: %u\n",
vc->output_surfaces[i]);
}
vc->num_shown_frames = 0;
}
if (vc->paused && vc->visible_buf)
if (vc->paused && had_frames)
if (video_to_output_surface(vo) >= 0)
flip_page(vo);
flip_page_timed(vo, 0, -1);
}
static void preemption_callback(VdpDevice device, void *context)
@ -448,6 +504,40 @@ static int win_x11_init_vdpau_flip_queue(struct vo *vo)
CHECK_ST_ERROR("Error when calling vdp_presentation_queue_create");
}
VdpTime vdp_time;
vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time);
CHECK_ST_ERROR("Error when calling vdp_presentation_queue_get_time");
vc->last_vdp_time = vdp_time;
vc->last_sync_update = GetTimer();
vc->vsync_interval = 1;
if (vc->user_fps > 0) {
vc->vsync_interval = 1e9 / vc->user_fps;
mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] Assuming user-specified display "
"refresh rate of %.3f Hz.\n", vc->user_fps);
} else if (vc->user_fps == 0) {
#ifdef CONFIG_XF86VM
double fps = vo_vm_get_fps(vo);
if (!fps)
mp_msg(MSGT_VO, MSGL_WARN, "[vdpau] Failed to get display FPS\n");
else {
vc->vsync_interval = 1e9 / fps;
// This is verbose, but I'm not yet sure how common wrong values are
mp_msg(MSGT_VO, MSGL_INFO,
"[vdpau] Got display refresh rate %.3f Hz.\n"
"[vdpau] If that value looks wrong give the "
"-vo vdpau:fps=X suboption manually.\n", fps);
}
#else
mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] This binary has been compiled "
"without XF86VidMode support.\n");
mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] Can't use vsync-aware timing "
"without manually provided -vo vdpau:fps=X suboption.\n");
#endif
} else
mp_msg(MSGT_VO, MSGL_V, "[vdpau] framedrop/timing logic disabled by "
"user.\n");
return 0;
}
@ -631,7 +721,6 @@ static int initialize_vdpau_objects(struct vo *vo)
&vc->eosd_surface.max_width,
&vc->eosd_surface.max_height);
CHECK_ST_WARNING("Query to get max EOSD surface size failed");
vc->surface_num = 0;
forget_frames(vo);
resize(vo);
return 0;
@ -656,7 +745,7 @@ static void mark_vdpau_objects_uninitialized(struct vo *vo)
};
vc->output_surface_width = vc->output_surface_height = -1;
vc->eosd_render_count = 0;
vc->visible_buf = false;
vc->num_shown_frames = 0;
}
static int handle_preemption(struct vo *vo)
@ -775,7 +864,7 @@ static void check_events(struct vo *vo)
resize(vo);
else if (e & VO_EVENT_EXPOSE && vc->paused) {
/* did we already draw a buffer */
if (vc->visible_buf) {
if (vc->num_shown_frames) {
/* redraw the last visible buffer */
VdpStatus vdp_st;
int last_surface = (vc->surface_num + NUM_OUTPUT_SURFACES - 1)
@ -1093,23 +1182,135 @@ static void draw_osd(struct vo *vo, struct osd_state *osd)
vc->vid_height, draw_osd_I8A8, vo);
}
static void flip_page(struct vo *vo)
static void wait_for_previous_frame(struct vo *vo)
{
struct vdpctx *vc = vo->priv;
struct vdp_functions *vdp = vc->vdp;
VdpStatus vdp_st;
if (vc->num_shown_frames < 2)
return;
VdpTime vtime;
VdpOutputSurface visible_s, prev_s;
int base = vc->surface_num + NUM_OUTPUT_SURFACES;
visible_s = vc->output_surfaces[(base - 1) % NUM_OUTPUT_SURFACES];
prev_s = vc->output_surfaces[(base - 2) % NUM_OUTPUT_SURFACES];
vdp_st = vdp->presentation_queue_block_until_surface_idle(vc->flip_queue,
prev_s, &vtime);
CHECK_ST_WARNING("Error calling "
"presentation_queue_block_until_surface_idle");
VdpPresentationQueueStatus status;
vdp_st = vdp->presentation_queue_query_surface_status(vc->flip_queue,
visible_s,
&status, &vtime);
CHECK_ST_WARNING("Error calling presentation_queue_query_surface_status");
vc->recent_vsync_time = vtime;
}
static inline uint64_t prev_vs2(struct vdpctx *vc, uint64_t ts, int shift)
{
uint64_t offset = ts - vc->recent_vsync_time;
// Fix negative values for 1<<shift vsyncs before vc->recent_vsync_time
offset += (uint64_t)vc->vsync_interval << shift;
offset %= vc->vsync_interval;
return ts - offset;
}
static void flip_page_timed(struct vo *vo, unsigned int pts_us, int duration)
{
struct vdpctx *vc = vo->priv;
struct vdp_functions *vdp = vc->vdp;
VdpStatus vdp_st;
uint32_t vsync_interval = vc->vsync_interval;
if (handle_preemption(vo) < 0)
return;
if (duration > INT_MAX / 1000)
duration = -1;
else
duration *= 1000;
if (vc->user_fps < 0)
duration = -1; // Make sure drop logic is disabled
uint64_t now = sync_vdptime(vo);
uint64_t pts = pts_us ? convert_to_vdptime(vo, pts_us) : now;
uint64_t ideal_pts = pts;
uint64_t npts = duration >= 0 ? pts + duration : UINT64_MAX;
#define PREV_VS2(ts, shift) prev_vs2(vc, ts, shift)
// Only gives accurate results for ts >= vc->recent_vsync_time
#define PREV_VSYNC(ts) PREV_VS2(ts, 0)
/* We hope to be here at least one vsync before the frame should be shown.
* If we are running late then don't drop the frame unless there is
* already one queued for the next vsync; even if we _hope_ to show the
* next frame soon enough to mean this one should be dropped we might
* not make the target time in reality. Without this check we could drop
* every frame, freezing the display completely if video lags behind.
*/
if (now > PREV_VSYNC(FFMAX(pts,
vc->last_queue_time + vsync_interval)))
npts = UINT64_MAX;
/* Allow flipping a frame at a vsync if its presentation time is a
* bit after that vsync and the change makes the flip time delta
* from previous frame better match the target timestamp delta.
* This avoids instability with frame timestamps falling near vsyncs.
* For example if the frame timestamps were (with vsyncs at
* integer values) 0.01, 1.99, 4.01, 5.99, 8.01, ... then
* straightforward timing at next vsync would flip the frames at
* 1, 2, 5, 6, 9; this changes it to 1, 2, 4, 6, 8 and so on with
* regular 2-vsync intervals.
*
* Also allow moving the frame forward if it looks like we dropped
* the previous frame incorrectly (now that we know better after
* having final exact timestamp information for this frame) and
* there would unnecessarily be a vsync without a frame change.
*/
uint64_t vsync = PREV_VSYNC(pts);
if (pts < vsync + vsync_interval / 4
&& (vsync - PREV_VS2(vc->last_queue_time, 16)
> pts - vc->last_ideal_time + vsync_interval / 2
|| vc->dropped_frame && vsync > vc->dropped_time))
pts -= vsync_interval / 2;
vc->dropped_frame = true; // changed at end if false
vc->dropped_time = ideal_pts;
pts = FFMAX(pts, vc->last_queue_time + vsync_interval);
pts = FFMAX(pts, now);
if (npts < PREV_VSYNC(pts) + vsync_interval)
return;
/* At least on my NVIDIA 9500GT with driver versions 185.18.36 and 190.42
* trying to queue two unshown frames simultaneously caused bad behavior
* (high CPU use in _other_ VDPAU functions called later). Avoiding
* longer queues also makes things simpler. So currently we always
* try to keep exactly one frame queued for the future, queuing the
* current frame immediately after the previous one is shown.
*/
wait_for_previous_frame(vo);
now = sync_vdptime(vo);
pts = FFMAX(pts, now);
vsync = PREV_VSYNC(pts);
if (npts < vsync + vsync_interval)
return;
pts = vsync + (vsync_interval >> 2);
vdp_st =
vdp->presentation_queue_display(vc->flip_queue,
vc->output_surfaces[vc->surface_num],
vo->dwidth, vo->dheight, 0);
vo->dwidth, vo->dheight, pts);
CHECK_ST_WARNING("Error when calling vdp_presentation_queue_display");
vc->last_queue_time = pts;
vc->last_ideal_time = ideal_pts;
vc->dropped_frame = false;
vc->surface_num = (vc->surface_num + 1) % NUM_OUTPUT_SURFACES;
vc->visible_buf = true;
vc->num_shown_frames = FFMIN(vc->num_shown_frames + 1, 1000);
}
static int draw_slice(struct vo *vo, uint8_t *image[], int stride[], int w,
@ -1348,6 +1549,7 @@ static int preinit(struct vo *vo, const char *arg)
{"pullup", OPT_ARG_BOOL, &vc->pullup, NULL},
{"denoise", OPT_ARG_FLOAT, &vc->denoise, NULL},
{"sharpen", OPT_ARG_FLOAT, &vc->sharpen, NULL},
{"fps", OPT_ARG_FLOAT, &vc->user_fps, NULL},
{NULL}
};
if (subopt_parse(arg, subopts) != 0) {
@ -1478,6 +1680,8 @@ static int control(struct vo *vo, uint32_t request, void *data)
}
return VO_TRUE;
case VOCTRL_PAUSE:
if (vc->dropped_frame)
flip_page_timed(vo, 0, -1);
return (vc->paused = true);
case VOCTRL_RESUME:
return (vc->paused = false);
@ -1538,7 +1742,7 @@ static int control(struct vo *vo, uint32_t request, void *data)
video_to_output_surface(vo);
draw_eosd(vo);
draw_osd(vo, data);
flip_page(vo);
flip_page_timed(vo, 0, -1);
return true;
case VOCTRL_RESET:
forget_frames(vo);
@ -1563,7 +1767,7 @@ const struct vo_driver video_out_vdpau = {
.get_buffered_frame = get_buffered_frame,
.draw_slice = draw_slice,
.draw_osd = draw_osd,
.flip_page = flip_page,
.flip_page_timed = flip_page_timed,
.check_events = check_events,
.uninit = uninit,
};

View File

@ -1642,6 +1642,18 @@ void vo_vm_close(struct vo *vo)
vidmodes = NULL;
}
}
double vo_vm_get_fps(struct vo *vo)
{
struct vo_x11_state *x11 = vo->x11;
int clock;
XF86VidModeModeLine modeline;
if (!XF86VidModeGetModeLine(x11->display, x11->screen, &clock, &modeline))
return 0;
if (modeline.privsize)
Xfree(modeline.private);
return 1e3 * clock / modeline.htotal / modeline.vtotal;
}
#endif
#endif /* X11_FULLSCREEN */

View File

@ -164,6 +164,7 @@ void vo_x11_putkey(struct vo *vo, int key);
#ifdef CONFIG_XF86VM
void vo_vm_switch(struct vo *vo);
void vo_vm_close(struct vo *vo);
double vo_vm_get_fps(struct vo *vo);
#endif
void update_xinerama_info(struct vo *vo);

View File

@ -208,7 +208,7 @@ int vo_draw_image(struct vo *vo, struct mp_image *mpi, double pts) { abort(); }
int vo_draw_frame(struct vo *vo, uint8_t *src[]) { abort(); }
int vo_draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, int x, int y) { abort(); }
void vo_draw_osd(struct vo *vo, struct osd_state *osd) { abort(); }
void vo_flip_page(struct vo *vo) { abort(); }
void vo_flip_page(struct vo *vo, uint32_t pts_us, int duration) { abort(); }
void vo_check_events(struct vo *vo) { abort(); }
// Needed by getch2

View File

@ -1180,6 +1180,8 @@ static void print_status(struct MPContext *mpctx, double a_pos, bool at_frame)
a_pos = playing_audio_pts(mpctx);
if (mpctx->sh_audio && sh_video && at_frame) {
mpctx->last_av_difference = a_pos - sh_video->pts - audio_delay;
if (mpctx->time_frame > 0)
mpctx->last_av_difference += mpctx->time_frame * opts->playback_speed;
if (mpctx->last_av_difference > 0.5 && drop_frame_cnt > 50
&& !mpctx->drop_message_shown) {
mp_tmsg(MSGT_AVSYNC,MSGL_WARN,SystemTooSlow);
@ -2104,9 +2106,13 @@ static int sleep_until_update(struct MPContext *mpctx, float *time_frame,
//============================== SLEEP: ===================================
if (mpctx->video_out->driver->flip_page_timed)
*time_frame -= 0.05;
// flag 256 means: libvo driver does its timing (dvb card)
if (*time_frame > 0.001 && !(mpctx->sh_video->output_flags&256))
*time_frame = timing_sleep(mpctx, *time_frame);
if (mpctx->video_out->driver->flip_page_timed)
*time_frame += 0.05;
return frame_time_remaining;
}
@ -4044,11 +4050,31 @@ if(!mpctx->sh_video) {
current_module="flip_page";
if (!frame_time_remaining && blit_frame) {
unsigned int t2=GetTimer();
vo_flip_page(mpctx->video_out);
unsigned int pts_us = mpctx->last_time + mpctx->time_frame * 1e6;
int duration = -1;
double pts2 = mpctx->video_out->next_pts2;
if (pts2 != MP_NOPTS_VALUE && opts->correct_pts) {
// expected A/V sync correction is ignored
double diff = (pts2 - mpctx->sh_video->pts);
diff /= opts->playback_speed;
if (mpctx->time_frame < 0)
diff += mpctx->time_frame;
if (diff < 0)
diff = 0;
if (diff > 10)
diff = 10;
duration = diff * 1e6;
}
vo_flip_page(mpctx->video_out, pts_us|1, duration);
mpctx->last_vo_flip_duration = (GetTimer() - t2) * 0.000001;
vout_time_usage += mpctx->last_vo_flip_duration;
if (mpctx->video_out->driver->flip_page_timed) {
// No need to adjust sync based on flip speed
mpctx->last_vo_flip_duration = 0;
// For print_status - VO call finishing early is OK for sync
mpctx->time_frame -= get_relative_time(mpctx);
}
print_status(mpctx, MP_NOPTS_VALUE, true);
}
else