From 9774be0d15e9477db521466cb5f77f07a46cc7c2 Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 11 Nov 2015 19:27:42 +0100 Subject: af_lavrresample: simplify set_compensation usage Just set the ratio directly by working around the intended semantics of the API function. The silly rounding stuff we had isn't needed anymore (and not entirely correct anyway). Note that since the compensation is virtually active forever, we need to reset if it's not needed. So always run this code to be sure to reset it. Also note that libswresample itself had a precision issue, until it was fixed in FFmpeg commit 351e625d. --- audio/filter/af_lavrresample.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/audio/filter/af_lavrresample.c b/audio/filter/af_lavrresample.c index 721bb30165..8f2efec858 100644 --- a/audio/filter/af_lavrresample.c +++ b/audio/filter/af_lavrresample.c @@ -91,8 +91,6 @@ struct af_resample { int out_rate; int out_format; struct mp_chmap out_channels; - - double missing_samples; // fractional samples not yet output }; #if HAVE_LIBAVRESAMPLE @@ -226,8 +224,6 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in, s->out_channels= out->channels; s->in_channels = in->channels; - s->missing_samples = 0; - av_opt_set_int(s->avrctx, "filter_size", s->opts.filter_size, 0); av_opt_set_int(s->avrctx, "phase_shift", s->opts.phase_shift, 0); av_opt_set_int(s->avrctx, "linear_interp", s->opts.linear, 0); @@ -521,15 +517,18 @@ static int filter(struct af_instance *af, struct mp_audio *in) int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed); bool need_reinit = fabs(new_rate / (double)s->in_rate - 1) > 0.01; - if (!need_reinit && s->avrctx) { - double speed_factor = s->playback_speed * s->in_rate_af / s->in_rate; - int in_samples = in ? in->samples : 0; - double wanted_samples = in_samples / speed_factor + s->missing_samples; - int wanted_samples_i = lrint(wanted_samples); - s->missing_samples = wanted_samples - wanted_samples_i; - if (avresample_set_compensation(s->avrctx, - (wanted_samples_i - in_samples) * s->out_rate / s->in_rate, - wanted_samples_i * s->out_rate / s->in_rate) < 0) + if (s->avrctx) { + AVRational r = av_d2q(s->playback_speed * s->in_rate_af / s->in_rate, + INT_MAX / 2); + // Essentially, swr/avresample_set_compensation() does 2 things: + // - adjust output sample rate by sample_delta/compensation_distance + // - reset the adjustment after compensation_distance output samples + // Increase the compensation_distance to avoid undesired reset + // semantics - we want to keep the ratio for the whole frame we're + // feeding it, until the next filter() call. + int mult = INT_MAX / 2 / MPMAX(MPMAX(abs(r.num), abs(r.den)), 1); + r = (AVRational){ r.num * mult, r.den * mult }; + if (avresample_set_compensation(s->avrctx, r.den - r.num, r.den) < 0) need_reinit = true; } -- cgit v1.2.3 From b7398cbdd89f083db564b03bc1dc25bdf6544ed6 Mon Sep 17 00:00:00 2001 From: rr- Date: Wed, 11 Nov 2015 19:06:50 +0100 Subject: drm: fix setting up connectors Fixes regression from 67caea357c23443cf583ad401a38bbaae19e3df8. --- video/out/drm_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/video/out/drm_common.c b/video/out/drm_common.c index 3462a5d4de..d3a05f56b0 100644 --- a/video/out/drm_common.c +++ b/video/out/drm_common.c @@ -194,7 +194,7 @@ bool kms_setup(struct kms *kms, const char *device_path, int connector_id, int m return false; } - if (!setup_connector(kms, res, mode_id)) + if (!setup_connector(kms, res, connector_id)) return false; if (!setup_crtc(kms, res)) return false; -- cgit v1.2.3 From 7c4fe02d01322ad09954d63522c0c8bf3f06b3d1 Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 11 Nov 2015 19:52:35 +0100 Subject: player: less naive rounding --- player/video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player/video.c b/player/video.c index d09cf3800a..0aebc6a7e9 100644 --- a/player/video.c +++ b/player/video.c @@ -1004,7 +1004,7 @@ static void handle_display_sync_frame(struct MPContext *mpctx, // We use the speed-adjusted (i.e. real) frame duration for this. double frame_duration = adjusted_duration / mpctx->speed_factor_v; double ratio = (frame_duration + mpctx->display_sync_error) / vsync; - int num_vsyncs = MPMAX(floor(ratio + 0.5), 0); + int num_vsyncs = MPMAX(lrint(ratio), 0); double prev_error = mpctx->display_sync_error; mpctx->display_sync_error += frame_duration - num_vsyncs * vsync; frame->vsync_offset = mpctx->display_sync_error * 1e6; -- cgit v1.2.3 From 977869a804b0fca932bcbe1d26b756d2e9860d0f Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 11 Nov 2015 21:25:08 +0100 Subject: demux_lavf: mark ASS tracks as always UTF-8 Stops mpv from trying to run a subtitle charset detector on .ass files loaded by libavformat. --- demux/demux_lavf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index eeed7a1b9a..2d12f5cfa2 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -612,6 +612,10 @@ static void handle_stream(demuxer_t *demuxer, int i) sh_sub->frame_based = 23.976; } } + + if (matches_avinputformat_name(priv, "ass")) + sh_sub->is_utf8 = true; + break; } case AVMEDIA_TYPE_ATTACHMENT: { -- cgit v1.2.3 From 384b13c5fd3fa87bad4bedbb3db369cdfb05de2b Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 11 Nov 2015 21:28:20 +0100 Subject: demux_libass: remove this demuxer This loaded external .ass files via libass. libavformat's .ass reader is now good enough, so use that instead. Apparently libavformat still doesn't support fonts embedded into text .ass files, but support for this has been accidentally broken in mpv for a while anyway. (And only 1 person complained.) --- TOOLS/old-makefile | 3 +- demux/demux.c | 4 -- demux/demux_libass.c | 111 --------------------------------------------------- misc/charset_conv.c | 18 --------- misc/charset_conv.h | 2 - wscript_build.py | 1 - 6 files changed, 1 insertion(+), 138 deletions(-) delete mode 100644 demux/demux_libass.c diff --git a/TOOLS/old-makefile b/TOOLS/old-makefile index 5db558b815..29d9b73eed 100644 --- a/TOOLS/old-makefile +++ b/TOOLS/old-makefile @@ -36,8 +36,7 @@ SOURCES-$(DVDNAV) += stream/stream_dvdnav.c \ stream/stream_dvd_common.c SOURCES-$(RUBBERBAND) += audio/filter/af_rubberband.c -SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c \ - demux/demux_libass.c +SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c SOURCES-$(LIBBLURAY) += stream/stream_bluray.c diff --git a/demux/demux.c b/demux/demux.c index 3233541be2..a00a3617cc 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -48,7 +48,6 @@ extern const demuxer_desc_t demuxer_desc_tv; extern const demuxer_desc_t demuxer_desc_mf; extern const demuxer_desc_t demuxer_desc_matroska; extern const demuxer_desc_t demuxer_desc_lavf; -extern const demuxer_desc_t demuxer_desc_libass; extern const demuxer_desc_t demuxer_desc_subreader; extern const demuxer_desc_t demuxer_desc_playlist; extern const demuxer_desc_t demuxer_desc_disc; @@ -67,9 +66,6 @@ const demuxer_desc_t *const demuxer_list[] = { &demuxer_desc_rawvideo, #if HAVE_TV &demuxer_desc_tv, -#endif -#if HAVE_LIBASS - &demuxer_desc_libass, #endif &demuxer_desc_matroska, #if HAVE_LIBARCHIVE diff --git a/demux/demux_libass.c b/demux/demux_libass.c deleted file mode 100644 index ef09f042ed..0000000000 --- a/demux/demux_libass.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * This file is part of mpv. - * - * mpv is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * mpv is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with mpv. If not, see . - */ - -// Note: just wraps libass, and makes the subtitle track available though -// sh_sub->track. It doesn't produce packets and doesn't support seeking. - -#include -#include - -#include "options/options.h" -#include "common/msg.h" -#include "misc/charset_conv.h" -#include "stream/stream.h" -#include "demux.h" - -#define PROBE_SIZE (8 * 1024) - -static void message_callback(int level, const char *format, va_list va, void *ctx) -{ - // ignore -} - -static int d_check_file(struct demuxer *demuxer, enum demux_check check) -{ - const char *user_cp = demuxer->opts->sub_cp; - struct stream *s = demuxer->stream; - struct mp_log *log = demuxer->log; - - if (!demuxer->params || !demuxer->params->expect_subtitle) - return -1; - - if (check >= DEMUX_CHECK_UNSAFE) { - ASS_Library *lib = ass_library_init(); - if (!lib) - return -1; - ass_set_message_cb(lib, message_callback, NULL); - - // Probe by loading a part of the beginning of the file with libass. - // Incomplete scripts are usually ok, and we hope libass is not verbose - // when dealing with (from its perspective) completely broken binary - // garbage. - - bstr buf = stream_peek(s, PROBE_SIZE); - // Older versions of libass will overwrite the input buffer, and despite - // passing length, expect a 0 termination. - void *tmp = talloc_size(NULL, buf.len + 1); - memcpy(tmp, buf.start, buf.len); - buf.start = tmp; - buf.start[buf.len] = '\0'; - bstr cbuf = mp_charset_guess_and_conv_to_utf8(log, buf, user_cp, - MP_ICONV_ALLOW_CUTOFF); - if (cbuf.start == NULL) - cbuf = buf; - ASS_Track *track = ass_read_memory(lib, cbuf.start, cbuf.len, NULL); - bool ok = !!track; - if (cbuf.start != buf.start) - talloc_free(cbuf.start); - talloc_free(buf.start); - if (track) - ass_free_track(track); - ass_library_done(lib); - if (!ok) - return -1; - } - - // Actually load the full thing. - - bstr buf = stream_read_complete(s, NULL, 100000000); - if (!buf.start) { - MP_ERR(demuxer, "Refusing to load subtitle file " - "larger than 100 MB: %s\n", demuxer->filename); - return -1; - } - bstr cbuf = mp_charset_guess_and_conv_to_utf8(log, buf, user_cp, - MP_ICONV_VERBOSE); - if (cbuf.start == NULL) - cbuf = buf; - if (cbuf.start != buf.start) - talloc_free(buf.start); - talloc_steal(demuxer, cbuf.start); - - struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB); - sh->codec = "ass"; - sh->extradata = cbuf.start; - sh->extradata_size = cbuf.len; - - demuxer->seekable = true; - demuxer->fully_read = true; - - return 0; -} - -const struct demuxer_desc demuxer_desc_libass = { - .name = "libass", - .desc = "ASS/SSA subtitles (libass)", - .open = d_check_file, -}; diff --git a/misc/charset_conv.c b/misc/charset_conv.c index bceb52aa58..3e7e47cc58 100644 --- a/misc/charset_conv.c +++ b/misc/charset_conv.c @@ -259,24 +259,6 @@ const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf, return res; } -// Convert the data in buf to UTF-8. The charset argument can be an iconv -// codepage, a value returned by mp_charset_conv_guess(), or a special value -// that triggers autodetection of the charset (e.g. using ENCA). -// The auto-detection is the only difference to mp_iconv_to_utf8(). -// buf: same as mp_iconv_to_utf8() -// user_cp: iconv codepage, special value, NULL -// flags: same as mp_iconv_to_utf8() -// returns: same as mp_iconv_to_utf8() -bstr mp_charset_guess_and_conv_to_utf8(struct mp_log *log, bstr buf, - const char *user_cp, int flags) -{ - void *tmp = talloc_new(NULL); - const char *cp = mp_charset_guess(tmp, log, buf, user_cp, flags); - bstr res = mp_iconv_to_utf8(log, buf, cp, flags); - talloc_free(tmp); - return res; -} - // Use iconv to convert buf to UTF-8. // Returns buf.start==NULL on error. Returns buf if cp is NULL, or if there is // obviously no conversion required (e.g. if cp is "UTF-8"). diff --git a/misc/charset_conv.h b/misc/charset_conv.h index bd76ae007a..3d3520fb2b 100644 --- a/misc/charset_conv.h +++ b/misc/charset_conv.h @@ -16,8 +16,6 @@ bool mp_charset_is_utf8(const char *user_cp); bool mp_charset_requires_guess(const char *user_cp); const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf, const char *user_cp, int flags); -bstr mp_charset_guess_and_conv_to_utf8(struct mp_log *log, bstr buf, - const char *user_cp, int flags); bstr mp_iconv_to_utf8(struct mp_log *log, bstr buf, const char *cp, int flags); #endif diff --git a/wscript_build.py b/wscript_build.py index 4e654eee99..76b3929c95 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -162,7 +162,6 @@ def build(ctx): ( "demux/demux_edl.c" ), ( "demux/demux_lavf.c" ), ( "demux/demux_libarchive.c", "libarchive" ), - ( "demux/demux_libass.c", "libass"), ( "demux/demux_mf.c" ), ( "demux/demux_mkv.c" ), ( "demux/demux_mkv_timeline.c" ), -- cgit v1.2.3 From 49a41bf4df85a7af2ef151841f0c6541bf8e7d96 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 12 Nov 2015 11:38:03 +0100 Subject: build: make vaapi-wayland depend on gl-wayland Wayland without GL isn't enough. Specifically, we need to make sure the EGL libs are included. (That's one tricky dependency tree.) Fixes #2476. --- wscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wscript b/wscript index d96f74b757..858dcc901c 100644 --- a/wscript +++ b/wscript @@ -682,7 +682,7 @@ video_output_features = [ }, { 'name': '--vaapi-wayland', 'desc': 'VAAPI (Wayland support)', - 'deps': [ 'vaapi', 'wayland' ], + 'deps': [ 'vaapi', 'gl-wayland' ], 'func': check_pkg_config('libva-wayland', '>= 0.36.0'), }, { -- cgit v1.2.3 From b726fefe5dce46ac0bf0b7d09155ba1a0760dca9 Mon Sep 17 00:00:00 2001 From: wm4 Date: Thu, 12 Nov 2015 22:40:02 +0100 Subject: vo_opengl_cb: do not block on flipping when redrawing Gives slightly better behavior when used with Qt. (Which tends not to flip buffers when the window is not visible.) --- video/out/vo_opengl_cb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c index a78b6a7fb7..687b9e3528 100644 --- a/video/out/vo_opengl_cb.c +++ b/video/out/vo_opengl_cb.c @@ -70,6 +70,7 @@ struct mpv_opengl_cb_context { struct vo_frame *next_frame; // next frame to draw int64_t present_count; // incremented when next frame can be shown int64_t expected_flip_count; // next vsync event for next_frame + bool redrawing; // next_frame was a redraw request int64_t flip_count; struct vo_frame *cur_frame; struct mp_image_params img_params; @@ -350,6 +351,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) assert(!p->ctx->next_frame); p->ctx->next_frame = vo_frame_ref(frame); p->ctx->expected_flip_count = p->ctx->flip_count + 1; + p->ctx->redrawing = frame ? frame->redraw : false; update(p); pthread_mutex_unlock(&p->ctx->lock); } @@ -373,6 +375,9 @@ static void flip_page(struct vo *vo) p->ctx->present_count += 1; pthread_cond_signal(&p->ctx->wakeup); + if (p->ctx->redrawing) + goto done; // do not block for redrawing + // Wait until frame was presented while (p->ctx->expected_flip_count > p->ctx->flip_count) { // mpv_opengl_cb_report_flip() is declared as optional API. -- cgit v1.2.3 From def87f1e5f693ed24b5c8f62e8e86dbaebe4d993 Mon Sep 17 00:00:00 2001 From: Martin Herkt Date: Fri, 13 Nov 2015 06:16:01 +0100 Subject: win32: avoid detection as exclusive fullscreen window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently Windows treats windows that use OpenGL, cover an entire screen and have the WS_POPUP style set or are topmost windows as exclusive fullscreen windows that bypass DWM and cannot be covered by other windows. This means we can’t use dwmflush in fullscreen mode, and it also means that no other window can cover mpv, and it makes the screen flicker when switching to fullscreen mode. This can be avoided by not setting the WS_POPUP flag. Users can still access the old behavior by enabling stay-on-top (which IMO at least makes sense—now we just need to get dwmflush autodetection right to avoid nasty surprises). fixes #2177 --- DOCS/man/options.rst | 4 ++++ video/out/w32_common.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index adf3f994be..76a4e5a33b 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1737,6 +1737,10 @@ Window ``--ontop`` Makes the player window stay on top of other windows. + On Windows, if combined with fullscreen mode, this causes mpv to be + treated as exclusive fullscreen window that bypasses the Desktop Window + Manager. + ``--border``, ``--no-border`` Play video with window border and decorations. Since this is on by default, use ``--no-border`` to disable the standard window decorations. diff --git a/video/out/w32_common.c b/video/out/w32_common.c index 667d7b425a..37e0057162 100644 --- a/video/out/w32_common.c +++ b/video/out/w32_common.c @@ -951,7 +951,7 @@ static void update_screen_rect(struct vo_w32_state *w32) static DWORD update_style(struct vo_w32_state *w32, DWORD style) { - const DWORD NO_FRAME = WS_POPUP; + const DWORD NO_FRAME = WS_OVERLAPPED; const DWORD FRAME = WS_OVERLAPPEDWINDOW | WS_SIZEBOX; style &= ~(NO_FRAME | FRAME); style |= (w32->opts->border && !w32->current_fs) ? FRAME : NO_FRAME; -- cgit v1.2.3 From f0feea55918e133ba3e6871e2bb1cbd9610cb2d1 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 13 Nov 2015 22:41:41 +0100 Subject: command: rename vo-missed-frame-count property "Missed" implies the frame was dropped, but what really happens is that the following frame will be shown later than intended (due to the current frame skipping a vsync). (As of this commit, this property is still inactive and always returns 0. See git blame for details.) --- DOCS/man/input.rst | 5 +++++ DOCS/man/mpv.rst | 12 +++++------- player/command.c | 10 +++++----- player/osd.c | 10 ++-------- video/out/vo.c | 8 ++++---- video/out/vo.h | 2 +- 6 files changed, 22 insertions(+), 25 deletions(-) diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 108c7a0de2..50ac4dbfbf 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -921,6 +921,11 @@ Property list (which can happen especially with bad source timestamps). For example, using the ``display-desync`` mode should never change this value from 0. +``vo-delayed-frame-count`` + Estimated number of frames delayed due to external circumstances in + display-sync mode. Note that in general, mpv has to guess that this is + happening, and the guess can be inaccurate. + ``percent-pos`` (RW) Position in current file (0-100). The advantage over using this instead of calculating it out of other properties is that it properly falls back to diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst index b8bd266aa4..3e45f93072 100644 --- a/DOCS/man/mpv.rst +++ b/DOCS/man/mpv.rst @@ -566,13 +566,11 @@ listed. 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: +0.02598%``, where the number is the speed change - factor applied to audio to achieve sync to display, expressed in percent - deviation from 1.0 (``audio-speed-correction`` property). In sync modes which - don't resample, this will always be ``+0.00000%``. -- 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. + property), this shows ``DS: 12/13``, where the first number is the number of + frames where a vsync was intentionally added or removed + (``mistimed-frame-count``), and the second number of estimated number of vsyncs + which took too long (``vo-delayed-frame-count`` property). The latter is a + heuristic, as it's generally not possible to determine this with certainty. - 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/player/command.c b/player/command.c index 6473de4af9..db368df079 100644 --- a/player/command.c +++ b/player/command.c @@ -583,14 +583,14 @@ 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) +static int mp_property_vo_delayed_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)); + return m_property_int_ro(action, arg, vo_get_delayed_count(mpctx->video_out)); } /// Current position in percent (RW) @@ -3395,7 +3395,7 @@ static const struct m_property mp_properties[] = { {"drop-frame-count", mp_property_drop_frame_cnt}, {"mistimed-frame-count", mp_property_mistimed_frame_count}, {"vo-drop-frame-count", mp_property_vo_drop_frame_count}, - {"vo-missed-frame-count", mp_property_vo_missed_frame_count}, + {"vo-delayed-frame-count", mp_property_vo_delayed_frame_count}, {"percent-pos", mp_property_percent_pos}, {"time-start", mp_property_time_start}, {"time-pos", mp_property_time_pos}, @@ -3612,7 +3612,7 @@ static const char *const *const mp_event_property_change[] = { "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", - "vo-missed-frame-count", "mistimed-frame-count"), + "vo-delayed-frame-count", "mistimed-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/osd.c b/player/osd.c index da14f8ff2b..596386ebae 100644 --- a/player/osd.c +++ b/player/osd.c @@ -234,14 +234,8 @@ static void print_status(struct MPContext *mpctx) // VO stats if (mpctx->d_video) { if (mpctx->display_sync_active) { - char *f = - mp_property_expand_string(mpctx, "${audio-speed-correction}"); - if (f) - saddf(&line, " DS: %s", f); - talloc_free(f); - int64_t m = vo_get_missed_count(mpctx->video_out); - if (m > 0) - saddf(&line, " Missed: %"PRId64, m); + saddf(&line, " DS: %d/%"PRId64, mpctx->mistimed_frames_total, + vo_get_delayed_count(mpctx->video_out)); } int64_t c = vo_get_drop_count(mpctx->video_out); if (c > 0 || mpctx->dropped_frames_total > 0) { diff --git a/video/out/vo.c b/video/out/vo.c index 35c583b35f..81537e80d9 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -144,7 +144,7 @@ struct vo_internal { int64_t flip_queue_offset; // queue flip events at most this much in advance - int64_t missed_count; + int64_t delayed_count; int64_t drop_count; bool dropped_frame; // the previous frame was dropped @@ -423,7 +423,7 @@ static void forget_frames(struct vo *vo) in->hasframe = false; in->hasframe_rendered = false; in->drop_count = 0; - in->missed_count = 0; + in->delayed_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 @@ -1050,11 +1050,11 @@ int64_t vo_get_next_frame_start_time(struct vo *vo) return res; } -int64_t vo_get_missed_count(struct vo *vo) +int64_t vo_get_delayed_count(struct vo *vo) { struct vo_internal *in = vo->in; pthread_mutex_lock(&in->lock); - int64_t res = vo->in->missed_count; + int64_t res = vo->in->delayed_count; pthread_mutex_unlock(&in->lock); return res; } diff --git a/video/out/vo.h b/video/out/vo.h index 213b287ab2..c7bcccd778 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -332,7 +332,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); +int64_t vo_get_delayed_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); -- cgit v1.2.3 From 624c9e46ce0bb4f547ffaf9cd6541700cea96ddb Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 13 Nov 2015 22:42:42 +0100 Subject: player: always require a future frame with display-sync enabled We need a frame duration even on start, because the number of vsyncs the frame is shown is predetermined. (vo_opengl actually makes use of this property in certain cases.) --- player/video.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/player/video.c b/player/video.c index 0aebc6a7e9..430fed530e 100644 --- a/player/video.c +++ b/player/video.c @@ -611,10 +611,14 @@ static void shift_frames(struct MPContext *mpctx) static int get_req_frames(struct MPContext *mpctx, bool eof) { // On EOF, drain all frames. - // On the first frame, output a new frame as quickly as possible. - if (eof || mpctx->video_pts == MP_NOPTS_VALUE) + if (eof) return 1; + // On the first frame, output a new frame as quickly as possible. + // But display-sync likes to have a correct frame duration always. + if (mpctx->video_pts == MP_NOPTS_VALUE) + return mpctx->opts->video_sync == VS_DEFAULT ? 1 : 2; + int req = vo_get_num_req_frames(mpctx->video_out); return MPCLAMP(req, 2, MP_ARRAY_SIZE(mpctx->next_frames)); } -- cgit v1.2.3 From d32c4c75ef1cf4d69474a0a0e8f8127e77910099 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 13 Nov 2015 22:45:40 +0100 Subject: player: refactor display-sync frame duration calculations Get rid of get_past_frame_durations(), which was a bit too messy. Add a past_frames array, which contains the same information in a more reasonable way. This also means that we can get the exact current and past frame durations without going through awful stuff. (The main problem is that vo_pts_history contains future frames as well, which is needed for frame backstepping etc., but gets in the way here.) Also disable the automatic disabling of display-sync if the frame duration changes, and extend the frame durations allowed for display sync. To allow arbitrarily high durations, vo.c needs to be changed to pause and potentially redraw OSD while showing a single frame, so they're still limited. In an attempt to deal with VFR, calculate the overall speed using the average FPS. The frame scheduling itself does not use the average FPS, but the duration of the current frame. This does not work too well, but provides a good base for further improvements. Where this commit actually helps a lot is dealing with rounded timestamps, e.g. if the container framerate is wrong or unknown, or if the muxer wrote incorrectly rounded timestamps. While the rounding errors apparently can't be get rid of completely in the general case, this is still much better than e.g. disabling display-sync completely just because some frame durations go out of bounds. --- player/command.c | 6 +- player/core.h | 18 ++++-- player/loadfile.c | 1 - player/playloop.c | 21 ------ player/video.c | 186 ++++++++++++++++++++++++------------------------------ 5 files changed, 98 insertions(+), 134 deletions(-) diff --git a/player/command.c b/player/command.c index db368df079..e3b8feab98 100644 --- a/player/command.c +++ b/player/command.c @@ -2687,10 +2687,10 @@ static int mp_property_vf_fps(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; if (!mpctx->d_video) return M_PROPERTY_UNAVAILABLE; - double res = stabilize_frame_duration(mpctx, false); - if (res <= 0) + double avg = calc_average_frame_duration(mpctx); + if (avg <= 0) return M_PROPERTY_UNAVAILABLE; - return m_property_double_ro(action, arg, 1 / res); + return m_property_double_ro(action, arg, 1.0 / avg); } /// Video aspect (RO) diff --git a/player/core.h b/player/core.h index 80bd08266c..fcdf0e3595 100644 --- a/player/core.h +++ b/player/core.h @@ -76,9 +76,6 @@ enum seek_precision { MPSEEK_VERY_EXACT, }; -// 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, @@ -97,6 +94,13 @@ enum video_sync { (x) == VS_DISP_VDROP || \ (x) == VS_DISP_NONE) +// Information about past video frames that have been sent to the VO. +struct frame_info { + double pts; + double duration; // PTS difference to next frame + double approx_duration; // possibly fixed/smoothed out duration +}; + struct track { enum stream_type type; @@ -264,7 +268,6 @@ typedef struct MPContext { 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; @@ -321,6 +324,10 @@ typedef struct MPContext { uint64_t vo_pts_history_seek_ts; uint64_t backstep_start_seek_ts; bool backstep_active; + // Past timestamps etc. (stupidly duplicated with vo_pts_history). + // The newest frame is at index 0. + struct frame_info *past_frames; + int num_past_frames; double next_heartbeat; double last_idle_tick; @@ -498,7 +505,6 @@ void mp_idle(struct MPContext *mpctx); void idle_loop(struct MPContext *mpctx); int handle_force_window(struct MPContext *mpctx, bool force); void add_frame_pts(struct MPContext *mpctx, double pts); -int get_past_frame_durations(struct MPContext *mpctx, double *fd, int num); void seek_to_last_frame(struct MPContext *mpctx); // scripting.c @@ -528,6 +534,6 @@ void write_video(struct MPContext *mpctx, double endpts); void mp_force_video_refresh(struct MPContext *mpctx); void uninit_video_out(struct MPContext *mpctx); void uninit_video_chain(struct MPContext *mpctx); -double stabilize_frame_duration(struct MPContext *mpctx, bool require_exact); +double calc_average_frame_duration(struct MPContext *mpctx); #endif /* MPLAYER_MP_CORE_H */ diff --git a/player/loadfile.c b/player/loadfile.c index c472cad344..d72f13da61 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -1034,7 +1034,6 @@ 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; diff --git a/player/playloop.c b/player/playloop.c index f5463e2622..643f290c48 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -693,27 +693,6 @@ void add_frame_pts(struct MPContext *mpctx, double pts) mpctx->vo_pts_history_pts[0] = pts; } -// Return the last (at most num) frame duration in fd[]. Return the number of -// entries written to fd[] (range [0, num]). fd[0] is the most recent frame. -int get_past_frame_durations(struct MPContext *mpctx, double *fd, int num) -{ - double next_pts = mpctx->vo_pts_history_pts[0]; - if (mpctx->vo_pts_history_seek[0] != mpctx->vo_pts_history_seek_ts || - next_pts == MP_NOPTS_VALUE) - return 0; - int num_ret = 0; - for (int n = 1; n < MAX_NUM_VO_PTS && num_ret < num; n++) { - double frame_pts = mpctx->vo_pts_history_pts[n]; - // Discontinuity -> refuse to return a value. - if (mpctx->vo_pts_history_seek[n] != mpctx->vo_pts_history_seek_ts || - next_pts <= frame_pts || frame_pts == MP_NOPTS_VALUE) - break; - fd[num_ret++] = next_pts - frame_pts; - next_pts = frame_pts; - } - return num_ret; -} - static double find_previous_pts(struct MPContext *mpctx, double pts) { for (int n = 0; n < MAX_NUM_VO_PTS - 1; n++) { diff --git a/player/video.c b/player/video.c index 430fed530e..398d74a643 100644 --- a/player/video.c +++ b/player/video.c @@ -206,6 +206,7 @@ void reset_video_state(struct MPContext *mpctx) mpctx->time_frame = 0; mpctx->video_pts = MP_NOPTS_VALUE; mpctx->video_next_pts = MP_NOPTS_VALUE; + mpctx->num_past_frames = 0; mpctx->total_avsync_change = 0; mpctx->last_av_difference = 0; mpctx->display_sync_disable_counter = 0; @@ -799,76 +800,18 @@ static void init_vo(struct MPContext *mpctx) mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL); } -// Attempt to stabilize frame duration from jittery timestamps. This is mostly -// needed with semi-broken file formats which round timestamps to ms, or files -// created from them. -// We do this to make a stable decision how much to change video playback speed. -// Otherwise calc_best_speed() could make a different decision every frame, and -// also audio speed would have to be readjusted all the time. -// Return -1 if the frame duration seems to be unstable. -// If require_exact is false, just return the average frame duration on failure. -double stabilize_frame_duration(struct MPContext *mpctx, bool require_exact) +double calc_average_frame_duration(struct MPContext *mpctx) { - if (require_exact && mpctx->broken_fps_header) - return -1; - - // Note: the past frame durations are raw and unadjusted. - double fd[10]; - int num = get_past_frame_durations(mpctx, fd, MP_ARRAY_SIZE(fd)); - if (num < MP_ARRAY_SIZE(fd)) - return -1; - - bool ok = true; - double min = fd[0]; - double max = fd[0]; - double total_duration = 0; - for (int n = 0; n < num; n++) { - double cur = fd[n]; - if (fabs(cur - fd[num - 1]) > FRAME_DURATION_TOLERANCE) - ok = false; - min = MPMIN(min, cur); - max = MPMAX(max, cur); - total_duration += cur; - } - - if (max - min > FRAME_DURATION_TOLERANCE || !ok) - goto fail; - - // It's not really possible to compute the actual, correct FPS, unless we - // e.g. consider a list of potentially correct values, detect cycles, or - // use similar guessing methods. - // Naively using the average between min and max should give a stable, but - // still relatively close value. - double modified_duration = (min + max) / 2; - - // Except for the demuxer reported FPS, which might be the correct one. - // VFR files could contain segments that don't match. - if (mpctx->d_video->fps > 0) { - double demux_duration = 1.0 / mpctx->d_video->fps; - if (fabs(modified_duration - demux_duration) <= FRAME_DURATION_TOLERANCE) - modified_duration = demux_duration; - } - - // Verify the estimated stabilized frame duration with the actual time - // passed in these frames. If it's wrong (wrong FPS in the header), then - // this will deviate a bit. - if (fabs(total_duration - modified_duration * num) > FRAME_DURATION_TOLERANCE) - { - if (require_exact && !mpctx->broken_fps_header) { - // The error message is slightly misleading: a framerate header - // field is not really needed, as long as the file has an exact - // timebase. - MP_WARN(mpctx, "File has broken or missing framerate header\n" - "field, or is VFR with broken timestamps.\n"); - mpctx->broken_fps_header = true; - } - goto fail; + double total = 0; + int num = 0; + for (int n = 0; n < mpctx->num_past_frames; n++) { + double dur = mpctx->past_frames[0].approx_duration; + if (dur <= 0) + continue; + total += dur; + num += 1; } - - return modified_duration; - -fail: - return require_exact ? -1 : total_duration / num; + return num > 0 ? total / num : 0; } static bool using_spdif_passthrough(struct MPContext *mpctx) @@ -969,24 +912,17 @@ static void handle_display_sync_frame(struct MPContext *mpctx, 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; + double adjusted_duration = mpctx->past_frames[0].approx_duration; + double avg_duration = calc_average_frame_duration(mpctx); + adjusted_duration /= opts->playback_speed; + avg_duration /= opts->playback_speed; + if (adjusted_duration <= 0.001 || adjusted_duration > 0.5) goto done; - } - mpctx->speed_factor_v = calc_best_speed(mpctx, vsync, adjusted_duration); - if (mpctx->speed_factor_v <= 0) { + mpctx->speed_factor_v = calc_best_speed(mpctx, vsync, avg_duration); + // If it doesn't work, play at normal speed. + if (mpctx->speed_factor_v <= 0) mpctx->speed_factor_v = 1.0; - goto done; - } double av_diff = mpctx->last_av_difference; if (fabs(av_diff) > 0.5) @@ -1085,26 +1021,58 @@ static void schedule_frame(struct MPContext *mpctx, struct vo_frame *frame) } } -// 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. -static double get_frame_duration(struct MPContext *mpctx, int frame) +// Determine the mpctx->past_frames[0] frame duration. +static void calculate_frame_duration(struct MPContext *mpctx) { - struct MPOpts *opts = mpctx->opts; - struct vo *vo = mpctx->video_out; + assert(mpctx->num_past_frames >= 1 && mpctx->num_next_frames >= 1); - double diff = -1; - if (frame + 2 <= mpctx->num_next_frames) { - double vpts0 = mpctx->next_frames[frame]->pts; - double vpts1 = mpctx->next_frames[frame + 1]->pts; - if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE) - diff = vpts1 - vpts0; + double demux_duration = + mpctx->d_video->fps > 0 ? 1.0 / mpctx->d_video->fps : -1; + double duration = -1; + + if (mpctx->num_next_frames >= 2) { + double pts0 = mpctx->next_frames[0]->pts; + double pts1 = mpctx->next_frames[1]->pts; + if (pts0 != MP_NOPTS_VALUE && pts1 != MP_NOPTS_VALUE && pts1 >= pts0) + duration = pts1 - pts0; + } else { + // E.g. last frame on EOF. + duration = demux_duration; } - if (diff < 0 && mpctx->d_video->fps > 0) - diff = 1.0 / mpctx->d_video->fps; // fallback to demuxer-reported fps - if (opts->untimed || vo->driver->untimed) - diff = -1; // disable frame dropping and aspects of frame timing - return diff; + + // The following code tries to compensate for rounded Matroska timestamps + // by "unrounding" frame durations, or if not possible, approximating them. + // These formats usually round on 1ms. (Some muxers do this incorrectly, + // and might be off by 2ms or more, and compensate for it later by an + // equal rounding error into the opposite direction. Don't try to deal + // with them; too much potential damage to timing.) + double tolerance = 0.0011; + + double total = 0; + int num_dur = 0; + for (int n = 1; n < mpctx->num_past_frames; n++) { + // Eliminate likely outliers using a really dumb heuristic. + double dur = mpctx->past_frames[n].duration; + if (dur <= 0 || fabs(dur - duration) >= tolerance) + break; + total += dur; + num_dur += 1; + } + + // Try if the demuxer frame rate fits - if so, just take it. + double approx_duration = duration; + if (demux_duration > 0) { + // Note that even if each timestamp is within rounding tolerance, it + // could literally not add up (e.g. if demuxer FPS is rounded itself). + if (fabs(duration - demux_duration) < tolerance && + fabs(total - demux_duration * num_dur) < tolerance) + { + approx_duration = demux_duration; + } + } + + mpctx->past_frames[0].duration = duration; + mpctx->past_frames[0].approx_duration = approx_duration; } void write_video(struct MPContext *mpctx, double endpts) @@ -1190,6 +1158,16 @@ void write_video(struct MPContext *mpctx, double endpts) } assert(mpctx->num_next_frames >= 1); + + if (mpctx->num_past_frames >= MAX_NUM_VO_PTS) + mpctx->num_past_frames--; + MP_TARRAY_INSERT_AT(mpctx, mpctx->past_frames, mpctx->num_past_frames, 0, + (struct frame_info){0}); + struct frame_info *frame_info = &mpctx->past_frames[0]; + + frame_info->pts = mpctx->next_frames[0]->pts; + calculate_frame_duration(mpctx); + struct vo_frame dummy = { .pts = pts, .duration = -1, @@ -1201,7 +1179,9 @@ void write_video(struct MPContext *mpctx, double endpts) dummy.frames[n] = mpctx->next_frames[n]; struct vo_frame *frame = vo_frame_ref(&dummy); - double diff = get_frame_duration(mpctx, 0); + double diff = frame_info->approx_duration; + if (opts->untimed || vo->driver->untimed) + diff = -1; // disable frame dropping and aspects of frame timing if (diff >= 0) { // expected A/V sync correction is ignored diff /= mpctx->video_speed; @@ -1214,6 +1194,8 @@ void write_video(struct MPContext *mpctx, double endpts) mpctx->last_vo_pts = mpctx->video_pts; mpctx->playback_pts = mpctx->video_pts; + shift_frames(mpctx); + schedule_frame(mpctx, frame); mpctx->osd_force_update = true; @@ -1222,8 +1204,6 @@ void write_video(struct MPContext *mpctx, double endpts) vo_queue_frame(vo, frame); - shift_frames(mpctx); - // The frames were shifted down; "initialize" the new first entry. if (mpctx->num_next_frames >= 1) handle_new_frame(mpctx); -- cgit v1.2.3 From fad254562b09dcff4d6c95aff5982a6252b09ebb Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 13 Nov 2015 22:46:55 +0100 Subject: player: smooth out frame durations by averaging them Helps somewhat with muxer-rounded timestamps. There is some danger that this introduces a timestamp drift. But since they are averaged values (unlike as when using an incorrect container framerate hint), any potential drift shouldn't be too brutal, or compensate itself soon. So I won't bother yet with comparing the results with the real timestamp, unless we run into actual problems. Of course we still prefer potentially real timestamps over the approximated ones. But unless the timestamps match the container FPS, we can't know whether they are (no, checking whether the they have microsecond components would be cheating). Perhaps in future, we could let the demuxer export the timebase - if the timebase is not 1000 (or divisible by it), we know that millisecond-rounded timestamps won't happen. --- player/video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player/video.c b/player/video.c index 398d74a643..d6140773eb 100644 --- a/player/video.c +++ b/player/video.c @@ -1058,9 +1058,9 @@ static void calculate_frame_duration(struct MPContext *mpctx) total += dur; num_dur += 1; } + double approx_duration = num_dur > 0 ? total / num_dur : duration; // Try if the demuxer frame rate fits - if so, just take it. - double approx_duration = duration; if (demux_duration > 0) { // Note that even if each timestamp is within rounding tolerance, it // could literally not add up (e.g. if demuxer FPS is rounded itself). -- cgit v1.2.3 From 62b386c2fdb6d113c2c1042f0b3a973f3fb11828 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 13 Nov 2015 22:47:14 +0100 Subject: player: compute required display-sync speed change differently Instead of looking at the current frame duration for the intended speedup, look at all past frames, and find a good average speed. This ties in with not wanting to average _all_ frame durations, which doesn't make sense in VFR situations. This is currently done in the most naive way possible, but already sort of works for VFR which switches between frame durations that are integer multiples of a base rate. Certainly more improvements could be made, such as trying to adjust directly on FPS changes, instead of averaging everything, but for now this is not needed at all. --- player/video.c | 58 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/player/video.c b/player/video.c index d6140773eb..8ee9c87e93 100644 --- a/player/video.c +++ b/player/video.c @@ -814,6 +814,40 @@ double calc_average_frame_duration(struct MPContext *mpctx) return num > 0 ? total / num : 0; } +// 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(double vsync, double frame) +{ + double ratio = frame / vsync; + double best_scale = -1; + double best_dev = INFINITY; + for (int factor = 1; factor <= 5; factor++) { + double scale = ratio * factor / rint(ratio * factor); + double dev = fabs(scale - 1); + if (dev < best_dev) { + best_scale = scale; + best_dev = dev; + } + } + return best_scale; +} + +static double find_best_speed(struct MPContext *mpctx, double vsync) +{ + double total = 0; + int num = 0; + for (int n = 0; n < mpctx->num_past_frames; n++) { + double dur = mpctx->past_frames[n].approx_duration; + if (dur <= 0) + continue; + total += calc_best_speed(vsync, dur / mpctx->opts->playback_speed); + num++; + } + return num > 0 ? total / num : 1; +} + static bool using_spdif_passthrough(struct MPContext *mpctx) { if (mpctx->d_audio && mpctx->d_audio->afilter) @@ -861,24 +895,6 @@ static void adjust_audio_speed(struct MPContext *mpctx, double vsync) MP_STATS(mpctx, "value %f aspeed", mpctx->speed_factor_a - 1); } -// 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) @@ -913,15 +929,13 @@ static void handle_display_sync_frame(struct MPContext *mpctx, goto done; double adjusted_duration = mpctx->past_frames[0].approx_duration; - double avg_duration = calc_average_frame_duration(mpctx); adjusted_duration /= opts->playback_speed; - avg_duration /= opts->playback_speed; if (adjusted_duration <= 0.001 || adjusted_duration > 0.5) goto done; - mpctx->speed_factor_v = calc_best_speed(mpctx, vsync, avg_duration); + mpctx->speed_factor_v = find_best_speed(mpctx, vsync); // If it doesn't work, play at normal speed. - if (mpctx->speed_factor_v <= 0) + if (fabs(mpctx->speed_factor_v - 1.0) > opts->sync_max_video_change / 100) mpctx->speed_factor_v = 1.0; double av_diff = mpctx->last_av_difference; -- cgit v1.2.3 From d5981924feb02384f9facbedc9fff2fb89ac8db3 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 13 Nov 2015 22:48:32 +0100 Subject: command: add vsync-ratio property This is very "illustrative", unlike the video-speed-correction property, and thus useful. It can also be used to observe scheduling errors, which are not detected by the core. (These happen due to rounding errors; possibly not evne our fault, but coming from files with rounded timestamps and so on.) --- DOCS/interface-changes.rst | 2 ++ DOCS/man/input.rst | 7 +++++++ player/command.c | 27 +++++++++++++++++++++++++-- player/core.h | 1 + player/video.c | 11 +++++++---- 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index b81312f8e9..dd42895203 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -19,6 +19,8 @@ Interface changes :: + --- mpv 0.14.0 --- + - add "vsync-ratio" property --- mpv 0.13.0 --- - remove VO opengl-cb frame queue suboptions (no replacement) --- mpv 0.12.0 --- diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 50ac4dbfbf..0233dbf057 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -921,6 +921,13 @@ Property list (which can happen especially with bad source timestamps). For example, using the ``display-desync`` mode should never change this value from 0. +``vsync-ratio`` + For how many vsyncs a frame is displayed on average. This is available if + display-sync is active only. For 30 FPS video on a 60 Hz screen, this will + be 2. This is the moving average of what actually has been scheduled, so + 24 FPS on 60 Hz will never remain exactly on 2.5, but jitter depending on + the last frame displayed. + ``vo-delayed-frame-count`` Estimated number of frames delayed due to external circumstances in display-sync mode. Note that in general, mpv has to guess that this is diff --git a/player/command.c b/player/command.c index e3b8feab98..eadcc27296 100644 --- a/player/command.c +++ b/player/command.c @@ -567,12 +567,34 @@ static int mp_property_mistimed_frame_count(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - if (!mpctx->d_video || !mpctx->display_sync_active) + if (!mpctx->d_video || !mpctx->display_sync_active) return M_PROPERTY_UNAVAILABLE; return m_property_int_ro(action, arg, mpctx->mistimed_frames_total); } +static int mp_property_vsync_ratio(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + if (!mpctx->d_video || !mpctx->display_sync_active) + return M_PROPERTY_UNAVAILABLE; + + int vsyncs = 0, frames = 0; + for (int n = 0; n < mpctx->num_past_frames; n++) { + int vsync = mpctx->past_frames[n].num_vsyncs; + if (vsync < 0) + break; + vsyncs += vsync; + frames += 1; + } + + if (!frames) + return M_PROPERTY_UNAVAILABLE; + + return m_property_double_ro(action, arg, vsyncs / (double)frames); +} + static int mp_property_vo_drop_frame_count(void *ctx, struct m_property *prop, int action, void *arg) { @@ -3394,6 +3416,7 @@ static const struct m_property mp_properties[] = { {"total-avsync-change", mp_property_total_avsync_change}, {"drop-frame-count", mp_property_drop_frame_cnt}, {"mistimed-frame-count", mp_property_mistimed_frame_count}, + {"vsync-ratio", mp_property_vsync_ratio}, {"vo-drop-frame-count", mp_property_vo_drop_frame_count}, {"vo-delayed-frame-count", mp_property_vo_delayed_frame_count}, {"percent-pos", mp_property_percent_pos}, @@ -3612,7 +3635,7 @@ static const char *const *const mp_event_property_change[] = { "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", - "vo-delayed-frame-count", "mistimed-frame-count"), + "vo-delayed-frame-count", "mistimed-frame-count", "vsync-ratio"), 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 fcdf0e3595..7031efc107 100644 --- a/player/core.h +++ b/player/core.h @@ -99,6 +99,7 @@ struct frame_info { double pts; double duration; // PTS difference to next frame double approx_duration; // possibly fixed/smoothed out duration + int num_vsyncs; // scheduled vsyncs, if using display-sync }; struct track { diff --git a/player/video.c b/player/video.c index 8ee9c87e93..5ef327aa09 100644 --- a/player/video.c +++ b/player/video.c @@ -1001,6 +1001,8 @@ static void handle_display_sync_frame(struct MPContext *mpctx, mpctx->total_avsync_change = 0; update_av_diff(mpctx, time_left * opts->playback_speed); + mpctx->past_frames[0].num_vsyncs = num_vsyncs; + if (resample) adjust_audio_speed(mpctx, vsync); @@ -1177,9 +1179,10 @@ void write_video(struct MPContext *mpctx, double endpts) mpctx->num_past_frames--; MP_TARRAY_INSERT_AT(mpctx, mpctx->past_frames, mpctx->num_past_frames, 0, (struct frame_info){0}); - struct frame_info *frame_info = &mpctx->past_frames[0]; - - frame_info->pts = mpctx->next_frames[0]->pts; + mpctx->past_frames[0] = (struct frame_info){ + .pts = mpctx->next_frames[0]->pts, + .num_vsyncs = -1, + }; calculate_frame_duration(mpctx); struct vo_frame dummy = { @@ -1193,7 +1196,7 @@ void write_video(struct MPContext *mpctx, double endpts) dummy.frames[n] = mpctx->next_frames[n]; struct vo_frame *frame = vo_frame_ref(&dummy); - double diff = frame_info->approx_duration; + double diff = mpctx->past_frames[0].approx_duration; if (opts->untimed || vo->driver->untimed) diff = -1; // disable frame dropping and aspects of frame timing if (diff >= 0) { -- cgit v1.2.3 From 07b8abbd62c8d5b28f52a3e5e0192f16cad2405b Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 13 Nov 2015 22:49:50 +0100 Subject: player: remove display_sync_disable_counter We can implement it differently and drop a tiny bit of state. --- player/core.h | 1 - player/video.c | 18 ++++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/player/core.h b/player/core.h index 7031efc107..ed433b098c 100644 --- a/player/core.h +++ b/player/core.h @@ -273,7 +273,6 @@ typedef struct MPContext { // Timing error (in seconds) due to rounding on vsync boundaries double display_sync_error; double audio_drop_throttle; - int display_sync_disable_counter; // Number of mistimed frames. int mistimed_frames_total; /* Set if audio should be timed to start with video frame after seeking, diff --git a/player/video.c b/player/video.c index 5ef327aa09..281789e2a4 100644 --- a/player/video.c +++ b/player/video.c @@ -209,7 +209,6 @@ void reset_video_state(struct MPContext *mpctx) mpctx->num_past_frames = 0; 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->mistimed_frames_total = 0; @@ -901,7 +900,6 @@ static void handle_display_sync_frame(struct MPContext *mpctx, { 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) { @@ -946,10 +944,11 @@ static void handle_display_sync_frame(struct MPContext *mpctx, // 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; + if (mpctx->num_past_frames > 1 && mpctx->past_frames[1].num_vsyncs < 0) { + for (int n = 1; n < mpctx->num_past_frames; n++) { + if (mpctx->past_frames[n].num_vsyncs >= 0) + goto done; + } } // Determine for how many vsyncs a frame should be displayed. This can be @@ -1018,13 +1017,12 @@ done: update_playback_speed(mpctx); - if (old_display_sync != mpctx->display_sync_active) { + if (mpctx->num_past_frames > 1 && + ((mpctx->past_frames[1].num_vsyncs >= 0) != 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); } static void schedule_frame(struct MPContext *mpctx, struct vo_frame *frame) -- cgit v1.2.3 From c362c3d7ae85ac65e5e87004d33775f3cb291498 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 13 Nov 2015 22:50:58 +0100 Subject: player: change display-sync audio speed only if needed As long as it's within the desync tolerance, do not change the audio speed at all for resampling. This reduces speed changes which might be caused by jittering timestamps and similar cases. (While in theory you could just not care and change speed every single frame, I'm afraid that such changes could possibly cause audio artifacts. So better just avoid it in the first place.) --- player/video.c | 86 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/player/video.c b/player/video.c index 281789e2a4..65982d319e 100644 --- a/player/video.c +++ b/player/video.c @@ -854,44 +854,47 @@ static bool using_spdif_passthrough(struct MPContext *mpctx) return false; } -static void adjust_audio_speed(struct MPContext *mpctx, double vsync) +static void adjust_audio_resample_speed(struct MPContext *mpctx, double vsync) { struct MPOpts *opts = mpctx->opts; int mode = opts->video_sync; - 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; - double av_diff = mpctx->last_av_difference; - 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 = av_diff >= 0 ? 1 : -1; - 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; + + if (mode != VS_DISP_RESAMPLE || mpctx->audio_status != STATUS_PLAYING) { + mpctx->speed_factor_a = mpctx->speed_factor_v; + return; } - mpctx->speed_factor_a = audio_factor * mpctx->speed_factor_v; + // 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; + double av_diff = mpctx->last_av_difference; + 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 = av_diff >= 0 ? 1 : -1; + + bool change = mpctx->display_sync_drift_dir != new; + if (new || change) { + if (change) + MP_VERBOSE(mpctx, "Change display sync audio drift: %d\n", new); + mpctx->display_sync_drift_dir = new; - MP_STATS(mpctx, "value %f aspeed", mpctx->speed_factor_a - 1); + double max_correct = opts->sync_max_audio_change / 100; + double audio_factor = 1 + max_correct * -mpctx->display_sync_drift_dir; + mpctx->speed_factor_a = audio_factor * mpctx->speed_factor_v; + } } // Manipulate frame timing for display sync, or do nothing for normal timing. @@ -908,8 +911,6 @@ static void handle_display_sync_frame(struct MPContext *mpctx, } mpctx->display_sync_active = false; - mpctx->speed_factor_a = 1.0; - mpctx->speed_factor_v = 1.0; if (!VS_IS_DISP(mode)) goto done; @@ -100