summaryrefslogtreecommitdiffstats
path: root/video/out
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-08-12 23:02:08 +0200
committerwm4 <wm4@nowhere>2014-08-12 23:24:08 +0200
commitdf58e822377af0a3802bba862de80eafaea732cb (patch)
tree05a5bfc611612c4397bdaec4c9127c537498bcec /video/out
parenta1be3cf147e18a49c88c613d65478ede9676a744 (diff)
downloadmpv-df58e822377af0a3802bba862de80eafaea732cb.tar.bz2
mpv-df58e822377af0a3802bba862de80eafaea732cb.tar.xz
video: move display and timing to a separate thread
The VO is run inside its own thread. It also does most of video timing. The playloop hands the image data and a realtime timestamp to the VO, and the VO does the rest. In particular, this allows the playloop to do other things, instead of blocking for video redraw. But if anything accesses the VO during video timing, it will block. This also fixes vo_sdl.c event handling; but that is only a side-effect, since reimplementing the broken way would require more effort. Also drop --softsleep. In theory, this option helps if the kernel's sleeping mechanism is too inaccurate for video timing. In practice, I haven't ever encountered a situation where it helps, and it just burns CPU cycles. On the other hand it's probably actively harmful, because it prevents the libavcodec decoder threads from doing real work. Side note: Originally, I intended that multiple frames can be queued to the VO. But this is not done, due to problems with OSD and other certain features. OSD in particular is simply designed in a way that it can be neither timed nor copied, so you do have to render it into the video frame before you can draw the next frame. (Subtitles have no such restriction. sd_lavc was even updated to fix this.) It seems the right solution to queuing multiple VO frames is rendering on VO-backed framebuffers, like vo_vdpau.c does. This requires VO driver support, and is out of scope of this commit. As consequence, the VO has a queue size of 1. The existing video queue is just needed to compute frame duration, and will be moved out in the next commit.
Diffstat (limited to 'video/out')
-rw-r--r--video/out/gl_video.c5
-rw-r--r--video/out/vo.c445
-rw-r--r--video/out/vo.h77
-rw-r--r--video/out/vo_image.c2
-rw-r--r--video/out/vo_lavc.c2
-rw-r--r--video/out/vo_opengl.c22
-rw-r--r--video/out/vo_sdl.c29
-rw-r--r--video/out/vo_vdpau.c13
-rw-r--r--video/out/w32_common.c2
9 files changed, 477 insertions, 120 deletions
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 <string.h>
#include <assert.h>
#include <stdbool.h>
+#include <pthread.h>
#include <unistd.h>
+#include <poll.h>
#include <libavutil/common.h>
@@ -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 = ptr;
+ vo_wakeup(vo);
+}
+
+// Does not include thread- and VO uninit.
+static void dealloc_vo(struct vo *vo)
{
- struct vo *vo = ctx;
- vo_check_events(vo);
- return MP_INPUT_NOTHING;
+ 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;
+}
+
+#ifndef __MINGW32__
+static void wait_event_fd(struct vo *vo, int64_t until_time)
+{
+ 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
-bool vo_get_want_redraw(struct vo *vo)
+// Called unlocked.
+static void wait_vo(struct vo *vo, int64_t until_time)
{
- return vo->config_ok && vo->want_redraw;
+ 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;
}
-void vo_redraw(struct vo *vo)
+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_control(vo, VOCTRL_REDRAW_FRAME, NULL) == true)
- vo_flip_page(vo, 0, -1);
+
+ 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_check_events(struct vo *vo)
+static void *vo_thread(void *ptr)
{
- vo_control(vo, VOCTRL_CHECK_EVENTS, NULL);
+ 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;
+}
+
+// Make the VO redraw the OSD at some point in the future.
+void vo_redraw(struct vo *vo)
+{
+ 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;