mirror of https://github.com/mpv-player/mpv
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.
This commit is contained in:
parent
a1be3cf147
commit
df58e82237
|
@ -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.
|
||||
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
110
player/video.c
110
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_queue_frame(vo, pts, duration);
|
||||
|
||||
vo_new_frame_imminent(vo);
|
||||
|
||||
MP_STATS(mpctx, "vo sleep");
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
447
video/out/vo.c
447
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 = 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue