summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2015-08-10 18:38:57 +0200
committerwm4 <wm4@nowhere>2015-08-10 18:38:57 +0200
commit3d1cc17ab27b98294f5710e69250e95347d84598 (patch)
tree5c38e248a621027277ca8636f4aa06ed2a74ceb2
parent8f2d9db79fc2f542b2973f245cc6b93fd35c1d80 (diff)
downloadmpv-3d1cc17ab27b98294f5710e69250e95347d84598.tar.bz2
mpv-3d1cc17ab27b98294f5710e69250e95347d84598.tar.xz
player: redo estimated-vf-fps calculation
Additionally to taking the average, this tries to use the demuxer FPS to eliminate jitter, and applies some other heuristics to check if the result is sane. This code will also be used for the display sync code (it will actually make use of the require_exact parameter). (The value of doing this over keeping the simpler demux_mkv hack is somewhat questionable. But at least it allows us to deal with other container formats that use jittery timestamps, such as mp4 remuxed from mkv.)
-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.