WebRTC的模塊處理機(jī)制

對于實(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>

圖1 WebRTC音視頻數(shù)據(jù)流水線

音視頻數(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ù),碼率估計(jì),前向糾錯,丟包重傳,等等。</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>

圖2 模塊虛基類Module定義

Module虛基類對外提供三個函數(shù)作為API:TimeUntilNextProcess()用來計(jì)算距下次調(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>

圖3 模塊處理線程虛基類ProcessThread及派生類ProcessThreadImpl

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ù):針對每個模塊,計(jì)算其下次調(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>

圖4 模塊處理線程實(shí)例

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ā)送過程中的碼率計(jì)算、RTT更新、RTCP報(bào)文發(fā)送等內(nèi)容。</br>

ModuleProcessThread線程由Worker線程在創(chuàng)建VideoChannel時創(chuàng)建,負(fù)責(zé)視頻端模塊的處理。掛載在該線程下的模塊如圖4所示,其中CallStats負(fù)責(zé)Call對象統(tǒng)計(jì)數(shù)據(jù)(如RTT)的更新,CongestionController負(fù)責(zé)擁塞控制[1][2],VideoSender負(fù)責(zé)視頻發(fā)送端統(tǒng)計(jì)數(shù)據(jù)的更新,VideoReceiver負(fù)責(zé)視頻接收端統(tǒng)計(jì)數(shù)據(jù)更新和處理狀態(tài)反饋(如請求關(guān)鍵幀),ModuleRtpRtcpImpl負(fù)責(zé)視頻RTP數(shù)據(jù)包發(fā)送過程中的碼率計(jì)算、RTT更新、RTCP報(bào)文發(fā)送等內(nèi)容,OveruseFrameDetector負(fù)責(zé)視頻幀采集端過載監(jiān)控,ReceiveStatisticsImpl負(fù)責(zé)由接收端統(tǒng)計(jì)數(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ā)送端碼率估計(jì)的情況下把接收端收集到的反饋信息發(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.itdecent.cn/p/0f7ee0e0b3be
[2] WebRTC基于GCC的擁塞控制(下) - 實(shí)現(xiàn)分析
http://www.itdecent.cn/p/5259a8659112

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容