WebRTC音視頻同步機(jī)制實(shí)現(xiàn)分析
2016-11-25 doraWebRTC編風(fēng)網(wǎng)WebRTC編風(fēng)網(wǎng)
來(lái)源:編風(fēng)網(wǎng)
作者:weizhenwei众辨,編風(fēng)網(wǎng)專欄作家
音視頻同步事關(guān)多媒體產(chǎn)品的最直觀用戶體驗(yàn)导梆,是音視頻媒體數(shù)據(jù)傳輸和渲染播放的最基本質(zhì)量保證。音視頻如果不同步,有可能造成延遲、卡頓等非常影響用戶體驗(yàn)的現(xiàn)象。因此婆殿,它非常重要。一般說(shuō)來(lái)待笑,音視頻同步維護(hù)媒體數(shù)據(jù)的時(shí)間線順序鸣皂,即發(fā)送端在某一時(shí)刻采集的音視頻數(shù)據(jù),接收端在另一時(shí)刻同時(shí)播放和渲染暮蹂。
本文在深入研究WebRTC源代碼的基礎(chǔ)上寞缝,分析其音視頻同步的實(shí)現(xiàn)細(xì)節(jié),包括RTP時(shí)間戳的產(chǎn)生仰泻,RTCP SR報(bào)文的構(gòu)造荆陆、發(fā)送和接收,音視頻同步的初始化和同步過(guò)程集侯。RTP時(shí)間戳是RTP數(shù)據(jù)包的基石被啼,而RTCP SR報(bào)文是時(shí)間戳和NTP時(shí)間之間進(jìn)行轉(zhuǎn)換的基準(zhǔn)。下面詳細(xì)描述之棠枉。
一浓体、RTP時(shí)間戳的產(chǎn)生
個(gè)人認(rèn)為,RTP時(shí)間戳和序列號(hào)是RTP協(xié)議的精華所在:前者定義媒體負(fù)載數(shù)據(jù)的采樣時(shí)刻辈讶,描述負(fù)載數(shù)據(jù)的幀間順序杀赢;后者定義RTP數(shù)據(jù)包的先后順序哈雏,描述媒體數(shù)據(jù)的幀內(nèi)順序寡壮。關(guān)于RTP時(shí)間戳:
“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). ”
由以上定義可知寡键,RTP時(shí)間戳反映RTP負(fù)載數(shù)據(jù)的采樣時(shí)刻,從單調(diào)線性遞增的時(shí)鐘中獲取月幌。時(shí)鐘的精度由RTP負(fù)載數(shù)據(jù)的采樣頻率決定碍讯,比如視頻的采樣頻率一般是90khz,那么時(shí)間戳增加1扯躺,則實(shí)際時(shí)間增加1/90000秒捉兴。
下面回到WebRTC源代碼內(nèi)部,以視頻采集為例分析RTP時(shí)間戳的產(chǎn)生過(guò)程录语,如圖1所示轴术。
[圖片上傳失敗...(image-6cee82-1543066451821)]
圖1 RTP時(shí)間戳構(gòu)造過(guò)程
視頻采集線程以幀為基本單位采集視頻數(shù)據(jù),視頻幀從系統(tǒng)API被采集出來(lái)钦无,經(jīng)過(guò)初步加工之后到達(dá)VideoCaptureImpl::IncomingFrame()函數(shù),設(shè)置render_time_ms_為當(dāng)前時(shí)間(其實(shí)就是采樣時(shí)刻)盖袭。
執(zhí)行流程到達(dá)VideoCaptureInput::IncomingCapturedFrame()函數(shù)后失暂,在該函數(shù)設(shè)置視頻幀的timestamp彼宠,ntp_time_ms和render_time_ms。其中render_time_ms為當(dāng)前時(shí)間弟塞,以毫秒為單位凭峡;ntp_time_ms為采樣時(shí)刻的絕對(duì)時(shí)間表示,以毫秒為單位决记;timestamp則是采樣時(shí)間的時(shí)間戳表示摧冀,是ntp_time_ms和采樣頻率frequency的乘積,以1/frequency秒為單位系宫。由此可知索昂,timestamp和ntp_time_ms是同一采樣時(shí)刻的不同表示。
接下來(lái)視頻幀經(jīng)過(guò)編碼器編碼之后扩借,發(fā)送到RTP模塊進(jìn)行RTP打包和發(fā)送椒惨。構(gòu)造RTP數(shù)據(jù)包頭部時(shí)調(diào)用RtpSender::BuildRTPheader()函數(shù),確定時(shí)間戳的最終值為rtphdr->timestamp = start_timestamp + timestamp潮罪,其中start_timestamp是RtpSender在初始化時(shí)設(shè)置的初始時(shí)間戳康谆。RTP報(bào)文構(gòu)造完畢之后,經(jīng)由網(wǎng)絡(luò)發(fā)送到對(duì)端嫉到。
二沃暗、SR報(bào)文構(gòu)造和收發(fā)
由上一節(jié)論述可知,NTP時(shí)間和RTP時(shí)間戳是同一時(shí)刻的不同表示何恶,區(qū)別在于精度不同孽锥。NTP時(shí)間是絕對(duì)時(shí)間,以毫秒為精度导而,而RTP時(shí)間戳則和媒體的采樣頻率有關(guān)忱叭。因此,我們需要維護(hù)一個(gè)NTP時(shí)間和RTP時(shí)間戳的對(duì)應(yīng)關(guān)系今艺,該用以對(duì)兩種時(shí)間的進(jìn)行轉(zhuǎn)換韵丑。RTCP協(xié)議定義的SR報(bào)文維護(hù)了這種對(duì)應(yīng)關(guān)系,下面詳細(xì)描述虚缎。
2.1 時(shí)間戳初始化
在初始化階段撵彻,ModuleRtpRtcpImpl::SetSendingStatus()函數(shù)會(huì)獲取當(dāng)前NTP時(shí)間的時(shí)間戳表示(ntp_time * frequency),作為時(shí)間戳初始值分別設(shè)置RTPSender和RTCPSender的start_timestamp參數(shù)(即上節(jié)在確定RTP數(shù)據(jù)包頭不時(shí)間戳?xí)r的初始值)实牡。
視頻數(shù)據(jù)在編碼完之后發(fā)往RTP模塊構(gòu)造RTP報(bào)文時(shí)陌僵,視頻幀的時(shí)間戳timestamp和本地時(shí)間capture_time_ms通過(guò)RTCPSender::SetLastRtpTime()函數(shù)記錄到RTCPSender對(duì)象的last_rtp_timestamp和last_frame_capture_time_ms參數(shù)中,以將來(lái)將來(lái)構(gòu)造RTCP SR報(bào)文使用创坞。
2.2 SR報(bào)文構(gòu)造及發(fā)送
WebRTC內(nèi)部通過(guò)ModuleProcessThread線程周期性發(fā)送RTCP報(bào)文碗短,其中SR報(bào)文通過(guò)RTCPSender::BuildSR(ctx)構(gòu)造。其中ctx中包含當(dāng)前時(shí)刻的NTP時(shí)間题涨,作為SR報(bào)文[1]中的NTP時(shí)間偎谁。接下來(lái)需要計(jì)算出此刻對(duì)應(yīng)的RTP時(shí)間戳总滩,即假設(shè)此刻有一幀數(shù)據(jù)剛好被采樣,則其時(shí)間戳為:
rtp_timestamp = start_timestamp_ + last_rtp_timestamp_ +
(clock_->TimeInMilliseconds() - last_frame_capture_time_ms_) *
(ctx.feedback_state_.frequency_hz / 1000);
至此巡雨,NTP時(shí)間和RTP時(shí)間戳全部齊活兒闰渔,就可以構(gòu)造SR報(bào)文進(jìn)行發(fā)送了。
2.3 SR接收
接收端在收到SR報(bào)文后铐望,把其中包含的NTP時(shí)間和RTP時(shí)間戳記錄在RTCPSenderInfo對(duì)象中冈涧,供其他模塊獲取使用。比如通過(guò)RTCPReceiver::NTP()或者SenderInfoReceived()函數(shù)獲取正蛙。
三督弓、音視頻同步
前面兩節(jié)做必要的鋪墊后,本節(jié)詳細(xì)分析WebRTC內(nèi)部的音視頻同步過(guò)程跟畅。
3.1 初始化配置
音視頻同步的核心就是根據(jù)媒體負(fù)載所攜帶RTP時(shí)間戳進(jìn)行同步咽筋。在WebRTC內(nèi)部,同步的基本對(duì)象是AudioReceiveStream/VideoReceiveStream徊件,根據(jù)sync_group進(jìn)行相互配對(duì)奸攻。同步的初始化設(shè)置過(guò)程如圖2所示。
[圖片上傳失敗...(image-62912e-1543066451820)]
圖2 音視頻同步初始化配置
Call對(duì)象在創(chuàng)建Audio/VideoReceiveStream時(shí)虱痕,調(diào)用ConfigureSync()進(jìn)行音視頻同步的配置睹耐。配置參數(shù)為sync_group,該參數(shù)在PeerConnectionFactory在創(chuàng)建MediaStream時(shí)指定部翘。在ConfigureSync()函數(shù)內(nèi)部硝训,通過(guò)sync_group查找得到AudioReceiveStream,然后再在video_receive_streams中查找得到VideoReceiveStream新思。得到兩個(gè)媒體流窖梁,調(diào)用VideoReceiveStream::SetSyncChannel同步,在ViESyncModule::ConfigureSync()函數(shù)中把音視頻參數(shù)進(jìn)行保存夹囚,包括音頻的voe_channel_id纵刘、voe_sync_interface, 和視頻的video_rtp_receiver、video_rtp_rtcp荸哟。
3.2 同步過(guò)程
音視頻的同步過(guò)程在ModuleProcessThread線程中執(zhí)行假哎。ViESyncModule作為一個(gè)模塊注冊(cè)到ModuleProcessThread線程中,其Process()函數(shù)被該線程周期性調(diào)用鞍历,實(shí)現(xiàn)音視頻同步操作舵抹。
音視頻同步的核心思想就是以RTCP SR報(bào)文中攜帶的NTP時(shí)間和RTP時(shí)間戳作為時(shí)間基準(zhǔn),以AudioReceiveStream和VideoReceiveStream各自收到最新RTP時(shí)間戳timestamp和對(duì)應(yīng)的本地時(shí)間receive_time_ms作為參數(shù)劣砍,計(jì)算音視頻流的相對(duì)延遲惧蛹,然后結(jié)合音視頻的當(dāng)前延遲計(jì)算最終的目標(biāo)延遲,最后把目標(biāo)延遲發(fā)送到音視頻模塊進(jìn)行設(shè)置。目標(biāo)延遲作為音視頻渲染時(shí)的延遲下限值赊淑。整個(gè)過(guò)程如圖3所示爵政。
[圖片上傳失敗...(image-2400d0-1543066451820)]
圖3 音視頻同步過(guò)程
首先,從VideoReceiver獲得當(dāng)前視頻延遲current_video_delay陶缺,即video_jitter_delay,decode_delay和render_delay的總和洁灵。然后從VoEVideoSyncImpl獲得當(dāng)前音頻延遲current_audio_delay饱岸,即audio_jitter_delay和playout_delay的總和。
然后徽千,音視頻分別以各自的rtp_rtcp和rtp_receiver更新各自的measure苫费。其基本操作包括:從rtp_receiver獲取最新接收到的RTP報(bào)文的RTP時(shí)間戳latest_timestamp和對(duì)應(yīng)的本地接收時(shí)刻latest_receive_time_ms,從rtp_rtcp獲取最新接收的RTCP SR報(bào)文中的NTP時(shí)間和RTP時(shí)間戳双抽。然后把這些數(shù)據(jù)都存儲(chǔ)到measure中百框。注意measure中保存最新兩對(duì)RTCP SR報(bào)文中的NTP時(shí)間和RTP時(shí)間戳,用來(lái)在下一步計(jì)算媒體流的采樣頻率牍汹。
接下來(lái)铐维,計(jì)算最新收到的音視頻數(shù)據(jù)的相對(duì)延遲。其基本流程如下:首先得到最新收到RTP時(shí)間戳latest_timestamp對(duì)應(yīng)的NTP時(shí)間latest_capture_time慎菲。這里用到measure中存儲(chǔ)的latest_timestamp和RTCP SR的NTP時(shí)間和RTP時(shí)間戳timestamp嫁蛇,利用兩對(duì)數(shù)值計(jì)算得到采樣頻率frequency,然后有l(wèi)atest_capture_time = latest_timestamp / frequency露该,得到單位為毫秒的采樣時(shí)間睬棚。最后得到音視頻的相對(duì)延遲:
relative_delay = video_measure.latest_receive_time_ms -
audio_measure.latest_receive_time_ms -
(video_last_capture_time - audio_last_capture_time);
至此,我們得到三個(gè)重要參數(shù):視頻當(dāng)前延遲current_video_delay, 音頻當(dāng)前延遲current_audio_delay和相對(duì)延遲relative_delay解幼。接下來(lái)用這三個(gè)參數(shù)計(jì)算音視頻的目標(biāo)延遲:首先計(jì)算總相對(duì)延遲current_diff = current_video_delay – current_audio_delay + relative_delay抑党,根據(jù)歷史值對(duì)其求加權(quán)平均值。如果current_diff > 0撵摆,表明當(dāng)前視頻延遲比音頻延遲長(zhǎng)底靠,需要減小視頻延遲或者增大音頻延遲;反之如果current < 0台汇,則需要增大視頻延遲或者減小視頻延遲苛骨。經(jīng)過(guò)此番調(diào)整之后,我們得到音視頻的目標(biāo)延遲audio_target_delay和video_target_delay苟呐。
最后痒芝,我們把得到的目標(biāo)延遲audio_target_delay和video_target_delay分別設(shè)置到音視頻模塊中,作為將來(lái)渲染延遲的下限值牵素。到此為止严衬,一次音視頻同步操作完成。該操作在ModuleProcessThread線程中會(huì)周期性執(zhí)行笆呆。
四请琳、總結(jié)