天天看點

(六)WebRTC手記之WebRtcVideoEngine2子產品

終于講到視訊資料的編碼發送子產品了,不容易。總體來說也看了不少時間WebRTC的源碼了,最大的感觸就是各個子產品在開發的時候非常獨立,每個子產品都定義了自己的一套接口,最後串起來的時候添加各種适配對象來轉接。這給我們這些剛開始源碼閱讀的人帶來非常大的苦惱,不過WebRTC的子產品内的結構設計還是很不錯的,不然我也沒有看下去的動力。

注意命名,WebRtcVideoEngine2帶了個2字,不用想,這肯定是個更新版本的VideoEngine,還有個WebRtcVideoEngine類。從目前我的了解來看,WebRtcVideoEngine2比WebRtcVideoEngine改進之處在于将視訊流一分為二:發送流(WebRtcVideoSendStream)和接收流(WebRtcVideoReceiveStream),進而結構上更合理,源碼更清晰。這個部分等下會細說。在介紹WebRtcVideoEngine2之前,先簡單地分析一下WebRTC的Media Engine結構,說實話,我真不會表達Engine是個怎樣的概念,但既然這樣命名,核心子產品肯定是錯不了的。結構很簡單:

(六)WebRTC手記之WebRtcVideoEngine2子產品

MediaEngineInterface:抽象Media Engine的邏輯接口,負責建立用于視訊傳輸的VideoMediaChannel、用于音頻傳輸的VoiceMediaChannel、注冊音頻資料處理接口等。

CompositeMediaEngine:實作MediaEngineInterface接口,本身也是個模闆類,兩個模闆參數分别是視訊Engine和音頻Engine。其派生類WebRtcMediaEngine依賴的模闆參數是WebRtcVoiceEngine和WebRtcVideoEngine,而用于Chromium的WebRtcMediaEngine2則依賴WebRtcVoiceEngine和WebRtcVideoEngine2。

WebRtcVideoEngine2主要作用在于建立視訊channel對象WebRtcVideoChannel2。結構如下:

(六)WebRTC手記之WebRtcVideoEngine2子產品

當調用WebRtcVideoChannel2的AddSendStream方法時,會建立一個WebRtcVideoSendStream對象,同樣,調用AddRecvStream成員方法,會建立一個WebRtcVideoReceiveStream對象。

當外部調用WebRtcVideoChannel2的SetCapturer方法時,會轉給WebRtcVideoSendStream來響應,WebRtcVideoSendStream内部将InputFrame成員方法挂接VideoCapturer的SignalVideoFrame信号來接收視訊采集器傳輸過來的視訊幀資料。

WebRtcVideoChannel2的AddSendStream和SetCapturer的調用時機這裡暫時不考慮,這些涉及到網絡連接配接,等每個節點的内容分析完後,再探讨整個流程。

如圖所示,WebRtcVideoSendStream和WebRtcVideoReceiveStream也隻是個包裝類,内部依賴Call接口建立對應的VideoSendStream接口實作類和VideoReceiveStream接口實作類。在internal命名空間内,分别有一個Call類、VideoSendStream類、VideoReceiveStream類來實作這三個接口,Call類建立關鍵的VideoEngine對象來管理視訊資料發送過程中的一系列處理邏輯。從代碼結構上看,VideoEngine是一個相對獨立的子產品,它封裝視訊資料采集後的處理、編碼等邏輯,下面仔細分析一下VideoEngine的結構:

(六)WebRTC手記之WebRtcVideoEngine2子產品

VideoEngine子產品裡有ViEBase、ViECodec、ViECapture、ViEImageProcess、ViENetwork、ViERender、ViERTP_RTCP、ViEExternalCodec接口,注意,這些都是功能性的接口,它們相應的實作分别對應于上圖中的XXXImpl類,VideoEngineImpl類從所有的XXXImpl接口派生,是以外部有了VideoEngine接口,都可以通過強轉的方式擷取ViEBase、ViECapture等之類的接口(根據VideoEngine強轉成相應的接口的邏輯封裝在目标接口的GetInterface靜态方法中),外界可以通過這些接口來完成視訊資料做相應的設定,而這些設定最終都反映到一個名叫ViESharedData的類對象裡。該對象由ViEBaseImpl建立并在各接口的實作之間共享,XXXImpl可以通過ViEBaseImpl的shared_data方法來通路,用于共享的資料有三類:ViEInputManager、ViEChannelManager和ViERenderManager。下面分别介紹一下這關鍵的三個對象。

ViEInputManager:封裝了視訊采集/輸入邏輯(哈哈,又是一套視訊輸入邏輯),結構:

(六)WebRTC手記之WebRtcVideoEngine2子產品

ViEInputManager為每個通道配置設定一個ViECapturer對象來做為視訊源,ViECapturer可以傳入也可以自己建立一個VideoCaptureModule視訊采集子產品,并通過VideoCaptureDataCallback接口從其接收資料,也可以直接通過ViEExternalCapture接口接收從外部直接傳入的視訊幀資料(調用ViEExternalCapture接口的IncomingFrame方法)。VideoSendStream就是通過ViEInputManager建立一個ViEExternalCapture對象來傳入外界傳來的視訊幀資料(通過WebRtcVideoSendStream的InputFrame傳來)。這裡要注意,ViEInputManager為建立的ViECapturer對象配置設定一個capture_id,外界可以通過這個capture_id來操作其對應的ViECapturer。視訊源傳入邏輯已經明了,接下來分析一下視訊是怎麼傳出去的。無論通過哪種視訊資料接收方法,ViECapturer都不會立即将資料傳遞出去,因為它内部需要對這些視訊資料做相關的處理。資料處理必然耗時,如果采用同步的方式,必将阻塞視訊傳入的流程。是以,在建立ViECapturer的時候,會啟動一采集線程,該線程調用ViECaptureProcess處理函數,在該處理函數裡,先調用VideoProcessingModule對視訊資料進行處理(燈光加亮、去閃爍),如果在ViEImageProcessImpl裡注冊了ViEEffectFilter處理對象,這裡也會調用該對象來處理視訊幀資料,最後通過DeliverFrame方法分發到注冊進來的所有ViEFrameCallback接口。

ViEChannelManager:封裝了視訊編碼和傳輸邏輯,這塊結構比較複雜,總體如下:

(六)WebRTC手記之WebRtcVideoEngine2子產品

ViEChannelManager維護了ViEEncoder和ViEChannel對象,ViEEncoder實作了ViEFrameCallback接口從ViECapturer對象中接收視訊幀資料,ViEEncoder對接收到的視訊幀資料進行編碼,然後将編碼後的資料傳給ViEChannel(通過兩者之間共享的PayloadRouter對象),ViEChannel将編碼後的視訊資料通過RTP/RTCP協定發送出去。下面分别分析一下ViEEncoder和ViEChannel。

    1) ViEEncoder類:封裝了視訊編碼流程。

(六)WebRTC手記之WebRtcVideoEngine2子產品

視訊編碼由VideoCodingModule子產品統一管理,視訊幀傳入接口是通過VideoCodingModule的的AddVideoFrame方法,編碼後的視訊傳出接口是借助VCMPacketizationCallback接口來回調。具體選取哪種視訊編碼的邏輯位于VCMCodecDataBase類,目前支援VP8編碼、VP9編碼和視訊格式到I420格式的轉換。

2)ViEChannel類:封裝了編碼後的視訊資料發送邏輯和視訊資料接收解碼邏輯。

(六)WebRTC手記之WebRtcVideoEngine2子產品

視訊資料發送邏輯是通過PayloadRouter對象委托給RtpRtcp子產品做RTP協定的封裝,具體的網絡發送操作還是回托給ViESender做資料的網絡發送操作。ViESender的邏輯相對簡單,限于篇幅,圖中無法做詳細的标注。ViESender的發送操作依賴外部設定給它的Transport接口(通過VideoEngine子產品的ViENetwork接口來完成設定)。

當WebRtcVideoChannel2接收到網路資料包後(通過OnPacketReceived或OnRtcpReceived方法響應),會在VideoReceiveStream對象中通過VideoEngine子產品暴露出去的ViENetwork接口來響應資料包處理,最終觸發到ViEChannel的ReceivedRTPPacket或ReceivedRTCPPacket方法。ViEChannel中将接收并解碼網絡視訊資料的任務配置設定給ViEReceiver對象。ViEReceiver先調用RTP/RTCP子產品做協定的解析(圖中限于篇幅未标注出來),解析完成後調用VideoCodingModule子產品進行資料的解碼操作(參見ViEReceiver的OnReceivedPayloadData方法),VideoCodingModule子產品内部維護了一個與VideoSender對應的VideoReceiver來完成解碼邏輯,這塊與VideoSender的編碼邏輯完全對稱,這裡不再表述。

ViERenderManager:這個類封裝了視訊渲染邏輯,結構如下:

(六)WebRTC手記之WebRtcVideoEngine2子產品

當ViEChannel接收到網絡資料解包并解碼後,就會開啟觸發渲染流程(參見FrameToRender方法),ViEChannel會調用向其注冊的ViEFrameCallback接口來派發視訊幀資料。ViERenderManager維護了一個ViERenderer對象來實作ViEFrameCallback接口,它将資料進一步派發,最終通過ExternalRenderer接口派發給WebRtcVideoChannel2的VideoReceiveStream對象。VideoReceiveStream通過VideoSource設定進來的VideoRenderer接口将資料派發給VideoTrack,使用者可以挂接VideoRendererInterface接口來接收視訊幀資料。真夠繞的,而且那麼多命名的相似性(比如VideoRender/VideoRenderer),感覺各子產品開發期間,都實作了自己的一套接口規範,最後強行串在一起了。