對於實時音視頻應用來講,媒體數據從採集到渲染,在數據流水線上依次完成一系列處理。流水線由不同的功能模塊組成,彼此分工協作:數據採集模塊負責從攝像頭/麥克風採集音視頻數據,編解碼模塊負責對數據進行編解碼,RTP模塊負責數據打包和解包。數據流水線上的數據處理速度是影響應用實時性的最重要因素。與此同時,從服務質量保證角度講,應用需要知道數據流水線的運行狀態,如視頻採集模塊的實時幀率、當前網絡的實時速率、接收端的數據丟包率,等等。各個功能模塊可以基於這些運行狀態信息作相應調整,從而在質量、速度等方面優化數據流水線的運行,實現更快、更好的用户體驗。
WebRTC採用模塊機制,把數據流水線上功能相對獨立的處理點定義為模塊,每個模塊專注於自己的任務,模塊之間基於數據流進行通信。與此同時,專有線程收集和處理模塊內部的運行狀態信息,並把這些信息反饋到目標模塊,實現模塊運行狀態監控和服務質量保證。本文在深入分析WebRTC源代碼基礎上,學習研究其模塊處理機制的實現細節,從另一個角度理解WebRTC的技術原理。
1 WebRTC數據流水線
我們可以把WebRTC看作是一個專注於實時音視頻通信的SDK。其對外的API主要負責PeerConnection建立、MediaStream創建、NAT穿透、SDP協商等工作,對內則主要集中於音視頻數據的處理,從數據採集到渲染的整個處理過程可以用一個數據流水線來描述,如圖1所示。
音視頻數據首先從採集端進行採集,一般來説音頻數據來自麥克風,視頻數據來自攝像頭。在某些應用場景下,音頻數據來自揚聲器,視頻數據來自桌面共享。採集端的輸出是音視頻Raw Data。然後Raw Data到達編碼模塊,數據被編碼器編碼成符合語法規則的NAL單元,到達發送端緩衝區PacedSender處。接下來PacedSender把NAL單元發送到RTP模塊打包為RTP數據包,最後經過網絡模塊發送到網絡。
在接收端,RTP數據包經過網絡模塊接收後進行解包得到NAL單元,接下來NAL單元到達接收端緩衝區(JitterBuffer或者NetEQ)進行亂序重排和組幀。一幀完整的數據接收並組幀之後,調用解碼模塊進行解碼,得到該幀數據的Raw Data。最後Raw Data交給渲染模塊進行播放/顯示。
在數據流水線上,還有一系列模塊負責服務質量監控,如丟幀策略,丟包策略,編碼器過度使用保護,碼率估計,前向糾錯,丟包重傳,等等。
WebRTC數據流水線上的功能單元被定義為模塊,每個模塊從上游模塊獲取輸入數據,在本模塊進行加工後得到輸出數據,交給下游模塊進行下一步處理。WebRTC的模塊處理機制包括模塊和模塊處理線程,前者把WebRTC數據流水線上的功能部件封裝為模塊,後者驅動模塊內部狀態更新和模塊之間狀態傳遞。模塊一般掛載到模塊處理線程上,由處理線程驅動模塊的處理函數。下面分別描述之。
WebRTC模塊
WebRTC模塊虛基類Module定義在webrtc/modules/include/modue.h中,如圖2所示。
Module虛基類對外提供三個函數作為API:TimeUntilNextProcess()用來計算距下次調用處理函數Process()的時間間隔;Process()是模塊的處理函數,負責模塊內部運行監控、狀態更新和模塊間通信;ProcessThreadAttached()用來把模塊掛載到模塊處理線程,或者從模塊處理線程分離出來,實際實現中這個函數暫時沒有被用到。
Module的派生類分佈在WebRTC數據流水線上的不同部分,各自承擔自己的數據處理和服務質量保證任務。
3 WebRTC模塊處理線程
WebRTC模塊處理線程是模塊處理機制的驅動器,它的核心作用是對所有掛載在本線程下的模塊,週期性調用其Process()處理函數處理模塊內部事務,並處理異步任務。其虛基類ProcessThread和派生類ProcessThreadImpl如圖3所示。
ProcessThread基類提供一系列API完成線程功能:Start()/Stop()函數用來啓動和結束線程;WakeUp()函數用來喚醒掛載在本線程下的某個模塊,使得該模塊有機會馬上執行其Process()處理函數;PostTask()函數用來郵遞一個任務給本線程;RegisterModule()和DeRegisterModule()用來向線程註冊/註銷模塊。
WebRTC基於ProcessThread線程實現派生類ProcessThreadImpl,如圖3所示。在成員變量方面,wake_up_用來喚醒處於等待狀態的線程;thread_是平台相關的線程實現如POSIX線程;modules_是註冊在本線程下的模塊集合;queue_是郵遞給本線程的任務集合;thread_name_是線程名字。在成員函數方面,Process()完成ProcessThread的核心任務,其偽代碼如下所示。
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()函數首先處理掛載在本線程下的模塊,這也是模塊處理線程的核心任務:針對每個模塊,計算其下次調用模塊Process()處理函數的時刻(調用該模塊的TimeUntilNextProcess()函數)。如果時刻是當前時刻,則調用模塊的Process()處理函數,並更新下次調用時刻。需要注意,不同模塊的執行頻率不一樣,線程在本輪調用末尾的等待時間和本線程下所有模塊的最近下次調用時刻相關。
接下來線程Process()函數處理ProcessTask隊列中的異步任務,針對每個任務調用Run()函數,然後任務出隊列並銷燬。等模塊調用和任務都處理完後,則把本次時間片的剩餘時間等待完畢,然後返回。如果在等待期間其他線程向本線程Wakeup模塊或者郵遞一個任務,則線程被立即喚醒並返回,進行下一輪時間片的執行。
至此,關於WebRTC的模塊和模塊處理線程的基本實現分析完畢,下一節將對WebRTC SDK內模塊實例和模塊處理線程實例進行詳細分析。
4 WebRTC模塊處理線程實例
WebRTC關於模塊和處理線程的實現在webrtc/modules目錄下,該目錄彙集了所有派生類模塊和模塊處理線程的實現及實例分佈。本節對這些內容進行總結。
WebRTC目前創建三個ProcessThreadImpl線程實例,分別是負責處理音頻的VoiceProcessTread,負責處理視頻和音視頻同步的ModuleProcessThread,以及負責數據平滑發送的PacerThread。這三個線程和掛載在線程下的模塊如圖4所示。
VoiceProcessThread線程由Worker線程在創建VoiceEngine時創建,負責音頻端模塊的處理。掛載在該線程下的模塊如圖4所示,其中MonitorModule負責對音頻數據混音處理過程中產生的警告和錯誤進行處理,AudioDeviceModuleImpl負責對音頻設備採集和播放音頻數據時產生的警告和錯誤進行處理,ModuleRtpRtcpImpl負責音頻RTP數據包發送過程中的碼率計算、RTT更新、RTCP報文發送等內容。
ModuleProcessThread線程由Worker線程在創建VideoChannel時創建,負責視頻端模塊的處理。掛載在該線程下的模塊如圖4所示,其中CallStats負責Call對象統計數據(如RTT)的更新,CongestionController負責擁塞控制1,VideoSender負責視頻發送端統計數據的更新,VideoReceiver負責視頻接收端統計數據更新和處理狀態反饋(如請求關鍵幀),ModuleRtpRtcpImpl負責視頻RTP數據包發送過程中的碼率計算、RTT更新、RTCP報文發送等內容,OveruseFrameDetector負責視頻幀採集端過載監控,ReceiveStatisticsImpl負責由接收端統計數據觸發的碼率更新過程,ViESyncModule負責音視頻同步。
PacerThread線程由Worker線程在創建VideoChannel時創建,負責數據平滑發送。掛載在該線程下的PacedSender負責發送端數據平滑發送;RemoteEstimatorProxy派生自RemoteBitrateEstimator,負責在啓用發送端碼率估計的情況下把接收端收集到的反饋信息發送回發送端。
由以上分析可知,WebRTC創建的模塊處理線程實例基本上涵蓋了音視頻數據從採集到渲染過程中的大部分數據操作。但還有一些模塊不依賴於模塊線程工作,這部分模塊是少數,本文不展開具體的描述。
參考文獻
[1] WebRTC基於GCC的擁塞控制(上) – 算法分析
[2] WebRTC基於GCC的擁塞控制(下) - 實現分析