diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 4741e5570cfc4..8a2d776942fff 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -2311,11 +2311,12 @@ Property list ``mistimed-frame-count`` Number of video frames that were not timed correctly in display-sync mode - for the sake of keeping A/V sync. This does not include external + for the sake of keeping A/V sync (i.e using the ``display-desync`` mode should + not change this value from 0), or in ``vrr-adjust`` mode for the sake of keeping + within the defined refresh rate range. This does not include external circumstances, such as video rendering being too slow or the graphics driver somehow skipping a vsync. It does not include rounding errors either - (which can happen especially with bad source timestamps). For example, - using the ``display-desync`` mode should never change this value from 0. + (which can happen especially with bad source timestamps). ``vsync-ratio`` For how many vsyncs a frame is displayed on average. This is available if diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index 2a359d4692d5b..a24f747da6f3e 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -1320,6 +1320,10 @@ Video Set this option only if you have reason to believe the automatically determined value is wrong. +``--minimum-display-fps=`` + Set the minimum display FPS used with the ``--vrr-adjust=true`` mode. By + default, it uses the maximum display fps. + ``--hwdec=`` Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If @@ -8224,6 +8228,56 @@ Video Sync frame dropping due to the audio "overshooting" and skipping multiple video frames before the sync logic can react. +``--vrr-adjust=`` + This option tries to increase the chance of the display being ready for + frames the moment they are sent (default: no), for cases where the + display is unreliable, primarily useful for VRR displays. This uses + ``--minimum-display-fps`` and display-fps (maximum fps). + + This is done by observing each individual frame and dynamically: + + 1. Not doing anything if they are perfectly between the minimum refresh time + and the middle refresh time. + 2. Repeating frames at a middle refresh time if possible, else repeating + tending towards the middle refresh time (the middle refresh time gives + us the best error leeway in case there are random application or OS delays + that would put us in a position that exceeds either the minimum or + the maximum refresh time). + 3. If we can't reliably repeat it, then we send it even if it's between the + middle refresh time and the maximum refresh time. Note that because of the + above mentioned potential delays, this may cause us to exceed our target + even if we initially appear in valid range. + 4. If the next frame exceeds our minimum refresh time, whether because our + repetition failed or that's how the frames exist, we will move it to the + closest valid position that's within the refresh range. If two frames are + put on the same position, we send the newest one only. + + Note: This is not compatible with ``--video-sync=display-...`` modes, yet, + which would be the only ones capable of getting rid of the last displaying + weirdnesses. + +``--vrr-adjust-max-refresh-variance=<0.0-1.0>`` + How much percentage wise the refresh time can vary from one frame to the next, + where 1.0 means the refresh time can immediatelly move between the minimum and + maximum refresh time, while 0 means the refresh time is static. (default: 1.0) + + The default does not take into account refresh rate flicker (which may happen + in specific cases, eg. when time between frames is constantly higher than + minimum refresh time + middle refresh time), and optimizes just for reaching + target position. If that's a problem, then this option can limit sudden changes in refresh + time. The lower the value set, the more refresh time stability, the higher chance + of missing our target. + +``--vrr-adjust-target-refresh-rate=`` + Set the refresh rate we should tend towards, instead of the default "middle refresh + time". Setting it at a lower refresh rate will reduce the amount of repetitions, + which can help if computational burden is a problem. However, increasing it or + reducing it will come at a cost of potential chance increase of missing our target. + + When needed, we will still use the entire range of ``--minimum-display-fps`` and + ``--display-fps-override``, so this option only increases the chance towards + the refresh rate we tend towards. + Miscellaneous ------------- diff --git a/options/options.c b/options/options.c index 2a5373d44d7b5..55e683f71d045 100644 --- a/options/options.c +++ b/options/options.c @@ -189,6 +189,8 @@ static const m_option_t mp_vo_opt_list[] = { {"show-in-taskbar", OPT_BOOL(show_in_taskbar)}, {"display-fps-override", OPT_DOUBLE(display_fps_override), M_RANGE(0, DBL_MAX)}, + {"minimum-display-fps", OPT_DOUBLE(minimum_display_fps), + M_RANGE(0, DBL_MAX)}, {"video-timing-offset", OPT_DOUBLE(timing_offset), M_RANGE(0.0, 1.0)}, {"video-sync", OPT_CHOICE(video_sync, {"audio", VS_DEFAULT}, @@ -200,6 +202,9 @@ static const m_option_t mp_vo_opt_list[] = { {"display-vdrop", VS_DISP_VDROP}, {"display-desync", VS_DISP_NONE}, {"desync", VS_NONE})}, + {"vrr-adjust", OPT_BOOL(vrr_adjust)}, + {"vrr-adjust-max-refresh-variance", OPT_DOUBLE(vrr_max_refresh_variance), M_RANGE(0.0, 1.0)}, + {"vrr-adjust-target-refresh-rate", OPT_DOUBLE(vrr_target_refresh_rate)}, #if HAVE_X11 {"x11-netwm", OPT_CHOICE(x11_netwm, {"auto", 0}, {"no", -1}, {"yes", 1})}, {"x11-bypass-compositor", OPT_CHOICE(x11_bypass_compositor, @@ -292,7 +297,10 @@ const struct m_sub_options vo_sub_opts = { .wl_present = true, .mmcss_profile = "Playback", .ontop_level = -1, + .minimum_display_fps = -1, .timing_offset = 0.050, + .vrr_max_refresh_variance = 1, + .vrr_target_refresh_rate = -1, .swapchain_depth = 2, .focus_on = 1, }, diff --git a/options/options.h b/options/options.h index 1524262379a66..a18d80b91dd6b 100644 --- a/options/options.h +++ b/options/options.h @@ -82,8 +82,12 @@ typedef struct mp_vo_opts { int window_corners; double display_fps_override; + double minimum_display_fps; double timing_offset; int video_sync; + bool vrr_adjust; + double vrr_max_refresh_variance; + double vrr_target_refresh_rate; struct m_geometry android_surface_size; diff --git a/player/command.c b/player/command.c index a0dccf4a37452..f50b93f99bbfb 100644 --- a/player/command.c +++ b/player/command.c @@ -765,7 +765,7 @@ static int mp_property_mistimed_frame_count(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - if (!mpctx->vo_chain || !mpctx->display_sync_active) + if (!mpctx->vo_chain || !(mpctx->display_sync_active || mpctx->video_out->opts->vrr_adjust)) return M_PROPERTY_UNAVAILABLE; return m_property_int_ro(action, arg, mpctx->mistimed_frames_total); diff --git a/player/video.c b/player/video.c index e2cc4801f28a0..5fac138508dff 100644 --- a/player/video.c +++ b/player/video.c @@ -808,6 +808,11 @@ static void handle_display_sync_frame(struct MPContext *mpctx, mpctx->display_sync_active = false; + if (vo->opts->vrr_adjust && vo_get_pts_offset(vo) != 0) { + mpctx->mistimed_frames_total += 1; + MP_STATS(mpctx, "mistimed"); + } + if (!VS_IS_DISP(mode) || !vo_is_visible(vo)) return; diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c index 5150c900a782a..3602255995bb5 100644 --- a/video/out/gpu/video.c +++ b/video/out/gpu/video.c @@ -3596,7 +3596,7 @@ void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, // For the non-interpolation case, we draw to a single "cache" // texture to speed up subsequent re-draws (if any exist) - bool repeats = frame->num_vsyncs > 1 && frame->display_synced; + bool repeats = frame->request_repeat; bool r = false; if ((repeats || frame->still) && !p->dumb_mode && (p->ra->caps & RA_CAP_BLIT) && fbo->tex->params.blit_dst) diff --git a/video/out/vo.c b/video/out/vo.c index 8318a2ec6709d..8004a2e393737 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -156,8 +156,13 @@ struct vo_internal { bool expecting_vsync; int64_t num_successive_vsyncs; - int64_t flip_queue_offset; // queue flip events at most this much in advance - int64_t timing_offset; // same (but from options; not VO configured) + // queue flip events at most this much in advance, without changing the + // presentation time + int64_t flip_queue_offset; + // render this much in advance, without changing the flip event time + //(from options; not VO configured) + int64_t timing_offset; + double pts_offset; // for vrr int64_t delayed_count; int64_t drop_count; @@ -174,6 +179,12 @@ struct vo_internal { double display_fps; double reported_display_fps; + double minimum_display_time; // for vrr + double minimum_display_fps; //from options + double maximum_display_time; // for vrr + double vrr_target_refresh_rate; //from options + double vrr_target_refresh_time; + double prev_valid_duration; // for vrr struct stats_ctx *stats; }; @@ -391,6 +402,15 @@ static void reset_vsync_timings(struct vo *vo) in->num_successive_vsyncs = 0; } +//always called locked +static void discard_timing_info(struct vo *vo) +{ + struct vo_internal *in = vo->in; + in->pts_offset = 0; + in->prev_valid_duration = 0; + reset_vsync_timings(vo); +} + static double vsync_stddef(struct vo *vo, double ref_vsync) { struct vo_internal *in = vo->in; @@ -548,6 +568,7 @@ static void update_display_fps(struct vo *vo) in->reported_display_fps = fps; } + bool vrr_update = false; double display_fps = vo->opts->display_fps_override; if (display_fps <= 0) display_fps = in->reported_display_fps; @@ -557,6 +578,10 @@ static void update_display_fps(struct vo *vo) in->vsync_interval = MPMAX(in->nominal_vsync_interval, 1); in->display_fps = display_fps; + in->minimum_display_time = display_fps > 0 ? + MPMAX(MP_TIME_S_TO_NS(1) / display_fps, 1) : DBL_MAX; + vrr_update = true; + MP_VERBOSE(vo, "Assuming %f FPS for display sync.\n", display_fps); // make sure to update the player @@ -564,6 +589,44 @@ static void update_display_fps(struct vo *vo) wakeup_core(vo); } + double minimum_display_fps = vo->opts->minimum_display_fps; + + if (minimum_display_fps < 0) { + minimum_display_fps = display_fps; + } + + if (in->minimum_display_fps != minimum_display_fps || vrr_update) { + in->minimum_display_fps = minimum_display_fps; + + //even when minimum_display_fps does not change, we still recalculate for + //the minimum_display_time's MPMAX + in->maximum_display_time = minimum_display_fps > 0 ? + MPMAX(MP_TIME_S_TO_NS(1) / minimum_display_fps, in->minimum_display_time) : DBL_MAX; + + vrr_update = true; + } + + double vrr_target_refresh_rate = vo->opts->vrr_target_refresh_rate; + + if (vrr_target_refresh_rate != in->vrr_target_refresh_rate) { + in->vrr_target_refresh_rate = vrr_target_refresh_rate; + + if (vrr_target_refresh_rate < 0) { + vrr_update = true; + } + else { + in->vrr_target_refresh_time = vrr_target_refresh_rate > 0 ? + MPCLAMP(MP_TIME_S_TO_NS(1) / vrr_target_refresh_rate, in->minimum_display_time, in->maximum_display_time) + : in->maximum_display_time; + vrr_update = false; + } + } + + if (vrr_update) { + in->vrr_target_refresh_time = in->maximum_display_time < DBL_MAX ? + (in->minimum_display_time + in->maximum_display_time) / 2 : in->maximum_display_time; + } + mp_mutex_unlock(&in->lock); } @@ -625,7 +688,7 @@ static void run_reconfig(void *p) talloc_free(in->current_frame); in->current_frame = NULL; forget_frames(vo); - reset_vsync_timings(vo); + discard_timing_info(vo); mp_mutex_unlock(&in->lock); update_display_fps(vo); @@ -705,6 +768,7 @@ static void forget_frames(struct vo *vo) if (in->current_frame) { in->current_frame->num_vsyncs = 0; // but reset future repeats in->current_frame->display_synced = false; // mark discontinuity + in->current_frame->request_repeat = false; } } @@ -837,7 +901,7 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts) struct vo_internal *in = vo->in; mp_mutex_lock(&in->lock); bool r = vo->config_ok && !in->frame_queued && - (!in->current_frame || in->current_frame->num_vsyncs < 1); + (!in->current_frame || !in->current_frame->request_repeat); 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. @@ -845,6 +909,7 @@ bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts) // time. next_pts -= in->timing_offset; next_pts -= in->flip_queue_offset; + next_pts += in->pts_offset; int64_t now = mp_time_ns(); if (next_pts > now) r = false; @@ -877,12 +942,10 @@ void vo_queue_frame(struct vo *vo, struct vo_frame *frame) struct vo_internal *in = vo->in; mp_mutex_lock(&in->lock); mp_assert(vo->config_ok && !in->frame_queued && - (!in->current_frame || in->current_frame->num_vsyncs < 1)); + (!in->current_frame || !in->current_frame->request_repeat)); in->hasframe = true; frame->frame_id = ++(in->current_frame_id); in->frame_queued = frame; - in->wakeup_pts = frame->display_synced - ? 0 : frame->pts + MPMAX(frame->duration, 0); wakeup_locked(vo); mp_mutex_unlock(&in->lock); } @@ -917,41 +980,160 @@ static bool render_frame(struct vo *vo) { struct vo_internal *in = vo->in; struct vo_frame *frame = NULL; + struct vo_frame *unmodified_frame = NULL; bool more_frames = false; update_display_fps(vo); mp_mutex_lock(&in->lock); - if (in->frame_queued) { + //if by odd chance we have a frame queued while request_repeat is true, + //prioritize ending request_repeat first, which may still have time to be validly + //displayed before frame_queued. + if (in->frame_queued && + !(vo->opts->vrr_adjust && in->current_frame && in->current_frame->request_repeat)) { 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->current_frame->display_synced && in->current_frame->num_vsyncs < 1) || - !in->current_frame->display_synced) + } else if (in->paused || !in->current_frame || !in->hasframe || !in->current_frame->request_repeat) { goto done; } + //in->current_frame may be modified by vo. + //this below frame may be modified by vo and driver. + //unmodified_frame may not be modified. frame = vo_frame_ref(in->current_frame); + unmodified_frame = vo_frame_ref(in->current_frame); mp_assert(frame); - - if (frame->display_synced) { + mp_assert(unmodified_frame); + double unmodified_pts_offset = in->pts_offset; //storing for later + + if (frame->display_synced && !vo->opts->vrr_adjust) { frame->pts = 0; frame->duration = -1; } + //we special case negative frame->duration inputs to not drop frames. + //if this becomes true, we may later switch to false if needed. + in->dropped_frame = frame->duration >= 0; + + //we are assuming valid frame inputs should always have >= 0 frame->duration, + //except the above special case. so now make sure it's non-negative so that + //it doesn't mess up future calculations. + frame->duration = MPMAX(frame->duration, 0); + + //we adjust the pts, while maintaining the end time the same. + frame->pts += in->pts_offset; + frame->duration -= in->pts_offset; + + in->current_frame->request_repeat = false; int64_t now = mp_time_ns(); - int64_t pts = frame->pts; - int64_t duration = frame->duration; - int64_t end_time = pts + duration; + if (now > frame->pts) { + //if time has moved past our starting position, then this reduces our + //expected duration. even if frame->duration becomes negative now, we allow frame drops. + //for purposes of vrr_max_refresh_variance, we do not update prev_valid_duration, + //as we only consider valid duration the moment it was given to the driver. + double offset = now-frame->pts; + frame->duration -= offset; + frame->pts = now; + } + + if (frame->duration <= 0) { + //move next frame to current position. this helps maintain the previously + //defined valid pts_offset. + in->pts_offset = -frame->duration; + } + else if (vo->opts->vrr_adjust){ + double minimum_display_time = in->minimum_display_time; + double maximum_display_time = in->maximum_display_time; + double target_refresh_time = in->vrr_target_refresh_time; + + if (vo->opts->vrr_max_refresh_variance < 1 && in->prev_valid_duration > 0) { + minimum_display_time = MPCLAMP(in->prev_valid_duration * (1 - vo->opts->vrr_max_refresh_variance), minimum_display_time, maximum_display_time); + maximum_display_time = MPCLAMP(in->prev_valid_duration / (1 - vo->opts->vrr_max_refresh_variance), minimum_display_time, maximum_display_time); + //we also meed to clamp target_refresh_time else we end up forcefully + //pushing towards it even when the clamped thresholds would cause us + //to miss our target. + //we will still slowly push towards it each time we do this clamp. + target_refresh_time = MPCLAMP(target_refresh_time, minimum_display_time, maximum_display_time); + } + + for (int i = 0; i <= 1; i++) { + //we put the effort into always reaching target_refresh_time positions, + //which gives us some error leeway in case there are random application + //or os delays that would cause us to go beyond our pts. + double targetDuration = frame->duration; + + //get the first target_refresh_time at i == 0, or the next at i == 1. + //we could perform this just once at i == 0 and then do a + target_refresh_time + //at i == 1, however that may cause loss of precision. + //since target_refresh_time is higher or equal to minimum_display_time, this + //guarantees i == 1 is always higher than minimum_display_time, which is + //our only significant threshold. + targetDuration -= (floor(frame->duration / target_refresh_time) - i) * target_refresh_time; + + bool newFrameRepeat = targetDuration != frame->duration; + + if (targetDuration < minimum_display_time) { + //move the next frame towards the closest valid position, to reduce any stuttering effects. + //this also benefits when the next frame is repeating, as it puts us closer to + //our target_refresh_time. + //we have to avoid reaching this point as much as reasonable. + //the lrint should return either 0 or 1, so the result is either 0 or in->minimum_display_time + targetDuration = lrint(targetDuration / minimum_display_time) * minimum_display_time; + } + else if (targetDuration > maximum_display_time) { + //reaching target_refresh_time positions should avoid us getting in this position, + //unless the frame is repeating. + //using lrint to get the closest full divisible of duration by target_refresh_time + //and capping it appropriatelly. + + //divisions should never be smaller than 1 + int64_t divisions = lrint(targetDuration / target_refresh_time); + targetDuration = MPCLAMP(targetDuration / divisions, minimum_display_time, maximum_display_time); + newFrameRepeat = true; + } + else { + //targetDuration should be perfectly fine in range + } + + if (targetDuration <= 0) { + if (!newFrameRepeat) { + //drop this frame and get the next frameQueued. + goto endLoop; + } + else { + //we only get here when the duration is lower than the minimum_display_time. + //so we loop back around to increase the targetDuration range that we consider, + //which should add an additional target_refresh_time, which should guarantee + //it is no longer lower than the minimum_display_time, so we only get here + //when i == 0 + mp_assert(i == 0); + } + } + else { + //we good + in->current_frame->request_repeat |= newFrameRepeat; + goto endLoop; + } + + continue; + endLoop: + in->pts_offset = targetDuration - frame->duration; + frame->duration = targetDuration; + break; + } + } + + //don't think there is a need to worry about any issues with pts or duration being negative + int64_t end_time = frame->pts + frame->duration; // Time at which we should flip_page on the VO. - int64_t target = frame->display_synced ? 0 : pts - in->flip_queue_offset; + int64_t target = frame->display_synced ? 0 : frame->pts - in->flip_queue_offset; // "normal" strict drop threshold. - in->dropped_frame = duration >= 0 && end_time < now; + in->dropped_frame &= frame->duration <= 0; in->dropped_frame &= !frame->display_synced; in->dropped_frame &= !(vo->driver->caps & VO_CAP_FRAMEDROP); @@ -974,8 +1156,14 @@ static bool render_frame(struct vo *vo) } in->dropped_frame |= in->current_frame->num_vsyncs < 1; } - if (in->current_frame->num_vsyncs > 0) + + if (in->current_frame->num_vsyncs > 0) { in->current_frame->num_vsyncs -= 1; + in->current_frame->request_repeat |= !!in->current_frame->num_vsyncs; + } + + //also update on this frame to let the driver know we will repeat + frame->request_repeat = in->current_frame->request_repeat; // Always render when paused (it's typically the last frame for a while). in->dropped_frame &= !in->paused; @@ -987,18 +1175,32 @@ static bool render_frame(struct vo *vo) // Store the initial value before we unlock. bool request_redraw = in->request_redraw; + bool current_controlled_drop = false; + + if (vo->opts->vrr_adjust && in->dropped_frame && frame->repeat) { + in->dropped_frame = false; + current_controlled_drop = true; + } + + bool driver_has_received_frame = false; + bool driver_dropped_frame = false; + + if (in->dropped_frame || current_controlled_drop) { + //do not log vrr repeat frame drops + if (in->dropped_frame) + in->drop_count += 1; - if (in->dropped_frame) { - in->drop_count += 1; wakeup_core(vo); } else { + double unmodified_prev_valid_duration = in->prev_valid_duration; + in->prev_valid_duration = frame->duration; in->rendering = true; in->hasframe_rendered = true; int64_t prev_drop_count = vo->in->drop_count; // Can the core queue new video now? Non-display-sync uses a separate // timer instead, but possibly benefits from preparing a frame early. bool can_queue = !in->frame_queued && - (in->current_frame->num_vsyncs < 1 || !use_vsync); + (!in->current_frame->request_repeat || in->paused); mp_mutex_unlock(&in->lock); if (can_queue) @@ -1007,6 +1209,7 @@ static bool render_frame(struct vo *vo) stats_time_start(in->stats, "video-draw"); in->visible = vo->driver->draw_frame(vo, frame); + driver_has_received_frame = true; stats_time_end(in->stats, "video-draw"); @@ -1030,19 +1233,42 @@ static bool render_frame(struct vo *vo) stats_time_end(in->stats, "video-flip"); mp_mutex_lock(&in->lock); - in->dropped_frame = prev_drop_count < vo->in->drop_count; + //if in->drop_count increases, assumption is that the current frame may have been dropped + driver_dropped_frame = prev_drop_count < vo->in->drop_count; + //if multiple frames have been dropped, always set to true. + in->dropped_frame = in->drop_count - prev_drop_count > 1; + + //we might still have valid time to output the current frame even after, for whatever + //reason, the driver has dropped it, so retry. we won't be retrying forever + //since it will become old and vo will properly drop it to go next. + if (vo->opts->vrr_adjust && in->current_frame && driver_dropped_frame) { + talloc_free(in->current_frame); + //reverting timing info + in->current_frame = unmodified_frame; + unmodified_frame = NULL; + in->current_frame->request_repeat = true; + in->pts_offset = unmodified_pts_offset; + in->prev_valid_duration = unmodified_prev_valid_duration; + in->drop_count -= 1; + current_controlled_drop = true; + } + else { + in->dropped_frame = driver_dropped_frame; + } + in->rendering = false; update_vsync_timing_after_swap(vo, &vsync); } - if (vo->driver->caps & VO_CAP_NORETAIN) { + if (vo->driver->caps & VO_CAP_NORETAIN && in->current_frame) { talloc_free(in->current_frame); in->current_frame = NULL; } - if (in->dropped_frame) { - MP_STATS(vo, "drop-vo"); + if (in->dropped_frame || current_controlled_drop) { + if (in->dropped_frame) + MP_STATS(vo, "drop-vo"); } else { // If the initial redraw request was true and mpv is still playing, // then we can clear it here since the next loop will guarantee that @@ -1054,9 +1280,14 @@ static bool render_frame(struct vo *vo) in->request_redraw = false; } - if (in->current_frame && in->current_frame->num_vsyncs && - in->current_frame->display_synced) + if (in->current_frame && in->current_frame->request_repeat) { more_frames = true; + //set it to 0 while repeating + in->wakeup_pts = 0; + } + else { + in->wakeup_pts = end_time; + } if (in->frame_queued && in->frame_queued->display_synced) more_frames = true; @@ -1064,8 +1295,13 @@ static bool render_frame(struct vo *vo) mp_cond_broadcast(&in->wakeup); // for vo_wait_frame() done: - if (!(vo->driver->caps & VO_CAP_FRAMEOWNER) || in->dropped_frame) + //why do we need to check for when the driver has dropped frame if we are not the frameowner? + if (!(vo->driver->caps & VO_CAP_FRAMEOWNER) || !driver_has_received_frame || driver_dropped_frame) talloc_free(frame); + + if (unmodified_frame) + talloc_free(unmodified_frame); + mp_mutex_unlock(&in->lock); return more_frames; @@ -1232,7 +1468,7 @@ void vo_set_paused(struct vo *vo, bool paused) in->request_redraw = true; wakeup_core(vo); } - reset_vsync_timings(vo); + discard_timing_info(vo); wakeup_locked(vo); } mp_mutex_unlock(&in->lock); @@ -1280,7 +1516,7 @@ void vo_seek_reset(struct vo *vo) struct vo_internal *in = vo->in; mp_mutex_lock(&in->lock); forget_frames(vo); - reset_vsync_timings(vo); + discard_timing_info(vo); in->send_reset = true; wakeup_locked(vo); mp_mutex_unlock(&in->lock); @@ -1392,11 +1628,20 @@ double vo_get_delay(struct vo *vo) return res ? MP_TIME_NS_TO_S(res - mp_time_ns()) : 0; } +double vo_get_pts_offset(struct vo *vo) +{ + struct vo_internal *in = vo->in; + mp_mutex_lock(&in->lock); + double res = in->pts_offset; + mp_mutex_unlock(&in->lock); + return res; +} + void vo_discard_timing_info(struct vo *vo) { struct vo_internal *in = vo->in; mp_mutex_lock(&in->lock); - reset_vsync_timings(vo); + discard_timing_info(vo); mp_mutex_unlock(&in->lock); } diff --git a/video/out/vo.h b/video/out/vo.h index 8e397618eb911..09ccfbd79e11b 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -255,6 +255,8 @@ struct vo_frame { // A repeat frame can be redrawn, in which case repeat==redraw==true, and // OSD should be updated. bool redraw, repeat; + // Set to request the vo to continue repeating the frame. + bool request_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. @@ -557,6 +559,7 @@ double vo_get_estimated_vsync_jitter(struct vo *vo); double vo_get_display_fps(struct vo *vo); void * vo_get_display_swapchain(struct vo *vo); double vo_get_delay(struct vo *vo); +double vo_get_pts_offset(struct vo *vo); void vo_discard_timing_info(struct vo *vo); struct vo_frame *vo_get_current_vo_frame(struct vo *vo); struct mp_image *vo_get_image(struct vo *vo, int imgfmt, int w, int h, diff --git a/video/out/vo_gpu_next.c b/video/out/vo_gpu_next.c index 87d6610600263..f49652fb1039d 100644 --- a/video/out/vo_gpu_next.c +++ b/video/out/vo_gpu_next.c @@ -1224,7 +1224,7 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame) struct pl_render_params params = pars->params; const struct gl_video_opts *opts = p->opts_cache->opts; - bool will_redraw = frame->display_synced && frame->num_vsyncs > 1; + bool will_redraw = frame->request_repeat; bool cache_frame = will_redraw || frame->still || p->paused; bool can_interpolate = opts->interpolation && frame->display_synced && !frame->still && frame->num_frames > 1 && !p->paused;