在会议中,有一个常用且非常的功能是桌面共享。将我们的我们的屏幕内容发送到其他与会人,让其他人能够看到我们的桌面演示。而桌面共享伴随的一个重要功能是鼠标共享,将屏幕内容和鼠标一起发送到接收端,这样别人才能够清楚地理解我们的操作。
今天我们将去剖析WebRTC中鼠标共享的代码,揭开鼠标共享的神秘面纱。
鼠标共享代码位于module/desktop_capture/
里面,名字含有mouse
和cursor
的都是鼠标相关的代码。
桌面采集的抽象接口类为DesktopCapturer
,定义了采集类应该实现的接口。这里要谈到的是其子类DesktopAndCursorComposer
是一个用来采集屏幕和鼠标并叠加的组合类(DesktopCapturer和MouseCursorMonitor的组合),继承了:
代码中通过DesktopAndCursorComposer::CaptureFrame
同时采集鼠标和屏幕内容,鼠标采集和屏幕采集完成会触发回调:
DesktopAndCursorComposer::OnMouseCursor
DesktopAndCursorComposer::OnMouseCursorPosition
DesktopAndCursorComposer::OnCaptureResult
返回包含鼠标和屏幕内容的DesktopFrameWithCursor
对象,DesktopFrameWithCursor的构造完成了鼠标和屏幕内容的叠加。
鼠标共享主要使用接口:`class MouseCursorMonitor,不同平台上有不通过的子类实现:
MouseCursorMonitorWin
MouseCursorMonitorMac
MouseCursorMonitorX11
MouseCursorMonitor
支持指定窗口或者指定显示器方式的鼠标采集。
使用前调用Init初始化MouseCursorMonitor:
virtual void Init(Callback* callback, Mode mode) = 0;
其中callback表示采集的回调(DesktopAndCursorComposer中有实现),采集(调用Capture)过程中如果发现形状和位置改变将会触发:Callback::OnMouseCursor
或Callback::OnMouseCursorPosition
。Mode表示鼠标的两种采集模式:
创建完成后就可以调用Capture进行鼠标采集:
virtual void Capture() = 0;
DesktopAndCursorComposer实现了MouseCursorMonitor::Callback和DesktopCapturer::Callback接口,将鼠标和画面混合一起发送。
鼠标采集形状数据采用MouseCursor
表示:
class MouseCursor {
...
std::unique_ptr<DesktopFrame> image_;
DesktopVector hotspot_;
};
主要保存了鼠标位图和热点,位图是32bit格式,有使用alpha通道做半透明,但是不支持反色
。
windows平台的实现MouseCursorMonitorWin构造函数可以指定窗口句柄,说明采集可以指定窗口范围,或者指定屏幕id,即可以采集某个屏幕的。
重点在于采集实现Capture,这里没啥好说的,细节的可以看源码:
windows采集里面有个重要的部分是从HCURSOR中获取鼠标图片信息,主要是通过调用GetIconInfo、GetObject、GetDIBits获取鼠标位图数据。鼠标的ICON数据主要有几种类型:
WebRTC鼠标采集的结果是一副RGBA图片,因此需要把鼠标数据写到RGBA数据中。如果是彩色格式鼠标(color!=NULL),则直接复制color数据;如果是黑白鼠标,则把XOR mask当作color字段,复制过去。复制后再处理AND mask,对于彩色鼠标如果有alpha通道则使用alpha通道,否则使用AND mask,这个阶段主要做最终颜色决策,以及给鼠标描边,保证再黑色或者被色背景下可见。
WebRTC实现的一个缺陷是不支持鼠标反色,虽然有给鼠标描边,但是这种做法效果并不好。
mac平台实现中规中矩,获取位置:
CGEventRef event = CGEventCreate(NULL);
CGPoint gc_position = CGEventGetLocation(event);
CFRelease(event);
获取鼠标图片位图数据:
NSCursor* nscursor = [NSCursor currentSystemCursor];
NSImage* nsimage = [nscursor image];
NSPoint nshotspot = [nscursor hotSpot];
CGImageRef cg_image =
[nsimage CGImageForProposedRect:NULL context:nil hints:nil];
CGDataProviderRef provider = CGImageGetDataProvider(cg_image);
CFDataRef image_data_ref = CGDataProviderCopyData(provider);
const uint8_t* src_data =
reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(image_data_ref));
int src_stride = CGImageGetBytesPerRow(cg_image);
WebRTC里面的鼠标采集还是太简单了,笔者自己之前的实现比WebRTC的功能更强大,细节更完善。但是WebRTC的实现也给了我很多启发,不得不说他们的代码写的很赞,鼠标采集、屏幕采集等抽象得很简单。
如果您觉得文章对您有用能够解决您的问题,欢迎您通过扫码进行打赏支持,谢谢!