本次分享的主題是WebRTC視頻流數(shù)據(jù)流程分析,主要內(nèi)容可以分為以下幾個(gè)部分:
WebRTC 代碼庫(kù)簡(jiǎn)介
分析方法
視頻流程介紹
實(shí)戰(zhàn):客戶端視頻錄制
01 PART WebRTC 代碼庫(kù)簡(jiǎn)介 1.1 WebRTC簡(jiǎn)單介紹
關(guān)于什么是WebRTC,如何用一兩句話簡(jiǎn)單說(shuō)明? WebRTC是一個(gè)Web端 RTC的互聯(lián)網(wǎng)標(biāo)準(zhǔn),同時(shí)我們也會(huì)用WebRTC來(lái)指代一個(gè)開(kāi)源項(xiàng)目,是目前完成度最高、最流行的RTC框架,是由Google開(kāi)源的項(xiàng)目。 1.2 WebRTC版本說(shuō)明
上圖展示的是Chromium項(xiàng)目網(wǎng)站上公開(kāi)的版本發(fā)布規(guī)劃表,圖中Milestone 81是Milestone的編號(hào),簡(jiǎn)寫為M81。表中其它列如Chromium、Skia以及WebRTC等都是其對(duì)應(yīng)的版本分支,例如Skia有同名的m81分支,之前WebRTC也是有同名的m75、m76分支,只不過(guò)后來(lái)改變了分支的命名方式。
在《WebRTC Native開(kāi)發(fā)實(shí)戰(zhàn)》中有提到過(guò)書中對(duì)于WebRTC的代碼分析是基于某次提交版本,例如“#30432提交”。如圖所示為WebRTC的Git提交記錄圖,藍(lán)色箭頭所指位置:Cr-Commit-Position:refs/head/master@{#30432},“#30432”的含義是指該項(xiàng)目從開(kāi)始到現(xiàn)在第幾次提交。WebRTC的代碼庫(kù)有一個(gè)特點(diǎn),其主干分支是一條直線,無(wú)其它分支(當(dāng)然在發(fā)布新版本時(shí),會(huì)開(kāi)出發(fā)布分支,可能會(huì)同步一些需要帶上去的提交,但是這個(gè)分支上的提交都不會(huì)再合并回來(lái)),這也就使得WebRTC的版本歷史非常清晰,開(kāi)發(fā)者在查詢提交記錄或變更歷史時(shí)會(huì)非常簡(jiǎn)單方便。
上圖是我在之前參與過(guò)的一個(gè)項(xiàng)目中截取的,是在Sourcetree中截取的。我們可以看到其分支非常復(fù)雜,但其實(shí)這是遵循GitFlow的版本控制模式運(yùn)行所導(dǎo)致的結(jié)果,Gitflow被很多人詬病的地方就是提交的版本記錄分支結(jié)構(gòu)非常復(fù)雜,難以追溯歷史。 1.3 WebRTC代碼目錄
圖為MacOS中WebRTC iOS的代碼庫(kù)目錄 首先是“api”,主要是C++代碼的公開(kāi)api,開(kāi)發(fā)者在使用C++開(kāi)發(fā)時(shí)就會(huì)用到其預(yù)先定義的接口程序,例如pPear_cConnection類。同時(shí),在安卓或iOS上使用Java或者ObjectiveC接口的話,其實(shí)也都是對(duì)C++接口的bouninding。 “call、pc、media”:這三個(gè)目錄在我理解,是WebRTC主要流程和業(yè)務(wù)邏輯的實(shí)現(xiàn)代碼。 “audio、common_aduio、video、common_video”:這四個(gè)目錄主要是音頻和視頻類相關(guān)的代碼。 “modules”:很多公司可能不會(huì)直接使用整個(gè)WebRTC的代碼庫(kù),而只是使用其中的一些常用模塊,這些模塊大都包含在“modules”中,例如回聲抑制、噪音抑制等處理,視頻編碼、Jitterbuffer等。 “p2p”:與p2p連接相關(guān)的代碼。 “sdk”:Android和iOS平臺(tái)相關(guān)的代碼,如視頻采集、預(yù)覽、渲染、編解碼等需要調(diào)用系統(tǒng)接口的代碼,對(duì)C++接口的bouninding。 “rtc_base”:Chromium項(xiàng)目中一些公用的基礎(chǔ)代碼,例如線程、鎖相關(guān)代碼。 “third_party”:包含許多Google的其它開(kāi)源項(xiàng)目以及非Google開(kāi)源的項(xiàng)目,被WebRTC用到的都放在third_party中,例如FFmpeg、libvpx等。 “system_wrappers”:包含另一個(gè)系統(tǒng)相關(guān)的代碼的目錄,如sleep函數(shù)。SDK主要涉及的是Androic和iOS平臺(tái)相關(guān)的代碼,system_wrappers則包含更多平臺(tái)如windows等相關(guān)的代碼。 “stats 、logging”:狀態(tài)統(tǒng)計(jì),日志打印相關(guān)的代碼。 “examples”:包含有各個(gè)平臺(tái)的demo,例如Android、iOS、Windows、Linux、MacOS等。 目前就我的學(xué)習(xí)和了解,還沒(méi)有觸及其它的一些目錄,不過(guò)它們應(yīng)該也不是主干流程相關(guān)的內(nèi)容。
本次分享的第一部分我們以《WebRTC Native開(kāi)發(fā)實(shí)戰(zhàn)》書中第一部分的標(biāo)題Hello WebRTC來(lái)做一個(gè)結(jié)尾。 第一章:開(kāi)發(fā)環(huán)境的搭建:書中有非常詳細(xì)的一步步的教程,只要解決了科學(xué)上網(wǎng)的問(wèn)題,按照教程基本上應(yīng)該不會(huì)再遇到其它問(wèn)題。 第二章:運(yùn)行官方Demo:主要是剛才提到的examples目錄中的各種Demo。 第三章:基本流程分析:這里的基本流程與我們此次分享的內(nèi)容有些區(qū)別,這里的基本流程更多的是如何使用WebRTC的接口,實(shí)現(xiàn)簡(jiǎn)單的1V1的音視頻通話,也就是Demo實(shí)現(xiàn)的一個(gè)功能。 02 PART 分析方法-如何上手大型項(xiàng)目
對(duì)于個(gè)人來(lái)說(shuō)如何盡快上手大型項(xiàng)目?例如WebRTC或者其它的開(kāi)源項(xiàng)目像FFmpeg、GStreamer等。包括大家入職新公司,很可能會(huì)接手或參與到較大的項(xiàng)目中,雖然可能不都會(huì)像WebRTC那么巨大,但還是存在一定的挑戰(zhàn)性。在這里分享一些我的經(jīng)驗(yàn),希望能為大家提供些幫助。 首先,第一步就是“跑起來(lái)”,只有把相關(guān)項(xiàng)目的demo運(yùn)行起來(lái),以此才能對(duì)項(xiàng)目有更加直觀的了解,了解其相關(guān)功能,以功能實(shí)現(xiàn)的位置作為切入點(diǎn),思考其實(shí)現(xiàn)方式、方法。 第二步,“從外部的API入手,順藤摸瓜”。例如下圖是iOS的代碼,首先找到外部的API,如代碼中RTCCameraVideoCapture是用來(lái)實(shí)現(xiàn)相機(jī)采集的,然后就可以看類中是如何調(diào)用接口和處理數(shù)據(jù)的。
第三步“基于基礎(chǔ)知識(shí)(音頻采播系統(tǒng)接口),搜索定位關(guān)鍵函數(shù)/類”,第二步例如在Android或者iOS下我們是先找到實(shí)現(xiàn)相應(yīng)功能所需要調(diào)用的外部接口,可以根據(jù)這些關(guān)鍵的接口在代碼中進(jìn)行搜索發(fā)現(xiàn)關(guān)鍵的函數(shù)和類。但不是所有的邏輯都會(huì)有外部Web接口,例如WebRTC中音頻相關(guān)的實(shí)現(xiàn)就是不需要調(diào)用任何接口的。下圖是一個(gè)iOS的例子,對(duì)于音頻播放最關(guān)鍵的函數(shù)是AudioOutputUnitStart,即開(kāi)啟一個(gè)Audio Unit。我們?cè)谒阉骱罂梢哉业絭oice_processing_audio_unit.m文件,其中包含的一個(gè)Start函數(shù),我們就可以進(jìn)一步觀察函數(shù)以及頭文件有哪些接口,例如初始化start、stop等,音頻就可以從這里進(jìn)行外擴(kuò)或閱讀源碼。
第四步“靜態(tài)閱讀源碼+單步調(diào)試”。靜態(tài)閱讀源碼主要是利用IDE的代碼跳轉(zhuǎn),但是gn其生成exXcode工程文件的方式有一些特殊,很多代碼跳轉(zhuǎn)會(huì)跳不過(guò)去或者跳轉(zhuǎn)到錯(cuò)誤地方。所以更多的時(shí)候我還是使用全局搜代碼,盡管效率稍低,但目前沒(méi)有其它更合適的辦法。單步調(diào)試,在代碼中的某些位置,我們希望了解其下一步是如何跳轉(zhuǎn)的,而代碼無(wú)法直接跳轉(zhuǎn),搜索的結(jié)果也并不知道是什么作用無(wú)法準(zhǔn)確判斷,這時(shí)我們可以通過(guò)加斷點(diǎn)進(jìn)行驗(yàn)證。
如圖所示,是視頻編碼相關(guān)的一個(gè)類的函數(shù),在加入斷點(diǎn)后,我們可以觀察到視頻數(shù)據(jù)是如何從系統(tǒng)的回調(diào)接口到采集RTCCameraVideoCapture的類再一步步到編碼的類,非常清晰。 在軟件開(kāi)發(fā)中,沒(méi)有銀彈,都是那些看似樸實(shí)無(wú)華但往往非常有效的辦法,掌握這些方法后,再上手一些新的項(xiàng)目就會(huì)有一些幫助。 03 PART 視頻流程介紹
WebRTC的視頻數(shù)據(jù)流程在各個(gè)平臺(tái)基本上都是一致的。 視頻數(shù)據(jù)首先由VideoCapturer采集,然后交給VideoSource,通過(guò)其中的VideoBroadcaster傳輸給接收對(duì)象,例如Encoder、Preview等。Preview負(fù)責(zé)進(jìn)行本地預(yù)覽,Encoder負(fù)責(zé)編碼發(fā)送。從網(wǎng)絡(luò)層接收到數(shù)據(jù)之后,首先會(huì)通過(guò)VideoDecoder進(jìn)行解碼,接下來(lái)同樣會(huì)將其傳輸VideoBroadcaster,再分發(fā)給數(shù)據(jù)的接收方。VideoSource在上圖中未體現(xiàn),但也是一個(gè)比較關(guān)鍵的類,它位于VideoTrack和VideoBroadcaster中間,其實(shí)是對(duì)VideoBroadcaster接口的封裝。 VideoTrack是WebRTC中比較重要的一個(gè)概念,音頻、視頻等媒體從概念上來(lái)說(shuō)其實(shí)就是一個(gè)Track,我們通常會(huì)添加或從遠(yuǎn)端接收一個(gè)Track。另外,IOS的流程與上圖中流程有一些區(qū)別,其視頻預(yù)覽不是從VideoBroadcaster接收每一幀的數(shù)據(jù)然后進(jìn)行渲染,而是其系統(tǒng)存在接口可以將采集和預(yù)覽兩個(gè)系統(tǒng)類關(guān)聯(lián)并自動(dòng)實(shí)現(xiàn)渲染。但其實(shí)我們也可以像RemoteRenderer類一樣,獲取到一幀個(gè)數(shù)據(jù)后再進(jìn)行渲染,用RemoteRenderer類添加到采集端的VideoBroadcaster中進(jìn)行渲染。 在非iOS的平臺(tái)上,本地預(yù)覽以及遠(yuǎn)端視頻的渲染其實(shí)都是通過(guò)一個(gè)類來(lái)實(shí)現(xiàn)的。
完整視頻數(shù)據(jù)流程(調(diào)用棧) 圖中詳細(xì)的列出了視頻數(shù)據(jù)的整體采集、處理、傳輸相關(guān)步驟。簡(jiǎn)單來(lái)看,就是從上到下到最底部網(wǎng)絡(luò)層,再由下到上最終到渲染的整體流程。所有平臺(tái)的視頻數(shù)據(jù)流程基本上都是大同小異的,區(qū)別只在于采集、編解碼和渲染的實(shí)現(xiàn)不同,其余的流程基本是一致的。 采集:
首先RTCCameraVideoCapture會(huì)從系統(tǒng)數(shù)據(jù)回調(diào),接收到實(shí)際的視頻數(shù)據(jù),交給VideoSource通過(guò)_nativeVideoSource將數(shù)據(jù)傳遞到C++這一層,最后提交AdaptedVideoTrackSource進(jìn)行一些如旋轉(zhuǎn)、裁剪之類的操作。 編碼:
視頻數(shù)據(jù)經(jīng)過(guò)AdaptedVideoTrackSource層之后,就可以通過(guò)broadcaster_進(jìn)行分發(fā)。在安卓或者linux中可能會(huì)有多個(gè)分支,一個(gè)預(yù)覽一個(gè)編碼,這里我們以編碼為主干進(jìn)行分析。Sink實(shí)際上就是數(shù)據(jù)的消費(fèi)者,通過(guò)VideoStreamEncoder來(lái)實(shí)現(xiàn)編碼,但其只是概念上的編碼,最終實(shí)際編碼還是調(diào)用系統(tǒng)相關(guān)的類,因此最終會(huì)回到ObjectiveC層,通過(guò)一些調(diào)用到達(dá)RTCVideoEncoderH.264,再調(diào)用VideotoolbBox接口,實(shí)現(xiàn)H.264的硬件編碼。編碼完成之后會(huì)實(shí)現(xiàn)系統(tǒng)的回調(diào),再將編碼后的數(shù)據(jù)交回給C++層,即VideoStreamEncoder的OnEncodedImage回調(diào)函數(shù)中,表示一幀視頻數(shù)據(jù)已經(jīng)完成編碼。 發(fā)送:
VideoSendStream表示要發(fā)送的視頻流,通過(guò)rtp_vVideo_sSender_進(jìn)行RTP打包處理,再接下來(lái)就是需要進(jìn)行的RTP封包和網(wǎng)絡(luò)傳輸。假設(shè)通過(guò)網(wǎng)絡(luò)傳輸數(shù)據(jù)已經(jīng)到達(dá)RtpVideoStreamReceiver,我們可以看到左右兩邊的sender和receiver在類以及函數(shù)的命名上會(huì)有一些對(duì)稱的地方。RtpVideoStreamReceiver接收到RTP,并且已經(jīng)完成解包以及其它的網(wǎng)絡(luò)亂序、錯(cuò)誤重傳等處理,獲得一幀完整可解碼的幀,然后就會(huì)調(diào)用解碼回調(diào),送到VideoReceiveStream中進(jìn)行解碼操作,在這里會(huì)調(diào)用vVideo_rReceiver_的Decode函數(shù)。 解碼:
vVideo_rReceiver_的Decode函數(shù)其實(shí)也是概念上的解碼,叫VCMGenericDecoder,最終也會(huì)調(diào)到平臺(tái)相關(guān)的ObjectiveC實(shí)現(xiàn)的視頻硬解,即RTCVideoDecoderH.264,也是調(diào)用VideoToolBox進(jìn)行解碼,解碼后通過(guò)DecodedFrameCallback交還給C++這一層。
解碼后的數(shù)據(jù)最終還是到達(dá)了VideoStreamdDecoder,交給了incoming_vVideo_sStream_。這里會(huì)存在一個(gè)視頻幀的隊(duì)列,解碼和編碼不太一樣,編碼是采集到一幀視頻幀,編碼完成后立刻發(fā)送,但解碼完成后卻不會(huì)立刻進(jìn)行渲染,而是需要一定的緩沖,以避免由于抖動(dòng)而導(dǎo)致卡頓。所以視頻數(shù)據(jù)解碼完成后會(huì)首先放入隊(duì)列中,等待渲染模塊控制節(jié)奏,需要時(shí)再獲取數(shù)據(jù)。
渲染: 獲取到視頻數(shù)據(jù)后,會(huì)通過(guò)Broadcaster將數(shù)據(jù)交給sink,sink在iOS上具體是通過(guò)RTCMTLVideoView對(duì)數(shù)據(jù)進(jìn)行渲染,MTL是調(diào)用iOS的Metal接口進(jìn)行視頻渲染。 其實(shí)圖中只是視頻流程中調(diào)用棧的總結(jié),書中有一章節(jié)的內(nèi)容總結(jié)了視頻數(shù)據(jù)流程的更多示例代碼的分析以及講解。 04 PART 實(shí)戰(zhàn):客戶端視頻錄制
首先要明確需求:1. 推流和收流都需要,即發(fā)送的數(shù)據(jù)需要錄制成文件并且接收到的內(nèi)容也要錄制成文件;2. 其次是不希望做額外的編碼,因?yàn)橥ǔ=邮栈蛘甙l(fā)送的視頻都是已經(jīng)處理(編碼)好的,額外的編碼會(huì)造成資源浪費(fèi)。3.在不需要額外編碼的情況下,我們只需要調(diào)用FFmpeg把編碼后的數(shù)據(jù)存儲(chǔ)到文件內(nèi)即可。4.我們應(yīng)該從哪里拿數(shù)據(jù)?
要回答從哪里拿數(shù)據(jù)這個(gè)問(wèn)題,首先需要對(duì)視頻數(shù)據(jù)流程有一定了解,也就是前面第三部分所介紹的內(nèi)容。如上圖紅框所示,VideoSendStreamlmpl::OnEncodedimage中已經(jīng)接收了編碼后的視頻數(shù)據(jù),但其數(shù)據(jù)存在形式還是完整一幀,并沒(méi)有拆分成一個(gè)一個(gè)的RTP包。接收端情況比較復(fù)雜,在網(wǎng)絡(luò)傳輸時(shí)會(huì)出現(xiàn)亂序到達(dá)、丟包缺失等問(wèn)題,造成網(wǎng)絡(luò)數(shù)據(jù)的不可用。因此,我們需要找到一個(gè)已經(jīng)對(duì)上述問(wèn)題進(jìn)行過(guò)處理的數(shù)據(jù)點(diǎn),即解碼之前的數(shù)據(jù)點(diǎn),VidioReceiveStream的HandleEncodedFrame函數(shù)中。 當(dāng)我們找到數(shù)據(jù)接入點(diǎn)后,需要進(jìn)行的操作就是修改代碼,增加API,實(shí)現(xiàn)相關(guān)功能。如在Android和iOS上希望有Java或Object C的接口暴露出來(lái)供APP層調(diào)用。想要修改iOS接口,就需要修改SDK目錄下的代碼。
舉例如圖所示 ,我們需要修改RTCPeerConnection文件,其中所定義為WebRTC的主類。增加Start/StopRecoder的接口,通過(guò)dir的參數(shù)表明想要錄制視頻的方向(發(fā)送或者接收)。
SDK僅為C++接口的boinunding,因此還需要修改API目錄里面的C++接口,即修改peer_connection_interface.h,為C++的PC類增加接口。
API里面只是程序接口,我們需要修改程序的實(shí)現(xiàn)類,實(shí)現(xiàn)類主要在pc中,但這里有一點(diǎn)特殊的是,業(yè)務(wù)流程和實(shí)現(xiàn)邏輯,call中也是很重要的一部分。 如圖所示,它是對(duì)api/peer_connection_interface的一個(gè)子類,一個(gè)具體集成的實(shí)現(xiàn)類,我們?yōu)槠湓黾咏涌?,但是在這里我們不在peer_connection_interface的類中調(diào)用錄制相關(guān)的代碼,而是在call里進(jìn)行修改。
前面我們介紹到的VideoSendStream和VideoReceiveStream以及本次沒(méi)有介紹到的VAideo相關(guān)的類,其實(shí)例的管理都在call對(duì)象里。在我理解,在以前WebRTC的概念模型中,主類其實(shí)是一個(gè)Call,而pPeercConnection是在后續(xù)標(biāo)準(zhǔn)化過(guò)程中所定義的接口。所以實(shí)際的視頻錄制調(diào)用功能被封裝成一個(gè)Recorder類,Recorder類的管理都會(huì)放在call里面,修改pc peer connection以及call的頭文件。 完成上述操作后,下一步就是截取數(shù)據(jù)。截取數(shù)據(jù)的操作其實(shí)就是VideoSendStream和VideoReciveStream的函數(shù)調(diào)用,Recorder的對(duì)象在call里面,兩種Stream的對(duì)象也在call里面,那我們就只需要將Recorder設(shè)置給Stream,注入進(jìn)去即可。
從call到VideoSendStream有如上圖所示的文件需要修改,Call里面有一個(gè)Stream接口的定義,然后在video目錄下會(huì)有call里面定義的Stream接口的子類、實(shí)現(xiàn)類,video_send_stream_impl,在OndecodedImage中,將完整的一幀給recorder,再調(diào)用FFmpeg的頭文件接口即可。
數(shù)據(jù)收留端和發(fā)送端情況類似,ReceiveStream和SendStream在功能上非常對(duì)稱,在call目錄下也有一個(gè)接口定義,在video目錄下也有一個(gè)接口的實(shí)現(xiàn)。 錄制相關(guān)完整的代碼在github上有一個(gè)完整的提交,大家可以作為參考。 (https://github.com/HackWebRTC/webrtc/commit/dfbcd2c75d27dafd24512d6ca3d24c6d86d63b82) 。
-
WebRTC
+關(guān)注
關(guān)注
0文章
56瀏覽量
11203
原文標(biāo)題:WebRTC視頻數(shù)據(jù)流程分析
文章出處:【微信號(hào):livevideostack,微信公眾號(hào):LiveVideoStack】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論