视频帧率与显示器刷新率不匹配问题
视频帧率与显示器刷新率不匹配问题
在视频播放过程中,我们有时会遇到画面卡顿、掉帧、不流畅等问题。这些问题往往与视频的帧率和显示器的刷新率不匹配有关。本文将从技术角度深入探讨这一现象,并通过代码分析其背后的原理。
如图所示,当视频的帧率为25fps时,每帧画面的渲染时间为40ms(1/25秒)。如果显示器的刷新率为50Hz,即每20ms刷新一次,那么显示器刷新两次正好可以显示一帧画面。但是,当显示器的刷新率为60Hz时,问题就出现了。60Hz意味着每16.67ms刷新一次,这与40ms的帧率无法完美匹配。
在这种情况下,程序会将一帧画面延迟40ms后渲染下一帧。由于显示器在第三次刷新时才会完成当前帧的渲染,因此第一帧画面实际上在屏幕上显示了3个屏幕时间(即3次刷新周期,约50ms)。随后的帧则会显示2个屏幕时间(约33ms)。这种不均匀的显示时间会导致画面出现卡顿、掉帧等现象。
这个问题在ffmpeg的视频同步代码中有所体现:
double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {
double frame_delay;
/* if we have pts, set video clock to it */
is->video_clock = pts;
} else {
/* if we aren't given a pts, set it to the clock */
pts = is->video_clock;
}
/* update the video clock */
frame_delay = av_q2d(is->video_st->codec->time_base);
/* if we are repeating a frame, adjust clock accordingly */
frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
is->video_clock += frame_delay;
return pts;
}
其中frame_delay
代表一帧画面在显示器上的时长。代码中使用了repeat_pict
来计算延迟,这个值表示画面重复的次数。官方解释指出,对于隔行扫描的帧,repeat_pict
可以设置为1,而对于逐行扫描的帧,它可以设置为2的倍数。然而,现代设备中已经很少使用隔行扫描,因此这个值通常为0。
在ffplay.c中,实际是通过frame_rate
来计算一帧视频的播放时长:
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
对于24fps的视频,frame_rate.den
为1,frame_rate.num
为24,通过av_q2d
计算出的duration
为1/24秒。
虽然很多播放器会优先使用AVFrame
中的duration
值,但如果该值为空,则会使用frame_rate
来计算:
frame.duration = avframe.pointee.duration
if frame.duration == 0, avframe.pointee.sample_rate != 0, frame.timebase.num != 0 {
frame.duration = Int64(avframe.pointee.nb_samples) * Int64(frame.timebase.den) / (Int64(avframe.pointee.sample_rate) * Int64(frame.timebase.num))
}
总之,repeat_pict
这个值主要是为了兼容老的播放设备,比如在3:2下拉转换中保持原始电影的帧率和运动感。在现代设备中,这个值通常为0,实际的帧率控制主要依赖于frame_rate
。