高效屏幕监控

题目:Windows屏幕变化捕捉与回放


前言:

这是2012年中国软件杯的一道题目,本来我之前选的就是这个题目,不过自己技术还不够强,没能够完成,只写出了捕捉部分,后续的压缩传输部分因能力有限没能够完成,但是我还是想总结一下这个题目所涉及到的一些东西,希望能够对那些想学习图形图像传输方面的人有帮助。


续:整个系统已经完成一段时间了,最终还是坚持下来将此监控系统写完了,期间停留过一段时间学其他技术,今天翻出此贴忽然想在这贴里对该系统再来做次总结,这也是我写过的历时最长也最大的一个系统,呵呵!当然也花了很多的心血在上面。

系统完成时间:2012-11-10

最后编辑时间:2013-01-29

总的来说这是为了做成一种远程屏幕图像实时传输的系统,针对传统的截取整幅图像传输的方法进行改进,不仅在传输效率上提高而且在时间上也有所提高。
首先是图像变化区域的检测部分,图形的检测只是一种手段,通过各种方法检测到变化区域后在通过一种方法将变化区域截取下来,压缩---传输---解压缩---显示,这就是整个流程,当然细节部分还是很重要 的。当时我做这个项目时不知道这个题目的真正意义,以为变化区域的检测是最难的,当然我当时也没想到这是对传统屏幕传输方法的一种改进,屏幕变化区域的捕获是我碰到 的第一个难点。


第一步:变化检测


其实变化区域检测方法还是很多的,首先说两中,其实这两种原理相同,准确说是用轮询检测的算法,通过对屏幕划分成n*m的几个矩形区域,定时的对举行区域进行前后对比,这两种方法只是对不的方式不同而已,一种是直接比对法,另一种是CRC比对,直接比对即用memcmp,对内存数据进行对比,这样需要的内存就比较多,因为要存放至少两张图片,CRC比对这是通过计算图片的CRC值,直接对比CRC值来进行判断,另外其实还有一种是对两张图片进行或运算,变化区域位值会变为1,未变化区域位值会变为0,这是通过比对的方法来检测变化区域,我当时是用的Hook来检测的,hook可以检测系统产生的所有消息,消息会首先通过钩子队列,当你截取到屏幕变化消息后,获取变化的举行区域,这样也能获取到变化的区域,方法是多种多样的,还有镜像驱动检测,鼠标广度扫描算法等,这里只是检测变化区域 部分。

这里要注意的是某种检测算法或检测技术只是检测出变化的矩形区域,并不需要保留完整的图像数据,但是轮询算法需要保存额外的采样数据,在Hook检测中,获取变化区域的代码如下:

	case WM_PAINT:
		if (bGetUpdateRgn)
		{
			HRGN region;
			region = CreateRectRgn(0, 0, 0, 0);

			// Get the affected region
			if (GetUpdateRgn(hWnd, region, FALSE) != ERROR)
			{
				int buffsize;
				UINT x;
				RGNDATA *buff;
				POINT TopLeft;

				// Get the top-left point of the client area
				TopLeft.x = 0;
				TopLeft.y = 0;
				if (!ClientToScreen(hWnd, &TopLeft))
					break;

				// Get the size of buffer required
				buffsize = GetRegionData(region, 0, 0);
				if (buffsize != 0)
				{
					buff = (RGNDATA *) new BYTE [buffsize];
					if (buff == NULL)
						break;

					// Now get the region data
					if(GetRegionData(region, buffsize, buff))
					{
						for (x=0; x<(buff->rdh.nCount); x++)
						{
							// Obtain the rectangles from the list
							RECT *urect = (RECT *) (((BYTE *) buff) + sizeof(RGNDATAHEADER) + (x * sizeof(RECT)));
							SendDeferredUpdateRect(
								hWnd,
								(SHORT) (TopLeft.x + urect->left),
								(SHORT) (TopLeft.y + urect->top),
								(SHORT) (TopLeft.x + urect->right),
								(SHORT) (TopLeft.y + urect->bottom)
								);
						}
					}

					delete [] buff;
				}
			}

			// Now free the region
			if (region != NULL)
				DeleteObject(region);
		}
		else
		{
			SendDeferredWindowRect(hWnd);
		}
		break;

第二步:屏幕截取


检测完后,你在用一种方法将变化区域截取下来,常用的有屏幕DC,通过创建屏幕兼容DC,bitblt到内存DC里面来获取图像,这种方法有点是能捕捉大部分的屏幕图像,但是速度较慢,另外一种是DirectX的方法,速度较快,但是要安装DirectX。

使用DC截屏比较简单,重要的是获取整个屏幕的数据,若不是24位的则转换成24位方便压缩。使用镜像驱动截屏需安装虚拟显卡驱动,若是windows7的操作系统,则需关闭Areo特效,截屏的速度明显快的多。BitBlt的时间大致是1~2ms,而DC截屏BitBlt的时间则需要40ms左右,在WDK里面有个Mirror Driver的例子可以参考。

第三步:压缩编码


对获取到时图像进行压缩,比如,你通过屏幕DC的方法获取到图像信息,不压缩的话大概有1M到2M左右,这样信息量是很大的,带宽和CPU都承受不了,这里比较常见的压缩方法有jpeg压缩、gif压缩,都是采用有损压缩的方法,效率都不错,这里我只想说下Jpeg压缩,因为是开源的,所以用的人多,网上也有很多源码,jpeg压缩算法步骤有1.颜色变换2.DCT变换3.量化4.编码,一般用哈夫曼编码。

libjpeg库是早期Jpeg压缩算法的工程文件,最新的Jpeg2000可支持无损压缩,关键是有损压缩比也高于Jpeg算法,另外Jpeg完全可扩展,使用时可根据需要进行修改。

若是保存成本地文件也可自定义协议,回放是根据自定义的协议进行解码。类似的Mpeg算法是采用关键帧来做的,类似一次完整的屏幕。

第四步:图像传输


这里涉及到socket的内容,因为是局域网内部的桌面监控,选择IP多播比较合适,将图像信息以2进制的形式发送出去。

图像传输需要采用自定义的协议来传输数据,选择合适的内存管理方案,让程序能高效稳定的运行,推荐使用内存池技术,如今高效的系统都离不开池化技术。

第五步:屏幕回放


如player.exe在接受到capture.exe进程发送来的数据后用jpeg解压缩,恢复成图像信息,然后再屏幕上显示出来。

使用强大的Cximage库对图像数据进行解码则能省去很多工作。


总结:

这样大致的步骤就完了,涉及到的知识面也是很广的,所涉及到的就有多线程、图像的结构、hook、图形压缩、网络、算法等。实际上还是图形处理与压缩那块是最麻烦的,因为要考虑到效率,发送来的图像不能产生冗余等等。
好了,以上就是我的总结,虽然没能做完但还是学到了很多东西。


再次总结:讲解的虽然比较简单,但是一个完整高效稳定的系统却需要多经过方面的考虑,融合多种编程思想,例如网络传输方面使用合适的IO模型,内存管理方案,界面设计,线程设计,设计模式的使用都是设计一个优秀的程序所要考虑的,重要的是你要做好准备去接触新思想新技术,并且在系统中使用高效算法可使程序更加健壮,总所周知,算法是程序的灵魂。在2013年,我希望能够接触更多优秀的编程思想,学习高级算法,看更多的优质代码来提高自己。


付上蒋晟关于高效屏幕录制的一些建议http://blog.joycode.com/jiangsheng/archives/2004/01/01/10410.joy

你可能感兴趣的:(图形)