WebRTC音视频同步详解

1 WebRTC版本

m74算法

2 时间戳

音视频采样后会给每一个音频采样、视频帧打一个时间戳,打包成RTP后放在RTP头中,称为RTP时间戳,RTP时间戳的单位依赖于音视频流各自的采样率。缓存

RTP Header格式以下:
在这里插入图片描述
网络

2.1 视频时间戳

视频时间戳的单位为1/90000秒,可是90000并非视频的采样率,而只是一个单位,帧率才是视频的采样率。ide

不一样打包方式下的时间戳:函数

  • Single Nalu:若是一个视频帧包含1个NALU,能够单独打包成一个RTP包,那么RTP时间戳就对应这个帧的采集时间;
  • FU-A:若是一个视频帧的NALU过大(超过MTU)须要拆分红多个包,可使用FU-A方式来拆分并打到不一样的RTP包里,那么这几个包的RTP时间戳是同样的;
  • STAP-A:若是某帧较大不能单独打包,可是该帧内部单独的NALU比较小,可使用STAP-A方式合并多个NALU打包发送,可是这些NALU的时间戳必须一致,打包后的RTP时间戳也必须一致。

2.2 音频时间戳

在这里插入图片描述
音频时间戳的单位就是采样率的倒数,例如采样率48000,那么1秒就有48000个采样,每一个采样1/48ms,每一个采样对应一个时间戳。RTP音频包通常打包20ms的数据,对应的采样数为 48000 * 20 / 1000 = 960,也就是说每一个音频包里携带960个音频采样,由于1个采样对应1个时间戳,那么相邻两个音频RTP包的时间戳之差就是960。
spa

2.3 NTP时间戳

RTP的标准并无规定音频、视频流的第一个包必须同时采集、发送,也就是说开始的一小段时间内可能只有音频或者视频,再加上可能的网络丢包,音频或者视频流的开始若干包可能丢失,那么不能简单认为接收端收到的第一个音频包和视频包是对齐的,须要一个共同的时间基准来作时间对齐,这就是NTP时间戳的做用。.net

NTP时间戳是从1900年1月1日00:00:00以来通过的秒数,发送端以必定的频率发送SR(Sender Report)这个RTCP包,分为视频SR和音频SR,SR包内包含一个RTP时间戳和对应的NTP时间戳,接收端收到后就能够肯定某个流的RTP时间戳和NTP时间戳的对应关系,这样音频、视频的时间戳就能够统一到同一个时间基准下。
在这里插入图片描述
如上图,发送端的音视频流并无对齐,可是周期地发送SR包,接收端获得音视频SR包的RTP时间戳、NTP时间戳后经过线性回归获得NTP时间戳Tntp和RTP时间戳Trtp时间戳的对应关系:

3d

  • Tntp_audio = f(Trtp_audio)
  • Tntp_video = f(Trtp_video)

其中Tntp = f(Trtp) = kTrtp + b 为线性函数,这样接收端每收到一个RTP包,均可以将RTP时间戳换算成NTP时间戳,从而在同一时间基准下进行音视频同步。code

2 延迟

视频延迟的单位为ms,对音频来讲,因为采样跟时间戳一一对应,全部时间延迟都会被换算成了缓存大小(音频包的个数),其值为:视频

音频延迟 = 时间延迟 << 8 / 20

也就是说,对48000的采样率,960个采样对应一个20ms包,时间延迟 / 20ms等于延迟了几个包,左移8(乘以256)也就是所谓的Q8,是为了用定点数表示必定精度的浮点数。

3 同步

3.1 一张图看懂音视频同步

在这里插入图片描述
首先接收端须要按照音、视频各自的帧率来解码、渲染,保证流畅地播放,在这个基础上,须要计算音视频两个流目前的相对延迟,分别给音、视频两个流施加必定的延迟,保证音视频的同步。

延迟播放,也就意味着在缓存中暂时存放数据,延迟换流畅。

对音频来讲,施加的延迟直接影响到音频缓存的大小,音频缓存的大小就体现了音频的播放延迟。

对视频来讲,施加的延迟影响到视频帧的渲染时间,经过比较渲染时间和当前时间来决定解码后的视频帧须要等待仍是须要马上渲染。

正确设置好音视频各自的播放延迟后,音视频达到同步的效果。

能够看到,音视频同步中主要须要作到两点:

  • 正确计算音视频相对延迟;
  • 正确设置音视频各自的播放延迟。

3.2 音视频相对延迟

在这里插入图片描述
如上图:

最近一对音视频包的相对延迟 = (Tvideo_recv - Taudio_recv) - (Tvideo_send - Taudio_send)

其中Tvideo_recv、Taudio_recv分别是接收端收到视频包、音频包记录的本地时间,能够直接获取,而Tvideo_send,Taudio_send做为视频包、音频包的发送时间没法直接获取,由于接收到的RTP包只有RTP时间戳,没法直接做为本地时间来与Tvideo_recv、Taudio_recv进行运算,这时候就须要SR包中携带的NTP时间戳和RTP的对应关系来进行换算。

经过SR包中的NTP时间戳和RTP时间戳作线性回归(经过采样概括映射关系)获得二者的线性关系:
Tntp = f(Trtp) = kTrtp + b

这样RTP时间戳就能够直接转化为NTP时间戳,也就是发送端本地时间。从最近一对音视频包相对延迟的计算公式能够看出,分别对发送端和接收端的时间作运算,二者都在同一时间基准,能够排除NTP时间同步问题的影响。

stream_synchronization.cc:34
StreamSynchronization::ComputeRelativeDelay

3.3 指望目标延迟

指望目标延迟就是保证音频流、视频流各自流畅播放的指望延迟

从3.1的图能够看出,对视频来讲,指望目标延迟 = 网络延迟 + 解码延迟 + 渲染延迟,对音频来讲,指望目标延迟 = 先后两个音频包之间的到达间隔的指望值。在接收时间的基础上,加上各自的指望目标延迟进行播放,能够保证音频、视频流能够按照各自的步调进行流畅无卡顿的播放。

既要流畅播放又要进行同步,这就是为何在计算音视频流相对延迟的时候要同时考虑最近一对音视频包的相对延迟又要考虑音视频目标延迟差的缘由。

stream_synchronization.cc:34
StreamSynchronization::ComputeRelativeDelay

在这里插入图片描述

当前音视频流相对延迟 = 最近一对音视频包的相对延迟 + 音视频目标延迟之差

3.3.1 指望视频目标延迟

在这里插入图片描述

指望视频目标延迟 = 网络延迟 + 解码延迟 + 渲染延迟

网络延迟其实就是视频JittterBuffer输出的延迟googJitterBufferMs,能够参考个人文章《WebRTC视频JitterBuffer详解》7.1节[抖动计算],简单说就是经过卡尔曼滤波器计算视频帧的到达延迟差(抖动),做为网络的延迟。

解码时间的统计方法:统计最近最多10000次解码的时间消耗,计算其95百分位数Tdecode,也就是说最近95%的帧的解码时间都小于Tdecode,以之做为解码时间。

视频渲染延迟默认是一个定值:10ms。

timing.cc:210
VCMTiming::TargetVideoDelay

3.3.2 指望音频目标延迟

在这里插入图片描述
指望音频目标延迟的算法和视频解码时间的算法相似,可是用直方图来存放最近的65个音频包的到达间隔,取95百分位数Taudio_target_delay,也就是说最近一段时间内,有95%的音频包的到达间隔都小于Taudio_target_delay。同时考虑到网络突发的可能,增长了峰值检测,去掉异常的时间间隔。

取这个值做为指望目标延迟来影响音频的播放,能够保证绝大多数状况下音频流的流畅。

neteq_impl.cc:311
NetEqImpl::FilteredCurrentDelayMs

3.4 音视频同步

在这里插入图片描述
同步器的外部输入有:

  • 指望音频目标延迟,以该延迟播放,音频是流畅的;
  • 指望视频目标延迟,以该延迟播放,视频是流畅的;
  • 最近一对音视频包的相对延迟。

最近一对音视频包的相对延迟与音视频的目标延迟差之和,获得当前时刻的音视频相对延迟,也就是音、视频流目前的时间误差。

  • 当相对延迟 > 0,说明视频比较慢,视频延迟与基准(base_target_delay_ms_,默认0)比较:
    • extra_video_delay_ms > base_target_delay_ms_,减少视频流延迟,设置音频延迟为基准;
    • extra_video_delay_ms <= base_target_delay_ms_,增大音频流延迟,设置视频延迟为基准;
  • 当相对延迟 < 0,说明音频比较慢,音频延迟与基准(base_target_delay_ms_,默认0)比较:
    • extra_audio_delay_ms > base_target_delay_ms_,减少音频流延迟,设置视频延迟为基准;
    • extra_audio_delay_ms <= base_target_delay_ms_,增大视频流延迟,设置音频延迟为基准。

使用这个算法,能够保证音、频流的延迟都趋向于逼近基准,不会出现无限增长、减少的状况。同时,一次延迟增大、减少的延迟diff_ms被设置为相对延迟的一半,并限制在80ms范围以内,也就是说WebRTC对一次同步的追赶时间作了限制,一次延迟增大、减少最大只能是80ms,所以若是某个时刻某个流发生了较大抖动,须要一段时间另一个流才能同步。

通过了以上校准以后,输出了同步后音频、视频流各自的最小播放延迟。

extra_audio_delay_ms -> 音频最小播放延迟
extra_video_delay_ms -> 视频最小播放延迟

理论上将这两个播放延迟分别施加到音、视频流后,这两个流就是同步的,再与音、视频流各自指望目标延迟取最大值,获得音、视频流的最优目标延迟(googTargetDelayMs),施加在音、视频流上,能够保证作到既同步、又流畅。

stream_synchronization.cc:64
StreamSynchronization::ComputeDelays

3.5 渲染时间

3.5.1 视频渲染时间

在这里插入图片描述
该图是计算视频渲染时间的整体描述图,仍然比较复杂,如下分几个部分描述。

3.5.1.1 指望接收时间

在这里插入图片描述
TimestampExtrapolator类负责指望接收时间的产生,视频JitterBuffer(的FrameBuffer)每收到一帧,会记录该帧的RTP时间戳Tframe_rtp和本地接收时间Tframe_rcv,其中第一帧的RTP时间戳为Tfirst_frame_rtp和本地接收时间Tfirst_frame_rcv
记帧RTP时间戳之差:Tframe_rtp_delta = Tframe_rtp - Tfirst_frame_rtp
帧本地接收时间之差:Tframe_recv_delta = Tframe_recv - Tfirst_frame_rcv
二者为线性关系,指望RTP时间戳之差Tframe_rtp_delta = _w[0] * Tframe_recv_delta + _w[1]
经过卡尔曼滤波器获得线性系数_w[0]、_w[1],进而获得指望接收时间的值:
Tframe_recv = Tfirst_frame_rcv + (Tframe_rtp_delta - _w[1]) / _w[0]





也就是说,卡尔曼滤波器输入视频帧的RTP时间戳和本地接收时间观测值,获得视频帧最优的指望接收时间,用于平滑网络的抖动。

timestamp_extrapolator.cc:137
TimestampExtrapolator::ExtrapolateLocalTime

3.5.1.2 视频当前延迟 - googCurrentDelayMs

在这里插入图片描述
解码器经过视频JitterBuffer的NextFrame方法获取一帧去解码时会设置该帧的指望渲染时间Texpect_render,以及该帧的实际开始解码时间Tactual_decode

该帧的指望开始解码时间为指望渲染时间减去解码、渲染的延迟:
Texpect_decode = Texpect_render - Tdecode_delay - Trender_delay

那么该帧产生的延迟为实际开始解码时间减去指望开始解码时间:
Tframe_delay = Tactual_decode - Texpect_decode

该帧延迟和上一个时刻的视频当前延迟叠加,若是仍然小于目标延迟,则增加视频当前延迟。
Tcurrent_delay = max(Tcurrent_delay + Tframe_delay, Ttarget_delay)

也就是视频当前延迟以目标延迟为上限逼近目标延迟。

timing.cc:96
VCMTiming::UpdateCurrentDelay

3.5.1.3 计算渲染时间

在这里插入图片描述
取同步后的延迟做为视频的实际延迟,也就是当前延迟和最小播放延迟的最大者:
Tactual_delay = max(Tcurrent_delay , Tmin_playout_delay)

至此,当前视频帧的指望接收时间Tframe_recv和视频实际延迟Tactual_delay都已经获得,能够计算最终的视频帧渲染时间:
Trender_time = Tframe_recv + Tactual_delay

timing.cc:169
VCMTiming::RenderTimeMs

3.5.2 音频渲染时间

在这里插入图片描述
NetEQ中有若干缓存用来暂存数据,主要的是JitterBuffer(PacketBuffer)、SyncBuffer,分别存放解码前和解码后的数据,这些缓存的大小就体现了音频当前的延迟。

NetEQ的BufferLevelFilter类维护音频的当前延迟,音频渲染器每取一次音频数据都根据当前剩余的缓存大小设置一次音频的当前延迟并进行平滑,获得平滑后的当前延迟(googCurrentDelayMs)。

buffer_level_filter.cc:29
BufferLevelFilter::Update

NetEQ的DecisionLogic类比较下一个音频包的时间戳与SynBuffer中的结尾时间戳,若是不相等,也就是不连续,那么须要进行丢包隐藏(Expand/PLC)或者融合(Merge);若是相等,也就是连续,则根据当前缓存的大小与目标延迟大小来决定是对音频数据进行加速、减速,或者正常播放。

decision_logic.cc:100
DecisionLogic::GetDecision
在这里插入图片描述

  • 若是音频当前延迟 < 3 / 4音频目标延迟,也就是缓存数据较少,须要减速播放等待目标延迟;
  • 若是音频当前延迟 > 音频目标延迟,也就是缓存数据过多,须要加速播放追赶目标延迟。

decision_logic.cc:283
DecisionLogic::ExpectedPacketAvailable

音频就是以缓存长度追赶目标延迟的方式达到延迟必定时间的效果,最终和视频的目标延迟对齐后,实现了音视频同步。

相关文章
相关标签/搜索