1. 程式人生 > >WebRTC手記之WebRtcVideoEngine2模組

WebRTC手記之WebRtcVideoEngine2模組

終於講到視訊資料的編碼傳送模組了,不容易。總體來說也看了不少時間WebRTC的原始碼了,最大的感觸就是各個模組在開發的時候非常獨立,每個模組都定義了自己的一套介面,最後串起來的時候新增各種適配物件來轉接。這給我們這些剛開始原始碼閱讀的人帶來非常大的苦惱,不過WebRTC的模組內的結構設計還是很不錯的,不然我也沒有看下去的動力。

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

  • MediaEngineInterface:抽象Media Engine的邏輯介面,負責建立用於視訊傳輸的VideoMediaChannel、用於音訊傳輸的VoiceMediaChannel、註冊音訊資料處理介面等。
  • CompositeMediaEngine:實現MediaEngineInterface介面,本身也是個模板類,兩個模板引數分別是視訊Engine和音訊Engine。其派生類WebRtcMediaEngine依賴的模板引數是WebRtcVoiceEngine和WebRtcVideoEngine,而用於Chromium的WebRtcMediaEngine2則依賴WebRtcVoiceEngine和WebRtcVideoEngine2。

WebRtcVideoEngine2主要作用在於建立視訊channel物件WebRtcVideoChannel2。結構如下:

當呼叫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的結構:

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:封裝了視訊採集/輸入邏輯(哈哈,又是一套視訊輸入邏輯),結構:

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:封裝了視訊編碼和傳輸邏輯,這塊結構比較複雜,總體如下:

ViEChannelManager維護了ViEEncoder和ViEChannel物件,ViEEncoder實現了ViEFrameCallback介面從ViECapturer物件中接收視訊幀資料,ViEEncoder對接收到的視訊幀資料進行編碼,然後將編碼後的資料傳給ViEChannel(通過兩者之間共享的PayloadRouter物件),ViEChannel將編碼後的視訊資料通過RTP/RTCP協議傳送出去。下面分別分析一下ViEEncoder和ViEChannel。

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

視訊編碼由VideoCodingModule模組統一管理,視訊幀傳入介面是通過VideoCodingModule的的AddVideoFrame方法,編碼後的視訊傳出介面是藉助VCMPacketizationCallback介面來回調。具體選取哪種視訊編碼的邏輯位於VCMCodecDataBase類,當前支援VP8編碼、VP9編碼和視訊格式到I420格式的轉換。

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

視訊資料傳送邏輯是通過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:這個類封裝了視訊渲染邏輯,結構如下:

當ViEChannel接收到網路資料解包並解碼後,就會開啟觸發渲染流程(參見FrameToRender方法),ViEChannel會呼叫向其註冊的ViEFrameCallback介面來派發視訊幀資料。ViERenderManager維護了一個ViERenderer物件來實現ViEFrameCallback介面,它將資料進一步派發,最終通過ExternalRenderer介面派發給WebRtcVideoChannel2的VideoReceiveStream物件。VideoReceiveStream通過VideoSource設定進來的VideoRenderer介面將資料派發給VideoTrack,使用者可以掛接VideoRendererInterface介面來接收視訊幀資料。真夠繞的,而且那麼多命名的相似性(比如VideoRender/VideoRenderer),感覺各模組開發期間,都實現了自己的一套介面規範,最後強行串在一起了。