obs的视屏录制主要分3种:
- 窗口采集:采集应用程序窗口
- 显示器采集:也叫全屏采集,可以采集整个屏幕,当有多个显示器时,可以设置采集其中一个显示器
- 游戏采集:可以采集游戏窗口
在plugin-main.c可以看到各个采集的定义,代码如下:
extern struct obs_source_info duplicator_capture_info;
extern struct obs_source_info monitor_capture_info;
extern struct obs_source_info window_capture_info;
extern struct obs_source_info game_capture_info;
这几个采集信息的结构体应分别在其它几个不同的文件定义,所以用extern声明。 在第74行,会加载这些采集方式,代码如下:
if (graphics_uses_d3d11)
wgc_supported = win_version_compare(&ver, &win1903) >= 0;
if (win8_or_above && graphics_uses_d3d11)
obs_register_source(&duplicator_capture_info); //win8以上显示采集
else
obs_register_source(&monitor_capture_info); //win7及以下显示器采集
//窗口采集
obs_register_source(&window_capture_info);
由于windows系统的窗口在win8版本换了实现方式,为了采集效率,所以分两种不同的形式进行采集。窗口采集则是BitBlt或者wcg. 关于windows的显示技术,可以去看微软文档:【显示基础结构的发展】 从该文档可知Windows 8 引入了新的 Microsoft DirectX 图形基础结构 (基于 DXGI) 的 API,使独立软件供应商能够更轻松地 (isv) 支持桌面协作和远程桌面访问方案。 本篇重点说一下win8以上的系统显示器采集方式,Windows Desktop duplication Windows 8引入了新的基于 Microsoft DirectX 图形基础设施 (DXGI) 的 API,使独立软件供应商 (ISV) 可以更轻松地支持桌面协作和远程桌面访问方案。 此类应用程序广泛用于企业和教育方案。 这些应用程序有一个共同的要求:访问桌面内容,以及将内容传输至远程位置的能力。 桌面Windows 8 API 提供对桌面内容的访问权限。 目前,Windows API 都允许应用程序无缝实现此方案。 因此,应用程序使用镜像驱动程序、屏幕抓取和其他专有方法来访问桌面的内容。 但是,这些方法具有以下一组限制: 优化性能可能很有挑战性。 这些解决方案可能不支持较新的图形呈现 API,因为 API 在产品发布后发布。 Windows并不总是提供丰富的元数据来帮助优化。 并非所有解决方案都与 Vista 和更高版本的 Windows 中的桌面组合Windows。 Windows 8引入了一个基于 DXGI 的 API,称为 桌面复制 API。 此 API 通过使用位图和关联的元数据进行优化,提供对桌面内容的访问。 此 API 适用于启用的都卡主题,不依赖于应用程序使用的图形 API。 如果用户可以在本地控制台上查看应用程序,则还可以远程查看内容。 这意味着,即使全屏 DirectX 应用程序也可以复制。 请注意,API 提供保护,防止访问受保护的视频内容。 API 使应用程序能够请求Windows,以提供对沿监视器边界的桌面内容的访问。 应用程序可以复制一个或多个活动显示。 当应用程序请求重复时,会发生以下情况: Windows呈现桌面,并向应用程序提供副本。每个呈现的帧都放置在 GPU 内存中。每个呈现的帧都附带以下元数据:
- 脏区域
- 屏幕到屏幕移动
- 鼠标光标信息
- 向应用程序提供对帧和元数据的访问权限。
- 应用程序负责处理每个帧:
- 应用程序可以选择基于脏区域进行优化。
- 应用程序可以选择使用硬件加速处理移动和鼠标数据。
- 应用程序可以选择在流式处理之前使用硬件加速进行压缩。
obs的桌面复制主要是在duplicator-monitor-capture.c、monitor-capture.c以及core的libobs-d3d11,主要是用DXGI技术获取屏幕数据,相比于GDI的截图技术有巨大的效率提升。 在添加采集源时,可以选择使用DXGI技术: 桌面采集,要达到60fps是很有难度的技术,采用DXGI是很好的选择。 在monitor-capture.c文件中实现了很多于显示器操作的代码,如果有需求,可以摘取这部分代码应用到项目中,例如:
- 枚举显示器
static BOOL CALLBACK enum_monitor(HMONITOR handle, HDC hdc, LPRECT rect,
LPARAM param)
{
struct monitor_info *monitor = (struct monitor_info *)param;
if (monitor->cur_id == 0 || monitor->desired_id == monitor->cur_id) {
monitor->rect = *rect;
monitor->id = monitor->cur_id;
}
UNUSED_PARAMETER(hdc);
UNUSED_PARAMETER(handle);
return (monitor->desired_id > monitor->cur_id++);
}
- 获取显示器的名字
static const char *monitor_capture_getname(void *unused)
{
UNUSED_PARAMETER(unused);
return TEXT_MONITOR_CAPTURE;
}
该文件主要是对应win7以下的显示器桌面,采集方法用的是BitBlt, 对于win8以上的桌面OBS主要提供WCG和DXGI这两种方式进行采集,则在duplicator-monitor-capture.c中实现,duplicator_capture_tick方法决定是采用WCG还是DXGI, 主要代码如下:
if (capture->capture_winrt) {
capture->exports.winrt_capture_free(
capture->capture_winrt);
capture->capture_winrt = NULL;
}
if (!capture->duplicator) {
capture->reset_timeout += seconds;
if (capture->reset_timeout >= RESET_INTERVAL_SEC) {
capture->duplicator = gs_duplicator_create(
capture->dxgi_index);
capture->reset_timeout = 0.0f;
}
}
if (capture->duplicator) {
if (capture->capture_cursor)
cursor_capture(&capture->cursor_data);
//调用libobs-d3d11的导出函数gs_duplicator_update_frame
if (!gs_duplicator_update_frame(capture->duplicator)) {
free_capture_data(capture);
} else if (capture->width == 0) {
reset_capture_data(capture);
}
}
如果是使用DXGI, 在采集时gs_duplicator_update_frame一直会被调用,获取桌面资源,该函数封装在libobs-d3d11这个dll中:
EXPORT bool gs_duplicator_update_frame(gs_duplicator_t *d)
{
DXGI_OUTDUPL_FRAME_INFO info;
ComPtr tex;
ComPtr res;
HRESULT hr;
if (!d->duplicator) {
return false;
}
if (d->updated) {
return true;
}
hr = d->duplicator->AcquireNextFrame(0, &info, res.Assign());
if (hr == DXGI_ERROR_ACCESS_LOST) {
return false;
} else if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
return true;
} else if (FAILED(hr)) {
blog(LOG_ERROR,
"gs_duplicator_update_frame: Failed to update "
"frame (%08lX)",
hr);
return true;
}
hr = res->QueryInterface(__uuidof(ID3D11Texture2D),
(void **)tex.Assign());
if (FAILED(hr)) {
blog(LOG_ERROR,
"gs_duplicator_update_frame: Failed to query "
"ID3D11Texture2D (%08lX)",
hr);
d->duplicator->ReleaseFrame();
return true;
}
copy_texture(d, tex);
d->duplicator->ReleaseFrame();
d->updated = true;
return true;
}
这里要注意的是AcquireNextFrame的返回值,当桌面画面没有变化或没有新图像到来时会返回DXGI_ERROR_WAIT_TIMEOUT,此时无需做图像更新操作,直接返回循环等待下一帧图像。下面是微软的官方说明:
AcquireNextFrame returns:
S_OK if it successfully received the next desktop image.
DXGI_ERROR_ACCESS_LOST if the desktop duplication interface is invalid. The desktop duplication interface typically becomes invalid when a different type of image is displayed on the desktop. Examples of this situation are:
Desktop switch
Mode change
Switch from DWM on, DWM off, or other full-screen application
In this situation, the application must release the IDXGIOutputDuplication interface and create a new IDXGIOutputDuplication for the new content.
DXGI_ERROR_WAIT_TIMEOUT if the time-out interval elapsed before the next desktop frame was available.
DXGI_ERROR_INVALID_CALL if the application called AcquireNextFrame without releasing the previous frame.
E_INVALIDARG if one of the parameters to AcquireNextFrame is incorrect; for example, if pFrameInfo is NULL.
Possibly other error codes that are described in the DXGI_ERROR topic.
获取到纹理数据后,需要做拷贝,调用copy_texture。 dx的接口是基于com开发的,如果不熟悉com技术,这些代码看起来很吃力,毕竟dx是微软最难的sdk。 了解com技术的会,应该知道,任何基于com的对象,都得派生于IUnknown,例如DXGIObject
MIDL_INTERFACE("aec22fb8-76f3-4639-9be0-28eb43a67a2e")
IDXGIObject : public IUnknown
IDXGIOutputDuplication派生于DXGIObject,作为结构体gs_duplicator的成员
struct gs_duplicator : gs_obj {
ComPtr duplicator;
gs_texture_2d *texture;
int idx;
long refs;
bool updated;
void Start();
inline void Release() { duplicator.Release(); }
gs_duplicator(gs_device_t *device, int monitor_idx);
~gs_duplicator();
};
在构造时,调用Start(). 另外说明一点,采用DXGI时,obs依赖于libobs-winrt项目. 在使用obs sdk二次开发时,要注意带上libobs-winrt生成的dll.