player: add display sync mode

If this mode is enabled, the player tries to strictly synchronize video
to display refresh. It will adjust playback speed to match the display,
so if you play 23.976 fps video on a 24 Hz screen, playback speed is
increased by approximately 1/1000. Audio wll be resampled to keep up
with playback.

This is different from the default sync mode, which will sync video to
audio, with the consequence that video might skip or repeat a frame once
in a while to make video keep up with audio.

This is still unpolished. There are some major problems as well; in
particular, mkv VFR files won't work well. The reason is that Matroska
is terrible and rounds timestamps to milliseconds. This makes it rather
hard to guess the framerate of a section of video that is playing. We
could probably fix this by just accepting jittery timestamps (instead
of explicitly disabling the sync code in this case), but I'm not ready
to accept such a solution yet.

Another issue is that we are extremely reliant on OS video and audio
APIs working in an expected manner, which of course is not too often
the case. Consequently, the new sync mode is a bit fragile.
This commit is contained in:
wm4 2015-08-10 18:43:25 +02:00
parent fedaad8250
commit 031555fbe6
13 changed files with 451 additions and 17 deletions

View File

@ -20,7 +20,10 @@ Interface changes
::
--- mpv 0.10.0 will be released ---
- add "audio-speed-correction" and "video-speed-correction" properties
- add --video-sync* options
"display-sync-active" property
"vo-missed-frame-count" property
"audio-speed-correction" and "video-speed-correction" properties
- remove --demuxer-readahead-packets and --demuxer-readahead-bytes
add --demuxer-max-packets and --demuxer-max-bytes
(the new options are not replacement and have very different semantics)

View File

@ -830,6 +830,12 @@ Property list
Factor multiplied with ``speed`` at which the player attempts to play the
file. Usually it's exactly 1. (Display sync mode will make this useful.)
OSD formatting will display it in the form of ``+1.23456%``, with the number
being ``(raw - 1) * 100`` for the given raw property value.
``display-sync-active``
Return whether ``--video-sync=display`` is actually active.
``filename``
Currently played file, with path stripped. If this is an URL, try to undo
percent encoding as well. (The result is not necessarily correct, but

View File

@ -526,6 +526,13 @@ listed.
if there is audio "missing", or not enough frames can be dropped. Usually
this will indicate a problem. (``total-avsync-change`` property.)
- Encoding state in ``{...}``, only shown in encoding mode.
- Display sync state. If display sync is active (``display-sync-active``
property), this shows ``DS: 1.002``, where the number is the speed change
factor applied to audio to achieve sync to display (``audio-speed-correction``
property). In sync modes which don't resample, this will always be ``1.000``.
- Missed frames, e.g. ``Missed: 4``. (``vo-missed-frame-count`` property.) Shows
up in display sync mode only. This is incremented each time a frame took
longer to display than intended.
- Dropped frames, e.g. ``Dropped: 4``. Shows up only if the count is not 0. Can
grow if the video framerate is higher than that of the display, or if video
rendering is too slow. Also can be incremented on "hiccups" and when the video

View File

@ -3363,6 +3363,72 @@ Miscellaneous
out. This delay in reaction time to sudden A/V offsets should be the only
side-effect of turning this option on, for all sound drivers.
``--video-sync=<audio|...>``
How the player synchronizes audio and video.
The modes starting with ``display-`` try to output video frames completely
synchronously to the display, using the detected display vertical refresh
rate as a hint how fast frames will be displayed on average. These modes
change video speed slightly to match the display. See ``--video-sync-...``
options for fine tuning. The robustness of this mode is further reduced by
making a some idealized assumptions, which may not always apply in reality.
Behavior can depend on the VO and the system's video and audio drivers.
Media files must use constant framerate. Section-wise VFR might work as well
with some container formats (but not e.g. mkv). If the sync code detects
severe A/V desync, or the framerate cannot be detected, the player
automatically reverts to ``audio`` mode for some time or permanently.
The modes with ``desync`` in their names do not attempt to keep audio/video
in sync. They will slowly (or quickly) desync, until e.g. the next seek
happens. These modes are meant for testing, not serious use.
:audio: Time video frames to audio. This is the most robust
mode, because the player doesn't have to assume anything
about how the display behaves. The disadvantage is that
it can lead to occasional frame drops or repeats. If
audio is disabled, this uses the system clock. This is
the default mode.
:display-resample: Resample audio to match the video. This mode will also
try to adjust audio speed to compensate for other drift.
(This means it will play the audio at a different speed
every once in a while to reduce the A/V difference.)
:display-resample-vdrop: Resample audio to match the video. Drop video
frames to compensate for drift.
:display-resample-desync: Like the previous mode, but no A/V compensation.
:display-vdrop: Drop or repeat video frames to compensate desyncing
video. (Although it should have the same effects as
``audio``, the implementation is very different.)
:display-desync: Sync video to display, and let audio play on its own.
:desync: Sync video according to system clock, and let audio play
on its own.
``--video-sync-max-video-change=<value>``
Maximum speed difference in percent that is applied to video with
``--video-sync=display-...`` (default: 1). Display sync mode will be
disabled if the monitor and video refresh way do not match within the
given range. It tries multiples as well: playing 30 fps video on a 60 Hz
screen will duplicate every second frame. Playing 24 fps video on a 60 Hz
screen will play video in a 2-3-2-3-... pattern.
The default settings are not loose enough to speed up 23.976 fps video to
25 fps. We consider the pitch change too extreme to allow this behavior
by default. Set this option to a value of ``5`` to enable it.
Note that in the ``--video-sync=display-resample`` mode, audio speed will
additionally be changed by a small amount if necessary for A/V sync. See
``--video-sync-max-audio-change``.
``--video-sync-max-video-change=<value>``
Maximum *additional* speed difference in percent that is applied to audio
with ``--video-sync=display-...`` (default: 0.125). Normally, the player
play the audio at the speed of the video. But if the difference between
audio and video position is too high, e.g. due to drift or other timing
errors, it will attempt to speed up or slow down audio by this additional
factor. Too low values could lead to video frame dropping or repeating if
the A/V desync cannot be compensated, too high values could lead to chaotic
frame dropping due to the audio "overshooting" and skipping multiple video
frames before the sync logic can react.
``--mf-fps=<value>``
Framerate used when decoding from multiple PNG or JPEG files with ``mf://``
(default: 1).

View File

@ -518,6 +518,18 @@ const m_option_t mp_opts[] = {
OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0,
({"auto", 0}, {"decoder", 1}, {"sort", 2})),
OPT_FLAG("initial-audio-sync", initial_audio_sync, 0),
OPT_CHOICE("video-sync", video_sync, 0,
({"audio", VS_DEFAULT},
{"display-resample", VS_DISP_RESAMPLE},
{"display-resample-vdrop", VS_DISP_RESAMPLE_VDROP},
{"display-resample-desync", VS_DISP_RESAMPLE_NONE},
{"display-vdrop", VS_DISP_VDROP},
{"display-desync", VS_DISP_NONE},
{"desync", VS_NONE})),
OPT_DOUBLE("video-sync-max-video-change", sync_max_video_change,
M_OPT_MIN, .min = 0),
OPT_DOUBLE("video-sync-max-audio-change", sync_max_audio_change,
M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
OPT_CHOICE("hr-seek", hr_seek, 0,
({"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1})),
OPT_FLOAT("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0),
@ -710,6 +722,8 @@ const struct MPOpts mp_default_opts = {
.chapter_merge_threshold = 100,
.chapter_seek_threshold = 5.0,
.hr_seek_framedrop = 1,
.sync_max_video_change = 1,
.sync_max_audio_change = 0.125,
.load_config = 1,
.position_resume = 1,
.stream_cache = {

View File

@ -144,6 +144,9 @@ typedef struct MPOpts {
int correct_pts;
int user_pts_assoc_mode;
int initial_audio_sync;
int video_sync;
double sync_max_video_change;
double sync_max_audio_change;
int hr_seek;
float hr_seek_demuxer_offset;
int hr_seek_framedrop;

View File

@ -295,11 +295,26 @@ static int mp_property_av_speed_correction(void *ctx, struct m_property *prop,
{
MPContext *mpctx = ctx;
char *type = prop->priv;
double val = 0;
switch (type[0]) {
case 'a': return m_property_double_ro(action, arg, mpctx->speed_factor_a);
case 'v': return m_property_double_ro(action, arg, mpctx->speed_factor_v);
case 'a': val = mpctx->speed_factor_a; break;
case 'v': val = mpctx->speed_factor_v; break;
default: abort();
}
abort();
if (action == M_PROPERTY_PRINT) {
*(char **)arg = talloc_asprintf(NULL, "%+.05f%%", (val - 1) * 100);
return M_PROPERTY_OK;
}
return m_property_double_ro(action, arg, val);
}
static int mp_property_display_sync_active(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
return m_property_flag_ro(action, arg, mpctx->display_sync_active);
}
/// filename with path (RO)
@ -557,6 +572,16 @@ static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop,
return m_property_int_ro(action, arg, vo_get_drop_count(mpctx->video_out));
}
static int mp_property_vo_missed_frame_count(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
if (!mpctx->d_video)
return M_PROPERTY_UNAVAILABLE;
return m_property_int_ro(action, arg, vo_get_missed_count(mpctx->video_out));
}
/// Current position in percent (RW)
static int mp_property_percent_pos(void *ctx, struct m_property *prop,
int action, void *arg)
@ -3318,6 +3343,7 @@ static const struct m_property mp_properties[] = {
{"speed", mp_property_playback_speed},
{"audio-speed-correction", mp_property_av_speed_correction, .priv = "a"},
{"video-speed-correction", mp_property_av_speed_correction, .priv = "v"},
{"display-sync-active", mp_property_display_sync_active},
{"filename", mp_property_filename},
{"stream-open-filename", mp_property_stream_open_filename},
{"file-size", mp_property_file_size},
@ -3335,6 +3361,7 @@ static const struct m_property mp_properties[] = {
{"total-avsync-change", mp_property_total_avsync_change},
{"drop-frame-count", mp_property_drop_frame_cnt},
{"vo-drop-frame-count", mp_property_vo_drop_frame_count},
{"vo-missed-frame-count", mp_property_vo_missed_frame_count},
{"percent-pos", mp_property_percent_pos},
{"time-start", mp_property_time_start},
{"time-pos", mp_property_time_pos},
@ -3549,7 +3576,8 @@ static const char *const *const mp_event_property_change[] = {
E(MPV_EVENT_TICK, "time-pos", "stream-pos", "stream-time-pos", "avsync",
"percent-pos", "time-remaining", "playtime-remaining", "playback-time",
"estimated-vf-fps", "drop-frame-count", "vo-drop-frame-count",
"total-avsync-change", "audio-speed-correction", "video-speed-correction"),
"total-avsync-change", "audio-speed-correction", "video-speed-correction",
"vo-missed-frame-count"),
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
"width", "height", "fps", "aspect", "vo-configured", "current-vo",

View File

@ -79,6 +79,22 @@ enum seek_precision {
// Comes from the assumption that some formats round timestamps to ms.
#define FRAME_DURATION_TOLERANCE 0.0011
enum video_sync {
VS_DEFAULT = 0,
VS_DISP_RESAMPLE,
VS_DISP_RESAMPLE_VDROP,
VS_DISP_RESAMPLE_NONE,
VS_DISP_VDROP,
VS_DISP_NONE,
VS_NONE,
};
#define VS_IS_DISP(x) ((x) == VS_DISP_RESAMPLE || \
(x) == VS_DISP_RESAMPLE_VDROP || \
(x) == VS_DISP_RESAMPLE_NONE || \
(x) == VS_DISP_VDROP || \
(x) == VS_DISP_NONE)
struct track {
enum stream_type type;
@ -244,7 +260,13 @@ typedef struct MPContext {
// Redundant values set from opts->playback_speed and speed_factor_*.
// update_playback_speed() updates them from the other fields.
double audio_speed, video_speed;
bool display_sync_active;
bool broken_fps_header;
double display_sync_frameduration;
int display_sync_drift_dir;
// Timing error (in seconds) due to rounding on vsync boundaries
double display_sync_error;
int display_sync_disable_counter;
/* Set if audio should be timed to start with video frame after seeking,
* not set when e.g. playing cover art */
bool sync_audio_to_video;

View File

@ -1020,7 +1020,10 @@ static void play_current_file(struct MPContext *mpctx)
mpctx->max_frames = -1;
mpctx->video_speed = mpctx->audio_speed = opts->playback_speed;
mpctx->speed_factor_a = mpctx->speed_factor_v = 1.0;
mpctx->display_sync_frameduration = 0.0;
mpctx->display_sync_error = 0.0;
mpctx->broken_fps_header = false;
mpctx->display_sync_active = false;
mpctx->seek = (struct seek_params){ 0 };
reset_playback_state(mpctx);

View File

@ -225,6 +225,12 @@ static void print_status(struct MPContext *mpctx)
{
// VO stats
if (mpctx->d_video) {
if (mpctx->display_sync_active) {
saddf(&line, " DS: %f", mpctx->speed_factor_a);
int64_t m = vo_get_missed_count(mpctx->video_out);
if (m > 0)
saddf(&line, " Missed: %"PRId64, m);
}
int64_t c = vo_get_drop_count(mpctx->video_out);
if (c > 0 || mpctx->dropped_frames_total > 0) {
saddf(&line, " Dropped: %"PRId64, c);

View File

@ -42,6 +42,8 @@
#include "video/decode/dec_video.h"
#include "video/decode/vd.h"
#include "video/out/vo.h"
#include "audio/filter/af.h"
#include "audio/decode/dec_audio.h"
#include "core.h"
#include "command.h"
@ -206,9 +208,11 @@ void reset_video_state(struct MPContext *mpctx)
mpctx->video_next_pts = MP_NOPTS_VALUE;
mpctx->total_avsync_change = 0;
mpctx->last_av_difference = 0;
mpctx->display_sync_disable_counter = 0;
mpctx->dropped_frames_total = 0;
mpctx->dropped_frames = 0;
mpctx->drop_message_shown = 0;
mpctx->display_sync_drift_dir = 0;
mpctx->video_status = mpctx->d_video ? STATUS_SYNCING : STATUS_EOF;
}
@ -606,8 +610,9 @@ static int get_req_frames(struct MPContext *mpctx, bool eof)
if (eof || mpctx->video_pts == MP_NOPTS_VALUE)
return 1;
int min = 2 + (VS_IS_DISP(mpctx->opts->video_sync) ? 1 : 0);
int req = vo_get_num_req_frames(mpctx->video_out);
return MPCLAMP(req, 2, MP_ARRAY_SIZE(mpctx->next_frames));
return MPCLAMP(req, min, MP_ARRAY_SIZE(mpctx->next_frames));
}
// Whether it's fine to call add_new_frame() now.
@ -705,6 +710,8 @@ static void update_avsync_before_frame(struct MPContext *mpctx)
if (!mpctx->sync_audio_to_video || mpctx->video_status < STATUS_READY) {
mpctx->time_frame = 0;
} else if (mpctx->display_sync_active || opts->video_sync == VS_NONE) {
// don't touch the timing
} else if (mpctx->audio_status == STATUS_PLAYING &&
mpctx->video_status == STATUS_PLAYING &&
!ao_untimed(mpctx->ao))
@ -856,6 +863,196 @@ fail:
return require_exact ? -1 : total_duration / num;
}
static bool using_spdif_passthrough(struct MPContext *mpctx)
{
if (mpctx->d_audio && mpctx->d_audio->afilter)
return !af_fmt_is_pcm(mpctx->d_audio->afilter->output.format);
return false;
}
// Find a speed factor such that the display FPS is an integer multiple of the
// effective video FPS. If this is not possible, try to do it for multiples,
// which still leads to an improved end result.
// Both parameters are durations in seconds.
static double calc_best_speed(struct MPContext *mpctx, double vsync, double frame)
{
struct MPOpts *opts = mpctx->opts;
double ratio = frame / vsync;
for (int factor = 1; factor <= 5; factor++) {
double scale = ratio * factor / floor(ratio * factor + 0.5);
if (fabs(scale - 1) > opts->sync_max_video_change / 100)
continue; // large deviation, skip
return scale; // decent match found
}
return -1;
}
// Manipulate frame timing for display sync, or do nothing for normal timing.
static void handle_display_sync_frame(struct MPContext *mpctx,
struct vo_frame *frame)
{
struct MPOpts *opts = mpctx->opts;
struct vo *vo = mpctx->video_out;
bool old_display_sync = mpctx->display_sync_active;
int mode = opts->video_sync;
if (!mpctx->display_sync_active) {
mpctx->display_sync_error = 0.0;
mpctx->display_sync_drift_dir = 0;
}
mpctx->display_sync_active = false;
mpctx->speed_factor_a = 1.0;
mpctx->speed_factor_v = 1.0;
if (!VS_IS_DISP(mode))
goto done;
bool resample = mode == VS_DISP_RESAMPLE || mode == VS_DISP_RESAMPLE_VDROP ||
mode == VS_DISP_RESAMPLE_NONE;
bool drop = mode == VS_DISP_VDROP || mode == VS_DISP_RESAMPLE ||
mode == VS_DISP_RESAMPLE_VDROP;
drop &= (opts->frame_dropping & 1);
if (resample && using_spdif_passthrough(mpctx))
goto done;
double vsync = vo_get_vsync_interval(vo) / 1e6;
if (vsync <= 0)
goto done;
double adjusted_duration = stabilize_frame_duration(mpctx, true);
if (adjusted_duration >= 0)
adjusted_duration /= opts->playback_speed;
if (adjusted_duration <= 0.002 || adjusted_duration > 0.05)
goto done;
double prev_duration = mpctx->display_sync_frameduration;
mpctx->display_sync_frameduration = adjusted_duration;
if (adjusted_duration != prev_duration) {
mpctx->display_sync_disable_counter = 50;
goto done;
}
double video_speed_correction = calc_best_speed(mpctx, vsync, adjusted_duration);
if (video_speed_correction <= 0)
goto done;
double av_diff = mpctx->last_av_difference;
if (fabs(av_diff) > 0.5)
goto done;
// At this point, we decided that we could use display sync for this frame.
// But if we switch too often between these modes, keep it disabled. In
// fact, we disable it if it just wants to switch between enable/disable
// more than once in the last N frames.
if (!old_display_sync) {
if (mpctx->display_sync_disable_counter > 0)
goto done; // keep disabled
mpctx->display_sync_disable_counter = 50;
}
MP_STATS(mpctx, "value %f avdiff", av_diff);
// Intended number of additional display frames to drop (<0) or repeat (>0)
int drop_repeat = 0;
// If we are too far ahead/behind, attempt to drop/repeat frames. In
// particular, don't attempt to change speed for them.
if (drop) {
drop_repeat = -av_diff / vsync; // round towards 0
av_diff -= drop_repeat * vsync;
}
if (resample) {
double audio_factor = 1.0;
if (mode == VS_DISP_RESAMPLE && mpctx->audio_status == STATUS_PLAYING) {
// Try to smooth out audio timing drifts. This can happen if either
// video isn't playing at expected speed, or audio is not playing at
// the requested speed. Both are unavoidable.
// The audio desync is made up of 2 parts: 1. drift due to rounding
// errors and imperfect information, and 2. an offset, due to
// unaligned audio/video start, or disruptive events halting audio
// or video for a small time.
// Instead of trying to be clever, just apply an awfully dumb drift
// compensation with a constant factor, which does what we want. In
// theory we could calculate the exact drift compensation needed,
// but it likely would be wrong anyway, and we'd run into the same
// issues again, except with more complex code.
// 1 means drifts to positive, -1 means drifts to negative
double max_drift = vsync / 2;
int new = mpctx->display_sync_drift_dir;
if (av_diff * -mpctx->display_sync_drift_dir >= 0)
new = 0;
if (fabs(av_diff) > max_drift)
new = copysign(1, av_diff);
if (mpctx->display_sync_drift_dir != new) {
MP_VERBOSE(mpctx, "Change display sync audio drift: %d\n", new);
mpctx->display_sync_drift_dir = new;
}
double max_correct = opts->sync_max_audio_change / 100;
audio_factor = 1 + max_correct * -mpctx->display_sync_drift_dir;
}
mpctx->speed_factor_a = audio_factor * video_speed_correction;
MP_STATS(mpctx, "value %f aspeed", mpctx->speed_factor_a - 1);
}
// Determine for how many vsyncs a frame should be displayed. This can be
// e.g. 2 for 30hz on a 60hz display. It can also be 0 if the video
// framerate is higher than the display framerate.
// We use the speed-adjusted (i.e. real) frame duration for this.
double frame_duration = adjusted_duration / video_speed_correction;
double ratio = (frame_duration + mpctx->display_sync_error) / vsync;
int num_vsyncs = MPMAX(floor(ratio + 0.5), 0);
mpctx->display_sync_error += frame_duration - num_vsyncs * vsync;
frame->vsync_offset = mpctx->display_sync_error * 1e6;
MP_DBG(mpctx, "s=%f vsyncs=%d dur=%f ratio=%f err=%.20f (%f)\n",
video_speed_correction, num_vsyncs, adjusted_duration, ratio,
mpctx->display_sync_error, mpctx->display_sync_error / vsync);
// We can only drop all frames at most. We can repeat much more frames,
// but we still limit it to 10 times the original frames to avoid that
// corner cases or exceptional situations cause too much havoc.
drop_repeat = MPCLAMP(drop_repeat, -num_vsyncs, num_vsyncs * 10);
num_vsyncs += drop_repeat;
if (drop_repeat < 0)
vo_increment_drop_count(vo, 1);
// Estimate the video position, so we can calculate a good A/V difference
// value with update_avsync_after_frame() later. This is used to estimate
// A/V drift.
mpctx->time_frame = 0;
double time_left = (vo_get_next_frame_start_time(vo) - mp_time_us()) / 1e6;
if (time_left >= 0)
mpctx->time_frame += time_left;
// We also know that the timing is (necessarily) off, because we have to
// align frame timings on the vsync boundaries. This is unavoidable, and
// for the sake of the video sync calculations we pretend it's perfect.
mpctx->time_frame -= mpctx->display_sync_error;
mpctx->speed_factor_v = video_speed_correction;
frame->num_vsyncs = num_vsyncs;
frame->display_synced = true;
mpctx->display_sync_active = true;
done:
update_playback_speed(mpctx);
if (old_display_sync != mpctx->display_sync_active) {
MP_VERBOSE(mpctx, "Video sync mode %s.\n",
mpctx->display_sync_active ? "enabled" : "disabled");
}
mpctx->display_sync_disable_counter =
MPMAX(0, mpctx->display_sync_disable_counter - 1);
}
// Return the next frame duration as stored in the file.
// frame=0 means the current frame, 1 the frame after that etc.
// Can return -1, though usually will return a fallback if frame unavailable.
@ -949,7 +1146,9 @@ void write_video(struct MPContext *mpctx, double endpts)
int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6);
// wait until VO wakes us up to get more frames
if (!vo_is_ready_for_frame(vo, pts)) {
// (NB: in theory, the 1st frame after display sync mode change uses the
// wrong waiting mode)
if (!vo_is_ready_for_frame(vo, mpctx->display_sync_active ? -1 : pts)) {
if (video_feed_async_filter(mpctx) < 0)
goto error;
return;
@ -960,6 +1159,7 @@ void write_video(struct MPContext *mpctx, double endpts)
.pts = pts,
.duration = -1,
.num_frames = mpctx->num_next_frames,
.num_vsyncs = 1,
};
for (int n = 0; n < dummy.num_frames; n++)
dummy.frames[n] = mpctx->next_frames[n];
@ -974,6 +1174,8 @@ void write_video(struct MPContext *mpctx, double endpts)
frame->duration = MPCLAMP(diff, 0, 10) * 1e6;
}
handle_display_sync_frame(mpctx, frame);
mpctx->video_pts = mpctx->next_frames[0]->pts;
mpctx->last_vo_pts = mpctx->video_pts;
mpctx->playback_pts = mpctx->video_pts;

View File

@ -139,6 +139,7 @@ struct vo_internal {
int64_t flip_queue_offset; // queue flip events at most this much in advance
int64_t missed_count;
int64_t drop_count;
bool dropped_frame; // the previous frame was dropped
@ -418,9 +419,14 @@ static void forget_frames(struct vo *vo)
in->hasframe = false;
in->hasframe_rendered = false;
in->drop_count = 0;
in->missed_count = 0;
talloc_free(in->frame_queued);
in->frame_queued = NULL;
// don't unref current_frame; we always want to be able to redraw it
if (in->current_frame) {
in->current_frame->num_vsyncs = 0; // but reset future repeats
in->current_frame->display_synced = false; // mark discontinuity
}
}
#ifndef __MINGW32__
@ -508,12 +514,15 @@ void vo_wakeup(struct vo *vo)
// 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.
// If next_pts is negative, disable any timing and draw the frame as fast as
// possible.
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->frame_queued;
if (r) {
bool r = vo->config_ok && !in->frame_queued &&
(!in->current_frame || in->current_frame->num_vsyncs < 1);
if (r && next_pts >= 0) {
// 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.
@ -538,10 +547,12 @@ void vo_queue_frame(struct vo *vo, struct vo_frame *frame)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
assert(vo->config_ok && !in->frame_queued);
assert(vo->config_ok && !in->frame_queued &&
(!in->current_frame || in->current_frame->num_vsyncs < 1));
in->hasframe = true;
in->frame_queued = frame;
in->wakeup_pts = in->vsync_timed ? 0 : frame->pts + MPMAX(frame->duration, 0);
in->wakeup_pts = (frame->display_synced || in->vsync_timed)
? 0 : frame->pts + MPMAX(frame->duration, 0);
wakeup_locked(vo);
pthread_mutex_unlock(&in->lock);
}
@ -598,19 +609,29 @@ static bool render_frame(struct vo *vo)
vo->in->vsync_interval = in->display_fps > 0 ? 1e6 / in->display_fps : 0;
vo->in->vsync_interval = MPMAX(vo->in->vsync_interval, 1);
bool continuous = in->current_frame && in->current_frame->display_synced;
if (in->frame_queued) {
talloc_free(in->current_frame);
in->current_frame = in->frame_queued;
in->frame_queued = NULL;
} else if (in->paused || !in->current_frame || !in->hasframe ||
!in->vsync_timed)
(!in->vsync_timed && !in->current_frame->display_synced))
{
goto done;
}
if (in->current_frame->display_synced && in->current_frame->num_vsyncs < 1)
goto done;
frame = vo_frame_ref(in->current_frame);
assert(frame);
if (frame->display_synced) {
frame->pts = 0;
frame->duration = -1;
}
int64_t pts = frame->pts;
int64_t duration = frame->duration;
int64_t end_time = pts + duration;
@ -621,11 +642,10 @@ static bool render_frame(struct vo *vo)
frame->next_vsync = next_vsync;
frame->prev_vsync = prev_vsync;
frame->vsync_offset = next_vsync - pts;
frame->num_vsyncs = 1;
// Time at which we should flip_page on the VO.
int64_t target = pts - in->flip_queue_offset;
int64_t target = frame->display_synced ? 0 : pts - in->flip_queue_offset;
bool prev_dropped_frame = in->dropped_frame;
@ -650,6 +670,7 @@ static bool render_frame(struct vo *vo)
in->dropped_frame |= end_time < prev_vsync;
}
in->dropped_frame &= !frame->display_synced;
in->dropped_frame &= !(vo->driver->caps & VO_CAP_FRAMEDROP);
in->dropped_frame &= (vo->global->opts->frame_dropping & 1);
// Even if we're hopelessly behind, rather degrade to 10 FPS playback,
@ -657,7 +678,7 @@ static bool render_frame(struct vo *vo)
in->dropped_frame &= mp_time_us() - in->last_flip < 100 * 1000;
in->dropped_frame &= in->hasframe_rendered;
if (in->vsync_timed) {
if (in->vsync_timed && !frame->display_synced) {
// this is a heuristic that wakes the thread up some
// time before the next vsync
target = next_vsync - MPMIN(in->vsync_interval / 2, 8e3);
@ -672,11 +693,17 @@ static bool render_frame(struct vo *vo)
in->dropped_frame = false;
goto done;
}
frame->vsync_offset = next_vsync - pts;
}
// Setup parameters for the next time this frame is drawn. ("frame" is the
// frame currently drawn, while in->current_frame is the potentially next.)
in->current_frame->repeat = true;
if (frame->display_synced)
in->current_frame->vsync_offset += in->vsync_interval;
if (in->current_frame->num_vsyncs > 0)
in->current_frame->num_vsyncs -= 1;
if (in->dropped_frame) {
in->drop_count += 1;
@ -711,10 +738,15 @@ static bool render_frame(struct vo *vo)
in->vsync_interval_approx = in->last_flip - prev_flip;
MP_STATS(vo, "end video");
MP_STATS(vo, "video_end");
pthread_mutex_lock(&in->lock);
in->dropped_frame = prev_drop_count < vo->in->drop_count;
in->rendering = false;
if (in->current_frame && in->current_frame->display_synced &&
continuous && in->vsync_interval_approx > in->vsync_interval * 3 / 2)
in->missed_count += 1;
}
if (!in->dropped_frame) {
@ -896,8 +928,11 @@ bool vo_still_displaying(struct vo *vo)
pthread_mutex_lock(&vo->in->lock);
int64_t now = mp_time_us();
int64_t frame_end = 0;
if (in->current_frame)
if (in->current_frame) {
frame_end = in->current_frame->pts + MPMAX(in->current_frame->duration, 0);
if (in->current_frame->num_vsyncs > 0)
frame_end = INT64_MAX;
}
bool working = now < frame_end || in->rendering || in->frame_queued;
pthread_mutex_unlock(&vo->in->lock);
return working && in->hasframe;
@ -991,6 +1026,39 @@ int64_t vo_get_vsync_interval(struct vo *vo)
return res;
}
// Get the mp_time_us() time at which the currently rendering frame will end
// (i.e. time of the last flip call needed to display it).
// This can only be called while no new frame is queued (after
// vo_is_ready_for_frame). Returns 0 for non-display synced frames, or if the
// deadline for continuous display was missed.
int64_t vo_get_next_frame_start_time(struct vo *vo)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
assert (!in->frame_queued);
int64_t res = 0;
if (in->last_flip && in->vsync_interval > 1 && in->current_frame) {
res = in->last_flip;
int extra = !!in->rendering;
res += (in->current_frame->num_vsyncs + extra) * in->vsync_interval;
if (!in->current_frame->display_synced)
res = 0;
if (in->current_frame->num_vsyncs < 1 && !in->rendering)
res = 0;
}
pthread_mutex_unlock(&in->lock);
return res;
}
int64_t vo_get_missed_count(struct vo *vo)
{
struct vo_internal *in = vo->in;
pthread_mutex_lock(&in->lock);
int64_t res = vo->in->missed_count;
pthread_mutex_unlock(&in->lock);
return res;
}
double vo_get_display_fps(struct vo *vo)
{
struct vo_internal *in = vo->in;

View File

@ -165,6 +165,8 @@ struct vo_frame {
int64_t prev_vsync;
// "ideal" display time within the vsync
int64_t vsync_offset;
// how often the frame will be repeated (does not include OSD redraws)
int num_vsyncs;
// Set if the current frame is repeated from the previous. It's guaranteed
// that the current is the same as the previous one, even if the image
// pointer is different.
@ -173,6 +175,8 @@ struct vo_frame {
bool redraw, repeat;
// The frame is not in movement - e.g. redrawing while paused.
bool still;
// Frames are output as fast as possible, with implied vsync blocking.
bool display_synced;
// The current frame to be drawn.
// Warning: When OSD should be redrawn in --force-window --idle mode, this
// can be NULL. The VO should draw a black background, OSD on top.
@ -333,6 +337,7 @@ void vo_destroy(struct vo *vo);
void vo_set_paused(struct vo *vo, bool paused);
int64_t vo_get_drop_count(struct vo *vo);
void vo_increment_drop_count(struct vo *vo, int64_t n);
int64_t vo_get_missed_count(struct vo *vo);
void vo_query_formats(struct vo *vo, uint8_t *list);
void vo_event(struct vo *vo, int event);
int vo_query_and_reset_events(struct vo *vo, int events);
@ -342,6 +347,7 @@ void vo_set_queue_params(struct vo *vo, int64_t offset_us, bool vsync_timed,
int vo_get_num_req_frames(struct vo *vo);
int64_t vo_get_vsync_interval(struct vo *vo);
double vo_get_display_fps(struct vo *vo);
int64_t vo_get_next_frame_start_time(struct vo *vo);
void vo_wakeup(struct vo *vo);