diff options
Diffstat (limited to 'player/video.c')
-rw-r--r-- | player/video.c | 441 |
1 files changed, 242 insertions, 199 deletions
diff --git a/player/video.c b/player/video.c index d09cf3800a..9be4f6a9a0 100644 --- a/player/video.c +++ b/player/video.c @@ -206,14 +206,15 @@ 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; mpctx->dropped_frames_total = 0; mpctx->dropped_frames = 0; mpctx->mistimed_frames_total = 0; mpctx->drop_message_shown = 0; mpctx->display_sync_drift_dir = 0; + mpctx->display_sync_broken = false; mpctx->video_status = mpctx->d_video ? STATUS_SYNCING : STATUS_EOF; } @@ -288,7 +289,7 @@ int reinit_video_chain(struct MPContext *mpctx) } #if HAVE_ENCODING - if (mpctx->encode_lavc_ctx && d_video) + if (mpctx->encode_lavc_ctx) encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, d_video->fps); #endif @@ -390,10 +391,6 @@ static int decode_image(struct MPContext *mpctx) struct demux_packet *pkt; if (demux_read_packet_async(d_video->header, &pkt) == 0) return VD_WAIT; - if (pkt && pkt->pts != MP_NOPTS_VALUE) - pkt->pts += mpctx->video_offset; - if (pkt && pkt->dts != MP_NOPTS_VALUE) - pkt->dts += mpctx->video_offset; if ((pkt && pkt->pts >= mpctx->hrseek_pts - .005) || d_video->has_broken_packet_pts || !mpctx->opts->hr_seek_framedrop) @@ -582,9 +579,9 @@ static void handle_new_frame(struct MPContext *mpctx) // Assume a discontinuity. MP_WARN(mpctx, "Invalid video timestamp: %f -> %f\n", mpctx->video_pts, pts); - frame_time = 0; - if (mpctx->d_audio) + if (mpctx->d_audio && fabs(frame_time) > 1.0) mpctx->audio_status = STATUS_SYNCING; + frame_time = 0; } } mpctx->video_next_pts = pts; @@ -611,10 +608,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)); } @@ -795,76 +796,52 @@ 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; + 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 num > 0 ? total / num : 0; +} - // 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; +// 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; } - goto fail; } + return best_scale; +} - return modified_duration; - -fail: - return require_exact ? -1 : total_duration / num; +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) @@ -874,62 +851,87 @@ static bool using_spdif_passthrough(struct MPContext *mpctx) return false; } -static void adjust_audio_speed(struct MPContext *mpctx, double vsync) +// Compute the relative audio speed difference by taking A/V dsync into account. +static double compute_audio_drift(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; + // Least-squares linear regression, using relative real time for x, and + // audio desync for y. Assume speed didn't change for the frames we're + // looking at for simplicity. This also should actually use the realtime + // (minus paused time) for x, but use vsync scheduling points instead. + if (mpctx->num_past_frames <= 10) + return NAN; + int num = mpctx->num_past_frames - 1; + double sum_x = 0, sum_y = 0, sum_xy = 0, sum_xx = 0; + double x = 0; + for (int n = 0; n < num; n++) { + struct frame_info *frame = &mpctx->past_frames[n + 1]; + if (frame->num_vsyncs < 0) + return NAN; + double y = frame->av_diff; + sum_x += x; + sum_y += y; + sum_xy += x * y; + sum_xx += x * x; + x -= frame->num_vsyncs * vsync; } - - mpctx->speed_factor_a = audio_factor * mpctx->speed_factor_v; - - MP_STATS(mpctx, "value %f aspeed", mpctx->speed_factor_a - 1); + return (sum_x * sum_y - num * sum_xy) / (sum_x * sum_x - num * sum_xx); } -// 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) +static void adjust_audio_resample_speed(struct MPContext *mpctx, double vsync) { struct MPOpts *opts = mpctx->opts; + int mode = opts->video_sync; - 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 + if (mode != VS_DISP_RESAMPLE || mpctx->audio_status != STATUS_PLAYING) { + mpctx->speed_factor_a = mpctx->speed_factor_v; + return; + } + + // 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; + + double max_correct = opts->sync_max_audio_change / 100; + double audio_factor = 1 + max_correct * -mpctx->display_sync_drift_dir; + + if (new == 0) { + // If we're resetting, actually try to be clever and pick a speed + // which compensates the general drift we're getting. + double drift = compute_audio_drift(mpctx, vsync); + if (isnormal(drift)) { + // other = will be multiplied with audio_factor for final speed + double other = mpctx->opts->playback_speed * mpctx->speed_factor_v; + audio_factor = (mpctx->audio_speed - drift) / other; + MP_VERBOSE(mpctx, "Compensation factor: %f\n", audio_factor); + } + } + + audio_factor = MPCLAMP(audio_factor, 1 - max_correct, 1 + max_correct); + mpctx->speed_factor_a = audio_factor * mpctx->speed_factor_v; } - return -1; } // Manipulate frame timing for display sync, or do nothing for normal timing. @@ -938,7 +940,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) { @@ -947,11 +948,9 @@ 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; + if (!VS_IS_DISP(mode) || mpctx->display_sync_broken) + return; 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 || @@ -959,43 +958,29 @@ static void handle_display_sync_frame(struct MPContext *mpctx, drop &= (opts->frame_dropping & 1); if (resample && using_spdif_passthrough(mpctx)) - goto done; + return; 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; - } + return; - mpctx->speed_factor_v = calc_best_speed(mpctx, vsync, adjusted_duration); - if (mpctx->speed_factor_v <= 0) { - mpctx->speed_factor_v = 1.0; - goto done; + double adjusted_duration = MPMAX(0, mpctx->past_frames[0].approx_duration); + adjusted_duration /= opts->playback_speed; + if (adjusted_duration > 0.5) + return; + + mpctx->speed_factor_v = 1.0; + if (mode != VS_DISP_VDROP) { + double best = find_best_speed(mpctx, vsync); + // If it doesn't work, play at normal speed. + if (fabs(best - 1.0) <= opts->sync_max_video_change / 100) + mpctx->speed_factor_v = best; } 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; + if (fabs(av_diff) > 0.5) { + mpctx->display_sync_broken = true; + return; } // Determine for how many vsyncs a frame should be displayed. This can be @@ -1004,24 +989,23 @@ 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; - MP_DBG(mpctx, "s=%f vsyncs=%d dur=%f ratio=%f err=%.20f (%f)\n", + MP_DBG(mpctx, "s=%f vsyncs=%d dur=%f ratio=%f err=%.20f (%f/%f)\n", mpctx->speed_factor_v, num_vsyncs, adjusted_duration, ratio, - mpctx->display_sync_error, mpctx->display_sync_error / vsync); + mpctx->display_sync_error, mpctx->display_sync_error / vsync, + mpctx->display_sync_error / frame_duration); 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 we are too far ahead/behind, attempt to drop/repeat frames. // Tolerate some desync to avoid frame dropping due to jitter. - if (drop && fabs(av_diff) >= 0.080 && fabs(av_diff) / vsync >= 2) + if (drop && fabs(av_diff) >= 0.020 && fabs(av_diff) / vsync >= 1) drop_repeat = -av_diff / vsync; // round towards 0 // We can only drop all frames at most. We can repeat much more frames, @@ -1032,8 +1016,8 @@ static void handle_display_sync_frame(struct MPContext *mpctx, // Estimate the video position, so we can calculate a good A/V difference // value below. This is used to estimate A/V drift. - double time_left = (vo_get_next_frame_start_time(vo) - mp_time_us()) / 1e6; - time_left = MPMAX(time_left, 0); + double time_left = vo_get_delay(vo); + // 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 A/V sync calculations we pretend it's perfect. @@ -1041,66 +1025,112 @@ static void handle_display_sync_frame(struct MPContext *mpctx, // Likewise, we know sync is off, but is going to be compensated. time_left += drop_repeat * vsync; - if (drop_repeat) + if (drop_repeat) { mpctx->mistimed_frames_total += 1; + MP_STATS(mpctx, "mistimed"); + } mpctx->total_avsync_change = 0; update_av_diff(mpctx, time_left * opts->playback_speed); - if (resample) - adjust_audio_speed(mpctx, vsync); + mpctx->past_frames[0].num_vsyncs = num_vsyncs; + mpctx->past_frames[0].av_diff = mpctx->last_av_difference; + + if (resample) { + adjust_audio_resample_speed(mpctx, vsync); + } else { + mpctx->speed_factor_a = 1.0; + } // A bad guess, only needed when reverting to audio sync. mpctx->time_frame = time_left; + frame->vsync_interval = vsync; + frame->vsync_offset = -prev_error; + frame->ideal_frame_duration = frame_duration; 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); + MP_STATS(mpctx, "value %f aspeed", mpctx->speed_factor_a - 1); + MP_STATS(mpctx, "value %f vspeed", mpctx->speed_factor_v - 1); } static void schedule_frame(struct MPContext *mpctx, struct vo_frame *frame) { handle_display_sync_frame(mpctx, frame); + 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"); + } + if (!mpctx->display_sync_active) { + mpctx->speed_factor_a = 1.0; + mpctx->speed_factor_v = 1.0; + update_playback_speed(mpctx); + update_av_diff(mpctx, mpctx->time_frame > 0 ? mpctx->time_frame * mpctx->video_speed : 0); } } -// 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 demux_duration = + mpctx->d_video->fps > 0 ? 1.0 / mpctx->d_video->fps : -1; + double duration = -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; + 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; + } + double approx_duration = num_dur > 0 ? total / num_dur : duration; + + // Try if the demuxer frame rate fits - if so, just take it. + 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) @@ -1186,6 +1216,17 @@ 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}); + mpctx->past_frames[0] = (struct frame_info){ + .pts = mpctx->next_frames[0]->pts, + .num_vsyncs = -1, + }; + calculate_frame_duration(mpctx); + struct vo_frame dummy = { .pts = pts, .duration = -1, @@ -1197,7 +1238,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 = 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) { // expected A/V sync correction is ignored diff /= mpctx->video_speed; @@ -1210,6 +1253,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; @@ -1218,8 +1263,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); |