diff --git a/libmpcodecs/vf.h b/libmpcodecs/vf.h index 99321b3afa..099135c5da 100644 --- a/libmpcodecs/vf.h +++ b/libmpcodecs/vf.h @@ -88,7 +88,6 @@ typedef struct vf_seteq_s #define VFCTRL_SCREENSHOT 14 /* Make a screenshot */ #define VFCTRL_INIT_EOSD 15 /* Select EOSD renderer */ #define VFCTRL_DRAW_EOSD 16 /* Render EOSD */ -#define VFCTRL_GET_PTS 17 /* Return last pts value that reached vf_vo*/ #define VFCTRL_SET_DEINTERLACE 18 /* Set deinterlacing status */ #define VFCTRL_GET_DEINTERLACE 19 /* Get deinterlacing status */ /* Hack to make the OSD state object available to vf_expand which accesses diff --git a/libmpcodecs/vf_vo.c b/libmpcodecs/vf_vo.c index 7b3e16224f..53dc8267b6 100644 --- a/libmpcodecs/vf_vo.c +++ b/libmpcodecs/vf_vo.c @@ -22,7 +22,6 @@ extern int sub_visibility; extern float sub_delay; struct vf_priv_s { - double pts; struct vo *vo; #ifdef CONFIG_ASS ASS_Renderer *ass_priv; @@ -127,7 +126,7 @@ static int control(struct vf_instance* vf, int request, void* data) case VFCTRL_DRAW_EOSD: { mp_eosd_images_t images = {NULL, 2}; - double pts = vf->priv->pts; + double pts = video_out->next_pts; if (!video_out->config_ok || !vf->priv->ass_priv) return CONTROL_FALSE; if (sub_visibility && vf->priv->ass_priv && ass_track && (pts != MP_NOPTS_VALUE)) { mp_eosd_res_t res; @@ -148,11 +147,6 @@ static int control(struct vf_instance* vf, int request, void* data) return vo_control(video_out, VOCTRL_DRAW_EOSD, &images) == VO_TRUE; } #endif - case VFCTRL_GET_PTS: - { - *(double *)data = vf->priv->pts; - return CONTROL_TRUE; - } } return CONTROL_UNKNOWN; } @@ -179,10 +173,9 @@ static void get_image(struct vf_instance* vf, static int put_image(struct vf_instance* vf, mp_image_t *mpi, double pts){ if(!video_out->config_ok) return 0; // vo not configured? - // record pts (potentially modified by filters) for main loop - vf->priv->pts = pts; // first check, maybe the vo/vf plugin implements draw_image using mpi: - if (vo_control(video_out, VOCTRL_DRAW_IMAGE,mpi)==VO_TRUE) return 1; // done. + if (vo_draw_image(video_out, mpi, pts) >= 0) + return 1; // nope, fallback to old draw_frame/draw_slice: if(!(mpi->flags&(MP_IMGFLAG_DIRECT|MP_IMGFLAG_DRAW_CALLBACK))){ // blit frame: diff --git a/libvo/video_out.c b/libvo/video_out.c index 9224212b35..b6506406c3 100644 --- a/libvo/video_out.c +++ b/libvo/video_out.c @@ -22,6 +22,7 @@ #include #include #include +#include #include //#include @@ -278,6 +279,35 @@ int vo_control(struct vo *vo, uint32_t request, void *data) return vo->driver->control(vo, request, data); } +// Return -1 if driver appears not to support a draw_image interface, +// 0 otherwise (whether the driver actually drew something or not). +int vo_draw_image(struct vo *vo, struct mp_image *mpi, double pts) +{ + if (!vo->config_ok) + return 0; + if (vo->driver->buffer_frames) { + vo->driver->draw_image(vo, mpi, pts); + return 0; + } + vo->frame_loaded = true; + vo->next_pts = pts; + if (vo_control(vo, VOCTRL_DRAW_IMAGE, mpi) == VO_NOTIMPL) + return -1; + return 0; +} + +int vo_get_buffered_frame(struct vo *vo, bool eof) +{ + if (!vo->config_ok) + return -1; + if (vo->frame_loaded) + return 0; + if (!vo->driver->buffer_frames) + return -1; + vo->driver->get_buffered_frame(vo, eof); + return vo->frame_loaded ? 0 : -1; +} + int vo_draw_frame(struct vo *vo, uint8_t *src[]) { assert(!vo->driver->is_new); @@ -302,6 +332,8 @@ void vo_flip_page(struct vo *vo) { if (!vo->config_ok) return; + vo->frame_loaded = false; + vo->next_pts = (-1LL<<63); // MP_NOPTS_VALUE vo->driver->flip_page(vo); } @@ -312,6 +344,14 @@ void vo_check_events(struct vo *vo) vo->driver->check_events(vo); } +void vo_seek_reset(struct vo *vo) +{ + if (!vo->config_ok) + return; + vo_control(vo, VOCTRL_RESET, NULL); + vo->frame_loaded = false; +} + void vo_destroy(struct vo *vo) { vo->driver->uninit(vo); diff --git a/libvo/video_out.h b/libvo/video_out.h index a911bfdd03..4ddeaa827e 100644 --- a/libvo/video_out.h +++ b/libvo/video_out.h @@ -129,10 +129,14 @@ typedef struct vo_info_s struct vo; struct osd_state; +struct mp_image; struct vo_driver { // Driver uses new API bool is_new; + // Driver buffers or adds (deinterlace) frames and will keep track + // of pts values itself + bool buffer_frames; // This is set if the driver is not new and contains pointers to // old-API functions to be used instead of the ones below. @@ -164,6 +168,16 @@ struct vo_driver { */ int (*control)(struct vo *vo, uint32_t request, void *data); + void (*draw_image)(struct vo *vo, struct mp_image *mpi, double pts); + + /* + * Get extra frames from the VO, such as those added by VDPAU + * deinterlace. Preparing the next such frame if any could be done + * automatically by the VO after a previous flip_page(), but having + * it as a separate step seems to allow making code more robust. + */ + void (*get_buffered_frame)(struct vo *vo, bool eof); + /* * Draw a planar YUV slice to the buffer: * params: @@ -214,6 +228,10 @@ struct vo_old_functions { struct vo { int config_ok; // Last config call was successful? int config_count; // Total number of successful config calls + + bool frame_loaded; // Is there a next frame the VO could flip to? + double next_pts; // pts value of the next frame if any + const struct vo_driver *driver; void *priv; struct MPOpts *opts; @@ -251,11 +269,14 @@ int vo_config(struct vo *vo, uint32_t width, uint32_t height, void list_video_out(void); int vo_control(struct vo *vo, uint32_t request, void *data); +int vo_draw_image(struct vo *vo, struct mp_image *mpi, double pts); +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_check_events(struct vo *vo); +void vo_seek_reset(struct vo *vo); void vo_destroy(struct vo *vo); diff --git a/libvo/vo_vdpau.c b/libvo/vo_vdpau.c index dcd2a7092e..bf760750cc 100644 --- a/libvo/vo_vdpau.c +++ b/libvo/vo_vdpau.c @@ -28,6 +28,7 @@ */ #include +#include #include #include #include @@ -110,14 +111,15 @@ struct vdpctx { #define osd_surface vc->output_surfaces[NUM_OUTPUT_SURFACES] VdpOutputSurface output_surfaces[NUM_OUTPUT_SURFACES + 1]; VdpVideoSurface deint_surfaces[3]; - mp_image_t *deint_mpi[2]; + double deint_pts[3]; + int deint_queue_pos; + mp_image_t *deint_mpi[3]; int output_surface_width, output_surface_height; VdpVideoMixer video_mixer; int deint; int deint_type; int deint_counter; - int deint_buffer_past_frames; int pullup; float denoise; float sharpen; @@ -133,7 +135,6 @@ struct vdpctx { struct vdpau_render_state surface_render[MAX_VIDEO_SURFACES]; int surface_num; - int vid_surface_num; uint32_t vid_width, vid_height; uint32_t image_format; VdpChromaType vdp_chroma_type; @@ -176,14 +177,6 @@ struct vdpctx { }; -static void push_deint_surface(struct vo *vo, VdpVideoSurface surface) -{ - struct vdpctx *vc = vo->priv; - vc->deint_surfaces[2] = vc->deint_surfaces[1]; - vc->deint_surfaces[1] = vc->deint_surfaces[0]; - vc->deint_surfaces[0] = surface; -} - static void flip_page(struct vo *vo); static void video_to_output_surface(struct vo *vo) { @@ -191,41 +184,81 @@ static void video_to_output_surface(struct vo *vo) struct vdp_functions *vdp = vc->vdp; VdpTime dummy; VdpStatus vdp_st; - int i; - if (vc->vid_surface_num < 0) + if (vc->deint_queue_pos < 0) return; - if (vc->deint < 2 || vc->deint_surfaces[0] == VDP_INVALID_HANDLE) - push_deint_surface(vo, vc->surface_render[vc->vid_surface_num].surface); + int field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME; + unsigned int dp = vc->deint_queue_pos; + // dp==0 means last field of latest frame, 1 earlier field of latest frame, + // 2 last field of previous frame and so on + if (vc->deint) { + field = vc->top_field_first ^ (dp & 1) ? + VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD: + VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD; + } + VdpVideoSurface *q = vc->deint_surfaces; + const VdpVideoSurface *past_fields = (const VdpVideoSurface []){ + q[(dp+1)/2], q[(dp+2)/2]}; + const VdpVideoSurface *future_fields = (const VdpVideoSurface []){ + q[(dp-1)/2]}; + VdpOutputSurface output_surface = vc->output_surfaces[vc->surface_num]; + vdp_st = vdp->presentation_queue_block_until_surface_idle(vc->flip_queue, + output_surface, + &dummy); + CHECK_ST_WARNING("Error when calling " + "vdp_presentation_queue_block_until_surface_idle"); - for (i = 0; i <= (vc->deint > 1); i++) { - int field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME; - VdpOutputSurface output_surface; - if (i) { - // draw_eosd() - // draw_osd() - flip_page(vo); - } - if (vc->deint) - field = (vc->top_field_first == i) ^ (vc->deint > 1) ? - VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD: - VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD; - output_surface = vc->output_surfaces[vc->surface_num]; - vdp_st = vdp-> - presentation_queue_block_until_surface_idle(vc->flip_queue, - output_surface, - &dummy); - CHECK_ST_WARNING("Error when calling " - "vdp_presentation_queue_block_until_surface_idle"); + vdp_st = vdp->video_mixer_render(vc->video_mixer, VDP_INVALID_HANDLE, + 0, field, 2, past_fields, + vc->deint_surfaces[dp/2], 1, future_fields, + &vc->src_rect_vid, output_surface, + NULL, &vc->out_rect_vid, 0, NULL); + CHECK_ST_WARNING("Error when calling vdp_video_mixer_render"); +} - vdp_st = vdp->video_mixer_render(vc->video_mixer, VDP_INVALID_HANDLE, - 0, field, 2, vc->deint_surfaces + 1, - vc->deint_surfaces[0], 1, - &vc->surface_render[vc->vid_surface_num].surface, - &vc->src_rect_vid, output_surface, - NULL, &vc->out_rect_vid, 0, NULL); - CHECK_ST_WARNING("Error when calling vdp_video_mixer_render"); - push_deint_surface(vo, vc->surface_render[vc->vid_surface_num].surface); +static void add_new_video_surface(struct vo *vo, VdpVideoSurface surface, + struct mp_image *reserved_mpi, double pts) +{ + struct vdpctx *vc = vo->priv; + + if (reserved_mpi) + reserved_mpi->usage_count++; + if (vc->deint_mpi[2]) + vc->deint_mpi[2]->usage_count--; + + for (int i = 2; i > 0; i--) { + vc->deint_mpi[i] = vc->deint_mpi[i - 1]; + vc->deint_surfaces[i] = vc->deint_surfaces[i - 1]; + vc->deint_pts[i] = vc->deint_pts[i - 1]; + } + vc->deint_mpi[0] = reserved_mpi; + vc->deint_surfaces[0] = surface; + vc->deint_pts[0] = pts; + + vo->frame_loaded = true; + vo->next_pts = pts; + if (vc->deint >= 2 && vc->deint_queue_pos >= 0) { + vc->deint_queue_pos = 2; + double diff = vc->deint_pts[0] - vc->deint_pts[1]; + if (diff > 0 && diff < 0.5) + vo->next_pts = (vc->deint_pts[0] + vc->deint_pts[1]) / 2; + else + vo->next_pts = vc->deint_pts[1]; + } else + vc->deint_queue_pos = 1; + video_to_output_surface(vo); +} + +static void forget_frames(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + vc->deint_queue_pos = -1; + for (int i = 0; i < 3; i++) { + vc->deint_surfaces[i] = VDP_INVALID_HANDLE; + if (vc->deint_mpi[i]) + vc->deint_mpi[i]->usage_count--; + vc->deint_mpi[i] = NULL; } } @@ -563,7 +596,7 @@ int initialize_vdpau_objects(struct vo *vo) &vc->eosd_surface.max_height); CHECK_ST_WARNING("Query to get max EOSD surface size failed"); vc->surface_num = 0; - vc->vid_surface_num = -1; + forget_frames(vo); resize(vo); return 0; } @@ -575,8 +608,7 @@ static void mark_vdpau_objects_uninitialized(struct vo *vo) vc->decoder = VDP_INVALID_HANDLE; for (int i = 0; i < MAX_VIDEO_SURFACES; i++) vc->surface_render[i].surface = VDP_INVALID_HANDLE; - for (int i = 0; i < 3; i++) - vc->deint_surfaces[i] = VDP_INVALID_HANDLE; + forget_frames(vo); vc->video_mixer = VDP_INVALID_HANDLE; vc->flip_queue = VDP_INVALID_HANDLE; vc->flip_target = VDP_INVALID_HANDLE; @@ -1033,10 +1065,6 @@ static void flip_page(struct vo *vo) if (handle_preemption(vo) < 0) return; - mp_msg(MSGT_VO, MSGL_DBG2, "\nFLIP_PAGE VID:%u -> OUT:%u\n", - vc->surface_render[vc->vid_surface_num].surface, - vc->output_surfaces[vc->surface_num]); - vdp_st = vdp->presentation_queue_display(vc->flip_queue, vc->output_surfaces[vc->surface_num], @@ -1095,27 +1123,26 @@ static struct vdpau_render_state *get_surface(struct vo *vo, int number) return &vc->surface_render[number]; } -static uint32_t draw_image(struct vo *vo, mp_image_t *mpi) +static void draw_image(struct vo *vo, mp_image_t *mpi, double pts) { struct vdpctx *vc = vo->priv; struct vdp_functions *vdp = vc->vdp; + struct mp_image *reserved_mpi = NULL; + struct vdpau_render_state *rndr; + + if (vc->is_preempted) { + vo->frame_loaded = true; + return; + } if (IMGFMT_IS_VDPAU(vc->image_format)) { - struct vdpau_render_state *rndr = mpi->priv; - vc->vid_surface_num = rndr - vc->surface_render; - if (vc->deint_buffer_past_frames) { - mpi->usage_count++; - if (vc->deint_mpi[1]) - vc->deint_mpi[1]->usage_count--; - vc->deint_mpi[1] = vc->deint_mpi[0]; - vc->deint_mpi[0] = mpi; - } + rndr = mpi->priv; + reserved_mpi = mpi; } else if (!(mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)) { VdpStatus vdp_st; void *destdata[3] = {mpi->planes[0], mpi->planes[2], mpi->planes[1]}; - struct vdpau_render_state *rndr = get_surface(vo, vc->deint_counter); + rndr = get_surface(vo, vc->deint_counter); vc->deint_counter = (vc->deint_counter + 1) % 3; - vc->vid_surface_num = rndr - vc->surface_render; if (vc->image_format == IMGFMT_NV12) destdata[1] = destdata[2]; vdp_st = @@ -1123,16 +1150,33 @@ static uint32_t draw_image(struct vo *vo, mp_image_t *mpi) vc->vdp_pixel_format, (const void *const*)destdata, mpi->stride); // pitch - CHECK_ST_ERROR("Error when calling " + CHECK_ST_WARNING("Error when calling " "vdp_video_surface_put_bits_y_cb_cr"); - } + } else + // We don't support slice callbacks so this shouldn't occur - + // I think the flags test above in pointless, but I'm adding + // this instead of removing it just in case. + abort(); if (mpi->fields & MP_IMGFIELD_ORDERED) vc->top_field_first = !!(mpi->fields & MP_IMGFIELD_TOP_FIRST); else vc->top_field_first = 1; + add_new_video_surface(vo, rndr->surface, mpi, pts); + + return; +} + +static void get_buffered_frame(struct vo *vo, bool eof) +{ + struct vdpctx *vc = vo->priv; + + if (vc->deint_queue_pos < 2) + return; + vc->deint_queue_pos = 1; video_to_output_surface(vo); - return VO_TRUE; + vo->next_pts = vc->deint_pts[0]; + vo->frame_loaded = true; } static uint32_t get_image(struct vo *vo, mp_image_t *mpi) @@ -1287,8 +1331,6 @@ static int preinit(struct vo *vo, const char *arg) } if (vc->deint) vc->deint_type = vc->deint; - if (vc->deint > 1) - vc->deint_buffer_past_frames = 1; char *vdpaulibrary = "libvdpau.so.1"; char *vdpau_device_create = "vdp_device_create_x11"; @@ -1408,7 +1450,6 @@ static int control(struct vo *vo, uint32_t request, void *data) 1, features, feature_enables); CHECK_ST_WARNING("Error changing deinterlacing settings"); - vc->deint_buffer_past_frames = 1; } return VO_TRUE; case VOCTRL_PAUSE: @@ -1420,9 +1461,7 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_GET_IMAGE: return get_image(vo, data); case VOCTRL_DRAW_IMAGE: - if (vc->is_preempted) - return true; - return draw_image(vo, data); + abort(); // draw_image() should get called directly case VOCTRL_BORDER: vo_x11_border(vo); resize(vo); @@ -1476,12 +1515,16 @@ static int control(struct vo *vo, uint32_t request, void *data) draw_osd(vo, data); flip_page(vo); return true; + case VOCTRL_RESET: + forget_frames(vo); + return true; } return VO_NOTIMPL; } const struct vo_driver video_out_vdpau = { - .is_new = 1, + .is_new = true, + .buffer_frames = true, .info = &(const struct vo_info_s){ "VDPAU with X11", "vdpau", @@ -1491,6 +1534,8 @@ const struct vo_driver video_out_vdpau = { .preinit = preinit, .config = config, .control = control, + .draw_image = draw_image, + .get_buffered_frame = get_buffered_frame, .draw_slice = draw_slice, .draw_osd = draw_osd, .flip_page = flip_page, diff --git a/mencoder.c b/mencoder.c index de862fac48..d1472a0568 100644 --- a/mencoder.c +++ b/mencoder.c @@ -204,6 +204,7 @@ int vo_config(struct vo *vo, uint32_t width, uint32_t height, uint32_t d_width, uint32_t d_height, uint32_t flags, char *title, uint32_t format) { abort(); } int vo_control(struct vo *vo, uint32_t request, void *data) { abort(); } +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(); } diff --git a/mp_core.h b/mp_core.h index 4092e49efb..a5c7e70c8a 100644 --- a/mp_core.h +++ b/mp_core.h @@ -103,10 +103,6 @@ typedef struct MPContext { struct demux_stream *d_sub; mixer_t mixer; struct vo *video_out; - // Frames buffered in the vo ready to flip. Currently always 0 or 1. - // This is really a vo variable but currently there's no suitable vo - // struct. - int num_buffered_frames; // Show a video frame as quickly as possible without trying to adjust // for AV sync. Used when starting a file or after seeking. diff --git a/mplayer.c b/mplayer.c index 7553621f9a..52731c7e4b 100644 --- a/mplayer.c +++ b/mplayer.c @@ -2240,6 +2240,7 @@ static double update_video_nocorrect_pts(struct MPContext *mpctx, static double update_video(struct MPContext *mpctx, int *blit_frame) { struct sh_video *sh_video = mpctx->sh_video; + struct vo *video_out = mpctx->video_out; *blit_frame = 0; sh_video->vfilter->control(sh_video->vfilter, VFCTRL_SET_OSD_OBJ, mpctx->osd); // hack for vf_expand @@ -2248,14 +2249,18 @@ static double update_video(struct MPContext *mpctx, int *blit_frame) double pts; - while (1) { + bool hit_eof = false; + while (!video_out->frame_loaded) { current_module = "filter_video"; + if (vo_get_buffered_frame(video_out, hit_eof) >= 0) + break; + if (hit_eof) + return -1; // XXX Time used in this call is not counted in any performance // timer now, OSD time is not updated correctly for filter-added frames if (vf_output_queued_frame(sh_video->vfilter)) break; unsigned char *packet = NULL; - bool hit_eof = false; int in_size = ds_get_packet_pts(mpctx->d_video, &packet, &pts); if (pts != MP_NOPTS_VALUE) pts += mpctx->video_offset; @@ -2278,12 +2283,12 @@ static double update_video(struct MPContext *mpctx, int *blit_frame) update_osd_msg(mpctx); current_module = "filter video"; if (filter_video(sh_video, decoded_frame, sh_video->pts)) - break; - } else if (hit_eof) - return -1; + if (!video_out->config_ok) + break; // We'd likely hang in this loop otherwise + } } - sh_video->vfilter->control(sh_video->vfilter, VFCTRL_GET_PTS, &pts); + pts = video_out->next_pts; if (pts == MP_NOPTS_VALUE) { mp_msg(MSGT_CPLAYER, MSGL_ERR, "Video pts after filters MISSING\n"); // Try to use decoder pts from before filters @@ -2539,11 +2544,9 @@ static int seek(MPContext *mpctx, double amount, int style) if (mpctx->sh_video) { current_module = "seek_video_reset"; resync_video_stream(mpctx->sh_video); - if (mpctx->video_out->config_ok) - vo_control(mpctx->video_out, VOCTRL_RESET, NULL); + vo_seek_reset(mpctx->video_out); mpctx->sh_video->num_buffered_pts = 0; mpctx->sh_video->last_pts = MP_NOPTS_VALUE; - mpctx->num_buffered_frames = 0; mpctx->delay = 0; mpctx->time_frame = 0; mpctx->update_video_immediately = true; @@ -3762,7 +3765,6 @@ if(verbose) term_osd = 0; int frame_time_remaining=0; // flag int blit_frame=0; -mpctx->num_buffered_frames=0; // Make sure old OSD does not stay around, // e.g. with -fixed-vo and same-resolution files @@ -3911,7 +3913,7 @@ if(!mpctx->sh_video) { vo_pts=mpctx->sh_video->timer*90000.0; vo_fps=mpctx->sh_video->fps; - if (!mpctx->num_buffered_frames) { + if (!mpctx->video_out->frame_loaded) { double frame_time = update_video(mpctx, &blit_frame); mp_dbg(MSGT_AVSYNC,MSGL_DBG2,"*** ftime=%5.3f ***\n",frame_time); if (mpctx->sh_video->vf_initialized < 0) { @@ -3928,8 +3930,6 @@ if(!mpctx->sh_video) { if (frame_time < 0) mpctx->stop_play = AT_END_OF_FILE; else { - // might return with !eof && !blit_frame if !correct_pts - mpctx->num_buffered_frames += blit_frame; if (mpctx->update_video_immediately) { // Show this frame immediately, rest normally mpctx->update_video_immediately = false; @@ -3981,7 +3981,6 @@ if(!mpctx->sh_video) { unsigned int t2=GetTimer(); vo_flip_page(mpctx->video_out); - mpctx->num_buffered_frames--; mpctx->last_vo_flip_duration = (GetTimer() - t2) * 0.000001; vout_time_usage += mpctx->last_vo_flip_duration;