Skip to content

Commit 031555f

Browse files
author
wm4
committed
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.
1 parent fedaad8 commit 031555f

File tree

13 files changed

+451
-17
lines changed

13 files changed

+451
-17
lines changed

DOCS/interface-changes.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ Interface changes
2020
::
2121

2222
--- mpv 0.10.0 will be released ---
23-
- add "audio-speed-correction" and "video-speed-correction" properties
23+
- add --video-sync* options
24+
"display-sync-active" property
25+
"vo-missed-frame-count" property
26+
"audio-speed-correction" and "video-speed-correction" properties
2427
- remove --demuxer-readahead-packets and --demuxer-readahead-bytes
2528
add --demuxer-max-packets and --demuxer-max-bytes
2629
(the new options are not replacement and have very different semantics)

DOCS/man/input.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,12 @@ Property list
830830
Factor multiplied with ``speed`` at which the player attempts to play the
831831
file. Usually it's exactly 1. (Display sync mode will make this useful.)
832832

833+
OSD formatting will display it in the form of ``+1.23456%``, with the number
834+
being ``(raw - 1) * 100`` for the given raw property value.
835+
836+
``display-sync-active``
837+
Return whether ``--video-sync=display`` is actually active.
838+
833839
``filename``
834840
Currently played file, with path stripped. If this is an URL, try to undo
835841
percent encoding as well. (The result is not necessarily correct, but

DOCS/man/mpv.rst

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

DOCS/man/options.rst

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3363,6 +3363,72 @@ Miscellaneous
33633363
out. This delay in reaction time to sudden A/V offsets should be the only
33643364
side-effect of turning this option on, for all sound drivers.
33653365

3366+
``--video-sync=<audio|...>``
3367+
How the player synchronizes audio and video.
3368+
3369+
The modes starting with ``display-`` try to output video frames completely
3370+
synchronously to the display, using the detected display vertical refresh
3371+
rate as a hint how fast frames will be displayed on average. These modes
3372+
change video speed slightly to match the display. See ``--video-sync-...``
3373+
options for fine tuning. The robustness of this mode is further reduced by
3374+
making a some idealized assumptions, which may not always apply in reality.
3375+
Behavior can depend on the VO and the system's video and audio drivers.
3376+
Media files must use constant framerate. Section-wise VFR might work as well
3377+
with some container formats (but not e.g. mkv). If the sync code detects
3378+
severe A/V desync, or the framerate cannot be detected, the player
3379+
automatically reverts to ``audio`` mode for some time or permanently.
3380+
3381+
The modes with ``desync`` in their names do not attempt to keep audio/video
3382+
in sync. They will slowly (or quickly) desync, until e.g. the next seek
3383+
happens. These modes are meant for testing, not serious use.
3384+
3385+
:audio: Time video frames to audio. This is the most robust
3386+
mode, because the player doesn't have to assume anything
3387+
about how the display behaves. The disadvantage is that
3388+
it can lead to occasional frame drops or repeats. If
3389+
audio is disabled, this uses the system clock. This is
3390+
the default mode.
3391+
:display-resample: Resample audio to match the video. This mode will also
3392+
try to adjust audio speed to compensate for other drift.
3393+
(This means it will play the audio at a different speed
3394+
every once in a while to reduce the A/V difference.)
3395+
:display-resample-vdrop: Resample audio to match the video. Drop video
3396+
frames to compensate for drift.
3397+
:display-resample-desync: Like the previous mode, but no A/V compensation.
3398+
:display-vdrop: Drop or repeat video frames to compensate desyncing
3399+
video. (Although it should have the same effects as
3400+
``audio``, the implementation is very different.)
3401+
:display-desync: Sync video to display, and let audio play on its own.
3402+
:desync: Sync video according to system clock, and let audio play
3403+
on its own.
3404+
3405+
``--video-sync-max-video-change=<value>``
3406+
Maximum speed difference in percent that is applied to video with
3407+
``--video-sync=display-...`` (default: 1). Display sync mode will be
3408+
disabled if the monitor and video refresh way do not match within the
3409+
given range. It tries multiples as well: playing 30 fps video on a 60 Hz
3410+
screen will duplicate every second frame. Playing 24 fps video on a 60 Hz
3411+
screen will play video in a 2-3-2-3-... pattern.
3412+
3413+
The default settings are not loose enough to speed up 23.976 fps video to
3414+
25 fps. We consider the pitch change too extreme to allow this behavior
3415+
by default. Set this option to a value of ``5`` to enable it.
3416+
3417+
Note that in the ``--video-sync=display-resample`` mode, audio speed will
3418+
additionally be changed by a small amount if necessary for A/V sync. See
3419+
``--video-sync-max-audio-change``.
3420+
3421+
``--video-sync-max-video-change=<value>``
3422+
Maximum *additional* speed difference in percent that is applied to audio
3423+
with ``--video-sync=display-...`` (default: 0.125). Normally, the player
3424+
play the audio at the speed of the video. But if the difference between
3425+
audio and video position is too high, e.g. due to drift or other timing
3426+
errors, it will attempt to speed up or slow down audio by this additional
3427+
factor. Too low values could lead to video frame dropping or repeating if
3428+
the A/V desync cannot be compensated, too high values could lead to chaotic
3429+
frame dropping due to the audio "overshooting" and skipping multiple video
3430+
frames before the sync logic can react.
3431+
33663432
``--mf-fps=<value>``
33673433
Framerate used when decoding from multiple PNG or JPEG files with ``mf://``
33683434
(default: 1).

options/options.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,18 @@ const m_option_t mp_opts[] = {
518518
OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0,
519519
({"auto", 0}, {"decoder", 1}, {"sort", 2})),
520520
OPT_FLAG("initial-audio-sync", initial_audio_sync, 0),
521+
OPT_CHOICE("video-sync", video_sync, 0,
522+
({"audio", VS_DEFAULT},
523+
{"display-resample", VS_DISP_RESAMPLE},
524+
{"display-resample-vdrop", VS_DISP_RESAMPLE_VDROP},
525+
{"display-resample-desync", VS_DISP_RESAMPLE_NONE},
526+
{"display-vdrop", VS_DISP_VDROP},
527+
{"display-desync", VS_DISP_NONE},
528+
{"desync", VS_NONE})),
529+
OPT_DOUBLE("video-sync-max-video-change", sync_max_video_change,
530+
M_OPT_MIN, .min = 0),
531+
OPT_DOUBLE("video-sync-max-audio-change", sync_max_audio_change,
532+
M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 1),
521533
OPT_CHOICE("hr-seek", hr_seek, 0,
522534
({"no", -1}, {"absolute", 0}, {"yes", 1}, {"always", 1})),
523535
OPT_FLOAT("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0),
@@ -710,6 +722,8 @@ const struct MPOpts mp_default_opts = {
710722
.chapter_merge_threshold = 100,
711723
.chapter_seek_threshold = 5.0,
712724
.hr_seek_framedrop = 1,
725+
.sync_max_video_change = 1,
726+
.sync_max_audio_change = 0.125,
713727
.load_config = 1,
714728
.position_resume = 1,
715729
.stream_cache = {

options/options.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ typedef struct MPOpts {
144144
int correct_pts;
145145
int user_pts_assoc_mode;
146146
int initial_audio_sync;
147+
int video_sync;
148+
double sync_max_video_change;
149+
double sync_max_audio_change;
147150
int hr_seek;
148151
float hr_seek_demuxer_offset;
149152
int hr_seek_framedrop;

player/command.c

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,26 @@ static int mp_property_av_speed_correction(void *ctx, struct m_property *prop,
295295
{
296296
MPContext *mpctx = ctx;
297297
char *type = prop->priv;
298+
double val = 0;
298299
switch (type[0]) {
299-
case 'a': return m_property_double_ro(action, arg, mpctx->speed_factor_a);
300-
case 'v': return m_property_double_ro(action, arg, mpctx->speed_factor_v);
300+
case 'a': val = mpctx->speed_factor_a; break;
301+
case 'v': val = mpctx->speed_factor_v; break;
302+
default: abort();
301303
}
302-
abort();
304+
305+
if (action == M_PROPERTY_PRINT) {
306+
*(char **)arg = talloc_asprintf(NULL, "%+.05f%%", (val - 1) * 100);
307+
return M_PROPERTY_OK;
308+
}
309+
310+
return m_property_double_ro(action, arg, val);
311+
}
312+
313+
static int mp_property_display_sync_active(void *ctx, struct m_property *prop,
314+
int action, void *arg)
315+
{
316+
MPContext *mpctx = ctx;
317+
return m_property_flag_ro(action, arg, mpctx->display_sync_active);
303318
}
304319

305320
/// filename with path (RO)
@@ -557,6 +572,16 @@ static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop,
557572
return m_property_int_ro(action, arg, vo_get_drop_count(mpctx->video_out));
558573
}
559574

575+
static int mp_property_vo_missed_frame_count(void *ctx, struct m_property *prop,
576+
int action, void *arg)
577+
{
578+
MPContext *mpctx = ctx;
579+
if (!mpctx->d_video)
580+
return M_PROPERTY_UNAVAILABLE;
581+
582+
return m_property_int_ro(action, arg, vo_get_missed_count(mpctx->video_out));
583+
}
584+
560585
/// Current position in percent (RW)
561586
static int mp_property_percent_pos(void *ctx, struct m_property *prop,
562587
int action, void *arg)
@@ -3318,6 +3343,7 @@ static const struct m_property mp_properties[] = {
33183343
{"speed", mp_property_playback_speed},
33193344
{"audio-speed-correction", mp_property_av_speed_correction, .priv = "a"},
33203345
{"video-speed-correction", mp_property_av_speed_correction, .priv = "v"},
3346+
{"display-sync-active", mp_property_display_sync_active},
33213347
{"filename", mp_property_filename},
33223348
{"stream-open-filename", mp_property_stream_open_filename},
33233349
{"file-size", mp_property_file_size},
@@ -3335,6 +3361,7 @@ static const struct m_property mp_properties[] = {
33353361
{"total-avsync-change", mp_property_total_avsync_change},
33363362
{"drop-frame-count", mp_property_drop_frame_cnt},
33373363
{"vo-drop-frame-count", mp_property_vo_drop_frame_count},
3364+
{"vo-missed-frame-count", mp_property_vo_missed_frame_count},
33383365
{"percent-pos", mp_property_percent_pos},
33393366
{"time-start", mp_property_time_start},
33403367
{"time-pos", mp_property_time_pos},
@@ -3549,7 +3576,8 @@ static const char *const *const mp_event_property_change[] = {
35493576
E(MPV_EVENT_TICK, "time-pos", "stream-pos", "stream-time-pos", "avsync",
35503577
"percent-pos", "time-remaining", "playtime-remaining", "playback-time",
35513578
"estimated-vf-fps", "drop-frame-count", "vo-drop-frame-count",
3552-
"total-avsync-change", "audio-speed-correction", "video-speed-correction"),
3579+
"total-avsync-change", "audio-speed-correction", "video-speed-correction",
3580+
"vo-missed-frame-count"),
35533581
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
35543582
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
35553583
"width", "height", "fps", "aspect", "vo-configured", "current-vo",

player/core.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ enum seek_precision {
7979
// Comes from the assumption that some formats round timestamps to ms.
8080
#define FRAME_DURATION_TOLERANCE 0.0011
8181

82+
enum video_sync {
83+
VS_DEFAULT = 0,
84+
VS_DISP_RESAMPLE,
85+
VS_DISP_RESAMPLE_VDROP,
86+
VS_DISP_RESAMPLE_NONE,
87+
VS_DISP_VDROP,
88+
VS_DISP_NONE,
89+
VS_NONE,
90+
};
91+
92+
#define VS_IS_DISP(x) ((x) == VS_DISP_RESAMPLE || \
93+
(x) == VS_DISP_RESAMPLE_VDROP || \
94+
(x) == VS_DISP_RESAMPLE_NONE || \
95+
(x) == VS_DISP_VDROP || \
96+
(x) == VS_DISP_NONE)
97+
8298
struct track {
8399
enum stream_type type;
84100

@@ -244,7 +260,13 @@ typedef struct MPContext {
244260
// Redundant values set from opts->playback_speed and speed_factor_*.
245261
// update_playback_speed() updates them from the other fields.
246262
double audio_speed, video_speed;
263+
bool display_sync_active;
247264
bool broken_fps_header;
265+
double display_sync_frameduration;
266+
int display_sync_drift_dir;
267+
// Timing error (in seconds) due to rounding on vsync boundaries
268+
double display_sync_error;
269+
int display_sync_disable_counter;
248270
/* Set if audio should be timed to start with video frame after seeking,
249271
* not set when e.g. playing cover art */
250272
bool sync_audio_to_video;

player/loadfile.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,10 @@ static void play_current_file(struct MPContext *mpctx)
10201020
mpctx->max_frames = -1;
10211021
mpctx->video_speed = mpctx->audio_speed = opts->playback_speed;
10221022
mpctx->speed_factor_a = mpctx->speed_factor_v = 1.0;
1023+
mpctx->display_sync_frameduration = 0.0;
1024+
mpctx->display_sync_error = 0.0;
10231025
mpctx->broken_fps_header = false;
1026+
mpctx->display_sync_active = false;
10241027
mpctx->seek = (struct seek_params){ 0 };
10251028

10261029
reset_playback_state(mpctx);

player/osd.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ static void print_status(struct MPContext *mpctx)
225225
{
226226
// VO stats
227227
if (mpctx->d_video) {
228+
if (mpctx->display_sync_active) {
229+
saddf(&line, " DS: %f", mpctx->speed_factor_a);
230+
int64_t m = vo_get_missed_count(mpctx->video_out);
231+
if (m > 0)
232+
saddf(&line, " Missed: %"PRId64, m);
233+
}
228234
int64_t c = vo_get_drop_count(mpctx->video_out);
229235
if (c > 0 || mpctx->dropped_frames_total > 0) {
230236
saddf(&line, " Dropped: %"PRId64, c);

0 commit comments

Comments
 (0)