對于實(shí)時音視頻應(yīng)用來講,媒體數(shù)據(jù)從采集到渲染,在數(shù)據(jù)流水線上依次完成一系列處理月杉。流水線由不同的功能模塊組成周伦,彼此分工協(xié)作:數(shù)據(jù)采集模塊負(fù)責(zé)從攝像頭/麥克風(fēng)采集音視頻數(shù)據(jù),編解碼模塊負(fù)責(zé)對數(shù)據(jù)進(jìn)行編解碼狞换,RTP模塊負(fù)責(zé)數(shù)據(jù)打包和解包。數(shù)據(jù)流水線上的數(shù)據(jù)處理速度是影響應(yīng)用實(shí)時性的最重要因素汪厨。與此同時低斋,從服務(wù)質(zhì)量保證角度講蜂厅,應(yīng)用需要知道數(shù)據(jù)流水線的運(yùn)行狀態(tài),如視頻采集模塊的實(shí)時幀率膊畴、當(dāng)前網(wǎng)絡(luò)的實(shí)時速率掘猿、接收端的數(shù)據(jù)丟包率,等等唇跨。各個功能模塊可以基于這些運(yùn)行狀態(tài)信息作相應(yīng)調(diào)整稠通,從而在質(zhì)量、速度等方面優(yōu)化數(shù)據(jù)流水線的運(yùn)行买猖,實(shí)現(xiàn)更快改橘、更好的用戶體驗(yàn)。</br>
WebRTC采用模塊機(jī)制玉控,把數(shù)據(jù)流水線上功能相對獨(dú)立的處理點(diǎn)定義為模塊飞主,每個模塊專注于自己的任務(wù),模塊之間基于數(shù)據(jù)流進(jìn)行通信高诺。與此同時碌识,專有線程收集和處理模塊內(nèi)部的運(yùn)行狀態(tài)信息,并把這些信息反饋到目標(biāo)模塊懒叛,實(shí)現(xiàn)模塊運(yùn)行狀態(tài)監(jiān)控和服務(wù)質(zhì)量保證丸冕。本文在深入分析WebRTC源代碼基礎(chǔ)上,學(xué)習(xí)研究其模塊處理機(jī)制的實(shí)現(xiàn)細(xì)節(jié)薛窥,從另一個角度理解WebRTC的技術(shù)原理胖烛。</br>
1 WebRTC數(shù)據(jù)流水線
</br>
</br>我們可以把WebRTC看作是一個專注于實(shí)時音視頻通信的SDK。其對外的API主要負(fù)責(zé)PeerConnection建立诅迷、MediaStream創(chuàng)建佩番、NAT穿透、SDP協(xié)商等工作罢杉,對內(nèi)則主要集中于音視頻數(shù)據(jù)的處理趟畏,從數(shù)據(jù)采集到渲染的整個處理過程可以用一個數(shù)據(jù)流水線來描述,如圖1所示滩租。</br>
音視頻數(shù)據(jù)首先從采集端進(jìn)行采集赋秀,一般來說音頻數(shù)據(jù)來自麥克風(fēng),視頻數(shù)據(jù)來自攝像頭律想。在某些應(yīng)用場景下猎莲,音頻數(shù)據(jù)來自揚(yáng)聲器,視頻數(shù)據(jù)來自桌面共享技即。采集端的輸出是音視頻Raw Data著洼。然后Raw Data到達(dá)編碼模塊,數(shù)據(jù)被編碼器編碼成符合語法規(guī)則的NAL單元,到達(dá)發(fā)送端緩沖區(qū)PacedSender處身笤。接下來PacedSender把NAL單元發(fā)送到RTP模塊打包為RTP數(shù)據(jù)包豹悬,最后經(jīng)過網(wǎng)絡(luò)模塊發(fā)送到網(wǎng)絡(luò)。</br>
在接收端液荸,RTP數(shù)據(jù)包經(jīng)過網(wǎng)絡(luò)模塊接收后進(jìn)行解包得到NAL單元瞻佛,接下來NAL單元到達(dá)接收端緩沖區(qū)(JitterBuffer或者NetEQ)進(jìn)行亂序重排和組幀。一幀完整的數(shù)據(jù)接收并組幀之后莹弊,調(diào)用解碼模塊進(jìn)行解碼涤久,得到該幀數(shù)據(jù)的Raw Data。最后Raw Data交給渲染模塊進(jìn)行播放/顯示忍弛。</br>
在數(shù)據(jù)流水線上,還有一系列模塊負(fù)責(zé)服務(wù)質(zhì)量監(jiān)控考抄,如丟幀策略细疚,丟包策略,編碼器過度使用保護(hù)川梅,碼率估計疯兼,前向糾錯,丟包重傳贫途,等等吧彪。</br>
WebRTC數(shù)據(jù)流水線上的功能單元被定義為模塊,每個模塊從上游模塊獲取輸入數(shù)據(jù)丢早,在本模塊進(jìn)行加工后得到輸出數(shù)據(jù)姨裸,交給下游模塊進(jìn)行下一步處理。WebRTC的模塊處理機(jī)制包括模塊和模塊處理線程怨酝,前者把WebRTC數(shù)據(jù)流水線上的功能部件封裝為模塊傀缩,后者驅(qū)動模塊內(nèi)部狀態(tài)更新和模塊之間狀態(tài)傳遞。模塊一般掛載到模塊處理線程上农猬,由處理線程驅(qū)動模塊的處理函數(shù)赡艰。下面分別描述之。</br>
2 WebRTC模塊
</br>
</br>WebRTC模塊虛基類Module定義在webrtc/modules/include/modue.h中斤葱,如圖2所示慷垮。</br>
Module虛基類對外提供三個函數(shù)作為API:TimeUntilNextProcess()用來計算距下次調(diào)用處理函數(shù)Process()的時間間隔;Process()是模塊的處理函數(shù)揍堕,負(fù)責(zé)模塊內(nèi)部運(yùn)行監(jiān)控料身、狀態(tài)更新和模塊間通信;ProcessThreadAttached()用來把模塊掛載到模塊處理線程鹤啡,或者從模塊處理線程分離出來惯驼,實(shí)際實(shí)現(xiàn)中這個函數(shù)暫時沒有被用到。</br>
Module的派生類分布在WebRTC數(shù)據(jù)流水線上的不同部分,各自承擔(dān)自己的數(shù)據(jù)處理和服務(wù)質(zhì)量保證任務(wù)祟牲。</br>
3 WebRTC模塊處理線程
</br>
</br>WebRTC模塊處理線程是模塊處理機(jī)制的驅(qū)動器隙畜,它的核心作用是對所有掛載在本線程下的模塊,周期性調(diào)用其Process()處理函數(shù)處理模塊內(nèi)部事務(wù)说贝,并處理異步任務(wù)议惰。其虛基類ProcessThread和派生類ProcessThreadImpl如圖3所示。</br>
ProcessThread基類提供一系列API完成線程功能:Start()/Stop()函數(shù)用來啟動和結(jié)束線程乡恕;WakeUp()函數(shù)用來喚醒掛載在本線程下的某個模塊言询,使得該模塊有機(jī)會馬上執(zhí)行其Process()處理函數(shù);PostTask()函數(shù)用來郵遞一個任務(wù)給本線程傲宜;RegisterModule()和DeRegisterModule()用來向線程注冊/注銷模塊运杭。</br>
WebRTC基于ProcessThread線程實(shí)現(xiàn)派生類ProcessThreadImpl,如圖3所示函卒。在成員變量方面辆憔,wake_up_用來喚醒處于等待狀態(tài)的線程;thread_是平臺相關(guān)的線程實(shí)現(xiàn)如POSIX線程报嵌;modules_是注冊在本線程下的模塊集合虱咧;queue_是郵遞給本線程的任務(wù)集合;thread_name_是線程名字锚国。在成員函數(shù)方面腕巡,Process()完成ProcessThread的核心任務(wù),其偽代碼如下所示血筑。</br>
bool ProcessThreadImpl::Process() {
for (ModuleCallback& m : modules_) {
if (m.next_callback == 0)
m.next_callback = GetNextCallbackTime(m.module, now);
if (m.next_callback <= now || m.next_callback == kCallProcessImmediately) {
m.module->Process();
m.next_callback = GetNextCallbackTime(m.module, rtc::TimeMillis(););
}
if (m.next_callback < next_checkpoint)
next_checkpoint = m.next_callback;
}
while (!queue_.empty()) {
ProcessTask* task = queue_.front();
queue_.pop();
task->Run();
delete task;
}
}
int64_t time_to_wait = next_checkpoint - rtc::TimeMillis();
if (time_to_wait > 0)
wake_up_->Wait(static_cast<unsigned long>(time_to_wait));
return true;
}
Process()函數(shù)首先處理掛載在本線程下的模塊绘沉,這也是模塊處理線程的核心任務(wù):針對每個模塊,計算其下次調(diào)用模塊Process()處理函數(shù)的時刻(調(diào)用該模塊的TimeUntilNextProcess()函數(shù))云挟。如果時刻是當(dāng)前時刻梆砸,則調(diào)用模塊的Process()處理函數(shù),并更新下次調(diào)用時刻园欣。需要注意帖世,不同模塊的執(zhí)行頻率不一樣,線程在本輪調(diào)用末尾的等待時間和本線程下所有模塊的最近下次調(diào)用時刻相關(guān)沸枯。</br>
接下來線程Process()函數(shù)處理ProcessTask隊(duì)列中的異步任務(wù)日矫,針對每個任務(wù)調(diào)用Run()函數(shù),然后任務(wù)出隊(duì)列并銷毀绑榴。等模塊調(diào)用和任務(wù)都處理完后哪轿,則把本次時間片的剩余時間等待完畢,然后返回翔怎。如果在等待期間其他線程向本線程Wakeup模塊或者郵遞一個任務(wù)窃诉,則線程被立即喚醒并返回杨耙,進(jìn)行下一輪時間片的執(zhí)行。</br>
至此飘痛,關(guān)于WebRTC的模塊和模塊處理線程的基本實(shí)現(xiàn)分析完畢珊膜,下一節(jié)將對WebRTC SDK內(nèi)模塊實(shí)例和模塊處理線程實(shí)例進(jìn)行詳細(xì)分析。</br>
4 WebRTC模塊處理線程實(shí)例
</br>
</br>WebRTC關(guān)于模塊和處理線程的實(shí)現(xiàn)在webrtc/modules目錄下宣脉,該目錄匯集了所有派生類模塊和模塊處理線程的實(shí)現(xiàn)及實(shí)例分布车柠。本節(jié)對這些內(nèi)容進(jìn)行總結(jié)。</br>
WebRTC目前創(chuàng)建三個ProcessThreadImpl線程實(shí)例塑猖,分別是負(fù)責(zé)處理音頻的VoiceProcessTread竹祷,負(fù)責(zé)處理視頻和音視頻同步的ModuleProcessThread,以及負(fù)責(zé)數(shù)據(jù)平滑發(fā)送的PacerThread羊苟。這三個線程和掛載在線程下的模塊如圖4所示塑陵。</br>
VoiceProcessThread線程由Worker線程在創(chuàng)建VoiceEngine時創(chuàng)建,負(fù)責(zé)音頻端模塊的處理蜡励。掛載在該線程下的模塊如圖4所示猿妈,其中MonitorModule負(fù)責(zé)對音頻數(shù)據(jù)混音處理過程中產(chǎn)生的警告和錯誤進(jìn)行處理,AudioDeviceModuleImpl負(fù)責(zé)對音頻設(shè)備采集和播放音頻數(shù)據(jù)時產(chǎn)生的警告和錯誤進(jìn)行處理巍虫,ModuleRtpRtcpImpl負(fù)責(zé)音頻RTP數(shù)據(jù)包發(fā)送過程中的碼率計算、RTT更新鳍刷、RTCP報文發(fā)送等內(nèi)容占遥。</br>
ModuleProcessThread線程由Worker線程在創(chuàng)建VideoChannel時創(chuàng)建,負(fù)責(zé)視頻端模塊的處理输瓜。掛載在該線程下的模塊如圖4所示瓦胎,其中CallStats負(fù)責(zé)Call對象統(tǒng)計數(shù)據(jù)(如RTT)的更新,CongestionController負(fù)責(zé)擁塞控制[1][2]尤揣,VideoSender負(fù)責(zé)視頻發(fā)送端統(tǒng)計數(shù)據(jù)的更新搔啊,VideoReceiver負(fù)責(zé)視頻接收端統(tǒng)計數(shù)據(jù)更新和處理狀態(tài)反饋(如請求關(guān)鍵幀),ModuleRtpRtcpImpl負(fù)責(zé)視頻RTP數(shù)據(jù)包發(fā)送過程中的碼率計算北戏、RTT更新负芋、RTCP報文發(fā)送等內(nèi)容,OveruseFrameDetector負(fù)責(zé)視頻幀采集端過載監(jiān)控嗜愈,ReceiveStatisticsImpl負(fù)責(zé)由接收端統(tǒng)計數(shù)據(jù)觸發(fā)的碼率更新過程旧蛾,ViESyncModule負(fù)責(zé)音視頻同步。</br>
PacerThread線程由Worker線程在創(chuàng)建VideoChannel時創(chuàng)建蠕嫁,負(fù)責(zé)數(shù)據(jù)平滑發(fā)送锨天。掛載在該線程下的PacedSender負(fù)責(zé)發(fā)送端數(shù)據(jù)平滑發(fā)送;RemoteEstimatorProxy派生自RemoteBitrateEstimator剃毒,負(fù)責(zé)在啟用發(fā)送端碼率估計的情況下把接收端收集到的反饋信息發(fā)送回發(fā)送端病袄。</br>
由以上分析可知搂赋,WebRTC創(chuàng)建的模塊處理線程實(shí)例基本上涵蓋了音視頻數(shù)據(jù)從采集到渲染過程中的大部分?jǐn)?shù)據(jù)操作。但還有一些模塊不依賴于模塊線程工作益缠,這部分模塊是少數(shù)脑奠,本文不展開具體的描述。</br>
5 總結(jié)
</br>
</br>本文在深入分析WebRTC源代碼基礎(chǔ)上左刽,學(xué)習(xí)研究其模塊處理機(jī)制的實(shí)現(xiàn)細(xì)節(jié)捺信,為進(jìn)一步全面理解WebRTC的技術(shù)原理奠定基礎(chǔ)。</br>
</br>
</br>
參考文獻(xiàn)
[1] WebRTC基于GCC的擁塞控制(上) – 算法分析
http://www.reibang.com/p/0f7ee0e0b3be
[2] WebRTC基于GCC的擁塞控制(下) - 實(shí)現(xiàn)分析
http://www.reibang.com/p/5259a8659112