queensf
总版主
总版主
  • 注册日期2003-12-04
  • 发帖数735
  • QQ
  • 铜币3枚
  • 威望0点
  • 贡献值0点
  • 银元0个
阅读:2212回复:8

远程屏幕图象实时传输的实现

楼主#
更多 发布于:2004-04-12 02:04
关键词:屏幕图象 屏幕抓取 实时传输  网络传输 数据压缩

随着计算机网络的不断推广运用,基于计算机网络的应用软件的开发也也越来越普遍。在这些应用软件当中,基于计算机网络的远程实时控制、管理软件因其具有极其广泛的应用领域如网络多媒体教室、网络管理与控制、网络服务、在线技术支持等,所以它具有有非常广阔的发展前景。但是在远程屏幕图象在网络上传输这个关键技术环节上存在的问题比较多。
经过反复研究与实践,终于找到了一些方法能够很好地解决远程屏幕图象网络传输占用网络带宽过大、实时性差、占用系统资源过多、稳定性差等关键问题,以下便就此展开叙述与探讨。
一、 远程屏幕图象在网络上传输过程
一般这类软件都采用典型的Client/Server结构,由客户端与服务端两部分构成。客户端主要是负责向服务端发出获取服务端屏幕图象的请求与将从服务端发送而来的屏幕图象在本地实时地显示出来,而服务端主要是负责响应客户端的请求并抓取与发送屏幕图象。由于服务端所抓取的屏幕图象一般为位图格式,其数据量较大,若直接发送则会导致占用网络带宽过大、实时性差、占用系统资源过多、稳定性差等问题,因此需经过压缩后才能将其发送给客户端,而客户端相应地也要将接受到到屏幕图象数据进行解压缩后才能正确地将屏幕图象显示出来。
解决目前普遍存在的问题的关键就在于屏幕图象数据的压缩与解压缩和屏幕图象的抓取上。对于屏幕图象数据的压缩与解压缩这一点,主要追求的是较高的压缩率与较快的压缩与解压缩速度,这可以通过选取一定的压缩与解压缩算法如Huffman、RLE、LZW等来实现,因此目前这一方面的提高余地已非常有限。对于屏幕图象的抓取这一点,很多开发者在开发过程中却不够注意甚至是忽略了所选取的屏幕图象抓取方法的重要性,而采用了常用的一般的抓取方法。其实,屏幕图象的抓取与数据的压缩与解压缩一样重要,将对屏幕图象的实时传输过程产生极其重要的影响,因此这一方面尚有较大的提高空间,本文也就此着重进行详细的阐述。
二、 屏幕抓取与传输方法及其改进实现
1.  屏幕抓取模式的选择
屏幕抓取模式有多种,如在Delphi中可用的抓取模式CopyMode有cmSrcCopy、cmSrcInvert、cmWhiteness等15种。当采用cmSrcCopy模式时,则直接将待拷贝的源位图拷贝至目的画布中,目前普遍的处理方式便是采用这种抓取模式抓取整个屏幕图象,然后直接将其进行压缩。而若采用cmSrcInvert模式时,在服务端则先将目的画布中已有的位图与待拷贝的源位图位值进行XOR异或运算后,然后将运算所得的位图进行压缩,相应地在客户端显示时,应将解压后的位图位值与当前的位图位值进行XOR异或运算,则运算所得的位图便是服务端当前所传送的屏幕图象。虽然后一种屏幕图象抓取方法比前一种分别在客户端与服务端多了一步位图位值XOR异或运算,但是经压缩后,采用后一种方法数据量一般比前一种方法的数据量要小得多。这主要是因为一般情况下屏幕图象总是在一个局部而非整个屏幕发生变化,将当前屏幕图象与上一屏幕图象进行XOR异或运算后,所得屏幕位图未变化部分的位值将为0,而变化部分的位值为1,当屏幕图象变化范围较小时,则所抓取的屏幕图象位图的大量位值将为0,同时压缩率除与压缩算法有关外,还与待压缩的数据本身有关,因此这样对其进行压缩将取得更加理想的压缩效果。
虽然采用两种抓取模式所获得的屏幕图象数据都一样,但从压缩后的数据量来看,cmSrcInvert 模式下的压缩数据量明显小于采用cmSrcCopy模式的压缩数据量。测试时使用的是Delphi5.0中自带的一个数据流压缩、解压缩解决方案Zlib.pas和 Zlibconst.pas两个单元文件来解决数据压缩、解压缩问题,达到了很高的数据压缩率。这就说明采用cmSrcInvert 模式将比采用cmSrcCopy模式在传输屏幕图象数据时占用少得多的网络带宽。
喜欢0 评分0
[color=blue][size=4][i][b][u] 【 解决不了的事情,就不要想。世界不会因为我而改变。 】 [/size][/u][/b][/i][/color]
queensf
总版主
总版主
  • 注册日期2003-12-04
  • 发帖数735
  • QQ
  • 铜币3枚
  • 威望0点
  • 贡献值0点
  • 银元0个
1楼#
发布于:2004-04-12 02:04
在Delphi中的采用cmSrcInvert 模式抓取屏幕图象的实现程序代码如下:
…………………………..
var
     bmp, difbmp: TBitmap;//两个用于保存屏幕图象的临时位图对象
R        : TRect;//用于保存抓取范围的一个Trect对象
tmp      : string;//用于保存压缩后的数据的变量
…………………..
CatchScreen(bmp, PixelFormat);//按指定的PixelFormat抓取全屏并将其存放于bmp中
difbmp := TBitmap.Create;
difbmp.Assign(bmp);//将bmp中的位图传递给difbmp
R := Rect(0, 0, difbmp.Width, difbmp.Height);//指定拷屏范围
difbmp.Canvas.CopyMode := cmSrcInvert;//设置拷贝模式为cmSrcInvert,非常重要
difbmp.Canvas.CopyRect(R, CurBmp.Canvas, R);//将当前图象与上一次的图象进行XOR运算,将结果位图存放于difbmp中,其中CurBmp是一个全局变量,存放的是上一次抓屏时的屏幕图象
CompressBitmap(difbmp, tmp);//将difbmp进行压缩,压缩后的数据存放于tmp中
CurBmp.Assign(bmp);//当前图象存入CurBmp,下一次抓屏时进行XOR运算使用
…………………………………
屏幕图象抓取过程CatchScreen的实现代码如下:
procedure GetScreen(var bmp: TBitmap; PixelFormat: TpixelFormat);
var
   dc : integer;
   c  : TCanvas;
   R  : TRect;
begin
   bmp := TBitmap.Create;
   dc := GetWindowDC(0);//获取整个屏幕的 DC,参数0指的是屏幕
   try
c := TCanvas.Create; //创建一个canvas对象
c.Handle := dc;
R := Rect(0, 0, Screen.Width, Screen.Height);//指定抓取范围,采用的是全屏
bmp.Width := R.Right;
bmp.Height := R.Bottom;
bmp.Canvas.CopyRect(R, c, R); //把整个屏幕复制到bmp中
bmp.PixelFormat:=PixelFormat;//设置位图格式
      c.Handle := 0;
      c.Free;
   finally
      ReleaseDC(0, dc);
   end;
end;
[color=blue][size=4][i][b][u] 【 解决不了的事情,就不要想。世界不会因为我而改变。 】 [/size][/u][/b][/i][/color]
举报 回复(0) 喜欢(0)     评分
queensf
总版主
总版主
  • 注册日期2003-12-04
  • 发帖数735
  • QQ
  • 铜币3枚
  • 威望0点
  • 贡献值0点
  • 银元0个
2楼#
发布于:2004-04-12 02:05
2.  屏幕分区域抓取与传输
如果采用的是一次性整屏抓取,由于数据量大,往往无法取得较好的实时效果,尤其是在网络带宽有限时这一点特别突出。其实,服务端完全可以通过将屏幕划分为一定数量的大小相等的矩形分别在抓取模式为cmSrcInvert下进行抓取与压缩,然后将压缩后的数据添加到一个待发送的队列中去等待传输。或者是比较所抓取的矩形区域图象是否发生了变化,若未发生变化则不处理,否则才进行后续处理。与此相对应,客户端从接受到的数据队列中取出队头数据进行解压,然后在相应位置上显示出一个矩形大小的图象。考虑到实时性要求,实际编程实现时可采用多线程程序设计,利用多个线程分别处理不同区域的屏幕图象。这种方法能够在不同的网络带宽情况下均获得较好的实时性,占用系统资源也较少,而且也会使软件的稳定性增强。但是必须要注意的是屏幕划分区域的个数应根据实际情况主要是网络带宽来设定,因为若划分区域的个数过多则会导致对每个矩形区域图象进行抓取、压缩、传输、解压和显示的时间总和反而超过整屏处理的时间,这样虽然网络带宽占用小,但实时性可能下降。又若划分区域的个数过少,则较整屏处理占用的网络带宽下降幅度不大,效果不明显。在10M的局域网中划分区域的个数为4~6个时效果较为理想,此时每个矩形区域图象的数据经压缩后为1~2KB(分辨率800*600,增强色16位),比采用整屏处理时的占用的网络带宽要低得多,而且实时性也有一定的提高,延迟较低。
服务端:
1.  抓取一个个矩形区域的屏幕图象;
2.  判断矩形区域图象是否改变;
3.  对发生改变的矩形区域图象进行压缩,并放入发送队列;
4.  跳转到第1步。
客户端:
1.  从接收队列中取出一个数据块
2.  解压数据块,确定矩形区域位置
3.  在指定位置显示矩形区域图象
4.  跳转到第1步

实际实现时,可用下列的数据结构来存储每个矩形区域图象的数据:
TYPE   PNext=^GdiRect;
        GdiRect=RECORD
                X1,Y1,X2,Y2:Integer;//保存矩形左上角与右下角的坐标
                Bitmap:TBitmap;//保存所抓取的屏幕图象
                PixelFormt:TpixelFormat;//保存所抓取的屏幕位图的位值格式
                CompressionLevel:TcompressionLevel;//保存压缩级别
                Changed:Boolean;//保存该矩形区域的屏幕图象是否发生变化
                GdiRectNext:PNext;//指向对列中下一个数据块的指针
                ………….;//根据需要所添加的其他一些变量
                …………… ;
                END;  
[color=blue][size=4][i][b][u] 【 解决不了的事情,就不要想。世界不会因为我而改变。 】 [/size][/u][/b][/i][/color]
举报 回复(0) 喜欢(0)     评分
queensf
总版主
总版主
  • 注册日期2003-12-04
  • 发帖数735
  • QQ
  • 铜币3枚
  • 威望0点
  • 贡献值0点
  • 银元0个
3楼#
发布于:2004-04-12 02:05
三、 屏幕图象数据流的压缩与解压缩
对于所抓获的屏幕图象数据的压缩与解压缩可以选用多种算法如Huffman、RLE、LZW等第三方提供的解决方法,Delphi5.0本身就自带了一个非常优秀的数据流压缩、解压缩的控件包Zlib,可以利用其中的Zlib.pas和 Zlibconst.pas两个单元文件提供的接口来使用其中的方法。具体实现的思路如下:首先利用屏幕拷贝捕捉到当前整个屏幕的图像,然后在内存中保存为 BMP文件格式。在服务端进行压缩时,使用 TCompressionStream对象对原始图像进行压缩并且保存为自定义的变量中;在客户端解压缩时,使用 TDecompressionStream对象对被压缩的图像进行解压缩,还原为 BMP格式的图像文件并随即显示出来。压缩与解压缩具体实现的代码如下:
服务端要使用数据压缩过程CompressBitmap的实现代码如下:
procedure CompressBitmap(bmp: TBitmap; var Data: string);
var
   cs      : TCompressionStream;//该类由Zlib.pas中定义
   ms      : TMemoryStream;
begin
   ms := TMemoryStream.Create;
   cs := TCompressionStream.Create(clDefault, ms); //clDefault为默认压缩级别
   bmp.SaveToStream(cs);//源数据存放于cs中,压缩后的数据存于ms中
   cs.Free;
   SetLength(Data, ms.Size);
   Move(ms.Memory^, Data[1], ms.Size);//将压缩后的数据存放于Data中
   ms.Free;
end;
客户端使用的解压缩过程UnCompressBitmap实现代码如下:
procedure UnCompressBitmap(const Data: string; bmp: TBitmap);
var
   ms    : TMemoryStream;
   buf   : pointer;
   size  : integer;
begin
   try
      DecompressBuf(@Data[1], Length(Data), Length(Data) * 3, buf, size);// 指针buf指向解压后的数据存放的内存地址
   except
      on E: Exception do begin
         E.Message := Format('Error Decompressing Buffer (Len = %d):'#13#10'%s', [Length(Data), e.Message]);//解压出错的异常处理
         raise;
      end;
   end;
   ms := TMemoryStream.Create;
   ms.Write(buf^, size);//将解压数据存入内存流中
   FreeMem(buf);
   ms.Position := 0;
   Assert(bmp<>nil);
   bmp.LoadFromStream(ms);//位图bmp对象从内存流中读图象数据
   ms.Free;
end;

在整个实现过程中,除了考虑屏幕抓取和数据压缩、解压缩这两个核心问题外,还要注意选择好用于传输的网络协议以及服务端和客户端双方的协议等。利用上述技术和方法能够有效地对网络上的计算机、网络设备进行控制与管理大大地提高了工作效率和减少了工作量。



参考书目:
Delphi5开发人员指南 Steve Teixeira Xavier Pacheco著 机械工业出版社
Delphi高级开发指南 Marco Cantu Tim Gooch John F.Lam著 电子工业出版社
[color=blue][size=4][i][b][u] 【 解决不了的事情,就不要想。世界不会因为我而改变。 】 [/size][/u][/b][/i][/color]
举报 回复(0) 喜欢(0)     评分
queensf
总版主
总版主
  • 注册日期2003-12-04
  • 发帖数735
  • QQ
  • 铜币3枚
  • 威望0点
  • 贡献值0点
  • 银元0个
4楼#
发布于:2004-04-12 20:32
自己顶!!
[color=blue][size=4][i][b][u] 【 解决不了的事情,就不要想。世界不会因为我而改变。 】 [/size][/u][/b][/i][/color]
举报 回复(0) 喜欢(0)     评分
Hope2004
路人甲
路人甲
  • 注册日期2004-04-19
  • 发帖数28
  • QQ
  • 铜币79枚
  • 威望0点
  • 贡献值0点
  • 银元0个
5楼#
发布于:2004-04-19 17:34
Very Good
举报 回复(0) 喜欢(0)     评分
zjjtwmj
路人甲
路人甲
  • 注册日期2003-08-02
  • 发帖数370
  • QQ
  • 铜币988枚
  • 威望0点
  • 贡献值0点
  • 银元0个
6楼#
发布于:2004-05-07 10:20
举报 回复(0) 喜欢(0)     评分
gis
gis
管理员
管理员
  • 注册日期2003-07-16
  • 发帖数15947
  • QQ554730525
  • 铜币25339枚
  • 威望15364点
  • 贡献值0点
  • 银元0个
  • GIS帝国居民
  • 帝国沙发管家
  • GIS帝国明星
  • GIS帝国铁杆
7楼#
发布于:2004-05-07 13:19
不错<img src="images/post/smile/dvbbs/em01.gif" />
举报 回复(0) 喜欢(0)     评分
queensf
总版主
总版主
  • 注册日期2003-12-04
  • 发帖数735
  • QQ
  • 铜币3枚
  • 威望0点
  • 贡献值0点
  • 银元0个
8楼#
发布于:2004-05-09 17:36
<P>有人来看就好啦!!呵呵!</P>
[color=blue][size=4][i][b][u] 【 解决不了的事情,就不要想。世界不会因为我而改变。 】 [/size][/u][/b][/i][/color]
举报 回复(0) 喜欢(0)     评分
游客

返回顶部