Intel owt-server VideoMixer設計
一、背景
在視訊會議中接入SIP
客戶端時,需要在MCU
伺服器裡完成視訊混屏。而owt-server
是Intel
開源的基於WebRTC
的流媒體服務。其中的MCU實現了VideoMixer
功能,對我們完成視訊混屏具有很好的參考意義。
這篇文章主要對owt-server
裡VideoMixer
的Pipeline流程和執行緒模型做一次梳理。
owt-server專案地址
Github。程式碼fork from2019/10/24
的版本,commit地址 61a95d68
二、概覽
下圖是owt-server
混屏模組的類圖,接下來會基於這個圖來展開Pipeline和執行緒模型。
簡單介紹:
這裡只畫出混屏模組類間關係,沒有畫出資料流向(資料流向參考下文的
Pipeline流程
)。整體從左向右看,分為三個區域。
- 區域一:完成視訊解碼;
- 區域二:完成視訊組合(Compose)、overlay暱稱/概覽等;
- 區域三:完成視訊編碼、完成後回撥;
三、Pipeline流程
(1)、Pipeline流程
FrameSource --> decode --> compositorIn(Buffer) --> compose/overlay --> encode(queue) --> Destination
複製程式碼
(2)、如何建立Pipeline
為方便閱讀和理解,下文出現的程式碼,除了
類名
和方法名
外,其餘可能是省略不重要環節的虛擬碼。
1. 給VideoMixer配置Input和Output
- 每一次
addInput
,都會帶上一個視訊資料來源FrameSource
;每一次addOutput
都會帶上一個視訊混屏(整個Pipeline)完成後回撥dest
; - 設定每一個
Input
時,會把Input
與一個解碼器
、解碼完成後的緩衝區
(compositorIn)繫結;設定每一個Output
時,會把Output
與一個編碼器繫結; - 在配置
Input
和Output
的過程中,通過設定當前功能模組(比如encode、compose等)完成後的下一個功能模組回撥(dest
),完成整個Pipeline順序關係
的建立;
2. FrameSource --> decode --> compositorIn
inline bool VideoFrameMixerImpl::addInput(owt_base::FrameSource* source ...)
{
...
owt_base::VideoFrameDecoder* decoder = new owt_base::FFmpegFrameDecoder();
CompositeIn* compositorIn = new CompositeIn(...);
source->addVideoDestination(decoder); // source -> decode
decoder->addVideoDestination(compositorIn); // decode -> compositorIn
...
}
複製程式碼
3. compositorIn(Buffer) --> encode(queue)
上面Pipeline流程中,compositorIn後是compose/overlay,在從緩衝區(compositorIn)在所有Input中各取一幀後完成混屏(compose)是在一個類中,所以不需要設定回撥。在完成混屏後才需要一個dest
(encode)。
inline bool VideoFrameMixerImpl::addOutput(int fps,int bitrate,owt_base::FrameDestination* dest ...)
{
...
owt_base::VideoFrameEncoder* encoder = new owt_base::VCMFrameEncoder(format,profile,m_useSimulcast);
m_compositor->addOutput(fps,bitrate,encoder); // compositorIn -> encode, encoder就是compositorIn的dest
...
}
複製程式碼
4. encode(queue) --> Destination
還是在上面一個函式:
inline bool VideoFrameMixerImpl::addOutput(int fps,m_useSimulcast);
encoder->generateStream(fps,dest); // encode -> dest
...
}
複製程式碼
在owt_base::VCMFrameEncoder::generateStream
內部,會把使用一個EncodeOut
的型別把dest
與其他一些資訊繫結。在VCMFrameEncoder
完成編碼後,通過這個EncodeOut
結構找到dest
,從而把混屏完成後的碼流拋到混屏模組外部。
四、執行緒模型
整個Pipeline涉及到3個執行緒,分別是:
-
通過FrameSource把視訊碼流送入混屏模組的外部執行緒。
- 這個執行緒會完成視訊碼流的解碼,解碼後的YUV儲存在
SoftVideoCompositor::m_inputs
中; - m_inputs是所有Input的幀緩衝區,但是每一個Input的緩衝區長度只有1,就是隻能緩衝一個幀,如果這個幀沒來得及混屏,後面解碼的幀會覆蓋這個幀;
- 這個執行緒會完成視訊碼流的解碼,解碼後的YUV儲存在
-
在SoftFrameGenerator中定時做混屏(compose)的執行緒。
- 混屏完成後,把YUV放到
VCMFrameEncoder
中的編碼佇列,由下一個執行緒順序完成編碼;
- 混屏完成後,把YUV放到
-
在VCMFrameEncoder中執行編碼的執行緒。
- 編碼完成後由該執行緒返回
EncodedFrame
給上層。
- 編碼完成後由該執行緒返回
五、對於owt-server執行緒模型的思考
上面執行緒模型中的三個執行緒:
- 第一個是外部的,完成解碼。解碼必須立即完成,不能緩衝。緩衝Buffer不能太長,否則緩衝的碼流就沒有意義了;Buffer也不能太短,太短就有被覆蓋或丟棄的風險。一旦碼流因為緩衝被部分丟棄,會導致後面的碼流解碼出問題導致花屏。所以由外部執行緒完成解碼是正確的。
- 第二個是內部的,完成混屏。如果考慮到Pipeline工作太重,由一個執行緒完成可能會導致很大延時,那這個執行緒也有存在的必要。
- 第三個也是內部的,完成編碼。這部分工作是否可以由第二個執行緒完成?畢竟是執行在伺服器上,太多的執行緒切換也會影響效能。
音視訊伺服器中,每一個客戶端都會對應一個endpoint
的物件,完成音視訊的接收、處理或轉發。每一個endpoint
給它對應的客戶端轉發的視訊都必然是不一樣的(至少沒有自己的視訊),也就意味著一個endpoint
對應一個VideoMixer
。而owt-server
的VideoMixer
至少引入了兩個執行緒
完成Pipeline,這麼多執行緒是不是會影響伺服器效能?