diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 81da53baa7..2bc7353299 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -2987,10 +2987,3 @@ Miscellaneous if there is enough interest). This affects most third-party GUI frontends. - -``--softsleep`` - Time frames by repeatedly checking the current time instead of asking - the kernel to wake up mpv at the correct time. Useful if your kernel - timing is imprecise and you cannot use the RTC either. Comes at the - price of higher CPU consumption. - diff --git a/options/options.c b/options/options.c index b5578218d3..f8a18f891a 100644 --- a/options/options.c +++ b/options/options.c @@ -494,8 +494,6 @@ const m_option_t mp_opts[] = { OPT_CHOICE_OR_INT("autosync", autosync, 0, 0, 10000, ({"no", -1})), - OPT_FLAG("softsleep", softsleep, 0), - OPT_CHOICE("term-osd", term_osd, 0, ({"force", 1}, {"auto", 2}, diff --git a/options/options.h b/options/options.h index 20b35d6864..92e3ca0637 100644 --- a/options/options.h +++ b/options/options.h @@ -136,7 +136,6 @@ typedef struct MPOpts { float audio_delay; float default_max_pts_correction; int autosync; - int softsleep; int frame_dropping; int term_osd; int term_osd_bar; diff --git a/player/core.h b/player/core.h index 61d78215b6..1a396cf25f 100644 --- a/player/core.h +++ b/player/core.h @@ -257,10 +257,6 @@ typedef struct MPContext { double delay; // AV sync: time until next frame should be shown double time_frame; - // How long the last vo flip() call took. Used to adjust timing with - // the goal of making flip() calls finish (rather than start) at the - // specified time. - double last_vo_flip_duration; // Display duration (as "intended") of the last flipped frame. double last_frame_duration; // Set to true some time after a new frame has been shown, and it turns out diff --git a/player/misc.c b/player/misc.c index 3ae4a8ab9a..a5f21ba3a5 100644 --- a/player/misc.c +++ b/player/misc.c @@ -148,10 +148,8 @@ void update_window_title(struct MPContext *mpctx, bool force) talloc_free(mpctx->last_window_title); mpctx->last_window_title = talloc_steal(mpctx, title); - if (mpctx->video_out) { - mpctx->video_out->window_title = talloc_strdup(mpctx->video_out, title); + if (mpctx->video_out) vo_control(mpctx->video_out, VOCTRL_UPDATE_WINDOW_TITLE, title); - } if (mpctx->ao) { ao_control(mpctx->ao, AOCONTROL_UPDATE_STREAM_TITLE, title); diff --git a/player/playloop.c b/player/playloop.c index 361b9284af..b96ba2caa2 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -516,8 +516,7 @@ static void handle_osd_redraw(struct MPContext *mpctx) // Don't redraw immediately during a seek (makes it significantly slower). if (mp_time_sec() - mpctx->start_timestamp < 0.1) return; - bool want_redraw = vo_get_want_redraw(mpctx->video_out) | - osd_query_and_reset_want_redraw(mpctx->osd); + bool want_redraw = osd_query_and_reset_want_redraw(mpctx->osd); if (!want_redraw) return; vo_redraw(mpctx->video_out); @@ -806,22 +805,6 @@ static void handle_dummy_ticks(struct MPContext *mpctx) } } -static double get_wakeup_period(struct MPContext *mpctx) -{ - double sleeptime = 100.0; // infinite for all practical purposes - -#if !HAVE_POSIX_SELECT - // No proper file descriptor event handling; keep waking up to poll input - sleeptime = MPMIN(sleeptime, 0.02); -#endif - - if (mpctx->video_out) - if (mpctx->video_out->wakeup_period > 0) - sleeptime = MPMIN(sleeptime, mpctx->video_out->wakeup_period); - - return sleeptime; -} - void run_playloop(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; @@ -851,9 +834,6 @@ void run_playloop(struct MPContext *mpctx) endpts = end; } - if (mpctx->video_out) - vo_check_events(mpctx->video_out); - handle_cursor_autohide(mpctx); handle_heartbeat_cmd(mpctx); @@ -943,7 +923,7 @@ void run_playloop(struct MPContext *mpctx) mp_input_get_cmd(mpctx->input, mpctx->sleeptime * 1000, true); MP_STATS(mpctx, "end sleep"); } - mpctx->sleeptime = get_wakeup_period(mpctx); + mpctx->sleeptime = 100.0; // infinite for all practical purposes handle_pause_on_low_cache(mpctx); @@ -982,13 +962,11 @@ void idle_loop(struct MPContext *mpctx) uninit |= INITIALIZED_VO; uninit_player(mpctx, uninit); handle_force_window(mpctx, false); - if (mpctx->video_out) - vo_check_events(mpctx->video_out); update_osd_msg(mpctx); handle_osd_redraw(mpctx); mp_cmd_t *cmd = mp_input_get_cmd(mpctx->input, mpctx->sleeptime * 1000, false); - mpctx->sleeptime = get_wakeup_period(mpctx); + mpctx->sleeptime = 100.0; if (cmd) run_command(mpctx, cmd); mp_cmd_free(cmd); diff --git a/player/screenshot.c b/player/screenshot.c index 1ee58c2196..63bf29fbd7 100644 --- a/player/screenshot.c +++ b/player/screenshot.c @@ -336,8 +336,10 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode) if (mpctx->d_video && mpctx->d_video->vfilter) vf_control_any(mpctx->d_video->vfilter, VFCTRL_SCREENSHOT, &args); - if (!args.out_image) + if (!args.out_image) { + vo_wait_frame(mpctx->video_out); // important for each-frame mode vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &args); + } image = args.out_image; if (image) { diff --git a/player/video.c b/player/video.c index 5b2748c894..1ee8ec1203 100644 --- a/player/video.c +++ b/player/video.c @@ -613,25 +613,6 @@ static int update_video(struct MPContext *mpctx, double endpts, bool reconfig_ok return VD_NEW_FRAME; } -static double timing_sleep(struct MPContext *mpctx, double time_frame) -{ - // assume kernel HZ=100 for softsleep, works with larger HZ but with - // unnecessarily high CPU usage - struct MPOpts *opts = mpctx->opts; - double margin = opts->softsleep ? 0.011 : 0; - while (time_frame > margin) { - mp_sleep_us(1000000 * (time_frame - margin)); - time_frame -= get_relative_time(mpctx); - } - if (opts->softsleep) { - if (time_frame < 0) - MP_WARN(mpctx, "Warning! Softsleep underflow!\n"); - while (time_frame > 0) - time_frame -= get_relative_time(mpctx); // burn the CPU - } - return time_frame; -} - static void update_avsync(struct MPContext *mpctx) { if (mpctx->audio_status != STATUS_PLAYING || @@ -673,7 +654,6 @@ static void adjust_sync(struct MPContext *mpctx, double frame_time) double v_pts = mpctx->video_next_pts; double av_delay = a_pts - v_pts; // Try to sync vo_flip() so it will *finish* at given time - av_delay += mpctx->last_vo_flip_duration; av_delay += mpctx->audio_delay; // This much pts difference is desired double change = av_delay * 0.1; @@ -772,7 +752,6 @@ void write_video(struct MPContext *mpctx, double endpts) return; mpctx->time_frame -= get_relative_time(mpctx); - double audio_pts = playing_audio_pts(mpctx); if (!mpctx->sync_audio_to_video || mpctx->video_status < STATUS_READY) { mpctx->time_frame = 0; } else if (mpctx->audio_status == STATUS_PLAYING && @@ -806,24 +785,32 @@ void write_video(struct MPContext *mpctx, double endpts) * If untimed is set always output frames immediately * without sleeping. */ - if (mpctx->time_frame < -0.2 || opts->untimed || vo->untimed) + if (mpctx->time_frame < -0.2 || opts->untimed || vo->driver->untimed) mpctx->time_frame = 0; } - double vsleep = mpctx->time_frame - vo->flip_queue_offset; - if (vsleep > 0.050) { - mpctx->sleeptime = MPMIN(mpctx->sleeptime, vsleep - 0.040); - return; - } - mpctx->sleeptime = 0; - mpctx->playing_last_frame = false; - // last frame case + // TODO: should be _after_ wait + mpctx->playing_last_frame = false; if (r != VD_NEW_FRAME) return; - //=================== FLIP PAGE (VIDEO BLT): ====================== + double time_frame = MPMAX(mpctx->time_frame, -1); + int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6); + if (!vo_is_ready_for_frame(vo, pts)) + return; // wait until VO wakes us up to get more frames + + int64_t duration = -1; + double vpts0 = vo_get_next_pts(vo, 0); + double vpts1 = vo_get_next_pts(vo, 1); + if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE) { + // expected A/V sync correction is ignored + double diff = (vpts1 - vpts0) / opts->playback_speed; + if (mpctx->time_frame < 0) + diff += mpctx->time_frame; + duration = MPCLAMP(diff, 0, 10) * 1e6; + } mpctx->video_pts = mpctx->video_next_pts; mpctx->last_vo_pts = mpctx->video_pts; @@ -832,66 +819,17 @@ void write_video(struct MPContext *mpctx, double endpts) update_subtitles(mpctx); update_osd_msg(mpctx); - MP_STATS(mpctx, "vo draw frame"); - - vo_new_frame_imminent(vo); - - MP_STATS(mpctx, "vo sleep"); + vo_queue_frame(vo, pts, duration); + // For print_status - VO call finishing early is OK for sync mpctx->time_frame -= get_relative_time(mpctx); - mpctx->time_frame -= vo->flip_queue_offset; - if (mpctx->time_frame > 0.001) - mpctx->time_frame = timing_sleep(mpctx, mpctx->time_frame); - mpctx->time_frame += vo->flip_queue_offset; - int64_t t2 = mp_time_us(); - /* Playing with playback speed it's possible to get pathological - * cases with mpctx->time_frame negative enough to cause an - * overflow in pts_us calculation, thus the MPMAX. */ - double time_frame = MPMAX(mpctx->time_frame, -1); - int64_t pts_us = mpctx->last_time + time_frame * 1e6; - int duration = -1; - double pts2 = vo_get_next_pts(vo, 0); // this is the next frame PTS - if (mpctx->video_pts != MP_NOPTS_VALUE && pts2 == MP_NOPTS_VALUE) { - // Make up a frame duration. Using the frame rate is not a good - // choice, since the frame rate could be unset/broken/random. - float fps = mpctx->d_video->fps; - double frame_duration = fps > 0 ? 1.0 / fps : 0; - pts2 = mpctx->video_pts + MPCLAMP(frame_duration, 0.0, 5.0); - } - if (pts2 != MP_NOPTS_VALUE) { - // expected A/V sync correction is ignored - double diff = (pts2 - mpctx->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; - mpctx->last_frame_duration = diff; - } - if (mpctx->video_status != STATUS_PLAYING) - duration = -1; - - MP_STATS(mpctx, "start flip"); - vo_flip_page(vo, pts_us | 1, duration); - MP_STATS(mpctx, "end flip"); - - if (audio_pts != MP_NOPTS_VALUE) - MP_STATS(mpctx, "value %f ptsdiff", mpctx->video_pts - audio_pts); - - mpctx->last_vo_flip_duration = (mp_time_us() - t2) * 0.000001; - if (vo->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); - } mpctx->shown_vframes++; - if (mpctx->video_status < STATUS_PLAYING) + if (mpctx->video_status < STATUS_PLAYING) { mpctx->video_status = STATUS_READY; + // After a seek, make sure to wait until the first frame is visible. + vo_wait_frame(vo); + } update_avsync(mpctx); screenshot_flip(mpctx); diff --git a/video/decode/vaapi.c b/video/decode/vaapi.c index 19395f6e09..9625987eb4 100644 --- a/video/decode/vaapi.c +++ b/video/decode/vaapi.c @@ -42,12 +42,12 @@ * So, additionally to the maximum number of reference frames, we need * surfaces for: * - 1 decode frame - * - decoding 1 frame ahead (done by generic playback code) + * - decoding 2 frames ahead (done by generic playback code) * - keeping the reference to the previous frame (done by vo_vaapi.c) * Note that redundant additional surfaces also might allow for some * buffering (i.e. not trying to reuse a surface while it's busy). */ -#define ADDTIONAL_SURFACES 3 +#define ADDTIONAL_SURFACES 4 // Magic number taken from original MPlayer vaapi patch. #define MAX_DECODER_SURFACES 21 diff --git a/video/hwdec.h b/video/hwdec.h index 3ff7e32a50..ca18340ae1 100644 --- a/video/hwdec.h +++ b/video/hwdec.h @@ -9,6 +9,8 @@ struct mp_hwdec_info { // Can be used to lazily load a requested API. // api_name is e.g. "vdpau" (like the fields above, without "_ctx") // Can be NULL, is idempotent, caller checks _ctx fields for success/access. + // Due to threading, the callback is the only code that is allowed to + // change fields in this struct after initialization. void (*load_api)(struct mp_hwdec_info *info, const char *api_name); void *load_api_ctx; }; diff --git a/video/out/gl_video.c b/video/out/gl_video.c index 2c6e2d35b7..9b075baf8a 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -1667,11 +1667,6 @@ draw_osd: osd_draw(p->osd_state, p->osd_rect, p->osd_pts, 0, p->osd->formats, draw_osd_cb, p); - - // 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 - // while we sleep (this happens asynchronously). - gl->Flush(); } static void update_window_sized_objects(struct gl_video *p) diff --git a/video/out/vo.c b/video/out/vo.c index 3962ed6135..ec9781d613 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -1,6 +1,4 @@ /* - * libvo common functions, variables used by many/all drivers. - * * This file is part of MPlayer. * * MPlayer is free software; you can redistribute it and/or modify @@ -23,8 +21,10 @@ #include #include #include +#include #include +#include #include @@ -32,6 +32,9 @@ #include "config.h" #include "osdep/timer.h" +#include "osdep/threads.h" +#include "misc/dispatch.h" +#include "misc/rendezvous.h" #include "options/options.h" #include "bstr/bstr.h" #include "vo.h" @@ -43,10 +46,8 @@ #include "video/mp_image.h" #include "video/vfcap.h" #include "sub/osd.h" +#include "osdep/io.h" -// -// Externally visible list of all vo drivers -// extern const struct vo_driver video_out_x11; extern const struct vo_driver video_out_vdpau; extern const struct vo_driver video_out_xv; @@ -112,7 +113,42 @@ const struct vo_driver *const video_out_drivers[] = NULL }; +#define VO_MAX_QUEUE 2 + +struct vo_internal { + pthread_t thread; + struct mp_dispatch_queue *dispatch; + + // --- The following fields are protected by lock + pthread_mutex_t lock; + pthread_cond_t wakeup; + + bool need_wakeup; + bool terminate; + + int wakeup_pipe[2]; // used for VOs that use a unix FD for waiting + + char *window_title; + + bool request_redraw; + + int64_t flip_queue_offset; // queue flip events at most this much in advance + + // 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; + + int64_t wakeup_pts; // time at which to pull frame from decoder + + bool rendering; // true if an image is being rendered + bool frame_queued; // frame queued, with parameters below + int64_t frame_pts; // realtime of intended display + int64_t frame_duration; // realtime frame duration (for framedrop) + struct mp_image *frame_image; // the image that should be rendered +}; + static void forget_frames(struct vo *vo); +static void *vo_thread(void *ptr); static bool get_desc(struct m_obj_desc *dst, int index) { @@ -144,11 +180,21 @@ const struct m_obj_list vo_obj_list = { .allow_trailer = true, }; -static int event_fd_callback(void *ctx, int fd) +static void dispatch_wakeup_cb(void *ptr) { - struct vo *vo = ctx; - vo_check_events(vo); - return MP_INPUT_NOTHING; + struct vo *vo = ptr; + vo_wakeup(vo); +} + +// Does not include thread- and VO uninit. +static void dealloc_vo(struct vo *vo) +{ + forget_frames(vo); // implicitly synchronized + pthread_mutex_destroy(&vo->in->lock); + pthread_cond_destroy(&vo->in->wakeup); + for (int n = 0; n < 2; n++) + close(vo->in->wakeup_pipe[n]); + talloc_free(vo); } static struct vo *vo_create(struct mpv_global *global, @@ -174,9 +220,17 @@ static struct vo *vo_create(struct mpv_global *global, .osd = osd, .event_fd = -1, .monitor_par = 1, - .max_video_queue = 1, + .in = talloc(vo, struct vo_internal), }; talloc_steal(vo, log); + *vo->in = (struct vo_internal) { + .dispatch = mp_dispatch_create(vo), + }; + mp_make_wakeup_pipe(vo->in->wakeup_pipe); + mp_dispatch_set_wakeup_fn(vo->in->dispatch, dispatch_wakeup_cb, vo); + pthread_mutex_init(&vo->in->lock, NULL); + pthread_cond_init(&vo->in->wakeup, NULL); + mp_input_set_mouse_transform(vo->input_ctx, NULL, NULL); if (vo->driver->encode != !!vo->encode_lavc_ctx) goto error; @@ -186,15 +240,17 @@ static struct vo *vo_create(struct mpv_global *global, if (m_config_set_obj_params(config, args) < 0) goto error; vo->priv = config->optstruct; - if (vo->driver->preinit(vo)) + + if (pthread_create(&vo->in->thread, NULL, vo_thread, vo)) + goto error; + if (mp_rendezvous(vo, 0) < 0) { // init barrier + pthread_join(vo->in->thread, NULL); goto error; - if (vo->event_fd != -1) { - mp_input_add_fd(vo->input_ctx, vo->event_fd, 1, NULL, event_fd_callback, - NULL, vo); } return vo; + error: - talloc_free(vo); + dealloc_vo(vo); return NULL; } @@ -230,11 +286,12 @@ autoprobe: void vo_destroy(struct vo *vo) { - if (vo->event_fd != -1) - mp_input_rm_key_fd(vo->input_ctx, vo->event_fd); - forget_frames(vo); - vo->driver->uninit(vo); - talloc_free(vo); + struct vo_internal *in = vo->in; + mp_dispatch_lock(in->dispatch); + vo->in->terminate = true; + mp_dispatch_unlock(in->dispatch); + pthread_join(vo->in->thread, NULL); + dealloc_vo(vo); } static void check_vo_caps(struct vo *vo) @@ -249,54 +306,90 @@ static void check_vo_caps(struct vo *vo) } } -int vo_reconfig(struct vo *vo, struct mp_image_params *params, int flags) +static void run_reconfig(void *p) { + void **pp = p; + struct vo *vo = pp[0]; + struct mp_image_params *params = pp[1]; + int flags = *(int *)pp[2]; + int *ret = pp[3]; + vo->dwidth = params->d_w; vo->dheight = params->d_h; talloc_free(vo->params); vo->params = talloc_memdup(vo, params, sizeof(*params)); - int ret = vo->driver->reconfig(vo, vo->params, flags); - vo->config_ok = ret >= 0; + *ret = vo->driver->reconfig(vo, vo->params, flags); + vo->config_ok = *ret >= 0; if (vo->config_ok) { check_vo_caps(vo); } else { talloc_free(vo->params); vo->params = NULL; } - forget_frames(vo); + forget_frames(vo); // implicitly synchronized vo->hasframe = false; +} + +int vo_reconfig(struct vo *vo, struct mp_image_params *params, int flags) +{ + int ret; + void *p[] = {vo, params, &flags, &ret}; + mp_dispatch_run(vo->in->dispatch, run_reconfig, p); return ret; } +static void run_control(void *p) +{ + void **pp = p; + struct vo *vo = pp[0]; + uint32_t request = *(int *)pp[1]; + void *data = pp[2]; + if (request == VOCTRL_UPDATE_WINDOW_TITLE) // legacy fallback + vo->in->window_title = talloc_strdup(vo, data); + int ret = vo->driver->control(vo, request, data); + *(int *)pp[3] = ret; +} + int vo_control(struct vo *vo, uint32_t request, void *data) { - return vo->driver->control(vo, request, data); + int ret; + void *p[] = {vo, &request, data, &ret}; + mp_dispatch_run(vo->in->dispatch, run_control, p); + return ret; } +// must be called locked 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; + struct vo_internal *in = vo->in; + for (int n = 0; n < in->num_video_queue; n++) + talloc_free(in->video_queue[n]); + in->num_video_queue = 0; + in->frame_queued = false; + mp_image_unrefp(&in->frame_image); } void vo_queue_image(struct vo *vo, struct mp_image *mpi) { + struct vo_internal *in = vo->in; assert(mpi); - if (!vo->config_ok) + if (!vo->config_ok) { + talloc_free(mpi); return; + } + pthread_mutex_lock(&in->lock); assert(mp_image_params_equal(vo->params, &mpi->params)); - 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; + assert(in->num_video_queue <= VO_MAX_QUEUE); + in->video_queue[in->num_video_queue++] = mpi; + pthread_mutex_unlock(&in->lock); } // Return whether vo_queue_image() should be called. bool vo_needs_new_image(struct vo *vo) { - return vo->config_ok && vo->num_video_queue < vo->max_video_queue; + return vo->config_ok && vo->in->num_video_queue < VO_MAX_QUEUE; } // Return whether a frame can be displayed. @@ -306,69 +399,280 @@ bool vo_has_next_frame(struct vo *vo, bool eof) { // 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. - return eof ? vo->num_video_queue : vo->num_video_queue == vo->max_video_queue; + return eof ? vo->in->num_video_queue : vo->in->num_video_queue == VO_MAX_QUEUE; } // Return the PTS of a future frame (where index==0 is the next frame) double vo_get_next_pts(struct vo *vo, int index) { - if (index < 0 || index >= vo->num_video_queue) + if (index < 0 || index >= vo->in->num_video_queue) return MP_NOPTS_VALUE; - return vo->video_queue[index]->pts; + return vo->in->video_queue[index]->pts; } -bool vo_get_want_redraw(struct vo *vo) +#ifndef __MINGW32__ +static void wait_event_fd(struct vo *vo, int64_t until_time) { - return vo->config_ok && vo->want_redraw; + struct vo_internal *in = vo->in; + + struct pollfd fds[2] = { + { .fd = vo->event_fd, .events = POLLIN }, + { .fd = in->wakeup_pipe[0], .events = POLLIN }, + }; + int64_t wait_us = until_time - mp_time_us(); + int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000); + + poll(fds, 2, timeout_ms); + + if (fds[1].revents & POLLIN) { + char buf[100]; + read(in->wakeup_pipe[0], buf, sizeof(buf)); // flush + } +} +static void wakeup_event_fd(struct vo *vo) +{ + struct vo_internal *in = vo->in; + + write(in->wakeup_pipe[1], &(char){0}, 1); +} +#else +static void wait_event_fd(struct vo *vo, int64_t until_time){} +static void wakeup_event_fd(struct vo *vo){} +#endif + +// Called unlocked. +static void wait_vo(struct vo *vo, int64_t until_time) +{ + struct vo_internal *in = vo->in; + + if (vo->event_fd >= 0) { + wait_event_fd(vo, until_time); + pthread_mutex_lock(&in->lock); + in->need_wakeup = false; + pthread_mutex_unlock(&in->lock); + } else if (vo->driver->wait_events) { + vo->driver->wait_events(vo, until_time); + pthread_mutex_lock(&in->lock); + in->need_wakeup = false; + pthread_mutex_unlock(&in->lock); + } else { + pthread_mutex_lock(&in->lock); + if (!in->need_wakeup) + mpthread_cond_timedwait(&in->wakeup, &in->lock, until_time); + in->need_wakeup = false; + pthread_mutex_unlock(&in->lock); + } } -// Remove vo->video_queue[0] -static void shift_queue(struct vo *vo) +static void wakeup_locked(struct vo *vo) { - if (!vo->num_video_queue) - return; - vo->num_video_queue--; - for (int n = 0; n < vo->num_video_queue; n++) - vo->video_queue[n] = vo->video_queue[n + 1]; + struct vo_internal *in = vo->in; + + pthread_cond_signal(&in->wakeup); + if (vo->event_fd >= 0) + wakeup_event_fd(vo); + if (vo->driver->wakeup) + vo->driver->wakeup(vo); + in->need_wakeup = true; } -void vo_new_frame_imminent(struct vo *vo) +// Wakeup VO thread, and make it check for new events with VOCTRL_CHECK_EVENTS. +// To be used by threaded VO backends. +void vo_wakeup(struct vo *vo) { - assert(vo->num_video_queue > 0); - struct mp_image *img = vo->video_queue[0]; - shift_queue(vo); - vo->driver->draw_image(vo, img); + struct vo_internal *in = vo->in; + + pthread_mutex_lock(&in->lock); + wakeup_locked(vo); + pthread_mutex_unlock(&in->lock); +} + +// Whether vo_queue_frame() can be called. If the VO is not ready yet (even +// though an image is queued), the function will return false, and the VO will +// call the wakeup callback once it's ready. +// next_pts is the exact time when the next frame should be displayed. If the +// VO is ready, but the time is too "early", return false, and call the wakeup +// callback once the time is right. +bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts) +{ + struct vo_internal *in = vo->in; + pthread_mutex_lock(&in->lock); + bool r = vo->config_ok && in->num_video_queue && !in->frame_queued; + if (r) { + // Don't show the frame too early - it would basically freeze the + // display by disallowing OSD redrawing or VO interaction. + // Actually render the frame at earliest 50ms before target time. + next_pts -= 0.050 * 1e6; + next_pts -= in->flip_queue_offset; + int64_t now = mp_time_us(); + if (next_pts > now) + r = false; + if (!in->wakeup_pts || next_pts < in->wakeup_pts) { + in->wakeup_pts = next_pts; + wakeup_locked(vo); + } + } + pthread_mutex_unlock(&in->lock); + return r; +} + +// 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. +void vo_queue_frame(struct vo *vo, int64_t pts_us, int64_t duration) +{ + struct vo_internal *in = vo->in; + pthread_mutex_lock(&in->lock); + assert(vo->config_ok && in->num_video_queue && !in->frame_queued); vo->hasframe = true; + in->frame_queued = true; + in->frame_pts = pts_us; + in->frame_duration = duration; + in->frame_image = in->video_queue[0]; + in->num_video_queue--; + for (int n = 0; n < in->num_video_queue; n++) + in->video_queue[n] = in->video_queue[n + 1]; + in->wakeup_pts = 0; + wakeup_locked(vo); + pthread_mutex_unlock(&in->lock); } -void vo_flip_page(struct vo *vo, int64_t pts_us, int duration) +// If a frame is currently being rendered (or queued), wait until it's done. +// Otherwise, return immediately. +void vo_wait_frame(struct vo *vo) { - if (!vo->config_ok) - return; - vo->want_redraw = false; + struct vo_internal *in = vo->in; + pthread_mutex_lock(&in->lock); + while (in->frame_queued || in->rendering) + pthread_cond_wait(&in->wakeup, &in->lock); + pthread_mutex_unlock(&in->lock); +} + +static bool render_frame(struct vo *vo) +{ + struct vo_internal *in = vo->in; + + pthread_mutex_lock(&in->lock); + + int64_t pts = in->frame_pts; + int64_t duration = in->frame_duration; + struct mp_image *img = in->frame_image; + if (!img) { + pthread_mutex_unlock(&in->lock); + return false; + } + + assert(!!img == in->frame_queued); + in->rendering = true; + in->frame_queued = false; + in->frame_image = NULL; + + pthread_mutex_unlock(&in->lock); + + vo->driver->draw_image(vo, img); + + int64_t target = pts - in->flip_queue_offset; + while (1) { + int64_t now = mp_time_us(); + if (target <= now) + break; + mp_sleep_us(target - now); + } + if (vo->driver->flip_page_timed) - vo->driver->flip_page_timed(vo, pts_us, duration); + vo->driver->flip_page_timed(vo, pts, duration); + else + vo->driver->flip_page(vo); + + vo->want_redraw = false; + + pthread_mutex_lock(&in->lock); + in->request_redraw = false; + in->rendering = false; + pthread_cond_signal(&in->wakeup); // for vo_wait_frame() + mp_input_wakeup(vo->input_ctx); + pthread_mutex_unlock(&in->lock); + + return true; +} + +static void do_redraw(struct vo *vo) +{ + struct vo_internal *in = vo->in; + + pthread_mutex_lock(&in->lock); + in->request_redraw = false; + pthread_mutex_unlock(&in->lock); + + vo->want_redraw = false; + + if (!vo->config_ok || vo->driver->control(vo, VOCTRL_REDRAW_FRAME, NULL) < 1) + return; + + if (vo->driver->flip_page_timed) + vo->driver->flip_page_timed(vo, 0, -1); else vo->driver->flip_page(vo); } -void vo_redraw(struct vo *vo) +static void *vo_thread(void *ptr) { - vo->want_redraw = false; - if (vo->config_ok && vo_control(vo, VOCTRL_REDRAW_FRAME, NULL) == true) - vo_flip_page(vo, 0, -1); + struct vo *vo = ptr; + struct vo_internal *in = vo->in; + + int r = vo->driver->preinit(vo) ? -1 : 0; + mp_rendezvous(vo, r); // init barrier + if (r < 0) + return NULL; + + while (1) { + mp_dispatch_queue_process(vo->in->dispatch, 0); + if (in->terminate) + break; + vo->driver->control(vo, VOCTRL_CHECK_EVENTS, NULL); + bool frame_shown = render_frame(vo); + int64_t now = mp_time_us(); + int64_t wait_until = now + (frame_shown ? 0 : (int64_t)1e9); + pthread_mutex_lock(&in->lock); + if (in->wakeup_pts) { + if (in->wakeup_pts > now) { + wait_until = MPMIN(wait_until, in->wakeup_pts); + } else { + in->wakeup_pts = 0; + mp_input_wakeup(vo->input_ctx); + } + } + vo->want_redraw |= in->request_redraw; + pthread_mutex_unlock(&in->lock); + if (wait_until > now && vo->want_redraw) { + do_redraw(vo); // now is a good time + continue; + } + wait_vo(vo, wait_until); + } + forget_frames(vo); // implicitly synchronized + vo->driver->uninit(vo); + return NULL; } -void vo_check_events(struct vo *vo) +// Make the VO redraw the OSD at some point in the future. +void vo_redraw(struct vo *vo) { - vo_control(vo, VOCTRL_CHECK_EVENTS, NULL); + struct vo_internal *in = vo->in; + pthread_mutex_lock(&in->lock); + if (!in->request_redraw) { + in->request_redraw = true; + wakeup_locked(vo); + } + pthread_mutex_unlock(&in->lock); } void vo_seek_reset(struct vo *vo) { - vo_control(vo, VOCTRL_RESET, NULL); + pthread_mutex_lock(&vo->in->lock); forget_frames(vo); vo->hasframe = false; + pthread_mutex_unlock(&vo->in->lock); + vo_control(vo, VOCTRL_RESET, NULL); } // Calculate the appropriate source and destination rectangle to @@ -376,6 +680,7 @@ void vo_seek_reset(struct vo *vo) // out_src: visible part of the video // out_dst: area of screen covered by the video source rectangle // out_osd: OSD size, OSD margins, etc. +// Must be called from the VO thread only. 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) { @@ -392,11 +697,23 @@ void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src, // Return the window title the VO should set. Always returns a null terminated // string. The string is valid until frontend code is invoked again. Copy it if // you need to keep the string for an extended period of time. +// Must be called from the VO thread only. +// Don't use for new code. const char *vo_get_window_title(struct vo *vo) { - if (!vo->window_title) - vo->window_title = talloc_strdup(vo, ""); - return vo->window_title; + if (!vo->in->window_title) + vo->in->window_title = talloc_strdup(vo, ""); + return vo->in->window_title; +} + +// flip_page[_timed] will be called this many microseconds too early. +// (For vo_vdpau, which does its own timing.) +void vo_set_flip_queue_offset(struct vo *vo, int64_t us) +{ + struct vo_internal *in = vo->in; + pthread_mutex_lock(&in->lock); + in->flip_queue_offset = us; + pthread_mutex_unlock(&in->lock); } /** diff --git a/video/out/vo.h b/video/out/vo.h index 1d8182f71a..0fe796dc73 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -53,6 +53,7 @@ enum mp_voctrl { /* for hardware decoding */ VOCTRL_GET_HWDEC_INFO, // struct mp_hwdec_info** + VOCTRL_LOAD_HWDEC_API, // private to vo_opengl // Redraw the image previously passed to draw_image() (basically, repeat // the previous draw_image call). If this is handled, the OSD should also @@ -134,8 +135,6 @@ 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; @@ -148,6 +147,9 @@ struct vo_driver { // VO_CAP_* bits int caps; + // Disable video timing, push frames as quickly as possible. + bool untimed; + const char *name; const char *description; @@ -195,6 +197,22 @@ struct vo_driver { void (*flip_page)(struct vo *vo); void (*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 + * when there are new events. + * + * wait_events() will wait for new events, until the timeout expires, or the + * function is interrupted. wakeup() is used to possibly interrupt the + * event loop (wakeup() itself must be thread-safe, and not call any other + * VO functions; it's the only vo_driver function with this requirement). + * wakeup() should behave like a binary semaphore; if wait_events() is not + * being called while wakeup() is, the next wait_events() call should exit + * immediately. + */ + void (*wakeup)(struct vo *vo); + int (*wait_events)(struct vo *vo, int64_t until_time_us); + /* * Closes driver. Should restore the original state of the system. */ @@ -206,49 +224,48 @@ struct vo_driver { // If not NULL, it's copied into the newly allocated private struct. const void *priv_defaults; - // List of options to parse into priv struct (requires privsize to be set) + // List of options to parse into priv struct (requires priv_size to be set) const struct m_option *options; }; struct vo { - struct mp_log *log; // Using e.g. "[vo/vdpau]" as prefix - int config_ok; // Last config call was successful? - struct mp_image_params *params; // Configured parameters (as in vo_reconfig) - - bool probing; - - bool untimed; // non-interactive, don't do sleep calls in playloop - - bool want_redraw; // visible frame wrong (window resize), needs refresh - 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; + struct mp_log *log; // Using e.g. "[vo/vdpau]" as prefix void *priv; - struct mp_vo_opts *opts; struct mpv_global *global; struct vo_x11_state *x11; struct vo_w32_state *w32; struct vo_cocoa_state *cocoa; struct vo_wayland_state *wayland; - struct encode_lavc_context *encode_lavc_ctx; struct input_ctx *input_ctx; struct osd_state *osd; + struct encode_lavc_context *encode_lavc_ctx; + struct vo_internal *in; + struct mp_vo_opts *opts; + + // --- The following fields are generally only changed during initialization. + int event_fd; // check_events() should be called when this has input + bool probing; + + // --- The following fields are only changed with vo_reconfig(), and can + // be accessed unsynchronized (read-only). + + int config_ok; // Last config call was successful? + struct mp_image_params *params; // Configured parameters (as in vo_reconfig) + + // --- The following fields can be accessed only by the VO thread, or from + // anywhere _if_ the VO thread is suspended (use vo->dispatch). + + bool want_redraw; // redraw as soon as possible // current window state int dwidth; int dheight; float monitor_par; - char *window_title; + // --- Accessed by user-thread only. + bool hasframe; // >= 1 frame has been drawn, so redraw is possible }; struct mpv_global; @@ -260,17 +277,19 @@ int vo_reconfig(struct vo *vo, struct mp_image_params *p, int flags); int vo_control(struct vo *vo, uint32_t request, void *data); void vo_queue_image(struct vo *vo, struct mp_image *mpi); -bool vo_get_want_redraw(struct vo *vo); bool vo_has_next_frame(struct vo *vo, bool eof); double vo_get_next_pts(struct vo *vo, int index); bool vo_needs_new_image(struct vo *vo); -void vo_new_frame_imminent(struct vo *vo); -void vo_flip_page(struct vo *vo, int64_t pts_us, int duration); +bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts); +void vo_queue_frame(struct vo *vo, int64_t pts_us, int64_t duration); +void vo_wait_frame(struct vo *vo); void vo_redraw(struct vo *vo); -void vo_check_events(struct vo *vo); void vo_seek_reset(struct vo *vo); void vo_destroy(struct vo *vo); +void vo_set_flip_queue_offset(struct vo *vo, int64_t us); +void vo_wakeup(struct vo *vo); + const char *vo_get_window_title(struct vo *vo); struct mp_keymap { diff --git a/video/out/vo_image.c b/video/out/vo_image.c index bec5ea9fd0..da6d78f3d8 100644 --- a/video/out/vo_image.c +++ b/video/out/vo_image.c @@ -123,7 +123,6 @@ static int preinit(struct vo *vo) struct priv *p = vo->priv; if (p->outdir && !checked_mkdir(vo, p->outdir)) return -1; - vo->untimed = true; return 0; } @@ -138,6 +137,7 @@ const struct vo_driver video_out_image = { .description = "Write video frames to image files", .name = "image", + .untimed = true, .priv_size = sizeof(struct priv), .options = (const struct m_option[]) { OPT_SUBSTRUCT("", opts, image_writer_conf, 0), diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c index da327b61ad..aa42bc6e1b 100644 --- a/video/out/vo_lavc.c +++ b/video/out/vo_lavc.c @@ -69,7 +69,6 @@ static int preinit(struct vo *vo) vo->priv = talloc_zero(vo, struct priv); vc = vo->priv; vc->harddup = vo->encode_lavc_ctx->options->harddup; - vo->untimed = true; return 0; } @@ -526,6 +525,7 @@ const struct vo_driver video_out_lavc = { .encode = true, .description = "video encoding using libavcodec", .name = "lavc", + .untimed = true, .preinit = preinit, .query_format = query_format, .reconfig = reconfig, diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index 7e6a03bbcf..ae5f1f9bd0 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -96,13 +96,9 @@ static void resize(struct gl_priv *p) static void flip_page(struct vo *vo) { struct gl_priv *p = vo->priv; - GL *gl = p->gl; mpgl_lock(p->glctx); - if (p->use_glFinish) - gl->Finish(); - p->glctx->swapGlBuffers(p->glctx); p->frames_rendered++; @@ -115,13 +111,24 @@ static void flip_page(struct vo *vo) static void draw_image(struct vo *vo, mp_image_t *mpi) { struct gl_priv *p = vo->priv; + GL *gl = p->gl; if (p->vo_flipped) mp_image_vflip(mpi); mpgl_lock(p->glctx); + gl_video_upload_image(p->renderer, mpi); gl_video_render_frame(p->renderer); + + // 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 + // while we sleep (this happens asynchronously). + gl->Flush(); + + if (p->use_glFinish) + gl->Finish(); + mpgl_unlock(p->glctx); } @@ -228,7 +235,9 @@ static void call_request_hwdec_api(struct mp_hwdec_info *info, { struct vo *vo = info->load_api_ctx; assert(&((struct gl_priv *)vo->priv)->hwdec_info == info); - request_hwdec_api(vo->priv, api_name); + // Roundabout way to run hwdec loading on the VO thread. + // Redirects to request_hwdec_api(). + vo_control(vo, VOCTRL_LOAD_HWDEC_API, (void *)api_name); } static void unload_hwdec_driver(struct gl_priv *p) @@ -360,6 +369,9 @@ static int control(struct vo *vo, uint32_t request, void *data) *arg = &p->hwdec_info; return true; } + case VOCTRL_LOAD_HWDEC_API: + request_hwdec_api(p, data); + return true; case VOCTRL_REDRAW_FRAME: mpgl_lock(p->glctx); gl_video_render_frame(p->renderer); diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c index ee8e0d879a..3c6a653208 100644 --- a/video/out/vo_sdl.c +++ b/video/out/vo_sdl.c @@ -193,6 +193,7 @@ struct priv { double osd_pts; int mouse_hidden; int brightness, contrast; + Uint32 wakeup_event; // options int allow_sw; @@ -392,6 +393,7 @@ static void resize(struct vo *vo, int w, int h) &vc->osd_res); SDL_RenderSetLogicalSize(vc->renderer, w, h); vo->want_redraw = true; + vo_wakeup(vo); } static void force_resize(struct vo *vo) @@ -535,11 +537,22 @@ static void flip_page(struct vo *vo) SDL_RenderPresent(vc->renderer); } -static void check_events(struct vo *vo) +static void wakeup(struct vo *vo) { + struct priv *vc = vo->priv; + SDL_Event event = {.type = vc->wakeup_event}; + // Note that there is no context - SDL is a singleton. + SDL_PushEvent(&event); +} + +static int wait_events(struct vo *vo, int64_t until_time_us) +{ + int64_t wait_us = until_time_us - mp_time_us(); + int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000); SDL_Event ev; - while (SDL_PollEvent(&ev)) { + while (SDL_WaitEventTimeout(&ev, timeout_ms)) { + timeout_ms = 0; switch (ev.type) { case SDL_WINDOWEVENT: switch (ev.window.event) { @@ -617,6 +630,8 @@ static void check_events(struct vo *vo) break; } } + + return 0; } static void uninit(struct vo *vo) @@ -824,8 +839,9 @@ static int preinit(struct vo *vo) // please reinitialize the renderer to proper size on config() vc->reinit_renderer = true; - // we don't have proper event handling - vo->wakeup_period = 0.2; + vc->wakeup_event = SDL_RegisterEvents(1); + if (vc->wakeup_event == (Uint32)-1) + MP_ERR(vo, "SDL_RegisterEvents() failed.\n"); return 0; } @@ -983,9 +999,6 @@ static int control(struct vo *vo, uint32_t request, void *data) struct priv *vc = vo->priv; switch (request) { - case VOCTRL_CHECK_EVENTS: - check_events(vo); - return 1; case VOCTRL_FULLSCREEN: set_fullscreen(vo); return 1; @@ -1047,4 +1060,6 @@ const struct vo_driver video_out_sdl = { .draw_image = draw_image, .uninit = uninit, .flip_page = flip_page, + .wait_events = wait_events, + .wakeup = wakeup, }; diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c index 7cf11543ed..86bd97c101 100644 --- a/video/out/vo_vdpau.c +++ b/video/out/vo_vdpau.c @@ -99,6 +99,7 @@ struct vdpctx { int chroma_deint; int flip_offset_window; int flip_offset_fs; + int flip_offset_ms; bool flip; VdpRect src_rect_vid; @@ -250,11 +251,11 @@ static void resize(struct vo *vo) vc->src_rect_vid.y0 = vc->flip ? src_rect.y1 : src_rect.y0; vc->src_rect_vid.y1 = vc->flip ? src_rect.y0 : src_rect.y1; - int flip_offset_ms = vo->opts->fullscreen ? + vc->flip_offset_ms = vo->opts->fullscreen ? vc->flip_offset_fs : vc->flip_offset_window; - vo->flip_queue_offset = flip_offset_ms / 1000.; + vo_set_flip_queue_offset(vo, vc->flip_offset_ms * 1000); if (vc->output_surface_width < vo->dwidth || vc->output_surface_height < vo->dheight) { @@ -765,12 +766,12 @@ static void flip_page_timed(struct vo *vo, int64_t pts_us, int duration) /* This should normally never happen. * - The last queued frame can't have a PTS that goes more than 50ms in the - * future. This is guaranteed by the playloop, which currently actually - * roughly queues 50ms ahead, plus the flip queue offset. Just to be sure + * future. This is guaranteed by vo.c, which currently actually queues + * ahead by roughly 50ms, plus the flip queue offset. Just to be sure * give some additional room by doubling the time. * - The last vsync can never be in the future. */ - int64_t max_pts_ahead = (vo->flip_queue_offset + 0.050) * 2 * 1e9; + int64_t max_pts_ahead = (vc->flip_offset_ms + 50) * 1000 * 1000 * 2; if (vc->last_queue_time > now + max_pts_ahead || vc->recent_vsync_time > now) { @@ -1025,8 +1026,6 @@ static int preinit(struct vo *vo) vc->video_eq.capabilities = MP_CSP_EQ_CAPS_COLORMATRIX; - vo->max_video_queue = 2; - return 0; } diff --git a/video/out/w32_common.c b/video/out/w32_common.c index f59c3cc259..ed23727754 100644 --- a/video/out/w32_common.c +++ b/video/out/w32_common.c @@ -501,7 +501,7 @@ static bool handle_char(struct vo_w32_state *w32, wchar_t wc) static void signal_events(struct vo_w32_state *w32, int events) { w32->event_flags |= events; - mp_input_wakeup(w32->input_ctx); + vo_wakeup(w32->vo); } static void wakeup_gui_thread(void *ctx)