共计 3258 个字符,预计需要花费 9 分钟才能阅读完成。
所有的基于网络传输的音视频采集播放零碎都会存在音视频同步的问题,作为古代互联网实时音视频通信零碎的代表,WebRTC 也不例外。本文将对音视频同步的原理以及 WebRTC 的实现做深入分析。
工夫戳 (timestamp)
同步问题就是快慢的问题,就会牵扯到工夫跟音视频流媒体的对应关系,就有了工夫戳的概念。
工夫戳 用来定义媒体负载数据的采样时刻,从枯燥线性递增的时钟中获取,时钟的精度由 RTP 负载数据的采样频率决定。音频和视频的采样频率是不一样的,个别音频的采样频率有 16KHz、44.1KHz、48KHz 等,而视频反映在采样帧率上,个别帧率有 25fps、29.97fps、30fps 等。
习惯上音频的工夫戳的增速就是其采样率,比方 16KHz 采样,每 10ms 采集一帧,则下一帧的工夫戳,比上一帧的工夫戳,从数值上多 16 x10=160,即音频工夫戳增速为 16/ms。而视频的采样频率习惯上是依照 90KHz 来计算的,就是每秒 90K 个时钟 tick,之所以用 90K 是因为它正好是下面所说的视频帧率的倍数,所以就采纳了 90K。所以视频帧的工夫戳的增长速率就是 90/ms。
工夫戳的生成
音频帧工夫戳的生成
WebRTC 的音频帧的工夫戳,从第一个包为 0,开始累加,每一帧减少 = 编码帧长 (ms) x 采样率 / 1000,如果采样率 16KHz,编码帧长 20ms,则每个音频帧的工夫戳递增 20 x 16000/1000 = 320。这里只是说的未打包之前的音频帧的工夫戳,而封装到 RTP 包外面的时候,会将这个音频帧的工夫戳再累加上一个随机偏移量(构造函数里生成),而后作为此 RTP 包的工夫戳,发送进来,如上面代码所示,留神,这个逻辑同样实用于视频包。
视频帧工夫戳的生成
WebRTC 的视频帧,生成机制跟音频帧齐全不同。视频帧的工夫戳来源于零碎时钟,采集实现后至编码之前的某个时刻(这个传递链路十分长,不同配置的视频帧,走不同的逻辑,会有不同的获取地位),获取以后零碎的工夫 timestamp_us_
,而后算出此零碎工夫对应的 ntp_time_ms_
,再依据此 ntp 工夫算出原始视频帧的工夫戳 timestamp_rtp_
,参看上面的代码,计算逻辑也在 OnFrame
这个函数中。
为什么视频帧采纳了跟音频帧不同的工夫戳计算机制呢?我的了解,个别状况音频的采集设施的采样距离和时钟精度更加精确,10ms 一帧,每秒是 100 帧,个别不会呈现大的抖动,而视频帧的帧间隔时间较大采集精度,每秒 25 帧的话,就是 40ms 一帧。如果还采纳音频的依照采样率来递增的话,可能会呈现跟理论时钟对不齐的状况,所以就间接每取一帧,依照取出时刻的零碎时钟算出一个工夫戳,这样能够再现实在视频帧跟理论工夫的对应关系。
跟下面音频一样,在封装到 RTP 包的时候,会将原始视频帧的工夫戳累加上一个随机偏移量(此偏移量跟音频的并不是同一个值),作为此 RTP 包的工夫戳发送进来。值得注意的是,这里计算的 NTP 工夫戳基本就不会随着 RTP 数据包一起发送进来,因为 RTP 包的包头外面没有 NTP 字段,即便是扩大字段里,咱们也没有放这个值,如上面视频的工夫相干的扩大字段。
音视频同步外围根据
从下面能够看出,RTP 包外面只蕴含每个流的独立的、枯燥递增的工夫戳信息,也就是说音频和视频两个工夫戳齐全是独立的,没有关系的,无奈只依据这个信息来进行同步,因为无奈对两个流的工夫进行关联,咱们须要一种映射关系,将两个独立的工夫戳关联起来。
这个时候 RTCP 包外面的一种发送端报告分组 SR (SenderReport) 包就上场了,详情请参考 RFC3550。
SR 包的其中一个作用就是来通知咱们每个流的 RTP 包的工夫戳和 NTP 工夫的对应关系的。靠的就是上边图片中标出的 NTP 工夫戳和 RTP 工夫戳,通过 RFC3550 的形容,咱们晓得 这两个工夫戳对应的是同一个时刻,这个时刻示意此 SR 包生成的时刻。这就是咱们对音视频进行同步的最外围的根据,所有的其它计算都是围绕这个外围根据来开展的。
SR 包的生成
由下面阐述可知,NTP 工夫和 RTP 工夫戳是同一时刻的不同示意,只是精度和单位不一样。NTP 工夫是相对工夫,以毫秒为单位,而 RTP 工夫戳则和媒体的采样频率无关,是一个枯燥递增数值。生成 SR 包的过程在 RTCPSender::BuildSR(const RtcpContext& ctx)
函数外面,老版本外面有 bug,写死了采样率为 8K,新版本曾经修复,上面截图是老版本的代码:
计算的思路如下
首先,咱们要获取以后时刻(即 SR 包生成时刻)的 NTP 工夫。这个间接从传过来的参数 ctx 中就能够取得:
其次,咱们要计算以后时刻,应该对应的 RTP 的工夫戳是多少。依据最初一个发送的 RTP 包的工夫戳 last_rtp_timestamp_
和它的采集时刻的零碎工夫 last_frame_capture_time_ms_
,和以后媒体流的工夫戳的每 ms 增长速率 rtp_rate
,以及从 last_frame_capture_time_ms_
到以后时刻的工夫流逝,就能够算进去。留神,last_rtp_timestamp_
是媒体流的原始工夫戳,不是通过随机偏移的 RTP 包工夫戳,所以最初又累加了偏移量 timestamp_offset_
。其中最初一个发送的 RTP 包的工夫信息是通过上面的函数进行更新的:
音视频同步的计算
因为同一台机器上音频流和视频流的本地零碎工夫是一样的,也就是零碎工夫对应的 NTP 格局的工夫也是一样的,是在同一个坐标系上的,所以能够把 NTP 工夫作为横轴 X,单位是 ms,而把 RTP 工夫戳的值作为纵轴 Y,画在一起。下图展现了计算音视频同步的原理和办法,其实很简略,就是应用最近的两个 SR 点,两点确定一条直线,之后给任意一个 RTP 工夫戳,都能够求出对应的 NTP 工夫,又因为视频和音频的 NTP 工夫是在同一基准上的,所以就能够算出两者的差值。
上图以音频的两个 SR 包为例,确定出了 RTP 和 NTP 对应关系的直线,而后给任意一个 rtp_a,就算出了其对应的 NTP_a,同理也能够求任意视频包 rtp_v 对应的 NTP_v 的工夫点,两个的差值就是时间差。
上面是 WebRTC 外面计算直线对应的系数 rate 和偏移 offset 的代码:
在 WebRTC 中计算的是最新收到的音频 RTP 包和最新收到的视频 RTP 包的对应的 NTP 工夫,作为网络传输引入的不同步时长,而后又依据以后音频和视频的 JitterBuffer 和播放缓冲区的大小,失去了播放引入的不同步时长,依据两个不同步时长,失去了最终的音视频不同步时长,计算过程在 StreamSynchronization::ComputeRelativeDelay()
函数中,之后又通过了 StreamSynchronization::ComputeDelays()
函数对其进行了指数平滑等一系列的解决和判断,得出最终管制音频和视频的最小延时工夫,别离通过 syncable_audio_->SetMinimumPlayoutDelay(target_audio_delay_ms)
和 syncable_video_->SetMinimumPlayoutDelay(target_video_delay_ms)
利用到了音视频的播放缓冲区。
这一系列操作都是由定时器调用 RtpStreamsSynchronizer::Process()
函数来解决的。
另外须要留神一下,在晓得采样率的状况下,是能够通过一个 SR 包来计算的,如果没有 SR 包,是无奈进行精确的音视频同步的。
WebRTC 中实现音视频同步的伎俩就是 SR 包,外围的根据就是 SR 包中的 NTP 工夫和 RTP 工夫戳。最初的两张 NTP 工夫 -RTP 工夫戳
坐标图如果你能看明确(其实很简略,就是求解出直线方程来计算 NTP),那么也就真正的了解了 WebRTC 中音视频同步的原理。如果有什么脱漏或者谬误,欢送大家一起交换!
「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实际技术文章,在这里与音视频畛域一流工程师交换切磋。