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