diff options
Diffstat (limited to 'player')
-rw-r--r-- | player/command.c | 6 | ||||
-rw-r--r-- | player/core.h | 18 | ||||
-rw-r--r-- | player/loadfile.c | 1 | ||||
-rw-r--r-- | player/playloop.c | 21 | ||||
-rw-r--r-- | 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); |