summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--player/command.c10
-rw-r--r--player/core.h5
-rw-r--r--player/loadfile.c1
-rw-r--r--player/video.c72
4 files changed, 81 insertions, 7 deletions
diff --git a/player/command.c b/player/command.c
index cb565df896..c87d9378b7 100644
--- a/player/command.c
+++ b/player/command.c
@@ -2625,14 +2625,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 durations[10];
- int num = get_past_frame_durations(mpctx, durations, MP_ARRAY_SIZE(durations));
- if (num < MP_ARRAY_SIZE(durations))
+ double res = stabilize_frame_duration(mpctx, false);
+ if (res <= 0)
return M_PROPERTY_UNAVAILABLE;
- double duration = 0;
- for (int n = 0; n < num; n++)
- duration += durations[n];
- return m_property_double_ro(action, arg, num / duration);
+ return m_property_double_ro(action, arg, 1 / res);
}
/// Video aspect (RO)
diff --git a/player/core.h b/player/core.h
index c34d2ddd6d..75dd2edec7 100644
--- a/player/core.h
+++ b/player/core.h
@@ -76,6 +76,9 @@ enum seek_precision {
MPSEEK_VERY_EXACT,
};
+// Comes from the assumption that some formats round timestamps to ms.
+#define FRAME_DURATION_TOLERANCE 0.0011
+
struct track {
enum stream_type type;
@@ -235,6 +238,7 @@ typedef struct MPContext {
enum playback_status video_status, audio_status;
bool restart_complete;
+ bool broken_fps_header;
/* Set if audio should be timed to start with video frame after seeking,
* not set when e.g. playing cover art */
bool sync_audio_to_video;
@@ -488,5 +492,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);
#endif /* MPLAYER_MP_CORE_H */
diff --git a/player/loadfile.c b/player/loadfile.c
index 59b8924948..6d94c13bd3 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -1018,6 +1018,7 @@ static void play_current_file(struct MPContext *mpctx)
mpctx->playing_msg_shown = false;
mpctx->backstep_active = false;
mpctx->max_frames = -1;
+ mpctx->broken_fps_header = false;
mpctx->seek = (struct seek_params){ 0 };
reset_playback_state(mpctx);
diff --git a/player/video.c b/player/video.c
index 1e30d289ec..539a75c08e 100644
--- a/player/video.c
+++ b/player/video.c
@@ -784,6 +784,78 @@ 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)
+{
+ 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;
+ }
+
+ return modified_duration;
+
+fail:
+ return require_exact ? -1 : total_duration / num;
+}
+
// 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.