如果在直播场景中,音视频不同步就会给观众造成不好的观感,不利于在线直播网站源码的长期发展,所以今天我们就一起来了解一下,在在线直播网站源码开发时,音视频同步的三种处理方案。
通常有以下三种:
1.视频时钟同步到音频时钟
以在线直播网站源码音频时钟为标准时钟,音频自然播放。视频帧播放时判断当前视频帧播放结束后的时间与当前的音频时钟时间对比,如果视频当前帧播放完时间比音频时钟时间早,则让当前视频播放线程暂时时间差,以保证播放完后与音频时钟同步。如果在线直播网站源码当前视频帧播放完时间比音频时间晚,则丢弃当前视频帧读取下一帧再判断,以保证播放完后与音频时钟同步。
2.音频时钟同步到视频时钟
以在线直播网站源码视频时钟为标准,视频自然播放。同步逻辑则与第1点的同步逻辑一致:即音频快了就暂停音频播放线程等待时间差,慢了则丢弃当前音频帧。以保证在线直播网站源码当前音频帧播放完与视频帧时钟同步。
3.以外部时钟为准,音频与视频时钟同时同步到外部时钟。
同步逻辑与1、2点一致。需要注意的是外部时钟应尽量使用毫秒时钟以确保在线直播网站源码中音视频同步的精准。
同步方案选择
以上3种方案都可以实现在线直播网站源码音频与视频的同步处理,但怎么选择更适合的方案呢?
人的眼睛与耳朵对图像与声音的敏感程度不一样,当画面偶尔缺少一帧或者几帧时人的眼睛可能不太容易察觉。这是因为画面的连贯性比较强,两帧画面之前的差异有时候很小,眼睛比耳机敏感度更低。当声音发生一变化,比如缺失了一点声音或者声音异常的,人的耳朵马上就察觉到了。
在大多数在线直播网站源码上声音的播放开销都比渲染画面小。声音的数据处理过程更简单,数量量也更小。声音线程播放声音卡顿的概率很小。
在线直播网站源码声音的播放缓存对象是重复利用的,而这个利用则是由实际播放声音的具体线程来回调的。不同于视频每一帧的渲染,声音的暂停与丢弃相比视频实现成本更高。
综合上以的三点,本文选择第1点同步方案视频时钟同步到音频时钟
编码实现音视频同步
在线直播网站源码音频视频同步基础
FFmpeg里有两种时间戳:DTS(Decoding Time Stamp)和PTS(Presentation Time Stamp)。 顾名思义,前者是解码的时间,后者是显示的时间。要仔细理解这两个概念,需要先了解FFmpeg中的packet和frame的概念。
FFmpeg中用AVPacket结构体来描述解码前或编码后的压缩包,用AVFrame结构体来描述解码后或编码前的信号帧。 对于在线直播网站源码的视频来说,AVFrame就是视频的一帧图像。这帧图像什么时候显示给用户,就取决于它的PTS。DTS是AVPacket里的一个成员,表示这个压缩包应该什么时候被解码。
如果在线直播网站源码视频里各帧的编码是按输入顺序(也就是显示顺序)依次进行的,那么解码和显示时间应该是一致的。可事实上,在大多数编解码标准(如H.264或HEVC)中,编码顺序和输入顺序并不一致。 于是才会需要PTS和DTS这两种不同的时间戳。
基本概念:
- I帧 :帧内编码帧 又称intra picture,I 帧通常是每个 GOP(MPEG
所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧可以看成是一个图像经过压缩后的产物。 - P帧: 前向预测编码帧,又称predictive-frame,通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧;
- B帧: 双向预测内插编码帧 又称bi-directional interpolated prediction
frame,既考虑与源图像序列前面已编码帧,也顾及源图像序列后面已编码帧之间的时间冗余信息来压缩传输数据量的编码图像,也叫双向预测帧; - PTS:Presentation Time Stamp。PTS主要用于度量解码后的视频帧什么时候被显示出来
- DTS:Decode Time Stamp。DTS主要是标识读入内存中的bit流在什么时候开始送入在线直播网站源码解码器中进行解码。
在没有B帧存在的情况下DTS的顺序和PTS的顺序应该是一样的。
IPB帧的不同:
- I帧:自身可以通过在线直播网站源码视频解压算法解压成一张单独的完整的图片。
- P帧:需要参考其前面的一个I frame 或者B frame来生成一张完整的图片。
- B帧:则要参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片。
两个I帧之间形成一个GOP,在x264中同时可以通过参数来设定bf的大小,即:I 和p或者两个P之间B的数量。
通过上述基本可以说明如果有B frame 存在的情况下一个GOP的最后一个frame一定是P。
DTS和PTS的不同:
DTS主要用于在线直播网站源码视频的解码,在解码阶段使用.PTS主要用于视频的同步和输出.在display的时候使用.在没有B frame的情况下.DTS和PTS的输出顺序是一样的.
音频与视频数据缓冲对象增加时钟数据
FFQueueAudioObject : NSObject
(nonatomic, assign, readonly)float pts;
(nonatomic, assign, readonly)float duration;
- (instancetype)initWithLength:(int64_t)length pts:(float)pts duration:(float)duration;
- (uint8_t *)data;
- (int64_t)length;
- (void)updateLength:(int64_t)length;
FFQueueVideoObject : NSObject
(nonatomic, assign)double unit;
(nonatomic, assign)double pts;
(nonatomic, assign)double duration;
- (instancetype)init;
- (AVFrame *)frame;
分别在上面的音频与视频缓冲对象上增加变量pts与duration。
- pts: 当前帧播放或显示的时间。
- duration: 当前帧播放或显示持续的时长。在线直播网站源码音频帧内包括多个音频数据包,而视频则可以通过FPS计算得到每一帧的显示持续时长。
视频时钟同步到音频时钟
pthread_mutex_lock(&(self->mutex));
/// 读取当前的音频时钟时间
double ac = self->audio_clock;
pthread_mutex_unlock(&(self->mutex));
FFQueueVideoObject *obj = NULL;
/// 统计路过的视频帧数量
int readCount = 0;
/// 首先读取一帧视频数据
obj = [self.videoFrameCacheQueue dequeue];
readCount ++;
/// 计算当前视频帖播放结束时的时间点
double vc = obj.pts + obj.duration;
if(ac - vc > self->tolerance_scope) {
/// 视频太慢,丢弃当前帧继续读取下一帧
/// 这里认为读取下一帧或者更下一帧不会造成视频缓冲队列枯竭,所以未做等待处理
/// 因为时时同步能形成的时间差比较有限
while (ac - vc > self->tolerance_scope) {
FFQueueVideoObject *_nextObj = [self.videoFrameCacheQueue dequeue];
if(!_nextObj) break;
obj = _nextObj;
vc = obj.pts + obj.duration;
readCount ++;
}
} else if (vc - ac > self->tolerance_scope) {
/// 视频太快,暂停一下再接着渲染显示当前视频帧
float sleep_time = vc - ac;
usleep(sleep_time * 1000 * 1000);
} else {
/// 在误差范围之后, 不需要处理
}
tolerance_scope为可允许的误差值,即在线直播网站源码音频与视频时间差小于这个数据则认为是同步的。这是因为要达到绝对的时间一致性是不可能的,在计算时间的过程中有精度的丢失。
获取当前在线直播网站源码音频时钟的时间(该时间为当前音频帧播放结束后的时间)
读取一帧视频帧,计算出该视频帧播放完之后的时间
判断在线直播网站源码音频时间与视频时间的差值,进行同步处理