随笔 - 62  文章 - 73  评论 - 592 

      上一次介绍了图像差异比较的方法,原想进一步修改算法,采用动态分块的实现方式。但是,“内心觉得不够宁静”,于是乎,打算先根据图像差异的实现算法实现屏幕传输功能。

      按照我的惯例,先来预览下效果图:

Dot Net 屏幕传输 v1.0 图1 屏幕传输效果图

      程序说明:本程序只适用于局域网。整个程序包含两部分,其中“屏幕分享 v1.0“ 为Control端,用于截取屏幕图片后将差异发送给Client端。屏幕分享终端为Client端,用于接收Control端发送过来的屏幕,并绘制在当前窗口。要想得到效果,必须先选择终端菜单中的“启动”按钮,而后配置Control端的IP信息后选择连接,便可以进行屏幕传输。

      开发工具:VS.NET 2008
      开发语言:C#
      开发测试平台:Windows XP sp2、Windows XP sp3、双核

      正 文

      原以为很简单,但是从着手到今天写博客已经花了近一星期的时间。其中大部分时间都花在了性能调整上,即使是我今天发布的1.0版本,性能上也是相当不尽人意。我的屏幕传输程序分为Control端和Client端:Control端负责截取屏幕,然后发送到Client端。

      将这两端分别置于局域网内两台机器上运行后发现,Control端在我的实验机(双核)上占内存接近54M,Client端占内存45M;如果图片差异大,则Control端占20%左右的CPU,而Client端可能会占到50%的CPU;如果差异小,则Control端占10%左右的CPU,而Client端最低占11%的CPU。而屏幕传输的延迟一般在0.3秒左右。(上述数据会因为具体的配置不同而不同)

      经过反复测试,发现Control端的主要性能瓶颈就在于这个差异算法,因为该差异算法需要0.11秒才能找出一个差异(差异较大的情况),利用while循环进行差异的不断查找,如果不在循环内设法让线程睡上一会时间,则CPU会占到70%,而我的解决方案就是让线程睡眠0.1s,但很明显,0.1s使得帧频一下子变成了原来的1/2。而Client端的主要性能瓶颈则在于循环接收的这个过程,我目前采用的是TCP的传输协议。

     目前看来,该版本的屏幕传输不管是在性能还是在效果上可能都不一定比得上直接整屏的截图发送(当前的差异算法还会产生不少的碎片),但是在整个传输过程中带宽的占用明显小于整屏截图。下一步的工作重点,我将主要改进差异算法及传输过程,也希望大家多多指点。

     好了,要说的基本上就这么多了。主要的代码在上一篇文章中也已经提到了(为了用于屏幕传输做了些细微的调整,其中主要就是调整了在捕获到差异后的处理流程。)

     项目打包下载:http://files.cnblogs.com/stg609/ScreenShare.rar

作者:stg609
出处:http://stg609.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

posted on 2009-12-10 08:38 stg609 阅读(3287) 评论(28) 编辑 收藏

 回复 引用 查看   
#1楼 2009-12-10 09:03 | Kevin Cheng      
随便逛逛.....见此贴,不由得想到了mpeg的压缩原理。
远程终端 mstsc 性能很好,且鼠标、键盘的消息传递都很实时,不知道有没有开放接口,如果可以直接调用也许会省事不少。
昨晚睡了,早见此贴,脑糊语乱莫怪

 回复 引用 查看   
#2楼 2009-12-10 09:17 | MagicHu      
很好的文章。。。。。。。
底层算法研究很重要啊

 回复 引用 查看   
#3楼 2009-12-10 09:49 | cnyao      
对这些数据是怎么得到的很感兴趣,比如0.11秒等。。。
 回复 引用 查看   
#4楼[楼主] 2009-12-10 10:07 | stg609      
我在测试的时候使用了log4net这个组件,因此可以记录我想要记录的一些时间,于是便有了0.11这种数据。其它关于CPU、内存的数据则是通过直接观察任务管理器来记录。
 回复 引用 查看   
#5楼 2009-12-10 10:29 | 徐少侠      
关于以下方法
private void CaptureScreen(out Bitmap bmp)
用out或者return bmp估计速度一样

不过里面的那个Graphics g每次都要从bmp新建
其实看起来g和bmp的生命周期如果一样不也可以么?
省掉每次捕捉都要g.Dispose();

正打算抽点时间也来搞搞


 回复 引用 查看   
#6楼 2009-12-10 10:54 | 兆子      
看来这个。我突然有个想法,有种游戏叫 ‘大家来找茬 ’ ,然后我一搜,发现果然有QQ找茬游戏外挂,想弄来研究研究怎么实现的。
 回复 引用 查看   
#7楼 2009-12-10 11:07 | toEverybody      
用Delphi或Vc++看看,差距有多大?
 回复 引用   
#8楼 2009-12-10 11:12 | 99书城[未注册用户]
按时间段吉萨肯德基
 回复 引用 查看   
#9楼 2009-12-10 11:17 | 徐少侠      
想了解一下目前带宽大概占用多少?
或者说一屏的传送平均要多少字节?

 回复 引用 查看   
#10楼[楼主] 2009-12-10 12:10 | stg609      
@徐少侠
如果用一个全局Graphic,那每次还是需要重新从图像中得到一个新的Graphic,这样只是使原有的Graphic指向一个新的托管堆中Graphic对象而已,而并不是直接修改原有托管堆中的Grahpic对象,这样做会导致旧的托管堆中Grahpic对象继续占用内存,直到垃圾回收器启动,而Grahpic对象是相当消耗内存的,如果不及时释放掉该对象,那很快内存就不够用了。

另外,现在这个算法只有在图片存在差异的时候才会有数据发送,如果差异是很小的局部,可能一次传输只有几K,如果差异比较大,数据量就比较大,会有几M(因为我目前没有采用任何的压缩方法,完全是原始的bitmap数据)。大概算了一下,平均每秒90K左右。

 回复 引用 查看   
#11楼 2009-12-10 12:26 | sun8134      
@兆子
大家来找茬其实用截图的话效率很低哦

以前给GF写过歌功能简单的
刚开始截图
后来改读内存了

QQ找茬是把图片的差异区域存储在内存中的
只要给程序发消息,让程序认为鼠标点击的地方是差异区域就行了

 回复 引用 查看   
#12楼 2009-12-10 12:52 | 徐少侠      
@sun8134
了解了,谢谢
你提到的压缩其实可以试试

 回复 引用 查看   
#13楼 2009-12-11 18:41 | 徐少侠      
今天花一天工夫写了个东西

基本思路是
1、截屏,获得BMP格式图片
2、用RtlCompareMemory,以一个像素高,100像素宽为单位比较新旧图片
3、每个有不同像素的线段复制为byte数组
4、每发现一个就用UDP发送一个**,包括该线段所在的起始像素坐标
5、接收端把获得的像素数组绘制到现有界面上

本质上没有对图片进行压缩,图像质量杠杠的

**今天还没用UDP发送,是本地事件传递。速度还行。周末用UDP发送,看看速度如何。
关键问题是发送次数很多,全屏都发送要4800次( 800/100 * 600 )才算一屏发完
以后的差异也要很多次才能发完
如果不行的话还要换个思路
比如以行为单位比较,再进行压缩

呵呵

PS:
我的捕捉是用Win32API实现,速度快时才23毫秒,慢的要50左右。我的全屏是1280*800的
用Win32API把鼠标的捕捉也搞定了。呵呵

 回复 引用 查看   
#14楼 2009-12-11 18:48 | 徐少侠      


菜单上有鼠标的哦

 回复 引用 查看   
#15楼[楼主] 2009-12-11 20:54 | stg609      
@徐少侠
我顶。
你的比较方法已经有点类似分块的方法了。用RtlCompareMemory比较出不同后,然后发送,但是你也说到你的发送次数很多,我不知道你整屏比较完大概要多少时间?大概有多少的延迟?
你可以考虑下采用动态分块的算法,这也是我这次想改进的。

另外,我很感兴趣的是,你是用了哪个Win32API来截图的,你的这个Win32API方法能否捕获视频的数据?

留抓!!!!
 回复 引用 查看   
#17楼 2009-12-12 21:57 | 徐少侠      
截屏主要使用了这个东西
Capturing the Screen Image Using C#
里面有源码

我附加了截获鼠标形状和位置的一段
//We copy the Bitmap to the memory device context.
PlatformInvokeGDI32.BitBlt(hMemDC, 0, 0,size.cx,size.cy, hDC, 0, 0, PlatformInvokeGDI32.SRCCOPY);

#region 绘制鼠标 上面那个语句是本身老外的,就夹在这个位置,获得hMemDC后
PlatformInvokeUSER32.CURSORINFO pci = new PlatformInvokeUSER32.CURSORINFO();
pci.cbSize = Marshal.SizeOf(pci);
PlatformInvokeUSER32.GetCursorInfo(out pci);
IntPtr dc = hMemDC;
//这个偏移量是我在使用时发现的问题,不一定是10,没有精确论证过。只是目前看起来位置正确。
PlatformInvokeUSER32.DrawIconEx(dc, pci.ptScreenPos.X-10, pci.ptScreenPos.Y-10, pci.hCursor, 32, 32, 1, IntPtr.Zero, PlatformInvokeUSER32.DI_NORMAL);
#endregion

 回复 引用 查看   
#18楼 2009-12-13 20:37 | 徐少侠      
测试结果是要换算法
主要问题是事件触发太多,也就是把屏幕分太细.
加上网络发送,结果只有每秒1帧了.完全不可行了

仅运算,不发送差异,和你的0.11秒还是差不多,每秒能在10帧左右

继续搞,呵呵

 回复 引用 查看   
#19楼[楼主] 2009-12-13 23:10 | stg609      
呵呵~~
 回复 引用 查看   
#20楼 2009-12-14 09:58 | 小罗      
楼主, 我尝试下载项目文件但失败了,想参考学习图像差异比较算法。能否发一份项目文件,我的邮箱lh154691780@msn.com,谢谢!
 回复 引用 查看   
#21楼[楼主] 2009-12-14 11:35 | stg609      
@小罗
已经发到你的邮箱,请查收。

 回复 引用 查看   
#22楼 2009-12-15 18:52 | 徐少侠      
挖咔咔

我搞定一大半了

目前本机测试,最慢每秒能有6屏,不过是广播方式发送,没有测试流量的压力
用播放器放电影,非大范围动态镜头时候,直接看啦,肉眼估计延时在0.5s-0.3s以下的。用JPEG方式发送,图像质量还是很不错的
哈哈

不过快速拖动窗体时候会丢数据,页面就很难看了。

打算再换回到TCP连接,或者在客户端那里再搞点花样
还有就是隔几秒发送个完整关键帧

最后的绝招就是客户端判断某些位置长期(比如3-5秒)不更新就主动到服务器拉

今天发了个截屏的帖,没贴在首页。里面有API的完整代码

 回复 引用 查看   
#23楼 2009-12-15 18:55 | 徐少侠      
硬性规定屏幕分割成48像素*50像素的小片

用RtlCompareMemory进行批量像素比较
使用了非安全代码来复制差异图片

程序特色就是没有直接比较每个像素

 回复 引用 查看   
#24楼[楼主] 2009-12-15 19:44 | stg609      
恭喜,希望早日看见你的作品。另外,jpeg对文字的编码不是很好,不知道你有没有感觉。
 回复 引用 查看   
#25楼[楼主] 2009-12-15 19:50 | stg609      
@徐少侠
你指的每秒6屏是什么意思?是找到整个屏幕中的所有差异并发送算一屏?还是找到一个差异并发送算一屏?

 回复 引用 查看   
#26楼 2009-12-15 21:02 | 徐少侠      
整个屏幕寻找差异并处理完成算一屏
我的截屏和处理在一个线程里
截屏
处理,期间发送
然后再去截

每次处理函数完成后递增计数器,屏幕差异小的时候每秒在10帧左右

目前正在把一堆原型代码重新重构,进一步明确类划分和组件关系。
还要等几天才敢发帖,否则给口水淹死

我觉得比较有成就的就是抛弃了逐像素比较差异的方式,很大程度上摆脱了图片大小对算法效率的影响。

在你的第一个相关帖子的回帖里,万仓一黍 提出的图像异或方式是理论上传输数据量最低的方式。也是我最早想实现的方式
不过对图像处理不熟,没搞。

等目前这个版本弄好后去尝试一下异或的处理方式。

 回复 引用 查看   
#27楼[楼主] 2009-12-15 22:44 | stg609      
@徐少侠
恩,呵呵。我目前大概实现了一个分块的算法,过几天也打算实现基于此的屏幕传输,里面应该也会采用异或的方式来进一步压缩数据

 回复 引用 查看   
#28楼 2010-03-07 19:01 | Net205 Blog      
好东西,收藏