開源直播工具OBS研究
專案簡介
OBS - Free and open source software for live streaming and screen recording(OBS是一款開源的用於錄屏直播的工具軟體)。
舊版的OBS只能支援Windows,目前已經停止開發。作者為了支援Windows/Mac/Linux重寫了整個軟體,專案地址為obs-studio in Github。
新版的OBS的目標有以下幾點:
- Make it multiplatform.(跨平臺支援)
- Separate the application from the core, allowing custom application of the core if desired, and easier extending of the user interface.(模組化、易擴充套件
- Simplify complex systems to not only make it easier to use, but easier to maintain.(簡化系統,使其易用易維護)
- Make a better/cleaner code base, use better coding standards, use standard libraries where possible (not just STL and C standard library, but also things like ffmpeg as well), and improve maintainability of the project as a whole.(儘量利用其他開源軟體成果
- Implement a new API-independent shader/effect system allowing better and easier shaders usage and customization without having to duplicate shader code.(實現獨立於API的shader/effect系統)
- Better device support. (更好的支援有錄屏需求的裝置)
OBS專案的語言分佈:
- C: 57.6%
- C++: 36.3%
- Objective-C/Objective-C++: 4%
- others: 3%
OBS程式碼主要包含這些部分:
- libobs: 核心程式碼,定義專案框架以及核心API,主要用C語言編寫。
- UI: 介面程式碼,採用C++的QT框架,開發出適用三大平臺的介面。
- plugins: 外掛程式碼,可獨立編譯成dll(windows平臺)或so(*nix平臺),包含Source(錄屏輸入源)、Output、Service(各種流播服務)等全部被定義為外掛。
- libobs-d3d11: 基於D3D的圖形子系統,主要用在Windows系統。
- libobs-opengl: 基於opengl的圖形子系統,主要用在*uix系統。
OBS軟體功能概述
OBS專案工程中以場景組的方式呈現給使用者,可以自由設定場景、輸入源、效果處理,配置直播服務。
OBS專案工程結構
OBS專案中一個工程結構如下
一個場景組包含多個場景,OBS直播的時候是把整個場景流播給使用者,那為什麼需要多個場景?因為播主在直播時有快速切換場景的需要,所以播主需要在直播前編輯好多個場景(比如純遊戲場景;遊戲+頭像;解說;休息場景等),然後直播的過程中可以根據不同的需要快速切換。
OBS場景的轉場
OBS中的轉場,是場景切換時的動畫效果,目前支援 Fade和Switch等多種效果。
OBS輸入源的種類
一個場景可以包含多個輸入源,一個直播工具可以支援的輸入源種類反應了其強大性。OBS支援 輸入源種類如下
OBS輸入源的效果設定
針對每個輸入源可以增加各種濾鏡效果,以下列出我覺得最實用的幾種:
- 音訊效果:
- Video Delay: 設定延遲時間,用來處理音視訊不同步的的場景。
- Noise Suppression: 噪音抑制
- Gain: 音訊增益
- Noise Gate: 噪聲門,把小噪音去掉
- 視訊效果:
- Crop: 就是最實用的Crop,不過OBS裡不能用滑鼠拖拽來控制Crop區域,略顯不便
- Chroma Key: 如果有綠幕背景,可以用來去背景,在攝像頭的輸入源中最常用。
- Image Mask: 打水印
- Scroll: 滾動效果,在一些瀏覽器的輸入源上最實用。
OBS工作室模式
左邊是預覽介面,可以進行編輯。右邊是正在直播的介面。中間是把預覽介面切到直播介面的各種轉場效果。
一般比較專業的直播都是使用這個模式,可以在預覽介面編輯好畫面之後再推送到直播畫面。
OBS外掛系統
OBS專案中把除了核心框架以及渲染系統之外的 部件全部抽象成了Module,一個或多個Module最後封裝到外掛中(以dll或so的形式),只要把外掛放入特定的目錄即可被主程式使用。
Mac版OBS的外掛目錄在/Applications/OBS.app/Contents/Resources/obs-plugins,其中
- mac-avcapture.so 對應Mac的視訊捕獲裝置
- mac-capture.so 對應螢幕捕獲 和 視窗捕獲
- mac-syphon.so 對應注入捕獲遊戲畫面
OBS外掛定義
一個典型的OBS外掛程式碼包含三個部分:
- 外掛定義 -> plugin-main.c
- 編譯打包 -> CMakeList.txt
- 內部實現程式碼 -> XX.c/YY.c …
下面以mac-capture.so外掛為例來看看它的外掛定義程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <obs-module.h> OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("mac-capture", "en-US") //多語言支援 extern struct obs_source_info coreaudio_input_capture_info; //輸入源1 extern struct obs_source_info coreaudio_output_capture_info;//輸入源2 extern struct obs_source_info display_capture_info;//輸入源3 extern struct obs_source_info window_capture_info;//輸入源4 bool obs_module_load(void) //註冊支援的輸入源 { obs_register_source(&coreaudio_input_capture_info); obs_register_source(&coreaudio_output_capture_info); obs_register_source(&display_capture_info); obs_register_source(&window_capture_info); return true; } |
可以看出這個外掛定義了四個輸入源,這裡你可能有個疑問,為什麼不是一個外掛 對應 一個輸入源,因為功能相近的輸入源整合到一個外掛裡可以減少冗餘程式碼。
所以OBS中外掛可以定義為 包含 一個 或 多個 輸入(或 輸出/編碼/服務)模組的動態庫程式碼。
任意一個開源庫比如FFmpeg,經過OBS統一的介面定義封裝即可編譯成OBS的一個外掛為OBS系統所用。
OBS外掛載入流程
外掛系統大體都有一個類似的套路,OBS的也不例外。簡單來說就是定義外掛存放在特定目錄,在程式啟動時,動態載入所有的外掛(儲存為物件或一系列函式指標),儲存在字典 或者 連結串列這樣的資料結構裡。
下面來詳細分析一下OBS中外掛載入流程:
然後以mac-capture.so中的display_capture_info,來看看它的結構定義,可以看出它主要定義了id、type、name 以及一些介面API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | struct obs_source_info display_capture_info = { .id = "display_capture", .type = OBS_SOURCE_TYPE_INPUT, .get_name = display_capture_getname, .create = display_capture_create, .destroy = display_capture_destroy, .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_DO_NOT_DUPLICATE, .video_tick = display_capture_video_tick, .video_render = display_capture_video_render, .get_width = display_capture_getwidth, .get_height = display_capture_getheight, .get_defaults = display_capture_defaults, .get_properties = display_capture_properties, .update = display_capture_update, }; |
其中需要注意一下get_properties這個介面,這個介面是幹啥用的?顧名思義是獲取模組的屬性資料,按我的理解 UI層可以利用這個屬性資料來構建這個模組對應的介面,並設定這個模組的屬性引數。
OBS視訊處理流程
視訊渲染輸出流程
OBS視訊渲染和輸出是系統的核心流程,我們以Mac桌面錄製輸入,以及ffmpeg輸出為例來分析一下整個流程(多路輸入 和 多路輸出道理也是類似的), 圖中為了簡單起見忽略了輸出編碼流程僅包含非編碼流程。
可以看出OBS建立了兩個執行緒,一個用於顯示渲染,另一個用於編碼輸出。
渲染部分最終呼叫的是 輸入模組裡的渲染程式碼,而編碼輸出部分最終也是呼叫 輸出模組的程式碼。
另外在渲染執行緒中 也負責把圖形系統的資料 拷貝到 輸出資料的快取中,以便於輸出執行緒進行處理。
視訊輸出資料結構分析
OBS的核心資料結構定義在libobs/obs-internal.h中 主體結構為obs_core如下圖所示(僅保留的主要的資料結構)
右下方的video(結構為video_output)用在輸出模組的raw_video介面進行處理,把 輸出資料中的cache轉成實際的輸出,以下是video_output詳細資料結構:
OBS音訊處理流程
OBS音訊處理是在一個執行緒中完成了先渲染後輸出的過程。而視訊處理則是 分別開了渲染執行緒 和 輸出執行緒。
具體流程如下, 在輸出函式中在判斷是否需要編碼,再呼叫對應的非編碼流程 或 編碼流程:
OBS圖形系統架構
OBS的圖形系統主要負責 場景的渲染、場景的切換、以及各種輸入源的音視訊效果的處理,屬於OBS的核心之一。
通過使用軟體以及視訊渲染流程的分析 得到OBS圖形系統的大體的邏輯關係。
針對圖形系統主要分析以下三個問題:
- 多濾鏡疊加的渲染處理。
- 濾鏡和轉場效果的實現與整合。
- 圖形API的封裝。
單個場景的渲染流程
場景(Scene)也被封裝成輸入源(Source)的一種,所以UI層只要把當前的場景取出來,呼叫它的obs_source_video_render即可。
在場景內部會渲染其包含的renderitem(也就是實際的輸入源),比如前一個圖所展示的遊戲錄製、桌面錄製輸入源等。
多個濾鏡疊加的輸入源渲染流程
這部分分析了很長時間一直沒看懂,主要有兩個原因:
- 之前不熟悉OpenGl的渲染流程,所以搞不懂濾鏡的渲染流程。
- 這部分的邏輯比較繞,沒分析出多個濾鏡是怎麼疊加渲染的。
前段時間花了點時間好好學習了一下OpenGl(僅僅學習和音視訊處理相關的章節),寫了一些demo。
現在再來分析這部分相對輕鬆一些,簡述一下:當渲染帶濾鏡的輸入源時,會先渲染它最後一個濾鏡,然後在這個濾鏡的渲染程式碼又會呼叫渲染前一個濾鏡,最後呼叫第一個濾鏡的渲染程式碼。
在第一個濾鏡的渲染程式碼裡 直接渲染 呼叫輸入源的渲染流程,然後生成texture。
每個濾鏡都在前一個濾鏡渲染生成的texture的基礎渲染生成新的texture。
流程圖如下:
濾鏡和轉場效果的實現與整合
OBS專案中,濾鏡和轉場效果都被抽象成外掛。
以Mac版OBS為例:
- 所有的濾鏡都在obs-filters.so這個外掛裡;
- 所有的轉場效果都在obs-transitions.so裡;
濾鏡和轉場其實分析起來是類似的,所以後續的暫時以濾鏡為例來加以說明。
首先如果濾鏡個數太多,拆分到兩個外掛裡是沒問題的。不過OBS專案中全部集中在一個外掛裡。
每個濾鏡其實都被定義成了輸入模組,以obs_source_info定義暴露API,以crop_filter為例見如下定義,唯一和普通輸入模組不同的是型別定義(.type)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct obs_source_info crop_filter = { .id = "crop_filter", .type = OBS_SOURCE_TYPE_FILTER, .output_flags = OBS_SOURCE_VIDEO, .get_name = crop_filter_get_name, .create = crop_filter_create, .destroy = crop_filter_destroy, .update = crop_filter_update, .get_properties = crop_filter_properties, .get_defaults = crop_filter_defaults, .video_tick = crop_filter_tick, .video_render = crop_filter_render, .get_width = crop_filter_width, .get_height = crop_filter_height }; |
細心觀察每個濾鏡的程式碼組成發現都是一個套路,主要由兩部分組成:
- XXX.c (C程式碼,用於暴露API以及例行處理)
- XXX.effect (自定義檔案,實際效果處理邏輯)
關於這個effect留到後續講解。
看到這裡得出一個結論,OBS專案要增加新的濾鏡效果只要編寫對應的XXX.c 和 XXX.effect,放到對應的外掛以輸入模組的API暴露出來,並註冊就可以了。
圖形API的封裝處理
目前OBS系統的圖形API包括OpenGl以及d3d11:
- 在API呼叫層面也進行了抽象統一,具體可以檢視 libobs/graphics/graphics-imports.c的定義。
- 自定義了效果描述檔案 XXX.effect,這樣就不用針對OpenGl和d3d11寫兩遍Shader。
我們以chroma-key-filter為例分析一下它的建立流程:
- 其中AddNewFilter是在介面中觸發的新增效果的功能。
- ep_parse把xxx.effect配置檔案解析成對應的配置結構。
- ep_compile把對應的配置結構解析 效果資料結構。
effect檔案的作用可以參考程式中的註釋
1 2 3 4 5 6 7 8 9 10 | /* * Effects introduce a means of bundling together shader text into one * file with shared functions and parameters. This is done because often * shaders must be duplicated when you need to alter minor aspects of the code * that cannot be done via constants. Effects allow developers to easily * switch shaders and set constants that can be used between shaders. * * Effects are built via the effect parser, and shaders are automatically * generated for each technique's pass. */ |
effect檔案包括這幾個部分:
pass對應 vertex_shader 和 pixel_shader
technique 對應一個具體效果的渲染設定,包含多個pass
effect檔案包含多個technique渲染設定、可以共享檔案中的引數和函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | SolidVertInOut VSSolid(SolidVertInOut vert_in) { SolidVertInOut vert_out; vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); return vert_out; } float4 PSSolid(SolidVertInOut vert_in) : TARGET { return color; } technique Solid { pass { vertex_shader = VSSolid(vert_in); pixel_shader = PSSolid(vert_in); } } |
effect檔案支援基本的C語法,支援巨集定義和include包含其他檔案,由libobs/util中的cf-lexer.c和cf-parser.c提供解析支援。
支援OBS外掛
OBS的外掛是在OBS專案定義的比較寬泛,外掛的範疇包括 整個錄屏、處理、推流 中的各個功能模組.
如果我們的軟體中可以直接支援OBS外掛,就可以節省大量的開發、測試的時間。但由於我們的程式框架和OBS的完全不同,要如何支援OBS專案的外掛呢?
想了兩種方法,並嘗試分析一下優缺點。
支援OBS的方法分析
二進位制級別的支援
顧名思義,就是把OBS的外掛直接放到我們程式的相應目錄就可以用。這種方式下維護、更新、新增 OBS外掛 代價是最小的。也是我心中理想的支援方式。
但是以這種方式支援的遇到較大困難。先看OBS專案中的程式碼的各個模組:
- libobs: 核心程式碼,定義專案框架以及核心API,主要用C語言編寫。
- UI: 介面程式碼,採用C++的QT框架,開發出適用三大平臺的介面。
- plugins: 外掛程式碼,可獨立編譯成dll(windows平臺)或so(*nix平臺),包含Source(錄屏輸入源)、Output、Service(各種流播服務)等全部被定義為外掛。
- libobs-d3d11: 基於D3D的圖形子系統,主要用在Windows系統。
- libobs-opengl: 基於opengl的圖形子系統,主要用在*uix系統。
我們想要支援plugins中的外掛,
但是plugins中的外掛要依賴libobs,
而libobs又要依賴libobs-opengl 和 QT介面庫。
也就是除非 我們的專案支援基於OBS專案改寫,否則這種支援方式的不太現實。
程式碼級別的支援
這是退而求其次的方式,簡單的說就是把OBS的外掛程式碼扣出來,確保其不依賴於libobs,然後整合我們的專案中。這種方式每支援一個外掛都存在整合的工作量,也可能會引入Bug,不過不失為一個較為可行的方案。
OBS專案編譯
嘗試了在Mac平臺上編譯OBS專案,還比較順利。具體可以參考install help
有個小問題,在cmake後報錯提示無法找到QT5的cmake模組。需要給cmake指定一下QT5的安裝目錄,以我的安裝目錄為例,命令如下:
1
| cmake .. -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.3.1/
|
cmake命令我是不熟悉的,不過看了這篇文章也基本懂了。
OBS專案最後的編譯結果如下:
主要有三個目錄:
- bin: 主程式
- data: 國際化資源 以及 視訊effect效果資源
- obs-plugin: 外掛編譯結果
使用otool -L分析中其中主要動態庫和外掛(僅以mac-capture為例)的依賴關係如下:
前面在程式碼層面分析直接二進位制支援OBS外掛感覺很困難。但這裡基於編譯後的動態庫依賴關係分析好像又有一定可行性。我們把mac-capture.so、libobs.0.dylib、ffmpeg獨立出來,去掉OBS主程式和QT等庫,自己寫程式碼來呼叫libobs.0.dylib提供的功能,以此直接支援OBS的外掛。
後續進行完相關的實驗,看看到底是否可行,再來補充。
模組列表
OBS專案中型別為OBS_SOURCE_TYPE_INPUT是我們可以考慮優先支援的模組。以下是功能說明。
外掛 | 子模組 | 功能描述 |
---|---|---|
mac-capture | coreaudio_input_capture | 音訊輸入獲取 |
coreaudio_output_capture | ||
display_capture | 桌面獲取 | |
window_capture | 窗體獲取 | |
mac-avcapture | av_capture | 攝像頭獲取 |
mac-syphon | syphon | 程式注入獲取介面 |
obs-ffmpeg | ffmpeg_source | ffmpeg輸入源 |
obs-browser | browser_source | 瀏覽器輸入 |
text-freetype2 | freetype2_source | text輸入 |
decklink | decklink-input | |
image-source | image_source | 圖片輸入 |
slideshow | ||
vlc-video | vlc_source | vlc輸入 |
mac-syphon外掛分析
mac-syphon是OBS專案中用於獲取遊戲畫面(僅用於mac平臺)的外掛,十分重要。下面來分析一下它的實現原理。
首先mac-syphon是OBS的輸入源外掛,所以遵循OBS的外掛API設定,具體可以檢視OBS外掛分析章節的介紹。
mac-syphon內部其實組合了多個開源專案功能來完成:獲取遊戲畫面,並展示到OBS介面上的功能。
我們以OBS獲取MineCraft這個遊戲的畫面為例,來看一張總的實現原理圖:
簡單解釋一下這個過程:
注入遊戲程序的方法這裡用的是Scripting Additions的方式,這是macOS獨有的技術,windows上肯定要用其他方式,到時候再單獨研究。除了注入方式的區別,其他流程Win和Mac平臺應該類似的。
另外由於注入的函式中替換的是OpenGL的渲染API,所以這個外掛支援的遊戲必然是使用OpenGL渲染的。假如某個程式或遊戲不使用OpenGL則無法注入。