summaryrefslogtreecommitdiffstats
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
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.
-rw-r--r--DOCS/man/options.rst7
-rw-r--r--options/options.c2
-rw-r--r--options/options.h1
-rw-r--r--player/core.h4
-rw-r--r--player/misc.c4
-rw-r--r--player/playloop.c28
-rw-r--r--player/screenshot.c4
-rw-r--r--player/video.c112
-rw-r--r--video/decode/vaapi.c4
-rw-r--r--video/hwdec.h2
-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
19 files changed, 513 insertions, 252 deletions
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 <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;