1. 程式人生 > 程式設計 >Intel owt-server VideoMixer設計

Intel owt-server VideoMixer設計

一、背景

在視訊會議中接入SIP客戶端時,需要在MCU伺服器裡完成視訊混屏。而owt-serverIntel開源的基於WebRTC的流媒體服務。其中的MCU實現了VideoMixer功能,對我們完成視訊混屏具有很好的參考意義。

這篇文章主要對owt-serverVideoMixerPipeline流程執行緒模型做一次梳理。

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與一個編碼器繫結;
  • 在配置InputOutput的過程中,通過設定當前功能模組(比如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個執行緒,分別是:

  1. 通過FrameSource把視訊碼流送入混屏模組的外部執行緒。

    • 這個執行緒會完成視訊碼流的解碼,解碼後的YUV儲存在SoftVideoCompositor::m_inputs中;
    • m_inputs是所有Input的幀緩衝區,但是每一個Input的緩衝區長度只有1,就是隻能緩衝一個幀,如果這個幀沒來得及混屏,後面解碼的幀會覆蓋這個幀;
  2. SoftFrameGenerator中定時做混屏(compose)的執行緒。

    • 混屏完成後,把YUV放到VCMFrameEncoder中的編碼佇列,由下一個執行緒順序完成編碼;
  3. VCMFrameEncoder中執行編碼的執行緒。

    • 編碼完成後由該執行緒返回EncodedFrame給上層。

五、對於owt-server執行緒模型的思考

上面執行緒模型中的三個執行緒

  • 第一個是外部的,完成解碼。解碼必須立即完成,不能緩衝。緩衝Buffer不能太長,否則緩衝的碼流就沒有意義了;Buffer也不能太短,太短就有被覆蓋或丟棄的風險。一旦碼流因為緩衝被部分丟棄,會導致後面的碼流解碼出問題導致花屏。所以由外部執行緒完成解碼是正確的。
  • 第二個是內部的,完成混屏。如果考慮到Pipeline工作太重,由一個執行緒完成可能會導致很大延時,那這個執行緒也有存在的必要。
  • 第三個也是內部的,完成編碼。這部分工作是否可以由第二個執行緒完成?畢竟是執行在伺服器上,太多的執行緒切換也會影響效能。

音視訊伺服器中,每一個客戶端都會對應一個endpoint的物件,完成音視訊的接收、處理或轉發。每一個endpoint給它對應的客戶端轉發的視訊都必然是不一樣的(至少沒有自己的視訊),也就意味著一個endpoint對應一個VideoMixer。而owt-serverVideoMixer至少引入了兩個執行緒完成Pipeline,這麼多執行緒是不是會影響伺服器效能