一 前言
</br>
RTP/RTCP協(xié)議是流媒體通信的基石碱璃。RTP協(xié)議定義流媒體數(shù)據(jù)在互聯(lián)網(wǎng)上傳輸?shù)臄?shù)據(jù)包格式,而RTCP協(xié)議則負(fù)責(zé)可靠傳輸脚曾、流量控制和擁塞控制等服務(wù)質(zhì)量保證闽瓢。在WebRTC項(xiàng)目中,RTP/RTCP模塊作為傳輸模塊的一部分蕊玷,負(fù)責(zé)對(duì)發(fā)送端采集到的媒體數(shù)據(jù)進(jìn)行進(jìn)行封包邮利,然后交給上層網(wǎng)絡(luò)模塊發(fā)送;在接收端RTP/RTCP模塊收到上層模塊的數(shù)據(jù)包后垃帅,進(jìn)行解包操作延届,最后把負(fù)載發(fā)送到解碼模塊。因此挺智,RTP/RTCP 模塊在WebRTC通信中發(fā)揮非常重要的作用祷愉。</br>
本文在深入研究WebRTC源代碼的基礎(chǔ)上窗宦,以Video數(shù)據(jù)的發(fā)送和接收為例赦颇,力求用簡(jiǎn)潔語(yǔ)言描述RTP/RTCP模塊的實(shí)現(xiàn)細(xì)節(jié)二鳄,為進(jìn)一步深入掌握WebRTC打下良好基礎(chǔ)。</br>
二 RTP/RTCP協(xié)議概述
</br>
RTP協(xié)議是Internet上針對(duì)流媒體傳輸?shù)幕A(chǔ)協(xié)議媒怯,該協(xié)議詳細(xì)說(shuō)明在互聯(lián)網(wǎng)上傳輸音視頻的標(biāo)準(zhǔn)數(shù)據(jù)包格式订讼。RTP協(xié)議本身只保證實(shí)時(shí)數(shù)據(jù)的傳輸,RTCP協(xié)議則負(fù)責(zé)流媒體的傳輸質(zhì)量保證扇苞,提供流量控制和擁塞控制等服務(wù)欺殿。在RTP會(huì)話期間,各參與者周期性彼此發(fā)送RTCP報(bào)文鳖敷。報(bào)文中包含各參與者數(shù)據(jù)發(fā)送和接收等統(tǒng)計(jì)信息脖苏,參與者可以據(jù)此動(dòng)態(tài)控制流媒體傳輸質(zhì)量。</br>
RFC3550 [1]定義RTP/RTCP協(xié)議的基本內(nèi)容定踱,包括報(bào)文格式棍潘、傳輸規(guī)則等。除此之外崖媚,IETF還定義一系列擴(kuò)展協(xié)議亦歉,包括RTP協(xié)議基于檔次的擴(kuò)展,和RTCP協(xié)議基于報(bào)文類型的擴(kuò)展畅哑,等等肴楷。詳細(xì)內(nèi)容可參考文獻(xiàn)[2]。</br>
三 WebRTC線程關(guān)系和數(shù)據(jù)流
</br>
WebRTC對(duì)外提供兩個(gè)線程:Signal和Worker荠呐,前者負(fù)責(zé)信令數(shù)據(jù)的處理和傳輸赛蔫,后者負(fù)責(zé)媒體數(shù)據(jù)的處理和傳輸。在WebRTC內(nèi)部泥张,有一系列線程各司其職呵恢,相互協(xié)作完成數(shù)據(jù)流管線。下面以Video數(shù)據(jù)的處理流程為例圾结,說(shuō)明WebRTC內(nèi)部的線程合作關(guān)系瑰剃。</br>
如圖1所示,Capture線程從攝像頭采集原始數(shù)據(jù)筝野,得到VideoFrame晌姚;Capture線程是系統(tǒng)相關(guān)的,在Linux系統(tǒng)上可能是調(diào)用V4L2接口的線程歇竟,而在Mac系統(tǒng)上可能是調(diào)用AVFoundation框架的接口挥唠。接下來(lái)原始數(shù)據(jù)VideoFrame從Capture線程到達(dá)Worker線程,Worker線程起搬運(yùn)工的作用焕议,沒(méi)有對(duì)數(shù)據(jù)做特別處理宝磨,而是轉(zhuǎn)發(fā)到Encoder線程。Encoder線程調(diào)用具體的編碼器(如VP8, H264)對(duì)原始數(shù)據(jù)VideoFrame進(jìn)行編碼,編碼后的輸出進(jìn)一步進(jìn)行RTP封包形成RTP數(shù)據(jù)包唤锉。然后RTP數(shù)據(jù)包發(fā)送到Pacer線程進(jìn)行平滑發(fā)送世囊,Pacer線程會(huì)把RTP數(shù)據(jù)包推送到Network線程。最終Network線程調(diào)用傳輸層系統(tǒng)函數(shù)把數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)窿祥。</br>
在接收端株憾,Network線程從網(wǎng)絡(luò)接收字節(jié)流,接著Worker線程反序列化為RTP數(shù)據(jù)包晒衩,并在VCM模塊進(jìn)行組幀操作嗤瞎。Decoder線程對(duì)組幀完成的數(shù)據(jù)幀進(jìn)行解碼操作,解碼后的原始數(shù)據(jù)VideoFrame會(huì)推送到IncomingVideoStream線程听系,該線程把VideoStream投放到render進(jìn)行渲染顯示贝奇。至此,一幀視頻數(shù)據(jù)完成從采集到顯示的完整過(guò)程靠胜。</br>
在上述過(guò)程中掉瞳,RTP數(shù)據(jù)包產(chǎn)生在發(fā)送端編碼完成后,其編碼輸出被封裝為RTP報(bào)文髓帽,然后經(jīng)序列化發(fā)送到網(wǎng)絡(luò)菠赚。在接收端由網(wǎng)絡(luò)線程收到網(wǎng)絡(luò)數(shù)據(jù)包后,經(jīng)過(guò)反序列化還原成RTP報(bào)文郑藏,然后經(jīng)過(guò)解包得到媒體數(shù)據(jù)負(fù)載衡查,供解碼器進(jìn)行解碼。RTP報(bào)文在發(fā)送和接收過(guò)程中必盖,會(huì)執(zhí)行一系列統(tǒng)計(jì)操作拌牲,統(tǒng)計(jì)結(jié)果作為數(shù)據(jù)源供構(gòu)造RTCP報(bào)文之用。RTP報(bào)文構(gòu)造歌粥、發(fā)送/接收統(tǒng)計(jì)和RTCP報(bào)文構(gòu)造塌忽、解析反饋,是接下來(lái)分析的重點(diǎn)失驶。</br>
四 RTP報(bào)文發(fā)送和接收
</br>
RTP報(bào)文的構(gòu)造和發(fā)送發(fā)生在編碼器編碼之后土居、網(wǎng)絡(luò)層發(fā)送數(shù)據(jù)包之前,而接收和解包發(fā)生在網(wǎng)絡(luò)層接收數(shù)據(jù)之后嬉探、解碼器編碼之前擦耀。本節(jié)詳細(xì)分析這兩部分的內(nèi)容。</br>
4.1 RTP報(bào)文構(gòu)造和發(fā)送
</br>
圖2描述發(fā)送端編碼之后RTP報(bào)文的構(gòu)造和發(fā)送過(guò)程涩堤,涉及三個(gè)線程:Encoder眷蜓、Pacer和Network,分別負(fù)責(zé)編碼和構(gòu)造RTP報(bào)文胎围,平滑發(fā)送和傳輸層發(fā)送吁系。下面詳細(xì)描述這三個(gè)線程的協(xié)同工作過(guò)程德召。</br>
Encode線程調(diào)用編碼器(比如VP8)對(duì)采集到的Raw VideoFrame進(jìn)行編碼,編碼完成以后汽纤,其輸出EncodedImage通過(guò)回調(diào)到達(dá)VideoSendStream::Encoded()函數(shù)上岗,進(jìn)而通過(guò)PayloadRouter路由到ModuleRtpRtcpImpl::SendOutgoingData()。接下來(lái)冒版,該函數(shù)向下調(diào)用RtpSender::SendOutgoingData()液茎,進(jìn)而調(diào)用RtpSenderVideo::SendVideo()逞姿。該函數(shù)對(duì)EncodedImage進(jìn)行打包辞嗡,然后填充RTP頭部構(gòu)造RTP報(bào)文;如果配置了FEC滞造,則進(jìn)一步封裝為FEC報(bào)文续室。最后返回RtpSender::SendToNetwork()進(jìn)行下一步發(fā)送。</br>
RtpSender::SendToNetwork()函數(shù)把報(bào)文存儲(chǔ)到RTPPacketHistory結(jié)構(gòu)中進(jìn)行緩存谒养。接下來(lái)如果開(kāi)啟PacedSending挺狰,則構(gòu)造Packe發(fā)送到PacedSender進(jìn)行排隊(duì),否則直接發(fā)送到網(wǎng)絡(luò)層买窟。</br>
Pacer線程周期性從隊(duì)列中獲取Packet丰泊,然后調(diào)用PacedSender::SendPacket()進(jìn)行發(fā)送,接下來(lái)經(jīng)過(guò)ModuleRtpRtcpImpl到達(dá)RtpSender::TimeToSendPacket()始绍。該函數(shù)首先從RtpPacketHistory緩存中拿到Packet的負(fù)載瞳购,然后調(diào)用PrepareAndSendPacket()函數(shù):更新RtpHeader的相關(guān)域,統(tǒng)計(jì)延遲和數(shù)據(jù)包亏推,調(diào)用SendPacketToNetwork()把報(bào)文發(fā)送到傳輸模塊学赛。</br>
Network線程則調(diào)用傳輸層套接字執(zhí)行數(shù)據(jù)發(fā)送操作。至此吞杭,發(fā)送端的RTP構(gòu)造和發(fā)送流程完成盏浇。需要注意的是,在RtpSender中進(jìn)行Rtp發(fā)送后芽狗,會(huì)統(tǒng)計(jì)RTP報(bào)文相關(guān)信息绢掰。這些信息作為RTCP構(gòu)造SR/RR報(bào)文的數(shù)據(jù)來(lái)源,因此非常重要童擎。</br>
4.2 RTP報(bào)文接收和解析
</br>
在接收端滴劲,RTP報(bào)文的接收和解包操作主要在Worker線程中執(zhí)行,RTP報(bào)文從Network線程拿到后柔昼,進(jìn)入Worker線程哑芹,經(jīng)過(guò)解包操作,進(jìn)入VCM模塊捕透,由Decode線程進(jìn)行解碼聪姿,最終由Render線程進(jìn)行渲染碴萧。下圖3描述RTP報(bào)文在Worker線程中的處理流程。</br>
RTP數(shù)據(jù)包經(jīng)網(wǎng)絡(luò)層到達(dá)Call對(duì)象末购,根據(jù)其SSRC找到對(duì)應(yīng)的VideoReceiveStream破喻,通過(guò)調(diào)用其DeliverRtp()函數(shù)到RtpStreamReceiver::DeliverRtp()。該函數(shù)首先解析數(shù)據(jù)包得到RTP頭部信息盟榴,接下來(lái)執(zhí)行三個(gè)操作:1.碼率估計(jì)曹质;2.繼續(xù)發(fā)送數(shù)據(jù)包;3.接收統(tǒng)計(jì)擎场。碼率估計(jì)模塊使用GCC算法估計(jì)碼率羽德,構(gòu)造REMB報(bào)文,交給RtpRtcp模塊發(fā)送回發(fā)送端迅办。而接收統(tǒng)計(jì)則統(tǒng)計(jì)RTP接收信息宅静,這些信息作為RTCP RR報(bào)文的數(shù)據(jù)來(lái)源。下面重點(diǎn)分析接下來(lái)的數(shù)據(jù)包發(fā)送流程站欺。</br>
RtpStreamReceiver::ReceivePacket()首先判斷數(shù)據(jù)包是否是FEC報(bào)文姨夹,如果是則調(diào)用FecReceiver進(jìn)行解包,否則直接調(diào)用RtpReceiver::IncomingRtpPacket()矾策。該函數(shù)分析RTP報(bào)文得到通用的RTP頭部描述結(jié)構(gòu)磷账,然后調(diào)用RtpReceiverVideo::ParseRtpPacket()進(jìn)一步得到Video相關(guān)信息和負(fù)載,接著經(jīng)過(guò)回調(diào)返回RtpStreamReceiver對(duì)象贾虽。該對(duì)象把Rtp描述信息和負(fù)載發(fā)送到VCM模塊逃糟,繼續(xù)接下來(lái)的JitterBuffer緩存和解碼渲染操作。</br>
RTP報(bào)文解包過(guò)程是封包的逆過(guò)程榄鉴,重要的輸出信息是RTP頭部描述和媒體負(fù)載履磨,這些信息是下一步JitterBuffer緩存和解碼的基礎(chǔ)。另外對(duì)RTP報(bào)文進(jìn)行統(tǒng)計(jì)得到的信息則是RTCP RR報(bào)文的數(shù)據(jù)來(lái)源庆尘。</br>
五 RTCP報(bào)文發(fā)送和接收
</br>
RTCP協(xié)議是RTP協(xié)議的控制下可以剃诅,負(fù)責(zé)流媒體的服務(wù)質(zhì)量保證。比較常用的RTCP報(bào)文由發(fā)送端報(bào)告SR和接收端報(bào)告RR驶忌,分別包含數(shù)據(jù)發(fā)送統(tǒng)計(jì)信息和數(shù)據(jù)接收信息矛辕。這些信息對(duì)于流媒體質(zhì)量保證非常重要,比如碼率控制付魔、負(fù)載反饋聊品,等等。其他RTCP報(bào)文還有諸如SDES几苍、BYE翻屈、SDES等,RFC3550對(duì)此有詳細(xì)定義妻坝。</br>
本節(jié)重點(diǎn)分析WebRTC內(nèi)部RTCP報(bào)文的構(gòu)造伸眶、發(fā)送惊窖、接收、解析厘贼、反饋等流程界酒。需要再次強(qiáng)調(diào)的是,RTCP報(bào)文的數(shù)據(jù)源來(lái)自RTP報(bào)文發(fā)送和接收時(shí)的統(tǒng)計(jì)信息嘴秸。在WebRTC內(nèi)部毁欣,RTCP報(bào)文的發(fā)送采取周期性發(fā)送和及時(shí)發(fā)送相結(jié)合的策略:ModuleProcess線程周期性發(fā)送RTCP報(bào)文;而RtpSender則在每次發(fā)送RTP報(bào)文之前都判斷是否需要發(fā)送RTCP報(bào)文岳掐;另外在接收端碼率估計(jì)模塊構(gòu)造出REMB報(bào)文后凭疮,通過(guò)設(shè)置超時(shí)讓ModuleProcess模塊立即發(fā)送RTCP報(bào)文。</br>
5.1 RTCP報(bào)文構(gòu)造和發(fā)送
</br>
在發(fā)送端岩四,RTCP以周期性發(fā)送為基準(zhǔn)哭尝,輔以RTP報(bào)文發(fā)送時(shí)的及時(shí)發(fā)送和REMB報(bào)文的立即發(fā)送。發(fā)送過(guò)程主要包括Feedback信息獲取剖煌、RTCP報(bào)文構(gòu)造、序列化和發(fā)送逝淹。圖4描述了RTCP報(bào)文的構(gòu)造和發(fā)送過(guò)程耕姊。</br>
ModuleProcess線程周期性調(diào)用ModuleRtpRtcpImpl::Process()函數(shù),該函數(shù)通過(guò)RTCPSender::TimeToSendRtcpReport()函數(shù)確定當(dāng)前是否需要立即發(fā)送RTCP報(bào)文栅葡。若是茉兰,則首先從RTPSender::GetDataCounters()獲取RTP發(fā)送統(tǒng)計(jì)信息,然后調(diào)用RTCPSender::SendRTCP()欣簇,接著是SendCompoundRTCP()發(fā)送RTCP組合報(bào)文规脸。關(guān)于RTCP組合報(bào)文的定義,請(qǐng)參考文獻(xiàn)[1]熊咽。</br>
在SendCompoundRTCP()函數(shù)中莫鸭,首先通過(guò)PrepareReport()確定將要發(fā)送何種類型的RTCP報(bào)文。然后針對(duì)每一種報(bào)文横殴,調(diào)用其構(gòu)造函數(shù)(如構(gòu)造SR報(bào)文為BuildSR()函數(shù))被因,構(gòu)造好的報(bào)文存儲(chǔ)在PacketContainer容器中。最后調(diào)用SendPackets()進(jìn)行發(fā)送衫仑。</br>
接下來(lái)每種RTCP報(bào)文都會(huì)調(diào)用各自的序列化函數(shù)梨与,把報(bào)文序列化為網(wǎng)絡(luò)字節(jié)流。最后通過(guò)回調(diào)到達(dá)PacketContainer::OnPacketReady()文狱,最終把字節(jié)流發(fā)送到傳輸層模塊:即通過(guò)TransportAdapter到達(dá)BaseChannel粥鞋,Network線程調(diào)用傳輸層套接字API發(fā)送數(shù)據(jù)到網(wǎng)絡(luò)。</br>
RTCP報(bào)文的構(gòu)造和發(fā)送過(guò)程總體不是很復(fù)雜瞄崇,最核心的操作就是獲取數(shù)據(jù)源呻粹、構(gòu)造報(bào)文到踏、序列化和發(fā)送。相對(duì)來(lái)說(shuō)構(gòu)造報(bào)文和序列化比較繁瑣尚猿,基于RFC定義的細(xì)節(jié)進(jìn)行窝稿。</br>
5.2 RTCP報(bào)文接收和解析
</br>
接收端的RTCP報(bào)文接收和解析過(guò)程如圖5所示。</br>
在接收端凿掂,RTCP報(bào)文的接收流程和RTP一樣伴榔,經(jīng)過(guò)網(wǎng)絡(luò)接收之后到達(dá)Call對(duì)象,進(jìn)而通過(guò)SSRC找到VideoReceiveStream庄萎,繼而到達(dá)RtpStreamReceiver踪少。接下來(lái)RTCP報(bào)文的解析和反饋操作都在ModuleRtpRtcpImpl::IncomingRtcpPacket()函數(shù)中完成。該函數(shù)首先調(diào)用RTCPReceiver::IncomingRtcpPacket()解析RTCP報(bào)文糠涛,得到RTCPPacketInformation對(duì)象援奢,然后調(diào)用 TriggerCallbacksFromRTCPPacket(),觸發(fā)注冊(cè)在此處的各路觀察者執(zhí)行回調(diào)操作忍捡。</br>
RTCPReceiver::IncomingRtcpPacket()使用RTCPParser解析組合報(bào)文集漾,針對(duì)每一種報(bào)文類型,調(diào)用對(duì)應(yīng)的處理函數(shù)(如處理SDES的HandleSDES函數(shù))砸脊,反序列化后拿到報(bào)文的描述結(jié)構(gòu)具篇。最后所有報(bào)文綜合在一起形成RTCPPacketInformation對(duì)象。該對(duì)象接下來(lái)作為參數(shù)調(diào)用TriggerCallbacksFromRTCPPacket()函數(shù)觸發(fā)回調(diào)操作凌埂,如處理NACK的回調(diào)驱显,處理SLI的回調(diào),處理REMB的回調(diào)瞳抓,等等埃疫。這些回調(diào)在各自模塊控制流媒體數(shù)據(jù)的編碼、發(fā)送孩哑、碼率等服務(wù)質(zhì)量保證栓霜,這也是RTCP報(bào)文最終起作用的地方。</br>
至此臭笆,我們分析了RTCP報(bào)文發(fā)送和接收的整個(gè)流程叙淌。</br>
六 總結(jié)
</br>
本文在深入分析WebRTC源代碼的基礎(chǔ)上,結(jié)合流程圖描述出RTP/RTCP模塊的實(shí)現(xiàn)流程愁铺,在關(guān)鍵問(wèn)題上(如RTCP報(bào)文的數(shù)據(jù)來(lái)源)進(jìn)行深入細(xì)致的研究鹰霍。為進(jìn)一步深入掌握WebRTC的實(shí)現(xiàn)原理和細(xì)節(jié)打下良好基礎(chǔ)。</br>
</br>
參考文獻(xiàn)
</br>
[1] RFC3550 - RTP: A Transport Protocol for Real-Time Applications
https://www.ietf.org/rfc/rfc3550.txt
[2] 超越RFC3550 - RTP/RTCP協(xié)議族分析 : http://www.reibang.com/p/e5e21aeb219f