参考:https://blog.youkuaiyun.com/fanxiushu/article/details/76039801
参考:https://blog.youkuaiyun.com/show0925/article/details/6850225
第一步:截屏获取屏幕数据,一般是变化的矩形区域图像,也可以是整个屏幕。
第二步:对图像数据进行压缩处理,推荐用H265
第三步:封装业务操作指令,组包通讯。
第四步:进行实时传输数据,一般采用以UDP+RTP+RTCP协议为主。
1.采集屏幕数据
DXGI只支持windows 8 以上的平台,而win7,winxp等系统不支持。
MirrorDriver驱动可以安全和方便的运行在WINXP, WIN7, WIN8, WIN10 等系统中。
因此MirrorDriver的通用性也是跟GDI一样支持所有windows平台,但是不支持古老的使用DirectDraw加速的程序,
MirrorDriver跟DXGI一样,截取图像数据以及更新过的矩形区域,占用的CPU都是极低的。
或者说他们在windows内核中的实现本质上都是一样的
然而都挺可惜,以上三种办法都没法截取处于屏幕独占模式下的游戏图像。
也就是对显卡硬件加速的比如Direct3D等程序不太友好。
DXGI不是太确定,我没试过win10运行全屏模式游戏能否使用DXGI截取到图像。
所谓独占模式,就是全屏下的游戏
(当然有些全屏游戏可以截取,那它应该属于那种伪独占模式,看起来在全屏中运行,实际上还是属于某个窗口)。
处理这种截屏,估计只有Hook DirectX(或者OpenGL)中函数库。
通过某种通讯方式告诉给我们的应用层程序:电脑屏幕的这个RECT区域的图像即将发生变化。
这样我们就会实时获取屏幕的变化区域。
这也是MirrorDriver驱动截屏的一个特点,他不是整个屏幕的截获,而是可以实时的获取频幕变化区域。
我们在实现远程桌面时候,以40毫秒间隔(相当于每秒25帧的速率),
读取draw_queue_t共享内存数据区这段时间内所有变化的矩形框区域,
然后合并这些矩形框,并且通过矩形框和从共享内存的RGB原始图像数据区,得到40毫秒这段时间内,所有变化的图像数据。
接着把这些数据压缩,通过网络传输;再到客户端接收,解压缩,显示。
客户端第一次连接到被控制端,获取一张完整的屏幕图片,然后接下来就只需要获取发生变化区域的图像数据。
这就是远程桌面中图像数据传输采用的一种基本办法,只传输发生变化的图像数据。
而像windows自带的远程桌面(RDP协议),除了采用只传输变化的图像数据,还使用只传输属性描述信息的办法,
比如TextOut,它就只传输在那个位置,输出什么字符串,使用什么颜色和背景色等属性信息,
之后RDP客户端根据这些信息画出来,这能大量减少传输的图像数据量。
利用MirrorDriver驱动获取变化的图像数据,接着压缩,接着再通过网络传输,这应该是是实现远程桌面比较高效的解决办法。
截获到变化的图像数据之后,在图像数据压缩这部分,即使屏幕图像发生剧烈变化,H264压缩后的数据量和图像质量都能达到一个很好的效果。
在做H264压缩时候,只需要把整个屏幕图像数据输入,而不用那么麻烦的考虑当前屏幕比上一帧图像是哪些区域发生了变化。
也就是上边强调的MirrorDriver的一个特性(能够实时获取变化的矩形区域)在H264面前变得一无是处。
这的确认让人变得有点气馁。
远程桌面中利用H264压缩图像,以 x264开源库编码成 H264。
图像帧是组成视频的最小单位。 在H.264中从大到小排序依次是:序列、图像、片组、片、NALU、宏块、亚宏块、块、像素。
借鉴开源的VNC,VNC的图像更新机制核心为,桌面区域更新记录策略和更新区域通知策略。桌面更新区域记录主要是通过hooks记录桌面上变化的矩形区域,只记录更新的矩形区不记录具体更新的数据。更新区域记录步骤大致如下:1.wm_hooks截获桌面变化的相关消息,并转化为自定义的消息发送给WMHooksThread线程处理。 2. WMHooksThread 中用SimpleUpdateTracker new_changes记录新的更新区域.3.把SimpleUpdateTracker new_changes更新拷贝到SDisplay中。4.每次要发送桌面更新的时候,把SDisplay中记录的更新区域传给VNCServerST 对象中。更新区域的通知主要有poll和push两种机制。push是服务器每隔10ms检查有没有更新,如果有更新则主动把更新推送给客户端,poll机制则是客户端主动请求更新,客户端通过发送framebufferupdate请求某一个区域更新,服务器处理该消息发送相应的更新。
详细分析如下:
1.Wm_hooks截获消息并转化为自定义的消息发送给WMHooksThread线程处理。
Wm_hooks自定义的消息:
UINT WM_HK_WindowChanged = RegisterWindowMessage(_T("RFB.WM_Hooks.WindowChanged"));
UINT WM_HK_WindowClientAreaChanged = UINT WM_HK_WindowBorderChanged = RegisterWindowMessage(_T("RFB.WM_Hooks.WindowBorderChanged"));
UINT WM_HK_RectangleChanged = RegisterWindowMessage(_T("RFB.WM_Hooks.RectangleChanged"));
UINT WM_HK_CursorChanged = RegisterWindowMessage(_T("RFB.WM_Hooks.CursorChanged"));
钩子截获到消息以后,把它转化为自定义的消息,然后发送给WMHooksThread线程处理,消息转化如下:
边框更新消息:WM_NCPAINT,WM_NCACTIVATE
客户区域更新消息:BM_SETCHECK, BM_SETSTATE,EM_SETSEL,WM_CHAR,WM_ENABLE,WM_KEYUP,WM_LBUTTONUP,WM_MBUTTONUP,WM_PALETTECHANGED,WM_RBUTTONUP,WM_SYSCOLORCHANGE,WM_SETTEXT。
窗口改变消息:WM_HSCROLL,WM_VSCROLL,482,485。
矩形区更新消息:WM_DESTROY
窗口客户区消息:WM_PAINT
鼠标消息:WM_NCMOUSEMOVE,WM_MOUSEMOVE
2 . WMHooksThread 中用SimpleUpdateTracker new_changes记录新的更新区域
3.把更新区域拷贝到SDisplay中
4.把SDisplay中记录的数据传给VNCServerST 对象,把变化的区域拷贝到VNCServerST中,
SdisplayCore 中IntervalTimer cursorTimer定时器,每隔10ms尝试着检查一下是否有更新,如果有更新就发送更新给客户端。