WebRTC音视频同步详解

1 WebRTC版本

m74算法

2 时间戳

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

RTP Header格式以下:

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

2.1 视频时间戳

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

不同打包方式下的时间戳:

- 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。

2.3 NTP时间戳

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

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时间戳的对应关系:

  • 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 一张图看懂音视频同步

 

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

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

对音频来说,施加的延迟直接影响到音频缓存的大小,音频缓存的大小就体现了音频的播放延迟。 对视频来说,施加的延迟影响到视频帧的渲染时间,经过比较渲染时间和当前时间来决定解码后的视频帧需要等待还是需要马上渲染。

正确设置好音视频各自的播放延迟后,音视频达到同步的效果。 可以看到,音视频同步中主要需要做到两点:

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

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 指望视频目标延迟

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

网络延迟实际上就是视频JitterBuffer输出的延迟googJitterBufferMs,简单说就是经过卡尔曼滤波器计算视频帧的到达延迟差(抖动),作为网络的延迟。 解码时间的统计方法:统计最近最多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类比较下一个音频包的时间戳与SyncBuffer中的结尾时间戳,若是不相等,也就是不连续,那么需要进行丢包隐藏(Expand/PLC)或者融合(Merge);若是相等,也就是连续,则根据当前缓存的大小与目标延迟大小来决定是对音频数据进行加速、减速,或者正常播放。

decision_logic.cc:100
DecisionLogic::GetDecision

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

decision_logic.cc:283
DecisionLogic::ExpectedPacketAvailable

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

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值