1. 程式人生 > >開源直播工具OBS研究

開源直播工具OBS研究

專案簡介

OBS - Free and open source software for live streaming and screen recording(OBS是一款開源的用於錄屏直播的工具軟體)。

舊版的OBS只能支援Windows,目前已經停止開發。作者為了支援Windows/Mac/Linux重寫了整個軟體,專案地址為obs-studio in Github

新版的OBS的目標有以下幾點:

  1. Make it multiplatform.(跨平臺支援)
  2. Separate the application from the core, allowing custom application of the core if desired, and easier extending of the user interface.(模組化、易擴充套件
    )
  3. Simplify complex systems to not only make it easier to use, but easier to maintain.(簡化系統,使其易用易維護)
  4. 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.(儘量利用其他開源軟體成果
    )
  5. 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系統)
  6. Better device support. (更好的支援有錄屏需求的裝置)

OBS專案的語言分佈:

  1. C: 57.6%
  2. C++: 36.3%
  3. Objective-C/Objective-C++: 4%
  4. others: 3%

OBS程式碼主要包含這些部分:

  1. libobs: 核心程式碼,定義專案框架以及核心API,主要用C語言編寫。
  2. UI: 介面程式碼,採用C++的QT框架,開發出適用三大平臺的介面。
  3. plugins: 外掛程式碼,可獨立編譯成dll(windows平臺)或so(*nix平臺),包含Source(錄屏輸入源)、Output、Service(各種流播服務)等全部被定義為外掛。
  4. libobs-d3d11: 基於D3D的圖形子系統,主要用在Windows系統。
  5. libobs-opengl: 基於opengl的圖形子系統,主要用在*uix系統。

OBS軟體功能概述

OBS專案工程中以場景組的方式呈現給使用者,可以自由設定場景、輸入源、效果處理,配置直播服務。
2016121483032obs_ui.png

OBS專案工程結構

OBS專案中一個工程結構如下201612147022obs_project_stucture.png

一個場景組包含多個場景,OBS直播的時候是把整個場景流播給使用者,那為什麼需要多個場景?因為播主在直播時有快速切換場景的需要,所以播主需要在直播前編輯好多個場景(比如純遊戲場景;遊戲+頭像;解說;休息場景等),然後直播的過程中可以根據不同的需要快速切換。

OBS場景的轉場

OBS中的轉場,是場景切換時的動畫效果,目前支援 Fade和Switch等多種效果。

OBS輸入源的種類

一個場景可以包含多個輸入源,一個直播工具可以支援的輸入源種類反應了其強大性。OBS支援 輸入源種類如下
2016121460496obs_source_type.png

OBS輸入源的效果設定

針對每個輸入源可以增加各種濾鏡效果,以下列出我覺得最實用的幾種:
2016122283761OBS_Effect.png

  1. 音訊效果:
    1. Video Delay: 設定延遲時間,用來處理音視訊不同步的的場景。
    2. Noise Suppression: 噪音抑制
    3. Gain: 音訊增益
    4. Noise Gate: 噪聲門,把小噪音去掉
  2. 視訊效果:
    1. Crop: 就是最實用的Crop,不過OBS裡不能用滑鼠拖拽來控制Crop區域,略顯不便
    2. Chroma Key: 如果有綠幕背景,可以用來去背景,在攝像頭的輸入源中最常用。
    3. Image Mask: 打水印
    4. Scroll: 滾動效果,在一些瀏覽器的輸入源上最實用。

OBS工作室模式

2016122215478OBS_StdioMode.png
左邊是預覽介面,可以進行編輯。右邊是正在直播的介面。中間是把預覽介面切到直播介面的各種轉場效果。

一般比較專業的直播都是使用這個模式,可以在預覽介面編輯好畫面之後再推送到直播畫面。

OBS外掛系統

OBS專案中把除了核心框架以及渲染系統之外的 部件全部抽象成了Module,一個或多個Module最後封裝到外掛中(以dll或so的形式),只要把外掛放入特定的目錄即可被主程式使用。

Mac版OBS的外掛目錄在/Applications/OBS.app/Contents/Resources/obs-plugins,其中

  1. mac-avcapture.so 對應Mac的視訊捕獲裝置
  2. mac-capture.so 對應螢幕捕獲 和 視窗捕獲
  3. mac-syphon.so 對應注入捕獲遊戲畫面

OBS外掛定義

一個典型的OBS外掛程式碼包含三個部分:

  1. 外掛定義 -> plugin-main.c
  2. 編譯打包 -> CMakeList.txt
  3. 內部實現程式碼 -> 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中外掛可以定義為 包含 一個 或 多個 輸入(或 輸出/編碼/服務)模組的動態庫程式碼。
2016121549583obs_module_define.png
任意一個開源庫比如FFmpeg,經過OBS統一的介面定義封裝即可編譯成OBS的一個外掛為OBS系統所用。

OBS外掛載入流程

外掛系統大體都有一個類似的套路,OBS的也不例外。簡單來說就是定義外掛存放在特定目錄,在程式啟動時,動態載入所有的外掛(儲存為物件或一系列函式指標),儲存在字典 或者 連結串列這樣的資料結構裡。

下面來詳細分析一下OBS中外掛載入流程:
2016121573630OBS_import_module-2.png

然後以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輸出為例來分析一下整個流程(多路輸入 和 多路輸出道理也是類似的), 圖中為了簡單起見忽略了輸出編碼流程僅包含非編碼流程。
2016122060378OBS_Video_Render_2.png
可以看出OBS建立了兩個執行緒,一個用於顯示渲染,另一個用於編碼輸出。
渲染部分最終呼叫的是 輸入模組裡的渲染程式碼,而編碼輸出部分最終也是呼叫 輸出模組的程式碼。

另外在渲染執行緒中 也負責把圖形系統的資料 拷貝到 輸出資料的快取中,以便於輸出執行緒進行處理

視訊輸出資料結構分析

OBS的核心資料結構定義在libobs/obs-internal.h中 主體結構為obs_core如下圖所示(僅保留的主要的資料結構)
2016121631085OBS_CoreVideo-2.jpg

右下方的video(結構為video_output)用在輸出模組的raw_video介面進行處理,把 輸出資料中的cache轉成實際的輸出,以下是video_output詳細資料結構:
2016121665597OBS_Video_detail_2.jpg

OBS音訊處理流程

OBS音訊處理是在一個執行緒中完成了先渲染後輸出的過程。而視訊處理則是 分別開了渲染執行緒 和 輸出執行緒。

具體流程如下, 在輸出函式中在判斷是否需要編碼,再呼叫對應的非編碼流程 或 編碼流程:
2016122068454OBS_Audio_Render.png

OBS圖形系統架構

OBS的圖形系統主要負責 場景的渲染、場景的切換、以及各種輸入源的音視訊效果的處理,屬於OBS的核心之一。
通過使用軟體以及視訊渲染流程的分析 得到OBS圖形系統的大體的邏輯關係。

2016122324058OBS_Graphic_Structure_1.png

針對圖形系統主要分析以下三個問題:

  1. 多濾鏡疊加的渲染處理。
  2. 濾鏡和轉場效果的實現與整合。
  3. 圖形API的封裝。

單個場景的渲染流程

場景(Scene)也被封裝成輸入源(Source)的一種,所以UI層只要把當前的場景取出來,呼叫它的obs_source_video_render即可。

2017011791095OBS_sceneRender.png

在場景內部會渲染其包含的renderitem(也就是實際的輸入源),比如前一個圖所展示的遊戲錄製、桌面錄製輸入源等。

多個濾鏡疊加的輸入源渲染流程

這部分分析了很長時間一直沒看懂,主要有兩個原因:

  1. 之前不熟悉OpenGl的渲染流程,所以搞不懂濾鏡的渲染流程。
  2. 這部分的邏輯比較繞,沒分析出多個濾鏡是怎麼疊加渲染的。

前段時間花了點時間好好學習了一下OpenGl(僅僅學習和音視訊處理相關的章節),寫了一些demo

現在再來分析這部分相對輕鬆一些,簡述一下:當渲染帶濾鏡的輸入源時,會先渲染它最後一個濾鏡,然後在這個濾鏡的渲染程式碼又會呼叫渲染前一個濾鏡,最後呼叫第一個濾鏡的渲染程式碼。

在第一個濾鏡的渲染程式碼裡 直接渲染 呼叫輸入源的渲染流程,然後生成texture。

每個濾鏡都在前一個濾鏡渲染生成的texture的基礎渲染生成新的texture。

流程圖如下:
2017011799373OBS_filterRender.png

濾鏡和轉場效果的實現與整合

OBS專案中,濾鏡和轉場效果都被抽象成外掛。

以Mac版OBS為例:

  1. 所有的濾鏡都在obs-filters.so這個外掛裡;
  2. 所有的轉場效果都在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
};

細心觀察每個濾鏡的程式碼組成發現都是一個套路,主要由兩部分組成:

  1. XXX.c (C程式碼,用於暴露API以及例行處理)
  2. XXX.effect (自定義檔案,實際效果處理邏輯)

關於這個effect留到後續講解。

看到這裡得出一個結論,OBS專案要增加新的濾鏡效果只要編寫對應的XXX.c 和 XXX.effect,放到對應的外掛以輸入模組的API暴露出來,並註冊就可以了。

圖形API的封裝處理

目前OBS系統的圖形API包括OpenGl以及d3d11:

  1. 在API呼叫層面也進行了抽象統一,具體可以檢視 libobs/graphics/graphics-imports.c的定義。
  2. 自定義了效果描述檔案 XXX.effect,這樣就不用針對OpenGl和d3d11寫兩遍Shader。

我們以chroma-key-filter為例分析一下它的建立流程:
2016122616450OBS_filter_create.png

  1. 其中AddNewFilter是在介面中觸發的新增效果的功能。
  2. ep_parse把xxx.effect配置檔案解析成對應的配置結構。
  3. 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檔案包括這幾個部分:
2016122616242effect_file_structure.png

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專案中的程式碼的各個模組:

  1. libobs: 核心程式碼,定義專案框架以及核心API,主要用C語言編寫。
  2. UI: 介面程式碼,採用C++的QT框架,開發出適用三大平臺的介面。
  3. plugins: 外掛程式碼,可獨立編譯成dll(windows平臺)或so(*nix平臺),包含Source(錄屏輸入源)、Output、Service(各種流播服務)等全部被定義為外掛。
  4. libobs-d3d11: 基於D3D的圖形子系統,主要用在Windows系統。
  5. 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專案最後的編譯結果如下:

201702058452OBS_Compile.png

主要有三個目錄:

  1. bin: 主程式
  2. data: 國際化資源 以及 視訊effect效果資源
  3. obs-plugin: 外掛編譯結果

使用otool -L分析中其中主要動態庫和外掛(僅以mac-capture為例)的依賴關係如下:

2017020524659OBS_dependency.png

前面在程式碼層面分析直接二進位制支援OBS外掛感覺很困難。但這裡基於編譯後的動態庫依賴關係分析好像又有一定可行性。我們把mac-capture.so、libobs.0.dylib、ffmpeg獨立出來,去掉OBS主程式和QT等庫,自己寫程式碼來呼叫libobs.0.dylib提供的功能,以此直接支援OBS的外掛。

後續進行完相關的實驗,看看到底是否可行,再來補充。

模組列表

OBS專案中型別為OBS_SOURCE_TYPE_INPUT是我們可以考慮優先支援的模組。以下是功能說明。

外掛子模組功能描述
mac-capturecoreaudio_input_capture音訊輸入獲取
coreaudio_output_capture
display_capture桌面獲取
window_capture窗體獲取
mac-avcaptureav_capture攝像頭獲取
mac-syphonsyphon程式注入獲取介面
obs-ffmpegffmpeg_sourceffmpeg輸入源
obs-browserbrowser_source瀏覽器輸入
text-freetype2freetype2_sourcetext輸入
decklinkdecklink-input
image-sourceimage_source圖片輸入
slideshow
vlc-videovlc_sourcevlc輸入

mac-syphon外掛分析

mac-syphon是OBS專案中用於獲取遊戲畫面(僅用於mac平臺)的外掛,十分重要。下面來分析一下它的實現原理。

首先mac-syphon是OBS的輸入源外掛,所以遵循OBS的外掛API設定,具體可以檢視OBS外掛分析章節的介紹。
mac-syphon內部其實組合了多個開源專案功能來完成:獲取遊戲畫面,並展示到OBS介面上的功能。

我們以OBS獲取MineCraft這個遊戲的畫面為例,來看一張總的實現原理圖:
2017010524004OBS_SyphonInject.png

簡單解釋一下這個過程:

2017010568862OBS_SyphonInject_flow.png

注入遊戲程序的方法這裡用的是Scripting Additions的方式,這是macOS獨有的技術,windows上肯定要用其他方式,到時候再單獨研究。除了注入方式的區別,其他流程Win和Mac平臺應該類似的。

另外由於注入的函式中替換的是OpenGL的渲染API,所以這個外掛支援的遊戲必然是使用OpenGL渲染的。假如某個程式或遊戲不使用OpenGL則無法注入。

相關推薦

no