aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--DOCS/interface-changes.rst5
-rw-r--r--DOCS/man/input.rst6
-rw-r--r--DOCS/man/mpv.rst7
-rw-r--r--DOCS/man/options.rst66
-rw-r--r--options/options.c14
-rw-r--r--options/options.h3
-rw-r--r--player/command.c36
-rw-r--r--player/core.h22
-rw-r--r--player/loadfile.c3
-rw-r--r--player/osd.c6
-rw-r--r--player/video.c206
-rw-r--r--video/out/vo.c88
-rw-r--r--video/out/vo.h6
13 files changed, 451 insertions, 17 deletions
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index 20b9664e59..e74296c557 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -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)
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 10be785ec5..9910d6b9c0 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -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
diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst
index 97c70e910b..e2487ddfda 100644
--- a/DOCS/man/mpv.rst
+++ b/DOCS/man/mpv.rst
@@ -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
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 4e60e64c22..883c13c9d8 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -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).
diff --git a/options/options.c b/options/options.c
index 963751f896..28466ed0ea 100644
--- a/options/options.c
+++ b/options/options.c
@@ -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 = {
diff --git a/options/options.h b/options/options.h
index 4382831883..1b65b4af4d 100644
--- a/options/options.h
+++ b/options/options.h
@@ -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;
diff --git a/player/command.c b/player/command.c
index 486c7262b8..c64427f930 100644
--- a/player/command.c
+++ b/player/command.c
@@ -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",
diff --git a/player/core.h b/player/core.h
index 092775f40e..6ac146624e 100644
--- a/player/core.h
+++ b/player/core.h
@@ -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;
diff --git a/player/loadfile.c b/player/loadfile.c
index 23ffafc1b9..fc4c294fc7 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -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);
diff --git a/player/osd.c b/player/osd.c
index 0bdb368f7e..ace465b18d 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -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);
diff --git a/player/video.c b/player/video.c
index 13f40430de..e8f4837e69 100644
--- a/player/video.c
+++ b/player/video.c
@@ -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;
diff --git a/video/out/vo.c b/video/out/vo.c
index 9960004e67..de79fb018e 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -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;
diff --git a/video/out/vo.h b/video/out/vo.h
index 06a923bf41..4404500e67 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -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);