搞音视频开发好些年,分享过许多博客文章,比如:前几年发布的《FFmpeg Tips》系列,《Android 音频开发》系列,《直播疑难杂症排查》系列等等。最近想把多年来开发和优化播放器的经验也分享出来,希望能帮助到音视频领域的初学者。第一期文章要推出的内容主要涉及到播放器比较核心的几个技术点,大概的目录如下:

1. 播放器技术分享(1):架构设计

2. 播放器技术分享(2):缓冲区管理

3. 播放器技术分享(3):音画同步

4. 播放器技术分享(4):首开时间

5. 播放器技术分享(5):延时优化

本篇是系列文章的第三篇,主要聊一聊播放器的音画同步。

1 概述

首先,我们给出音画同步的定义:

音画同步是指播放器正在渲染的每一帧画面和正在播放的每一段声音都是严格对应起来的,不存在人耳和肉眼可以分辨出来的偏差。

2 时间戳

机器没有眼睛也没有耳朵,那它是如何去感知视频文件中的某一帧视频数据跟某一帧音频数据是属于 同一时间 的呢 ?

现实生活中,判断两件事情是否同一时刻发生的方法就是记录并且比较事件发生的日期时间,同样,我们可以在音频和视频数据“生产”出来的时候,为数据标记上一个“时间戳”,代表着它的出生时间。由此,播放器就知道如何判断了。

image.png

如图所示,音视频在采集(生产)的时候,就给每一帧都打上 “时间戳”(注意,音视频的时间戳需要使用一个共同的时间戳基准),假设以音频开始采集时刻的系统绝对时间点作为时间戳 0 点,假设音频 20ms 采样一帧,视频帧率 30fps -> 即 33ms 产生一帧,那么,视频帧和音频帧的时间戳大致的递增规律为:

视频帧:10,43,76,109,142,……

音频帧:0,20,40,60,80,120,……


注:实际上数据采集不会这么 “均匀”,会有略微的浮动。

如此一来,播放器就可以通过附加在音频和视频帧上的时间戳信息,感知到音频帧和视频帧的 “相对关系”,实现音画同步就轻而易举了,判断方法如下;

音画同步的方法:判断视频与音频帧的时间戳差值,是不是在一定的 “阈值” 范围内,如果是,则可以渲染/播放,否则就 “等” 到合适的时间。

3 音画同步的 “阈值”

由于音视频是分开采集的,所以它们的时间戳不可能完全对应得上,因此,只要在一个合理的 “阈值” 范围内即可认为是同步的。那么,这个 “阈值” 是怎么定义的呢 ?

image.png

如图所示,这个 “阈值” 有一个国际标准定义了它,叫做:RFC-1359,它是这么定义的:

1. 无法察觉:音频和视频的时间戳差值在:-100ms ~ +25ms 之间

2. 能够察觉:音频滞后了 100ms 以上,或者超前了 25ms 以上

3. 无法接受:音频滞后了 185ms 以上,或者超前了 90ms 以上

有了这个国际标准和经验值,我们做音画同步取的阈值就不用瞎猜了~~

4 音画同步的实现

音画同步的实现策略一般有 3 种:

1. 视频同步到音频

2. 音频同步到视频

3. 音视频同步到外部时钟

比较常用的策略是:视频同步到音频。

因为音频是流式的,按照规律的匀速的速率去播放,才能显得更加 “平滑”,而视频的播放其实是一张一张图片进行刷新显示,它的刷新时间的调整相对而言更容易一些,用户肉眼的敏感度也更弱一些。

下面就讲讲使用 “视频同步到音频” 的方式,如何实现播放器的音画同步:

image.png

如图所示,A 代表音频帧,P 代表视频帧,音频帧持续送入扬声器以既定的速率播放,每播放一帧音频数据,则把该音频帧的时间戳更新到 Master Clock,作为主时钟,视频帧则参考 Master Clock 来决定自己是否渲染、何时渲染或者丢弃,算法如下:

1. 假设 min 为音画同步阈值(如:25ms),则当前视频时间戳如果与 master clock 的绝对差值在 25ms 阈值范围内则都可以送入渲染

2. 如果 diff > 0,即 pts > m-clock,则代表视频帧提前准备好了。这种情况下,是可以通过 sleep 来等待主时钟的,但是为了防止异常时间戳导致 sleep 时间过长,可以设置一个 max 异常阈值(如:1000ms),如果 diff 超过这个 max 可以认为是异常帧,丢弃掉

3. 如果 diff < 0,即 pts < m-clock,则代表视频帧滞后了。这种情况下,如果滞后超出约定阈值(如:25ms)的视频帧就应该被丢弃

音画同步最核心的思想就在这里了,看起来好像也并不是有多复杂,对吧~

6 总结

播放器的音画同步,就分享到这里了,如有疑问的小伙伴欢迎来信 lujun.hust@gmail.com 交流。另外,也欢迎大家关注我的新浪微博 @卢_俊 或者 微信公众号 @Jhuster 获取最新的文章和资讯。

weixin_jhuster.jpg