技术背景
好多开发者在传统行业监控的时候,跟我们对接Windows平台屏幕或摄像头RTMP推送|轻量级RTSP服务模块,有这个的一个技术诉求,他们需要同时采集到多个屏幕,并输出到不同的RTMP或RTSP URL,确保每个屏幕都可以被看到,本文基于此,主要介绍,如何在Windows平台实现多屏幕采集并实现RTMP或轻量级RTSP服务。
技术实现
我们知道,在 Windows平台上,可以使用 EnumDisplayMonitors
函数进行多屏采集。以下是关于这个函数的详细介绍:
一、函数概述
EnumDisplayMonitors
是 Windows API 中的一个函数,用于枚举所有的显示监视器(屏幕)。
函数原型:
BOOL EnumDisplayMonitors(
HDC hdc,
LPCRECT lprcClip,
MONITORENUMPROC lpfnEnum,
LPARAM dwData
);
参数说明:
hdc
:设备上下文环境的句柄。如果此参数为NULL
,则使用整个桌面。lprcClip
:指向一个矩形区域的指针,用于指定裁剪区域。如果此参数为NULL
,则不进行裁剪。lpfnEnum
:指向一个回调函数的指针。每当找到一个监视器时,系统将调用这个回调函数。dwData
:应用程序定义的回调函数的参数。
返回值:如果函数成功,则返回 TRUE
;否则,返回 FALSE
。
二、使用步骤
定义回调函数
回调函数是在枚举过程中,系统为每个找到的监视器调用的函数。回调函数的原型通常如下:
BOOL CALLBACK MonitorEnumProc(
HMONITOR hMonitor,
HDC hdcMonitor,
LPRECT lprcMonitor,
LPARAM dwData
);
在回调函数中,可以根据需要对每个监视器进行处理。例如,可以获取监视器的信息、进行屏幕采集等。
调用 EnumDisplayMonitors
函数
在主程序中,调用 EnumDisplayMonitors
函数,并传入相应的参数。例如:
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
// 在这里处理每个监视器
return TRUE;
}
int main()
{
EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, 0);
return 0;
}
获取监视器信息
在回调函数中,可以使用其他 Windows API 函数来获取监视器的信息。例如,可以使用 GetMonitorInfo
函数获取监视器的详细信息,包括名称、位置、尺寸等。
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
MONITORINFO mi;
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(hMonitor, &mi))
{
// 处理监视器信息
std::cout << "Monitor Name: " << mi.szDevice << std::endl;
std::cout << "Monitor Position: (" << mi.rcMonitor.left << ", " << mi.rcMonitor.top << ")";
std::cout << ", Size: (" << mi.rcMonitor.right - mi.rcMonitor.left << ", " << mi.rcMonitor.bottom - mi.rcMonitor.top << ")" << std::endl;
}
return TRUE;
}
进行屏幕采集
一旦获取了监视器的信息,就可以使用相应的技术进行屏幕采集。例如,可以使用 BitBlt
函数将屏幕内容复制到一个内存设备上下文,然后进行进一步的处理或保存。
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
MONITORINFO mi;
mi.cbSize = sizeof(MONITORINFO);
if (GetMonitorInfo(hMonitor, &mi))
{
// 进行屏幕采集
HDC hdcSrc = CreateDC(mi.szDevice, NULL, NULL, NULL);
HDC hdcDst = CreateCompatibleDC(hdcSrc);
int width = mi.rcMonitor.right - mi.rcMonitor.left;
int height = mi.rcMonitor.bottom - mi.rcMonitor.top;
HBITMAP hbmp = CreateCompatibleBitmap(hdcSrc, width, height);
SelectObject(hdcDst, hbmp);
BitBlt(hdcDst, 0, 0, width, height, hdcSrc, 0, 0, SRCCOPY);
// 保存采集的图像或进行其他处理
DeleteObject(hbmp);
DeleteDC(hdcDst);
DeleteDC(hdcSrc);
}
return TRUE;
}
在获取到显示器数量、名称、坐标、大小和主辅设备等信息后,采集屏幕图像,从Win8开始,不再支持32位以下bpp显示设置, 实现中可以假设采集到的都是32位RGB图像,再把RGB转YUV, 编码输出RTMP/RTSP就好了,屏幕选择的和屏幕层叠加的接口设计如下:
/*
* Copyright (C) daniusdk.com. All rights reserved.
* WeChat: xinsheng120
*/
typedef struct _NT_PB_ScreenLayerConfigV3
{
NT_PB_LayerBaseConfig base_;
NT_PB_RectRegion clip_region_; // 如果是全屏幕的话,请全部填充0
NT_PVOID reserve1_; // 保留字段1
#define NT_PB_SCREEN_LAYER_FLAG_NONE (0)
#define NT_PB_SCREEN_LAYER_FLAG_DISPLAY_OPTION_DEVICE_NAME (1u<<0)
NT_UINT32 flags_;
NT_INT32 scale_filter_mode_; // 缩放质量, 0的话SDK将使用默认值, 目前可设置范围为[1, 3], 值越大缩放质量越好,但越耗性能
#define NT_PB_SCREEN_LAYER_DISPLAY_OPTION_BUFFER_SIZE (256)
NT_CHAR display_option_[NT_PB_SCREEN_LAYER_DISPLAY_OPTION_BUFFER_SIZE];
} NT_PB_ScreenLayerConfigV3;
typedef layer_conf_wrapper<NT_PB_ScreenLayerConfigV3, NT_PB_E_LAYER_TYPE_SCREEN> ScreenLayerConfigWrapperV3;
NT_UINT32 NT_API NT_PB_SetCaptureDisplayDeviceName(NT_HANDLE handle, NT_PCSTR name_utf8);
以大牛直播SDK启动RTSP服务为例,每个屏幕采集编码,对应一个RTSP拉流的URL。
选择好帧率、码率后,点击“配置查看RTSP服务”按钮,进入配置轻量级RTSP服务页面:
配置查看RTSP服务:
发布RTSP流后,页面回调界面,生成两个rtsp拉流的url,如果是三个屏幕,默认会生成三个rtsp的url,然后用windwows平台RTSP直播播放模块,做测试即可。是不是非常方便?
三、注意事项
- 权限问题
在进行屏幕采集时,可能需要管理员权限。如果程序在没有管理员权限的情况下运行,可能会导致采集失败。 - 性能考虑
屏幕采集可能会消耗一定的系统资源,特别是在同时采集多个屏幕时。需要注意性能问题,避免影响系统的正常运行。 - 兼容性
不同版本的 Windows 系统可能会有一些差异,需要确保代码在目标系统上的兼容性。
总结
总之,使用 EnumDisplayMonitors
函数可以方便地进行 Windows 平台上的多屏采集。通过定义回调函数并结合其他 Windows API 函数,可以获取监视器信息并进行屏幕采集等操作。在使用过程中,需要注意权限、性能和兼容性等问题。配合大牛直播SDK的推送模块,可以轻松实现Windows平台下的多屏幕采集功能逻辑。感兴趣的开发者,可以私信单独跟我沟通探讨。