上两篇文章单独写了FFmpeg解码视频与播放《FFmpeg音频解码播放》《FFmpeg视频解码播放》在项目中不可能直接使用一个线程来做视频的解码播放,通常视频流与音频流的解码播放都是存在单独的线程。线程随机抢占cpu来执行任务,解码过程中各帧数据解码的复杂度不一,导致执行任务速度上存在差异的,就会存在声音与画面不同步,严重的影响观看体验。
要做音视频的同步就要引入帧的缓冲对列,来缓存视频解码的帧数据,通过调节音频播放线程或画面渲染线程控制播放的速度来达到声音与画面同步,也可以解决因为解码复杂度的不一导致解码的抖动导致的视频播放卡顿,影响到视频播放。
机器是并不能像我们人一样去感知声音与画面是否在同一时间上播放。因此在视频录制过程中都会给每帧数据标记一个时间戳,以方便来确定什么时刻去播放。这样就能严格的将声音与画面对应起来。
通常视频画面帧率25帧每秒,声音的采样率44100HZ,每秒都会抽取44100个采样点,理论上来说每生产一帧数据都会有一个固定的时长,实际中还是会存在略微偏差。包括声音和画面都是分开采集的时间上也会存在略微偏差,但在一定的阈值之内人是感觉不到音画的差异。声音与画面的同步就是控制好在同一时刻播放器该去渲染帧画面,播放那帧音频数据。
音视频同步的三种方式:
1.以声音为播放基准,调控画面渲染的速度
2.以画面渲染为基准,调控声音播放速度快慢
3.参考一个外部时钟,将声音与画面同步至此时间
通常我们都采用第一种方式,以声音为基准去调控画面的播放速度,一般画面的播放速度要比声音播放要快,人耳对声音的快慢感知度是比较强的,能够快速的感知声音的不正常(快或者是慢),对于画面的渲染其实就是一张张的图片刷新,反而人眼的感知度却没有那么强。
具体看看怎么实现画面同步到音频的先来了解一下FFmpeg 中一些时间相关概念
DTS与PTS
每帧数据都有标识DTS(解码时间戳)和PTS(显示时间戳),音频的PTS和DTS是一致的,而某些视频中因为有B帧的存在DTS和PTS不一至,DTS与PTS用来确定该帧数据的解码时刻与显示时刻。
时间基:time_base(时间的基准,作为时间的度量单位)
对于视频流与音频流的time_base不一定是相同的,因此在做同步的时候要取对应流中的时间基作为计算标准来计算当前的时间。time_base简单的理解就是一个时间的度量单位,通过当前帧的PTS*time_base就可以获取到当前的实际播放的时间。
获取time_base
timeBase = pFormatContext->streams[stream_index]->time_base;
计算当前帧实际时间
double times = av_frame_get_best_effort_timestamp(pFrame) * av_q2d(timeBase);
画面同步声音,在音频播放线程不断的去计算当前播放的音频帧的实际时间,在画面渲染线程获取当前画面的播放实际时间,两个时间做比较,如果画面当前时间大于音频播放时间,计算出画面应该延时的时间。将画面渲染线程进入休眠等待音频追上画面播放再去播放。如果画面当前时间小于音频时间(这种情况比较少出现,一般画面的速度都是快与声音的播放速度),当时间差大于一点的阈值这时候我们可以不进行该帧的渲染(跳帧丢帧去追上音频的播放)
double defaultDelayTime = 0.04;
//计算出画面应该延时多少
double VideoPlayer::get_current_frame_delay_time(double audioTime,AVFrame *pFrame) {
//获取当前将要播放的视频帧时间
double times = av_frame_get_best_effort_timestamp(pFrame) * av_q2d(timeBase);
//记录视频画面的当前时间
currentTime = times;
// 计算音频时间与视频时间相差多少
double diffTime = audioTime - currentTime;
if (diffTime > 0.016 || diffTime < -0.016) {
if (diffTime > 0.016) {
delayTime = delayTime * 2 / 3;
} else if (diffTime < -0.016) {
delayTime = delayTime * 3 / 2;
}
if (delayTime < defaultDelayTime / 2) {
delayTime = defaultDelayTime * 2 / 3;
} else if (delayTime > defaultDelayTime * 2) {
delayTime = defaultDelayTime * 3 / 2;
}
}
if (diffTime >= 0.25) {
delayTime = 0;
} else if (diffTime <= -0.25) {
delayTime = defaultDelayTime * 2;
}
return delayTime;
}
网友评论