WebRTC音视频同步机制实现分析

音视频同步事关多媒体产品的最直观用户体验,是音视频媒体数据传输和渲染播放的最基本质量保证。音视频如果不同步,有可能造成延迟、卡顿等非常影响用户体验的现象。因此,它非常重要。一般说来,音视频同步维护媒体数据的时间线顺序,即发送端在某一时刻采集的音视频数据,接收端在另一时刻同时播放和渲染。</br>

本文在深入研究WebRTC源代码的基础上,分析其音视频同步的实现细节,包括RTP时间戳的产生,RTCP SR报文的构造、发送和接收,音视频同步的初始化和同步过程。RTP时间戳是RTP数据包的基石,而RTCP SR报文是时间戳和NTP时间之间进行转换的基准。下面详细描述之。</br>

1 RTP时间戳的产生

</br>
个人认为,RTP时间戳和序列号是RTP协议的精华所在:前者定义媒体负载数据的采样时刻,描述负载数据的帧间顺序;后者定义RTP数据包的先后顺序,描述媒体数据的帧内顺序。关于RTP时间戳,摘录RFC3550定义如下[1]:</br>

“The timestamp reflects the sampling instant of the first octet in the RTP data packet. The sampling instant must be derived from a clock that increments monotonically and linearly in time to allow synchronization and jitter calculations. The resolution of the clock must be sufficient for the desired synchronization accuracy and for measuring packet arrival jitter (one tick per video frame is typically not sufficient). ”</br>

由以上定义可知,RTP时间戳反映RTP负载数据的采样时刻,从单调线性递增的时钟中获取。时钟的精度由RTP负载数据的采样频率决定,比如视频的采样频率一般是90khz,那么时间戳增加1,则实际时间增加1/90000秒。</br>

下面回到WebRTC源代码内部,以视频采集为例分析RTP时间戳的产生过程,如图1所示。</br>

图1 RTP时间戳构造过程

视频采集线程以帧为基本单位采集视频数据,视频帧从系统API被采集出来,经过初步加工之后到达VideoCaptureImpl::IncomingFrame()函数,设置render_time_ms_为当前时间(其实就是采样时刻)。</br>

执行流程到达VideoCaptureInput::IncomingCapturedFrame()函数后,在该函数设置视频帧的timestamp,ntp_time_ms和render_time_ms。其中render_time_ms为当前时间,以毫秒为单位;ntp_time_ms为采样时刻的绝对时间表示,以毫秒为单位;timestamp则是采样时间的时间戳表示,是ntp_time_ms和采样频率frequency的乘积,以1/frequency秒为单位。由此可知,timestamp和ntp_time_ms是同一采样时刻的不同表示。</br>

接下来视频帧经过编码器编码之后,发送到RTP模块进行RTP打包和发送。构造RTP数据包头部时调用RtpSender::BuildRTPheader()函数,确定时间戳的最终值为rtphdr->timestamp = start_timestamp + timestamp,其中start_timestamp是RtpSender在初始化时设置的初始时间戳。RTP报文构造完毕之后,经由网络发送到对端。</br>

2 SR报文构造和收发

</br>
由上一节论述可知,NTP时间和RTP时间戳是同一时刻的不同表示,区别在于精度不同。NTP时间是绝对时间,以毫秒为精度,而RTP时间戳则和媒体的采样频率有关。因此,我们需要维护一个NTP时间和RTP时间戳的对应关系,该用以对两种时间的进行转换。RTCP协议定义的SR报文维护了这种对应关系,下面详细描述。</br>

2.1 时间戳初始化

</br>
在初始化阶段,ModuleRtpRtcpImpl::SetSendingStatus()函数会获取当前NTP时间的时间戳表示(ntp_time * frequency),作为时间戳初始值分别设置RTPSender和RTCPSender的start_timestamp参数(即上节在确定RTP数据包头部时间戳时的初始值)。</br>

视频数据在编码完之后发往RTP模块构造RTP报文时,视频帧的时间戳timestamp和本地时间capture_time_ms通过RTCPSender::SetLastRtpTime()函数记录到RTCPSender对象的last_rtp_timestamp和last_frame_capture_time_ms参数中,以将来将来构造RTCP SR报文使用。</br>

2.2 SR报文构造及发送

</br>
WebRTC内部通过ModuleProcessThread线程周期性发送RTCP报文,其中SR报文通过RTCPSender::BuildSR(ctx)构造。其中ctx中包含当前时刻的NTP时间,作为SR报文[1]中的NTP时间。接下来需要计算出此刻对应的RTP时间戳,即假设此刻有一帧数据刚好被采样,则其时间戳为:</br>

rtp_timestamp = start_timestamp_ + last_rtp_timestamp_ +
(clock_->TimeInMilliseconds() - last_frame_capture_time_ms_) *
(ctx.feedback_state_.frequency_hz / 1000);</br>

至此,NTP时间和RTP时间戳全部齐活儿,就可以构造SR报文进行发送了。</br>

2.3 SR接收

</br>
接收端在收到SR报文后,把其中包含的NTP时间和RTP时间戳记录在RTCPSenderInfo对象中,供其他模块获取使用。比如通过RTCPReceiver::NTP()或者SenderInfoReceived()函数获取。</br>

3 音视频同步

</br>
前面两节做必要的铺垫后,本节详细分析WebRTC内部的音视频同步过程。</br>

3.1 初始化配置

</br>
音视频同步的核心就是根据媒体负载所携带RTP时间戳进行同步。在WebRTC内部,同步的基本对象是AudioReceiveStream/VideoReceiveStream,根据sync_group进行相互配对。同步的初始化设置过程如图2所示。</br>

图2 音视频同步初始化配置

Call对象在创建Audio/VideoReceiveStream时,调用ConfigureSync()进行音视频同步的配置。配置参数为sync_group,该参数在PeerConnectionFactory在创建MediaStream时指定。在ConfigureSync()函数内部,通过sync_group查找得到AudioReceiveStream,然后再在video_receive_streams中查找得到VideoReceiveStream。得到两个媒体流,调用VideoReceiveStream::SetSyncChannel同步,在ViESyncModule::ConfigureSync()函数中把音视频参数进行保存,包括音频的voe_channel_id、voe_sync_interface, 和视频的video_rtp_receiver、video_rtp_rtcp。</br>

3.2 同步过程

</br>
音视频的同步过程在ModuleProcessThread线程中执行。ViESyncModule作为一个模块注册到ModuleProcessThread线程中,其Process()函数被该线程周期性调用,实现音视频同步操作。</br>

音视频同步的核心思想就是以RTCP SR报文中携带的NTP时间和RTP时间戳作为时间基准,以AudioReceiveStream和VideoReceiveStream各自收到最新RTP时间戳timestamp和对应的本地时间receive_time_ms作为参数,计算音视频流的相对延迟,然后结合音视频的当前延迟计算最终的目标延迟,最后把目标延迟发送到音视频模块进行设置。目标延迟作为音视频渲染时的延迟下限值。整个过程如图3所示。</br>

图3 音视频同步过程

首先,从VideoReceiver获得当前视频延迟current_video_delay,即video_jitter_delay,decode_delay和render_delay的总和。然后从VoEVideoSyncImpl获得当前音频延迟current_audio_delay,即audio_jitter_delay和playout_delay的总和。</br>

然后,音视频分别以各自的rtp_rtcp和rtp_receiver更新各自的measure。其基本操作包括:从rtp_receiver获取最新接收到的RTP报文的RTP时间戳latest_timestamp和对应的本地接收时刻latest_receive_time_ms,从rtp_rtcp获取最新接收的RTCP SR报文中的NTP时间和RTP时间戳。然后把这些数据都存储到measure中。注意measure中保存最新两对RTCP SR报文中的NTP时间和RTP时间戳,用来在下一步计算媒体流的采样频率。</br>

接下来,计算最新收到的音视频数据的相对延迟。其基本流程如下:首先得到最新收到RTP时间戳latest_timestamp对应的NTP时间latest_capture_time。这里用到measure中存储的latest_timestamp和RTCP SR的NTP时间和RTP时间戳timestamp,利用两对数值计算得到采样频率frequency,然后有latest_capture_time = latest_timestamp / frequency,得到单位为毫秒的采样时间。最后得到音视频的相对延迟:</br>

relative_delay = video_measure.latest_receive_time_ms -
audio_measure.latest_receive_time_ms -
(video_last_capture_time - audio_last_capture_time); </br>

至此,我们得到三个重要参数:视频当前延迟current_video_delay, 音频当前延迟current_audio_delay和相对延迟relative_delay。接下来用这三个参数计算音视频的目标延迟:首先计算总相对延迟current_diff = current_video_delay – current_audio_delay + relative_delay,根据历史值对其求加权平均值。如果current_diff > 0,表明当前视频延迟比音频延迟长,需要减小视频延迟或者增大音频延迟;反之如果current < 0,则需要增大视频延迟或者减小音频延迟。经过此番调整之后,我们得到音视频的目标延迟audio_target_delay和video_target_delay。</br>

最后,我们把得到的目标延迟audio_target_delay和video_target_delay分别设置到音视频模块中,作为将来渲染延迟的下限值。到此为止,一次音视频同步操作完成。该操作在ModuleProcessThread线程中会周期性执行。</br>

4 总结

</br>
本文详细分析了WebRTC内部音视频同步的实现细节,包括RTP时间戳的产生,RTCP SR报文的构造、发送和接收,音视频同步的初始化和同步过程。通过本文,对RTP协议、流媒体通信和音视频同步有更深入的认识。</br>
</br>
</br>

参考文献

</br>
[1] RFC3550 - RTP: A Transport Protocol for Real-Time Applications

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容