summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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;
+ 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);