FFmpeg音视频同步
SDL2文章列表
前两篇文章分别做了音频和视频的播放,要实现一个完整的简易播放器就必须要做到音视频同步播放了,而音视频同步在音视频开发中又是非常重要的知识点,所以在这里记录下音视频同步相关知识的理解。
音视频同步简介
如何做到音视频同步?要知道音视频同步是一个动态的过程,同步是暂时的,不同步才是常态,需要一种随着时间会线性增长的量,视频和音频的播放速度都以该量为标准,播放快了就减慢播放速度;播放慢了就加快播放的速度,在你追我赶中达到同步的状态。目前主要有三种方式实现同步:
比较主流的是第三种,将视频同步到音频上。至于为什么不使用前两种,因为一般来说,人对于声音的敏感度更高,如果频繁地去调整音频会产生杂音让人感觉到刺耳不舒服,而人对图像的敏感度就低很多了,所以一般都会采用第三种方式。
复习DTS、PTS和时间基
- PTS: Presentation Time Stamp,显示渲染用的时间戳,告诉我们什么时候需要显示
- DTS: Decode Time Stamp,视频解码时的时间戳,告诉我们什么时候需要解码
- 用当前帧的PTS - 上一播放帧的PTS得到一个延迟时间
- 用当前帧的PTS和Audio_Clock进行比较,来判断视频的播放速度是快了还是慢了
在音频中PTS和DTS一般相同。但是在视频中,由于B帧的存在,PTS和DTS可能会不同。
实际帧顺序:I B B P
存放帧顺序:I P B B
解码时间戳:1 4 2 3
展示时间戳:1 2 3 4
1 |
/** |
时间基是一个分数,以秒为单位,比如1/50秒,那它到底表示的是什么意思呢?以帧率为例,如果它的时间基是1/50秒,那么就表示每隔1/50秒显示一帧数据,也就是每1秒显示50帧,帧率为50FPS。
每一帧数据都有对应的PTS,在播放视频或音频的时候我们需要将PTS时间戳转化为以秒为单位的时间,用来最后的展示。那如何计算一桢在整个视频中的时间位置?
1 |
static inline double av_q2d(AVRational a){ |
Audio_Clock
Audio_Clock,也就是Audio的播放时长,从开始到当前的时间。获取Audio_Clock:
1 |
if (pkt->pts != AV_NOPTS_VALUE) { |
还没有结束,由于一个packet中可以包含多个Frame帧,packet中的PTS比真正的播放的PTS可能会早很多,可以根据Sample Rate 和 Sample Format来计算出该packet中的数据可以播放的时长,再次更新Audio_Clock。
1 |
// 每秒钟音频播放的字节数 采样率 * 通道数 * 采样位数 (一个sample占用的字节数) |
最后还有一步,在我们获取这个Audio_Clock时,很有可能音频缓冲区还有没有播放结束的数据,也就是有一部分数据实际还没有播放,所以就要在Audio_Clock上减去这部分数据的播放时间,才是真正的Audio_Clock。
1 |
double get_audio_clock(VideoState *state) { |
get_audio_clock
中返回的才是我们最终需要的Audio_Clock,当前的音频的播放时长。
Video_Clock
Video_Clock,视频播放到当前帧时的已播放的时间长度。
1 |
avcodec_send_packet(state->video_ctx, packet); |
旧版的FFmpeg使用
av_frame_get_best_effort_timestamp
函数获取视频的最合适PTS,新版本的则在解码时生成了
best_effort_timestamp
。但是依然可能会获取不到正确的PTS,所以在
synchronize_video
中进行处理。
1 |
double synchronize_video(VideoState *state, AVFrame *src_frame, double pts) { |
同步
上面两步获得了Audio_Clock和Video_Clock,这样我们就有了视频流中Frame的显示时间,并且得到了作为基准时间的音频播放时长Audio clock ,可以将视频同步到音频了。