WebRTC音視頻同步機(jī)制實(shí)現(xiàn)分析(轉(zhuǎn))

音視頻同步事關(guān)多媒體產(chǎn)品的最直觀用戶(hù)體驗(yàn)挂滓,是音視頻媒體數(shù)據(jù)傳輸和渲染播放的最基本質(zhì)量保證。音視頻如果不同步啸胧,有可能造成延遲赶站、卡頓等非常影響用戶(hù)體驗(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ì)描述之囤官。

1 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所示朽基。

圖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ì)端卦碾。

2 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ù)獲取。

3 音視頻同步

前面兩節(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所示洪灯。

圖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所示暇咆。

圖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í)行公荧。

4 總結(jié)

本文詳細(xì)分析了WebRTC內(nèi)部音視頻同步的實(shí)現(xiàn)細(xì)節(jié),包括RTP時(shí)間戳的產(chǎn)生同规,RTCP SR報(bào)文的構(gòu)造循狰、發(fā)送和接收庸诱,音視頻同步的初始化和同步過(guò)程。通過(guò)本文晤揣,對(duì)RTP協(xié)議桥爽、流媒體通信和音視頻同步有更深入的認(rèn)識(shí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昧识,一起剝皮案震驚了整個(gè)濱河市钠四,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跪楞,老刑警劉巖缀去,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異甸祭,居然都是意外死亡缕碎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)池户,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咏雌,“玉大人,你說(shuō)我怎么就攤上這事校焦∩薅叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵寨典,是天一觀的道長(zhǎng)氛雪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)耸成,這世上最難降的妖魔是什么报亩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮井氢,結(jié)果婚禮上弦追,老公的妹妹穿的比我還像新娘。我一直安慰自己毙沾,他們只是感情好骗卜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著左胞,像睡著了一般寇仓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烤宙,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天遍烦,我揣著相機(jī)與錄音,去河邊找鬼躺枕。 笑死服猪,一個(gè)胖子當(dāng)著我的面吹牛供填,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罢猪,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼近她,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了膳帕?” 一聲冷哼從身側(cè)響起粘捎,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎危彩,沒(méi)想到半個(gè)月后攒磨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汤徽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年娩缰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谒府。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拼坎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狱掂,到底是詐尸還是另有隱情演痒,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布趋惨,位于F島的核電站,受9級(jí)特大地震影響惦蚊,放射性物質(zhì)發(fā)生泄漏器虾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一蹦锋、第九天 我趴在偏房一處隱蔽的房頂上張望兆沙。 院中可真熱鬧,春花似錦莉掂、人聲如沸葛圃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)库正。三九已至,卻和暖如春厘唾,著一層夾襖步出監(jiān)牢的瞬間褥符,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工抚垃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喷楣,地道東北人趟大。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像铣焊,于是被迫代替她去往敵國(guó)和親逊朽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容