From 6775487a46e42663cf96f4f34091d700f0c5889c Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 29 Apr 2014 15:19:03 +0200 Subject: video: move video frame queue from vo_vdpau.c to vo.c Remove the special casing of vo_vdpau vs. other VOs. Replace the complicated interaction between vo.c and vo_vdpau.c with a simple queue in vo.c. VOs other than vdpau are handled by setting the length of the queue to 1 (this is essentially what waiting_mpi was). Note that vo_vdpau.c seems to have buffered only 1 or 2 frames into the future, while the remaining 3 or 4 frames were past frames. So the new code buffers 2 frames (vo_vdpau.c requests this queue length by setting vo->max_video_queue to 2). It should probably be investigated why vo_vdpau.c kept so many past frames. The field vo->redrawing is removed. I'm not really sure what that would be needed for; it seems pointless. Future directions include making the interface between playloop and VO simpler, as well as making rendering a frame a single operation, as opposed to the weird 3-step sequence of rendering, drawing OSD, and flipping. --- video/out/vo.c | 95 +++++++++++++++++++++++++----------------- video/out/vo.h | 37 ++++++++++------- video/out/vo_vdpau.c | 115 +++++++++++---------------------------------------- 3 files changed, 102 insertions(+), 145 deletions(-) (limited to 'video') diff --git a/video/out/vo.c b/video/out/vo.c index 8556b6470c..6c0cb44c14 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -112,6 +112,8 @@ const struct vo_driver *video_out_drivers[] = NULL }; +static void forget_frames(struct vo *vo); + static bool get_desc(struct m_obj_desc *dst, int index) { if (index >= MP_ARRAY_SIZE(video_out_drivers) - 1) @@ -171,6 +173,7 @@ static struct vo *vo_create(struct mpv_global *global, .input_ctx = input_ctx, .event_fd = -1, .monitor_par = 1, + .max_video_queue = 1, .next_pts = MP_NOPTS_VALUE, .next_pts2 = MP_NOPTS_VALUE, }; @@ -228,7 +231,7 @@ void vo_destroy(struct vo *vo) { if (vo->event_fd != -1) mp_input_rm_key_fd(vo->input_ctx, vo->event_fd); - mp_image_unrefp(&vo->waiting_mpi); + forget_frames(vo); vo->driver->uninit(vo); talloc_free(vo); } @@ -345,9 +348,7 @@ int vo_reconfig(struct vo *vo, struct mp_image_params *params, int flags) talloc_free(vo->params); vo->params = NULL; } - vo->frame_loaded = false; - vo->waiting_mpi = NULL; - vo->redrawing = false; + forget_frames(vo); vo->hasframe = false; return ret; } @@ -357,19 +358,45 @@ int vo_control(struct vo *vo, uint32_t request, void *data) return vo->driver->control(vo, request, data); } +static void update_video_queue_state(struct vo *vo, bool eof) +{ + int num = vo->num_video_queue; + // Normally, buffer 1 image ahead, except if the queue is limited to less + // than 2 entries, or if EOF is reached and there aren't enough images left. + int min = 2; + if (vo->max_video_queue < 2 || (vo->num_video_queue < 2 && eof)) + min = 1; + vo->frame_loaded = num >= min; + if (!vo->frame_loaded) + num = -1; + vo->next_pts = num > 0 ? vo->video_queue[0]->pts : MP_NOPTS_VALUE; + vo->next_pts2 = num > 1 ? vo->video_queue[1]->pts : MP_NOPTS_VALUE; +} + +static void forget_frames(struct vo *vo) +{ + for (int n = 0; n < vo->num_video_queue; n++) + talloc_free(vo->video_queue[n]); + vo->num_video_queue = 0; + update_video_queue_state(vo, false); +} + void vo_queue_image(struct vo *vo, struct mp_image *mpi) { + assert(mpi); if (!vo->config_ok) return; - if (vo->driver->buffer_frames) { - vo->driver->draw_image(vo, mpi); + mpi = mp_image_new_ref(mpi); + if (vo->driver->filter_image) + mpi = vo->driver->filter_image(vo, mpi); + if (!mpi) { + MP_ERR(vo, "Could not upload image.\n"); return; } - vo->frame_loaded = true; - vo->next_pts = mpi->pts; - vo->next_pts2 = MP_NOPTS_VALUE; - assert(!vo->waiting_mpi); - vo->waiting_mpi = mp_image_new_ref(mpi); + assert(vo->max_video_queue <= VO_MAX_QUEUE); + assert(vo->num_video_queue < vo->max_video_queue); + vo->video_queue[vo->num_video_queue++] = mpi; + update_video_queue_state(vo, false); } int vo_redraw_frame(struct vo *vo) @@ -378,7 +405,6 @@ int vo_redraw_frame(struct vo *vo) return -1; if (vo_control(vo, VOCTRL_REDRAW_FRAME, NULL) == true) { vo->want_redraw = false; - vo->redrawing = true; return 0; } return -1; @@ -395,32 +421,33 @@ 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); + update_video_queue_state(vo, eof); return vo->frame_loaded ? 0 : -1; } +// Remove vo->video_queue[0] +static void shift_queue(struct vo *vo) +{ + if (!vo->num_video_queue) + return; + talloc_free(vo->video_queue[0]); + vo->num_video_queue--; + for (int n = 0; n < vo->num_video_queue; n++) + vo->video_queue[n] = vo->video_queue[n + 1]; +} + void vo_skip_frame(struct vo *vo) { - vo_control(vo, VOCTRL_SKIPFRAME, NULL); + shift_queue(vo); vo->frame_loaded = false; - mp_image_unrefp(&vo->waiting_mpi); } void vo_new_frame_imminent(struct vo *vo) { - if (vo->driver->buffer_frames) - vo_control(vo, VOCTRL_NEWFRAME, NULL); - else { - assert(vo->frame_loaded); - assert(vo->waiting_mpi); - assert(vo->waiting_mpi->pts == vo->next_pts); - vo->driver->draw_image(vo, vo->waiting_mpi); - mp_image_unrefp(&vo->waiting_mpi); - } + assert(vo->frame_loaded); + assert(vo->num_video_queue > 0); + vo->driver->draw_image(vo, vo->video_queue[0]); + shift_queue(vo); } void vo_draw_osd(struct vo *vo, struct osd_state *osd) @@ -433,18 +460,13 @@ void vo_flip_page(struct vo *vo, int64_t pts_us, int duration) { if (!vo->config_ok) return; - if (!vo->redrawing) { - vo->frame_loaded = false; - vo->next_pts = MP_NOPTS_VALUE; - vo->next_pts2 = MP_NOPTS_VALUE; - } vo->want_redraw = false; - vo->redrawing = false; if (vo->driver->flip_page_timed) vo->driver->flip_page_timed(vo, pts_us, duration); else vo->driver->flip_page(vo); vo->hasframe = true; + update_video_queue_state(vo, false); } void vo_check_events(struct vo *vo) @@ -457,11 +479,8 @@ void vo_check_events(struct vo *vo) void vo_seek_reset(struct vo *vo) { vo_control(vo, VOCTRL_RESET, NULL); - vo->frame_loaded = false; - vo->next_pts = MP_NOPTS_VALUE; - vo->next_pts2 = MP_NOPTS_VALUE; + forget_frames(vo); vo->hasframe = false; - mp_image_unrefp(&vo->waiting_mpi); } // Calculate the appropriate source and destination rectangle to diff --git a/video/out/vo.h b/video/out/vo.h index 26c90d184a..e3ab562839 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -54,8 +54,6 @@ enum mp_voctrl { /* for hardware decoding */ VOCTRL_GET_HWDEC_INFO, // struct mp_hwdec_info* - VOCTRL_NEWFRAME, - VOCTRL_SKIPFRAME, VOCTRL_REDRAW_FRAME, VOCTRL_ONTOP, @@ -135,16 +133,14 @@ struct voctrl_screenshot_args { // VO does handle mp_image_params.rotate in 90 degree steps #define VO_CAP_ROTATE90 1 +#define VO_MAX_QUEUE 5 + struct vo; struct osd_state; struct mp_image; struct mp_image_params; struct vo_driver { - // Driver buffers or adds (deinterlace) frames and will keep track - // of pts values itself - bool buffer_frames; - // Encoding functionality, which can be invoked via --o only. bool encode; @@ -166,6 +162,20 @@ struct vo_driver { */ int (*query_format)(struct vo *vo, uint32_t format); + /* + * Optional. Can be used to convert the input image into something VO + * internal, such as GPU surfaces. Ownership of mpi is passed to the + * function, and the returned image is owned by the caller. + * The following guarantees are given: + * - mpi has the format with which the VO was configured + * - the returned image can be arbitrary, and the VO merely manages its + * lifetime + * - images passed to draw_image are always passed through this function + * - the maximum number of images kept alive is not over vo->max_video_queue + * - if vo->max_video_queue is large enough, some images may be buffered ahead + */ + struct mp_image *(*filter_image)(struct vo *vo, struct mp_image *mpi); + /* * Initialize or reconfigure the display driver. * params: video parameters, like pixel format and frame size @@ -187,14 +197,6 @@ struct vo_driver { */ void (*draw_image)(struct vo *vo, struct mp_image *mpi); - /* - * 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); - /* * Draws OSD to the screen buffer */ @@ -236,15 +238,18 @@ struct vo { bool untimed; // non-interactive, don't do sleep calls in playloop bool frame_loaded; // Is there a next frame the VO could flip to? - struct mp_image *waiting_mpi; double next_pts; // pts value of the next frame if any double next_pts2; // optional pts of frame after that bool want_redraw; // visible frame wrong (window resize), needs refresh - bool redrawing; // between redrawing frame and flipping it bool hasframe; // >= 1 frame has been drawn, so redraw is possible double wakeup_period; // if > 0, this sets the maximum wakeup period for event polling double flip_queue_offset; // queue flip events at most this much in advance + int max_video_queue; // queue this many decoded video frames (<=VO_MAX_QUEUE) + + // Frames to display; the next (i.e. oldest, lowest PTS) image has index 0. + struct mp_image *video_queue[VO_MAX_QUEUE]; + int num_video_queue; const struct vo_driver *driver; void *priv; diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c index 9aa081f409..aae98f618d 100644 --- a/video/out/vo_vdpau.c +++ b/video/out/vo_vdpau.c @@ -86,11 +86,8 @@ struct vdpctx { VdpOutputSurface rgb_surfaces[NUM_BUFFERED_VIDEO]; bool rgb_surfaces_used[NUM_BUFFERED_VIDEO]; VdpOutputSurface black_pixel; - struct mp_image *buffered_video[NUM_BUFFERED_VIDEO]; - int buffer_pos; - // State for redrawing the screen after seek-reset - int prev_buffer_pos; + struct mp_image *current_image; int output_surface_width, output_surface_height; @@ -161,12 +158,9 @@ static int render_video_to_output_surface(struct vo *vo, struct vdp_functions *vdp = vc->vdp; VdpTime dummy; VdpStatus vdp_st; - int dp = vc->buffer_pos; + struct mp_image *mpi = vc->current_image; - // Redraw frame from before seek reset? - if (dp < 0) - dp = vc->prev_buffer_pos; - if (dp < 0) { + if (!mpi) { // At least clear the screen if there is nothing to render int flags = VDP_OUTPUT_SURFACE_RENDER_ROTATE_0; vdp_st = vdp->output_surface_render_output_surface(output_surface, @@ -176,8 +170,6 @@ static int render_video_to_output_surface(struct vo *vo, return -1; } - struct mp_image *mpi = vc->buffered_video[dp]; - vdp_st = vdp->presentation_queue_block_until_surface_idle(vc->flip_queue, output_surface, &dummy); @@ -230,73 +222,14 @@ static int video_to_output_surface(struct vo *vo) &vc->out_rect_vid, &vc->src_rect_vid); } -static int next_buffer_pos(struct vo *vo, bool eof) -{ - struct vdpctx *vc = vo->priv; - - int dqp = vc->buffer_pos; - if (dqp < 0) - dqp += 1000; - else - dqp -= 1; - if (dqp < (eof ? 0 : 1)) - return -1; - return dqp; -} - -static void set_next_frame_info(struct vo *vo, bool eof) -{ - struct vdpctx *vc = vo->priv; - - vo->frame_loaded = false; - int dqp = next_buffer_pos(vo, eof); - if (dqp < 0) - return; - vo->frame_loaded = true; - - // Set pts values - struct mp_image **bv = &vc->buffered_video[0]; - if (dqp == 0) { // no future frame/pts available - vo->next_pts = bv[0]->pts; - vo->next_pts2 = MP_NOPTS_VALUE; - } else { - vo->next_pts = bv[dqp]->pts; - vo->next_pts2 = bv[dqp - 1]->pts; - } -} - -static void add_new_video_surface(struct vo *vo, struct mp_image *mpi) -{ - struct vdpctx *vc = vo->priv; - struct mp_image **bv = vc->buffered_video; - - mp_image_unrefp(&bv[NUM_BUFFERED_VIDEO - 1]); - - for (int i = NUM_BUFFERED_VIDEO - 1; i > 0; i--) - bv[i] = bv[i - 1]; - bv[0] = mpi; - - vc->buffer_pos = FFMIN(vc->buffer_pos + 1, NUM_BUFFERED_VIDEO - 2); - set_next_frame_info(vo, false); -} - static void forget_frames(struct vo *vo, bool seek_reset) { struct vdpctx *vc = vo->priv; - if (seek_reset) { - if (vc->buffer_pos >= 0) - vc->prev_buffer_pos = vc->buffer_pos; - } else { - vc->prev_buffer_pos = -1001; - } + if (!seek_reset) + mp_image_unrefp(&vc->current_image); - vc->buffer_pos = -1001; vc->dropped_frame = false; - if (vc->prev_buffer_pos < 0) { - for (int i = 0; i < NUM_BUFFERED_VIDEO; i++) - mp_image_unrefp(&vc->buffered_video[i]); - } } static void resize(struct vo *vo) @@ -975,23 +908,29 @@ static struct mp_image *get_rgb_surface(struct vo *vo) return NULL; } -static void draw_image(struct vo *vo, mp_image_t *mpi) +static void draw_image(struct vo *vo, struct mp_image *mpi) +{ + struct vdpctx *vc = vo->priv; + + mp_image_setrefp(&vc->current_image, mpi); + + if (status_ok(vo)) + video_to_output_surface(vo); +} + +static struct mp_image *filter_image(struct vo *vo, struct mp_image *mpi) { struct vdpctx *vc = vo->priv; struct vdp_functions *vdp = vc->vdp; struct mp_image *reserved_mpi = NULL; VdpStatus vdp_st; - // Forget previous frames, as we can display a new one now. - vc->prev_buffer_pos = -1001; - mp_image_unrefp(&vc->buffered_video[NUM_BUFFERED_VIDEO - 1]); - if (vc->image_format == IMGFMT_VDPAU) { reserved_mpi = mp_image_new_ref(mpi); } else if (vc->rgb_mode) { reserved_mpi = get_rgb_surface(vo); if (!reserved_mpi) - return; + goto end; VdpOutputSurface rgb_surface = (uintptr_t)reserved_mpi->planes[0]; if (rgb_surface != VDP_INVALID_HANDLE) { vdp_st = vdp->output_surface_put_bits_native(rgb_surface, @@ -1005,7 +944,7 @@ static void draw_image(struct vo *vo, mp_image_t *mpi) reserved_mpi = mp_vdpau_get_video_surface(vc->mpvdp, vc->vdp_chroma_type, mpi->w, mpi->h); if (!reserved_mpi) - return; + goto end; VdpVideoSurface surface = (VdpVideoSurface)(intptr_t)reserved_mpi->planes[3]; if (handle_preemption(vo) >= 0) { const void *destdata[3] = {mpi->planes[0], mpi->planes[2], @@ -1020,9 +959,10 @@ static void draw_image(struct vo *vo, mp_image_t *mpi) } reserved_mpi->pts = mpi->pts; - add_new_video_surface(vo, reserved_mpi); - return; +end: + talloc_free(mpi); + return reserved_mpi; } // warning: the size and pixel format of surface must match that of the @@ -1175,6 +1115,8 @@ static int preinit(struct vo *vo) vc->video_eq.capabilities = MP_CSP_EQ_CAPS_COLORMATRIX; + vo->max_video_queue = 2; + return 0; } @@ -1248,14 +1190,6 @@ static int control(struct vo *vo, uint32_t request, void *data) } return true; } - case VOCTRL_NEWFRAME: - vc->buffer_pos = next_buffer_pos(vo, true); - if (status_ok(vo)) - video_to_output_surface(vo); - return true; - case VOCTRL_SKIPFRAME: - vc->buffer_pos = next_buffer_pos(vo, true); - return true; case VOCTRL_REDRAW_FRAME: if (status_ok(vo)) video_to_output_surface(vo); @@ -1293,7 +1227,6 @@ static int control(struct vo *vo, uint32_t request, void *data) #define OPT_BASE_STRUCT struct vdpctx const struct vo_driver video_out_vdpau = { - .buffer_frames = true, .description = "VDPAU with X11", .name = "vdpau", .preinit = preinit, @@ -1301,7 +1234,7 @@ const struct vo_driver video_out_vdpau = { .reconfig = reconfig, .control = control, .draw_image = draw_image, - .get_buffered_frame = set_next_frame_info, + .filter_image = filter_image, .draw_osd = draw_osd, .flip_page_timed = flip_page_timed, .uninit = uninit, -- cgit v1.2.3