diff --git a/player/video.c b/player/video.c index da93203b7a..87e6a8fb60 100644 --- a/player/video.c +++ b/player/video.c @@ -582,6 +582,17 @@ static void handle_new_frame(struct MPContext *mpctx) MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time); } +// 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]); + 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; +} + static int get_req_frames(struct MPContext *mpctx, bool eof) { struct MPOpts *opts = mpctx->opts; @@ -880,17 +891,16 @@ void write_video(struct MPContext *mpctx, double endpts) update_subtitles(mpctx); assert(mpctx->num_next_frames >= 1); - struct mp_image *frames[VO_MAX_FUTURE_FRAMES + 2] = {0}; - frames[0] = 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; - for (int n = 0; n < mpctx->num_next_frames && n < VO_MAX_FUTURE_FRAMES; n++) { - frames[n + 1] = mp_image_new_ref(mpctx->next_frames[n]); - if (!frames[n + 1]) - break; // OOM - } - vo_queue_frame(vo, frames, pts, duration); + struct vo_frame dummy = { + .pts = pts, + .duration = duration, + .num_frames = mpctx->num_next_frames, + }; + for (int n = 0; n < dummy.num_frames; n++) + dummy.frames[n] = mpctx->next_frames[n]; + vo_queue_frame(vo, vo_frame_ref(&dummy)); + + shift_frames(mpctx); // The frames were shifted down; "initialize" the new first entry. if (mpctx->num_next_frames >= 1) diff --git a/video/out/gl_video.c b/video/out/gl_video.c index 478cd2263b..3d867512f3 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -484,6 +484,7 @@ static void uninit_scaler(struct gl_video *p, struct scaler *scaler); static void check_gl_features(struct gl_video *p); static bool init_format(int fmt, struct gl_video *init); static void gl_video_upload_image(struct gl_video *p); +static void gl_video_set_image(struct gl_video *p, struct mp_image *mpi); #define GLSL(x) gl_sc_add(p->sc, #x "\n"); #define GLSLF(...) gl_sc_addf(p->sc, __VA_ARGS__) @@ -2063,8 +2064,8 @@ static void pass_draw_to_screen(struct gl_video *p, int fbo) } // Draws an interpolate frame to fbo, based on the frame timing in t -static void gl_video_interpolate_frame(struct gl_video *p, int fbo, - struct frame_timing *t) +static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t, + int fbo) { int vp_w = p->dst_rect.x1 - p->dst_rect.x0, vp_h = p->dst_rect.y1 - p->dst_rect.y0; @@ -2072,7 +2073,7 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo, // First of all, figure out if we have a frame availble at all, and draw // it manually + reset the queue if not if (p->surfaces[p->surface_now].pts == MP_NOPTS_VALUE) { - pass_render_frame(p, t->frame); + pass_render_frame(p, t->current); finish_pass_fbo(p, &p->surfaces[p->surface_now].fbotex, vp_w, vp_h, 0, FBOTEX_FUZZY); p->surfaces[p->surface_now].pts = p->image.mpi->pts; @@ -2080,11 +2081,11 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo, } // Find the right frame for this instant - if (t->frame && t->frame->pts != MP_NOPTS_VALUE) { + if (t->current&& t->current->pts != MP_NOPTS_VALUE) { int next = fbosurface_wrap(p->surface_now + 1); while (p->surfaces[next].pts != MP_NOPTS_VALUE && p->surfaces[next].pts > p->surfaces[p->surface_now].pts && - p->surfaces[p->surface_now].pts < t->frame->pts) + p->surfaces[p->surface_now].pts < t->current->pts) { p->surface_now = next; next = fbosurface_wrap(next + 1); @@ -2115,12 +2116,12 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo, // it only barely matters at the very beginning of playback, and this way // makes the code much more linear. int surface_dst = fbosurface_wrap(p->surface_idx+1); - for (int i = -1; i < t->num_future_frames; i++) { + for (int i = 0; i < t->num_frames; i++) { // Avoid overwriting data we might still need if (surface_dst == surface_bse - 1) break; - struct mp_image *f = i < 0 ? t->frame : t->future_frames[i]; + struct mp_image *f = t->frames[i]; if (!f || f->pts == MP_NOPTS_VALUE) continue; @@ -2202,15 +2203,16 @@ static void gl_video_interpolate_frame(struct gl_video *p, int fbo, } // (fbo==0 makes BindFramebuffer select the screen backbuffer) -void gl_video_render_frame(struct gl_video *p, struct mp_image *mpi, int fbo, - struct frame_timing *t) +void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo) { GL *gl = p->gl; struct video_image *vimg = &p->image; gl->BindFramebuffer(GL_FRAMEBUFFER, fbo); - if ((!mpi && !vimg->mpi) || p->dst_rect.x0 > 0 || p->dst_rect.y0 > 0 || + bool has_frame = frame->current || vimg->mpi; + + if (!has_frame || p->dst_rect.x0 > 0 || p->dst_rect.y0 > 0 || p->dst_rect.x1 < p->vp_w || p->dst_rect.y1 < abs(p->vp_h)) { struct m_color c = p->opts.background; @@ -2218,14 +2220,16 @@ void gl_video_render_frame(struct gl_video *p, struct mp_image *mpi, int fbo, gl->Clear(GL_COLOR_BUFFER_BIT); } - gl_sc_set_vao(p->sc, &p->vao); + if (has_frame) { + gl_sc_set_vao(p->sc, &p->vao); - if (p->opts.interpolation && t) { - gl_video_interpolate_frame(p, fbo, t); - } else { - // Skip interpolation if there's nothing to be done - pass_render_frame(p, mpi); - pass_draw_to_screen(p, fbo); + if (p->opts.interpolation && !frame->still) { + gl_video_interpolate_frame(p, frame, fbo); + } else { + // Skip interpolation if there's nothing to be done + pass_render_frame(p, frame->redraw ? NULL : frame->current); + pass_draw_to_screen(p, fbo); + } } debug_check_gl(p, "after video rendering"); @@ -2293,7 +2297,7 @@ static bool get_image(struct gl_video *p, struct mp_image *mpi) return true; } -void gl_video_set_image(struct gl_video *p, struct mp_image *mpi) +static void gl_video_set_image(struct gl_video *p, struct mp_image *mpi) { assert(mpi); diff --git a/video/out/gl_video.h b/video/out/gl_video.h index e3ce40b606..4e82215268 100644 --- a/video/out/gl_video.h +++ b/video/out/gl_video.h @@ -77,6 +77,7 @@ extern const struct gl_video_opts gl_video_opts_hq_def; extern const struct gl_video_opts gl_video_opts_def; struct gl_video; +struct vo_frame; struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g); void gl_video_uninit(struct gl_video *p); @@ -87,9 +88,7 @@ bool gl_video_check_format(struct gl_video *p, int mp_format); void gl_video_config(struct gl_video *p, struct mp_image_params *params); void gl_video_set_output_depth(struct gl_video *p, int r, int g, int b); void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d); -void gl_video_set_image(struct gl_video *p, struct mp_image *img); -void gl_video_render_frame(struct gl_video *p, struct mp_image *img, int fbo, - struct frame_timing *t); +void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo); void gl_video_resize(struct gl_video *p, int vp_w, int vp_h, struct mp_rect *src, struct mp_rect *dst, struct mp_osd_res *osd); diff --git a/video/out/vo.c b/video/out/vo.c index ae5eca7ba5..920fb00e6b 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -140,17 +140,13 @@ struct vo_internal { int64_t drop_count; bool dropped_frame; // the previous frame was dropped - struct mp_image *current_frame; // last frame queued to the VO + struct vo_frame *current_frame; // last frame queued to the VO int64_t wakeup_pts; // time at which to pull frame from decoder bool rendering; // true if an image is being rendered - struct mp_image *frame_queued; // the image that should be rendered - struct mp_image *future_frames[VO_MAX_FUTURE_FRAMES]; - int num_future_frames; - int req_future_frames; // VO's requested value of num_future_frames - int64_t frame_pts; // realtime of intended display - int64_t frame_duration; // realtime frame duration (for framedrop) + struct vo_frame *frame_queued; // should be drawn next + int req_frames; // VO's requested value of num_frames double display_fps; @@ -239,6 +235,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, talloc_steal(vo, log); *vo->in = (struct vo_internal) { .dispatch = mp_dispatch_create(vo), + .req_frames = 1, }; mp_make_wakeup_pipe(vo->in->wakeup_pipe); mp_dispatch_set_wakeup_fn(vo->in->dispatch, dispatch_wakeup_cb, vo); @@ -377,7 +374,8 @@ static void run_reconfig(void *p) } pthread_mutex_lock(&in->lock); - mp_image_unrefp(&in->current_frame); + talloc_free(in->current_frame); + in->current_frame = NULL; forget_frames(vo); pthread_mutex_unlock(&in->lock); @@ -412,23 +410,6 @@ int vo_control(struct vo *vo, uint32_t request, void *data) return ret; } -// must be called locked -// transfers ownership of frames[] items to the VO -static void set_future_frames(struct vo *vo, struct mp_image **frames) -{ - struct vo_internal *in = vo->in; - for (int n = 0; n < in->num_future_frames; n++) - talloc_free(in->future_frames[n]); - in->num_future_frames = 0; - for (int n = 0; frames && frames[n]; n++) { - if (n < in->req_future_frames) { - in->future_frames[in->num_future_frames++] = frames[n]; - } else { - talloc_free(frames[n]); - } - } -} - // must be called locked static void forget_frames(struct vo *vo) { @@ -436,8 +417,8 @@ static void forget_frames(struct vo *vo) in->hasframe = false; in->hasframe_rendered = false; in->drop_count = 0; - mp_image_unrefp(&in->frame_queued); - set_future_frames(vo, NULL); + talloc_free(in->frame_queued); + in->frame_queued = NULL; // don't unref current_frame; we always want to be able to redraw it } @@ -551,22 +532,15 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts) // Direct the VO thread to put the currently queued image on the screen. // vo_is_ready_for_frame() must have returned true before this call. -// images[0] is the frame to draw, images[n+1] are future frames (NULL -// terminated). Ownership of all the images is handed to the vo. -void vo_queue_frame(struct vo *vo, struct mp_image **images, - int64_t pts_us, int64_t duration) +// Ownership of frame is handed to the vo. +void vo_queue_frame(struct vo *vo, struct vo_frame *frame) { struct vo_internal *in = vo->in; pthread_mutex_lock(&in->lock); - struct mp_image *image = images[0]; - assert(image); assert(vo->config_ok && !in->frame_queued); in->hasframe = true; - in->frame_queued = image; - in->frame_pts = pts_us; - in->frame_duration = duration; - in->wakeup_pts = in->vsync_timed ? 0 : in->frame_pts + MPMAX(duration, 0); - set_future_frames(vo, images + 1); + in->frame_queued = frame; + in->wakeup_pts = in->vsync_timed ? 0 : frame->pts + MPMAX(frame->duration, 0); wakeup_locked(vo); pthread_mutex_unlock(&in->lock); } @@ -613,6 +587,8 @@ static int64_t prev_sync(struct vo *vo, int64_t ts) static bool render_frame(struct vo *vo) { struct vo_internal *in = vo->in; + struct vo_frame *frame = NULL; + bool got_frame = false; update_display_fps(vo); @@ -621,32 +597,35 @@ static bool render_frame(struct vo *vo) vo->in->vsync_interval = in->display_fps > 0 ? 1e6 / in->display_fps : 0; vo->in->vsync_interval = MPMAX(vo->in->vsync_interval, 1); - int64_t pts = in->frame_pts; - int64_t duration = in->frame_duration; - struct mp_image *img = in->frame_queued; + if (in->frame_queued) { + talloc_free(in->current_frame); + in->current_frame = in->frame_queued; + in->frame_queued = NULL; + } else if (in->paused || !in->current_frame || !in->hasframe || + !in->vsync_timed) + { + goto done; + } - if (!img && (!in->vsync_timed || in->paused)) - goto nothing_done; + frame = vo_frame_ref(in->current_frame); + assert(frame); - if (in->vsync_timed && !in->hasframe) - goto nothing_done; - - if (img) - mp_image_setrefp(&in->current_frame, img); - - in->frame_queued = NULL; + int64_t pts = frame->pts; + int64_t duration = frame->duration; + int64_t end_time = pts + duration; // The next time a flip (probably) happens. int64_t prev_vsync = prev_sync(vo, mp_time_us()); int64_t next_vsync = prev_vsync + in->vsync_interval; - int64_t end_time = pts + duration; + + frame->next_vsync = next_vsync; + frame->prev_vsync = prev_vsync; + + frame->vsync_offset = next_vsync - pts; // Time at which we should flip_page on the VO. int64_t target = pts - in->flip_queue_offset; - if (!in->hasframe_rendered) - duration = -1; // disable framedrop - bool prev_dropped_frame = in->dropped_frame; // "normal" strict drop threshold. @@ -675,6 +654,7 @@ static bool render_frame(struct vo *vo) // Even if we're hopelessly behind, rather degrade to 10 FPS playback, // instead of just freezing the display forever. in->dropped_frame &= mp_time_us() - in->last_flip < 100 * 1000; + in->dropped_frame &= in->hasframe_rendered; if (in->vsync_timed) { // this is a heuristic that wakes the thread up some @@ -684,55 +664,37 @@ static bool render_frame(struct vo *vo) // We are very late with the frame and using vsync timing: probably // no new frames are coming in. This must be done whether or not // framedrop is enabled. Also, if the frame is to be dropped, even - // though it's an interpolated frame (img==NULL), exit early. - if (!img && ((in->hasframe_rendered && - prev_vsync > pts + duration + in->vsync_interval_approx) - || in->dropped_frame)) + // though it's an interpolated frame (repeat set), exit early. + bool late = prev_vsync > pts + duration + in->vsync_interval_approx; + if (frame->repeat && ((in->hasframe_rendered && late) || in->dropped_frame)) { in->dropped_frame = false; - goto nothing_done; + goto done; } } - if (in->dropped_frame) { - talloc_free(img); - } else { + // Setup parameters for the next time this frame is drawn. ("frame" is the + // frame currently drawn, while in->current_frame is the potentially next.) + in->current_frame->repeat = true; + + if (!in->dropped_frame) { in->rendering = true; in->hasframe_rendered = true; - int num_future_frames = in->num_future_frames; - in->num_future_frames = 0; - struct mp_image *future_frames[VO_MAX_FUTURE_FRAMES]; - for (int n = 0; n < num_future_frames; n++) { - future_frames[n] = in->future_frames[n]; - in->future_frames[n] = NULL; - } + int64_t prev_drop_count = vo->in->drop_count; pthread_mutex_unlock(&in->lock); mp_input_wakeup(vo->input_ctx); // core can queue new video now MP_STATS(vo, "start video"); - if (vo->driver->draw_image_timed) { - struct frame_timing t = (struct frame_timing) { - .pts = pts, - .next_vsync = next_vsync, - .prev_vsync = prev_vsync, - .vsync_offset = next_vsync - pts, - .frame = img, - .num_future_frames = num_future_frames, - .future_frames = future_frames, - }; - vo->driver->draw_image_timed(vo, img, &t); + if (vo->driver->draw_frame) { + vo->driver->draw_frame(vo, frame); } else { - vo->driver->draw_image(vo, img); + vo->driver->draw_image(vo, mp_image_new_ref(frame->current)); } wait_until(vo, target); - bool drop = false; - if (vo->driver->flip_page_timed) - drop = vo->driver->flip_page_timed(vo, pts, duration) < 1; - else - vo->driver->flip_page(vo); + vo->driver->flip_page(vo); int64_t prev_flip = in->last_flip; @@ -748,10 +710,8 @@ static bool render_frame(struct vo *vo) MP_STATS(vo, "end video"); pthread_mutex_lock(&in->lock); - in->dropped_frame = drop; + in->dropped_frame = prev_drop_count < vo->in->drop_count; in->rendering = false; - for (int n = 0; n < num_future_frames; n++) - talloc_free(future_frames[n]); } if (in->dropped_frame) { @@ -765,12 +725,12 @@ static bool render_frame(struct vo *vo) pthread_cond_broadcast(&in->wakeup); // for vo_wait_frame() mp_input_wakeup(vo->input_ctx); - pthread_mutex_unlock(&in->lock); - return true; + got_frame = true; -nothing_done: +done: + talloc_free(frame); pthread_mutex_unlock(&in->lock); - return false; + return got_frame; } static void do_redraw(struct vo *vo) @@ -779,28 +739,37 @@ static void do_redraw(struct vo *vo) vo->want_redraw = false; + if (!vo->config_ok) + return; + pthread_mutex_lock(&in->lock); in->request_redraw = false; in->want_redraw = false; bool full_redraw = in->dropped_frame; - struct mp_image *img = NULL; - if (vo->config_ok && !(vo->driver->untimed)) - img = mp_image_new_ref(in->current_frame); - if (img) + struct vo_frame *frame = NULL; + if (!vo->driver->untimed) + frame = vo_frame_ref(in->current_frame); + if (frame) in->dropped_frame = false; + struct vo_frame dummy = {0}; + if (!frame) + frame = &dummy; + frame->redraw = !full_redraw; // unconditionally redraw if it was dropped + frame->still = true; pthread_mutex_unlock(&in->lock); - if (full_redraw || vo->driver->control(vo, VOCTRL_REDRAW_FRAME, NULL) < 1) { - if (img) - vo->driver->draw_image(vo, img); - } else { - talloc_free(img); + if (vo->driver->draw_frame) { + vo->driver->draw_frame(vo, frame); + } else if ((!full_redraw || vo->driver->control(vo, VOCTRL_REDRAW_FRAME, NULL) < 1) + && frame->current) + { + vo->driver->draw_image(vo, mp_image_new_ref(frame->current)); } - if (vo->driver->flip_page_timed) - vo->driver->flip_page_timed(vo, 0, -1); - else - vo->driver->flip_page(vo); + vo->driver->flip_page(vo); + + if (frame != &dummy) + talloc_free(frame); } static void *vo_thread(void *ptr) @@ -851,9 +820,10 @@ static void *vo_thread(void *ptr) } wait_vo(vo, wait_until); } - forget_frames(vo); // implicitly synchronized - mp_image_unrefp(&in->current_frame); vo->driver->uninit(vo); + forget_frames(vo); // implicitly synchronized + talloc_free(in->current_frame); + in->current_frame = NULL; return NULL; } @@ -922,7 +892,9 @@ bool vo_still_displaying(struct vo *vo) struct vo_internal *in = vo->in; pthread_mutex_lock(&vo->in->lock); int64_t now = mp_time_us(); - int64_t frame_end = in->frame_pts + MPMAX(in->frame_duration, 0); + int64_t frame_end = 0; + if (in->current_frame) + frame_end = in->current_frame->pts + MPMAX(in->current_frame->duration, 0); bool working = now < frame_end || in->rendering || in->frame_queued; pthread_mutex_unlock(&vo->in->lock); return working && in->hasframe; @@ -995,7 +967,7 @@ void vo_set_queue_params(struct vo *vo, int64_t offset_us, bool vsync_timed, pthread_mutex_lock(&in->lock); in->flip_queue_offset = offset_us; in->vsync_timed = vsync_timed; - in->req_future_frames = MPMIN(num_future_frames, VO_MAX_FUTURE_FRAMES); + in->req_frames = 1 + MPMIN(num_future_frames, VO_MAX_FUTURE_FRAMES); pthread_mutex_unlock(&in->lock); } @@ -1003,7 +975,7 @@ int vo_get_num_future_frames(struct vo *vo) { struct vo_internal *in = vo->in; pthread_mutex_lock(&in->lock); - int res = in->req_future_frames; + int res = in->req_frames + 1; pthread_mutex_unlock(&in->lock); return res; } @@ -1058,11 +1030,40 @@ struct mp_image *vo_get_current_frame(struct vo *vo) { struct vo_internal *in = vo->in; pthread_mutex_lock(&in->lock); - struct mp_image *r = mp_image_new_ref(vo->in->current_frame); + struct mp_image *r = NULL; + if (vo->in->current_frame) + r = mp_image_new_ref(vo->in->current_frame->current); pthread_mutex_unlock(&in->lock); return r; } +static void destroy_frame(void *p) +{ + struct vo_frame *frame = p; + for (int n = 0; n < frame->num_frames; n++) + talloc_free(frame->frames[n]); +} + +// Return a new reference to the given frame. The image pointers are also new +// references. Calling talloc_free() on the frame unrefs all currently set +// image references. (Assuming current==frames[0].) +struct vo_frame *vo_frame_ref(struct vo_frame *frame) +{ + if (!frame) + return NULL; + + struct vo_frame *new = talloc_ptrtype(NULL, new); + talloc_set_destructor(new, destroy_frame); + *new = *frame; + for (int n = 0; n < frame->num_frames; n++) { + new->frames[n] = mp_image_new_ref(frame->frames[n]); + if (!new->frames[n]) + abort(); // OOM on tiny allocs + } + new->current = new->num_frames ? new->frames[0] : NULL; + return new; +} + /* * lookup an integer in a table, table must have 0 as the last key * param: key key to search for diff --git a/video/out/vo.h b/video/out/vo.h index 3d36269d26..9e1a6db93c 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -154,29 +154,38 @@ struct vo_extra { struct mpv_opengl_cb_context *opengl_cb_context; }; -struct frame_timing { +struct vo_frame { // If > 0, realtime when frame should be shown, in mp_time_us() units. + // If 0, present immediately. int64_t pts; + // Approximate frame duration, in us. + int duration; // Realtime of estimated previous and next vsync events. int64_t next_vsync; int64_t prev_vsync; // "ideal" display time within the vsync int64_t vsync_offset; - // The current frame to be drawn. NULL means redraw previous frame - // (e.g. repeated frames). - // (Equivalent to the mp_image parameter of draw_image_timed, until the - // parameter is removed.) - struct mp_image *frame; + // Set if the current frame is repeated from the previous. It's guaranteed + // that the current is the same as the previous one, even if the image + // pointer is different. + // The repeat flag is additionally set if the OSD does not need to be + // redrawn. + bool redraw, repeat; + // The frame is not in movement - e.g. redrawing while paused. + bool still; + // The current frame to be drawn. + // Warning: When OSD should be redrawn in --force-window --idle mode, this + // can be NULL. The VO should draw a black background, OSD on top. + struct mp_image *current; // List of future images, starting with the next one. This does not // care about repeated frames - it simply contains the next real frames. - // vo_set_queue_params() sets how many frames this should include, though - // the actual number can be lower. - // future_frames[0] is the next frame. - // Note that this has frames only when a new real frame is pushed. Redraw - // calls or repeated frames do not include this. - // Ownership of the frames belongs to the caller. - int num_future_frames; - struct mp_image **future_frames; + // vo_set_queue_params() sets how many future frames this should include. + // The actual number of frames delivered to the VO can be lower. + // frames[0] is current, frames[1] is the next frame. + // Note that some future frames may never be sent as current frame to the + // VO if frames are dropped. + int num_frames; + struct mp_image *frames[VO_MAX_FUTURE_FRAMES + 1]; }; struct vo_driver { @@ -223,31 +232,21 @@ struct vo_driver { * mpi belongs to the VO; the VO must free it eventually. * * This also should draw the OSD. + * + * Deprecated for draw_frame. A VO should have only either callback set. */ void (*draw_image)(struct vo *vo, struct mp_image *mpi); - /* Like draw image, but is called before every vsync with timing - * information + /* Render the given frame. Note that this is also called when repeating + * or redrawing frames. */ - void (*draw_image_timed)(struct vo *vo, struct mp_image *mpi, - struct frame_timing *t); + void (*draw_frame)(struct vo *vo, struct vo_frame *frame); /* * Blit/Flip buffer to the screen. Must be called after each frame! */ void (*flip_page)(struct vo *vo); - /* - * Timed version of flip_page (optional). - * pts_us is the frame presentation time, linked to mp_time_us(). - * pts_us is 0 if the frame should be presented immediately. - * duration is estimated time in us until the next frame is shown. - * duration is -1 if it is unknown or unset (also: disable framedrop). - * If the VO does manual framedropping, VO_CAP_FRAMEDROP should be set. - * Returns 1 on display, or 0 if the frame was dropped. - */ - int (*flip_page_timed)(struct vo *vo, int64_t pts_us, int duration); - /* These optional callbacks can be provided if the GUI framework used by * the VO requires entering a message loop for receiving events, does not * provide event_fd, and does not call vo_wakeup() from a separate thread @@ -323,8 +322,7 @@ int vo_reconfig(struct vo *vo, struct mp_image_params *p, int flags); int vo_control(struct vo *vo, uint32_t request, void *data); bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts); -void vo_queue_frame(struct vo *vo, struct mp_image **images, - int64_t pts_us, int64_t duration); +void vo_queue_frame(struct vo *vo, struct vo_frame *frame); void vo_wait_frame(struct vo *vo); bool vo_still_displaying(struct vo *vo); bool vo_has_frame(struct vo *vo); @@ -359,4 +357,6 @@ struct mp_osd_res; void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src, struct mp_rect *out_dst, struct mp_osd_res *out_osd); +struct vo_frame *vo_frame_ref(struct vo_frame *frame); + #endif /* MPLAYER_VIDEO_OUT_H */ diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index 2d3ab5ba93..043ddaa836 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -164,8 +164,7 @@ static void flip_page(struct vo *vo) } } -static void draw_image_timed(struct vo *vo, mp_image_t *mpi, - struct frame_timing *t) +static void draw_frame(struct vo *vo, struct vo_frame *frame) { struct gl_priv *p = vo->priv; GL *gl = p->gl; @@ -174,7 +173,7 @@ static void draw_image_timed(struct vo *vo, mp_image_t *mpi, return; p->frame_started = true; - gl_video_render_frame(p->renderer, mpi, 0, t); + gl_video_render_frame(p->renderer, frame, 0); // The playloop calls this last before waiting some time until it decides // to call flip_page(). Tell OpenGL to start execution of the GPU commands @@ -183,13 +182,6 @@ static void draw_image_timed(struct vo *vo, mp_image_t *mpi, if (p->use_glFinish) gl->Finish(); - - talloc_free(mpi); -} - -static void draw_image(struct vo *vo, mp_image_t *mpi) -{ - draw_image_timed(vo, mpi, NULL); } static int query_format(struct vo *vo, int format) @@ -355,12 +347,6 @@ static int control(struct vo *vo, uint32_t request, void *data) case VOCTRL_LOAD_HWDEC_API: request_hwdec_api(p, data); return true; - case VOCTRL_REDRAW_FRAME: - if (!(p->glctx->start_frame && !p->glctx->start_frame(p->glctx))) { - p->frame_started = true; - gl_video_render_frame(p->renderer, NULL, 0, NULL); - } - return true; case VOCTRL_SET_COMMAND_LINE: { char *arg = data; return reparse_cmdline(p, arg); @@ -489,8 +475,7 @@ const struct vo_driver video_out_opengl = { .query_format = query_format, .reconfig = reconfig, .control = control, - .draw_image = draw_image, - .draw_image_timed = draw_image_timed, + .draw_frame = draw_frame, .flip_page = flip_page, .uninit = uninit, .priv_size = sizeof(struct gl_priv), @@ -505,8 +490,7 @@ const struct vo_driver video_out_opengl_hq = { .query_format = query_format, .reconfig = reconfig, .control = control, - .draw_image = draw_image, - .draw_image_timed = draw_image_timed, + .draw_frame = draw_frame, .flip_page = flip_page, .uninit = uninit, .priv_size = sizeof(struct gl_priv), diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c index 96a312f98c..c1a8748f4e 100644 --- a/video/out/vo_opengl_cb.c +++ b/video/out/vo_opengl_cb.c @@ -64,9 +64,10 @@ struct mpv_opengl_cb_context { bool initialized; mpv_opengl_cb_update_fn update_cb; void *update_cb_ctx; - struct mp_image *waiting_frame; - struct mp_image **frame_queue; + struct vo_frame *waiting_frame; + struct vo_frame **frame_queue; int queued_frames; + struct vo_frame *cur_frame; struct mp_image_params img_params; bool reconfigured; int vp_w, vp_h; @@ -81,7 +82,6 @@ struct mpv_opengl_cb_context { struct mp_csp_equalizer eq; int64_t recent_flip; int64_t approx_vsync; - int64_t cur_pts; bool vsync_timed; // --- All of these can only be accessed from the thread where the host @@ -103,11 +103,11 @@ static void update(struct vo_priv *p); // all queue manipulation functions shold be called under locked state -static struct mp_image *frame_queue_pop(struct mpv_opengl_cb_context *ctx) +static struct vo_frame *frame_queue_pop(struct mpv_opengl_cb_context *ctx) { if (ctx->queued_frames == 0) return NULL; - struct mp_image *ret = ctx->frame_queue[0]; + struct vo_frame *ret = ctx->frame_queue[0]; MP_TARRAY_REMOVE_AT(ctx->frame_queue, ctx->queued_frames, 0); pthread_cond_broadcast(&ctx->wakeup); return ret; @@ -115,9 +115,9 @@ static struct mp_image *frame_queue_pop(struct mpv_opengl_cb_context *ctx) static void frame_queue_drop(struct mpv_opengl_cb_context *ctx) { - struct mp_image *mpi = frame_queue_pop(ctx); - if (mpi) { - talloc_free(mpi); + struct vo_frame *frame = frame_queue_pop(ctx); + if (frame) { + talloc_free(frame); if (ctx->active) vo_increment_drop_count(ctx->active, 1); pthread_cond_broadcast(&ctx->wakeup); @@ -143,9 +143,10 @@ static void frame_queue_drop_all(struct mpv_opengl_cb_context *ctx) pthread_cond_broadcast(&ctx->wakeup); } -static void frame_queue_push(struct mpv_opengl_cb_context *ctx, struct mp_image *mpi) +static void frame_queue_push(struct mpv_opengl_cb_context *ctx, + struct vo_frame *frame) { - MP_TARRAY_APPEND(ctx, ctx->frame_queue, ctx->queued_frames, mpi); + MP_TARRAY_APPEND(ctx, ctx->frame_queue, ctx->queued_frames, frame); pthread_cond_broadcast(&ctx->wakeup); } @@ -156,11 +157,16 @@ static void frame_queue_shrink(struct mpv_opengl_cb_context *ctx, int size) frame_queue_drop(ctx); } -static void forget_frames(struct mpv_opengl_cb_context *ctx) +static void forget_frames(struct mpv_opengl_cb_context *ctx, bool all) { pthread_cond_broadcast(&ctx->wakeup); frame_queue_clear(ctx); - mp_image_unrefp(&ctx->waiting_frame); + talloc_free(ctx->waiting_frame); + ctx->waiting_frame = NULL; + if (all) { + talloc_free(ctx->cur_frame); + ctx->cur_frame = NULL; + } } static void free_ctx(void *ptr) @@ -261,7 +267,7 @@ int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx) // context. Setting initialized=false guarantees it can't come back. pthread_mutex_lock(&ctx->lock); - forget_frames(ctx); + forget_frames(ctx, true); ctx->initialized = false; pthread_mutex_unlock(&ctx->lock); @@ -352,30 +358,31 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h) ctx->eq_changed = false; ctx->eq = *eq; - struct mp_image *mpi = frame_queue_pop(ctx); - if (mpi) { - struct frame_timing *t = mpi->priv; // set by draw_image_timed - if (t) - ctx->cur_pts = t->pts; + struct vo_frame *frame = frame_queue_pop(ctx); + if (frame) { + talloc_free(ctx->cur_frame); + ctx->cur_frame = vo_frame_ref(frame); + } else { + frame = vo_frame_ref(ctx->cur_frame); } + struct vo_frame dummy = {0}; + if (!frame) + frame = &dummy; - struct frame_timing timing = { - .pts = ctx->cur_pts, - }; if (ctx->approx_vsync > 0) { - timing.prev_vsync = prev_sync(ctx, mp_time_us()); - timing.next_vsync = timing.prev_vsync + ctx->approx_vsync; + frame->prev_vsync = prev_sync(ctx, mp_time_us()); + frame->next_vsync = frame->prev_vsync + ctx->approx_vsync; } pthread_mutex_unlock(&ctx->lock); - if (mpi) - gl_video_set_image(ctx->renderer, mpi); - - gl_video_render_frame(ctx->renderer, mpi, fbo, timing.pts ? &timing : NULL); + gl_video_render_frame(ctx->renderer, frame, fbo); gl_video_unset_gl_state(ctx->renderer); + if (frame != &dummy) + talloc_free(frame); + pthread_mutex_lock(&ctx->lock); const int left = ctx->queued_frames; if (vo && (left > 0 || ctx->vsync_timed)) @@ -397,27 +404,6 @@ int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time) return 0; } -static void draw_image_timed(struct vo *vo, mp_image_t *mpi, - struct frame_timing *t) -{ - struct vo_priv *p = vo->priv; - - pthread_mutex_lock(&p->ctx->lock); - mp_image_setrefp(&p->ctx->waiting_frame, mpi); - if (p->ctx->waiting_frame) { - p->ctx->waiting_frame->priv = - t ? talloc_memdup(p->ctx->waiting_frame, t, sizeof(*t)) - : NULL; - } - talloc_free(mpi); - pthread_mutex_unlock(&p->ctx->lock); -} - -static void draw_image(struct vo *vo, mp_image_t *mpi) -{ - draw_image_timed(vo, mpi, NULL); -} - // Called locked. static void update(struct vo_priv *p) { @@ -425,6 +411,16 @@ static void update(struct vo_priv *p) p->ctx->update_cb(p->ctx->update_cb_ctx); } +static void draw_frame(struct vo *vo, struct vo_frame *frame) +{ + struct vo_priv *p = vo->priv; + + pthread_mutex_lock(&p->ctx->lock); + talloc_free(p->ctx->waiting_frame); + p->ctx->waiting_frame = vo_frame_ref(frame); + pthread_mutex_unlock(&p->ctx->lock); +} + static void flip_page(struct vo *vo) { struct vo_priv *p = vo->priv; @@ -468,7 +464,7 @@ static int reconfig(struct vo *vo, struct mp_image_params *params, int flags) struct vo_priv *p = vo->priv; pthread_mutex_lock(&p->ctx->lock); - forget_frames(p->ctx); + forget_frames(p->ctx, true); p->ctx->img_params = *params; p->ctx->reconfigured = true; pthread_mutex_unlock(&p->ctx->lock); @@ -541,11 +537,6 @@ static int control(struct vo *vo, uint32_t request, void *data) pthread_mutex_unlock(&p->ctx->lock); return r ? VO_TRUE : VO_NOTIMPL; } - case VOCTRL_REDRAW_FRAME: - pthread_mutex_lock(&p->ctx->lock); - update(p); - pthread_mutex_unlock(&p->ctx->lock); - return VO_TRUE; case VOCTRL_SET_PANSCAN: pthread_mutex_lock(&p->ctx->lock); copy_vo_opts(vo); @@ -582,7 +573,7 @@ static void uninit(struct vo *vo) struct vo_priv *p = vo->priv; pthread_mutex_lock(&p->ctx->lock); - forget_frames(p->ctx); + forget_frames(p->ctx, true); p->ctx->img_params = (struct mp_image_params){0}; p->ctx->reconfigured = true; p->ctx->active = NULL; @@ -635,8 +626,7 @@ const struct vo_driver video_out_opengl_cb = { .query_format = query_format, .reconfig = reconfig, .control = control, - .draw_image = draw_image, - .draw_image_timed = draw_image_timed, + .draw_frame = draw_frame, .flip_page = flip_page, .uninit = uninit, .priv_size = sizeof(struct vo_priv), diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c index f326a62262..bcee3daba2 100644 --- a/video/out/vo_vdpau.c +++ b/video/out/vo_vdpau.c @@ -83,6 +83,8 @@ struct vdpctx { VdpOutputSurface black_pixel; struct mp_image *current_image; + int64_t current_pts; + int current_duration; int output_surface_w, output_surface_h; @@ -701,16 +703,19 @@ static inline uint64_t prev_vsync(struct vdpctx *vc, uint64_t ts) return ts - offset; } -static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration) +static void flip_page(struct vo *vo) { struct vdpctx *vc = vo->priv; struct vdp_functions *vdp = vc->vdp; VdpStatus vdp_st; + int64_t pts_us = vc->current_pts; + int duration = vc->current_duration; + vc->dropped_frame = true; // changed at end if false if (!check_preemption(vo)) - return 0; + goto drop; vc->vsync_interval = 1; if (vc->user_fps > 0) { @@ -795,7 +800,7 @@ static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration) pts = FFMAX(pts, vc->last_queue_time + vc->vsync_interval); pts = FFMAX(pts, now); if (npts < PREV_VSYNC(pts) + vc->vsync_interval) - return 0; + goto drop; int num_flips = update_presentation_queue_status(vo); vsync = vc->recent_vsync_time + num_flips * vc->vsync_interval; @@ -803,7 +808,7 @@ static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration) pts = FFMAX(pts, vsync + (vc->vsync_interval >> 2)); vsync = PREV_VSYNC(pts); if (npts < vsync + vc->vsync_interval) - return 0; + goto drop; pts = vsync + (vc->vsync_interval >> 2); VdpOutputSurface frame = vc->output_surfaces[vc->surface_num]; vdp_st = vdp->presentation_queue_display(vc->flip_queue, frame, @@ -818,22 +823,30 @@ static int flip_page_timed(struct vo *vo, int64_t pts_us, int duration) vc->last_ideal_time = ideal_pts; vc->dropped_frame = false; vc->surface_num = WRAP_ADD(vc->surface_num, 1, vc->num_output_surfaces); - return 1; + return; + +drop: + vo_increment_drop_count(vo, 1); } -static void draw_image(struct vo *vo, struct mp_image *mpi) +static void draw_frame(struct vo *vo, struct vo_frame *frame) { struct vdpctx *vc = vo->priv; check_preemption(vo); - struct mp_image *vdp_mpi = mp_vdpau_upload_video_surface(vc->mpvdp, mpi); - if (!vdp_mpi) - MP_ERR(vo, "Could not upload image.\n"); - talloc_free(mpi); + if (frame->current && !frame->redraw) { + struct mp_image *vdp_mpi = + mp_vdpau_upload_video_surface(vc->mpvdp, frame->current); + if (!vdp_mpi) + MP_ERR(vo, "Could not upload image.\n"); - talloc_free(vc->current_image); - vc->current_image = vdp_mpi; + talloc_free(vc->current_image); + vc->current_image = vdp_mpi; + } + + vc->current_pts = frame->pts; + vc->current_duration = frame->duration; if (status_ok(vo)) video_to_output_surface(vo); @@ -1040,10 +1053,6 @@ static int control(struct vo *vo, uint32_t request, void *data) struct voctrl_get_equalizer_args *args = data; return get_equalizer(vo, args->name, args->valueptr); } - case VOCTRL_REDRAW_FRAME: - if (status_ok(vo)) - video_to_output_surface(vo); - return true; case VOCTRL_RESET: forget_frames(vo, true); return true; @@ -1080,8 +1089,8 @@ const struct vo_driver video_out_vdpau = { .query_format = query_format, .reconfig = reconfig, .control = control, - .draw_image = draw_image, - .flip_page_timed = flip_page_timed, + .draw_frame = draw_frame, + .flip_page = flip_page, .uninit = uninit, .priv_size = sizeof(struct vdpctx), .options = (const struct m_option []){