设为首页收藏本站官方微博

【汉化资料】电脑屏幕捕获编码设计案例

[复制链接]
查看: 1768|回复: 0
打印 上一主题 下一主题

【汉化资料】电脑屏幕捕获编码设计案例

跳转到指定楼层
楼主
发表于 2009-5-28 00:22 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

【汉化资料】电脑屏幕捕获编码设计案例

原文1 ?; K/ f& n( c8 G1 a/ _/ U- y
http://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx! V4 C% ?- j. W, T2 W
$ V5 T7 Y2 u  N5 l* s* F
       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。
; n0 y5 U# u0 u: c  _( `
! C: ?3 }$ `8 m$ L3 F& _, S3 g
4 |3 m( Z" ^8 ~/ t# c' y2 K+ o* r' y7 K
用GDI方法捕获( j2 x( r$ S" t  o; d% w
# }2 I6 v! }4 I& h3 S! y  _( k$ M
       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:
4 F3 K2 T& \$ \. y9 o  z9 n! T9 b1 ]/ o: ]1 d0 I, k( W5 S+ ^
1)  使用GetDesktopWindow获得桌面句柄# }2 d$ b. u* ^4 J+ y+ A# T3 h

. @4 w( f) o# w( B3 b2)  使用函数GetDC获得桌面窗口的DC% E3 i1 V$ T, ]1 f( A
* H. M# h5 g2 @( n- r8 u$ I: ^1 u
3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC
6 I) J) Y, B3 `# }8 }8 A+ Q/ @
, B2 K; \' R1 U+ N% X4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容
" |7 b0 k0 f$ N0 T+ e& R: a6 w
4 j/ z2 Y! C" ^5)  当完成捕获后,不要忘记释放对象0 ?* O2 {4 V+ {# r

. x, Z; Y) t! \% I' y* R$ z例子:/ z+ r: w8 s1 c
0 c  V) I: ^+ G% j
Void CaptureScreen()" g& R. y. G% y# y7 p% J5 I/ s
{5 r' n5 g: l5 \5 U( @/ X
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
4 {( m: }* F. t$ I- v+ x    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);( |$ ]$ r& f& _* Q4 f# O* l
    HWND hDesktopWnd = GetDesktopWindow();
7 D1 C- F5 |4 K$ c4 Q$ B    HDC hDesktopDC = GetDC(hDesktopWnd);9 Q% z5 a5 L* g4 L% T  L, g4 u
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
; ?' f' O- Z6 o, c9 T4 G    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
, Q- o8 o: _* V6 m) D                            nScreenWidth, nScreenHeight);
+ ?$ p+ q3 ?/ ~2 x$ g  ^) @    SelectObject(hCaptureDC,hCaptureBitmap); , H* e; S+ \4 Q! a7 a
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
7 W* \% ^7 j- o- i( m0 C9 N           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT);
6 w+ l2 ^( u4 R. D    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码- ^7 V8 b  W+ w: T+ H7 F' h
    ReleaseDC(hDesktopWnd,hDesktopDC);; t. d  w9 }6 S6 H. `8 d" {. I1 Y& S
    DeleteDC(hCaptureDC);
5 B4 K( N, T1 K5 O  E7 V    DeleteObject(hCaptureBitmap);
# E5 t0 {& v6 f" F/ n5 b}$ v% s) ~9 S0 }
上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。
  ]1 U5 \" h" I  A  y- z6 i
3 P7 e9 T* ~, ~! e* X/ `6 v . v+ S# h6 L' d0 X
' M# V5 i% t) |! m5 w
采用DirectX的方法:
$ u( v% O3 A0 _1 e) X
/ M3 u- ^1 X: m5 a$ q# A    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。  q5 m( z# ?* R6 B7 H6 `

' U8 V1 S/ ^) F" D0 a每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。* x8 f' @& f2 v8 o6 C
( R+ L- t' \; t

. {8 U$ F& L, @3 z
6 R, Y' ~: f9 N& I* a通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:
2 F# B7 [$ @6 `( b1 E( I9 X7 |0 q: z/ e# K# T# e
extern IDirect3DDevice9* g_pd3dDevice;) z; S% T' S; b; a0 p
Void CaptureScreen()! j2 c- W. T0 f  q, `0 M
{
0 K% V1 j  J) J$ k/ Z    IDirect3DSurface9* pSurface;
* ~' I. [' l& e    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,$ w7 T6 ~% Q# {! O/ ]  ^
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);/ @5 ^" D6 i& F6 ^
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
/ H7 `* X% c+ U2 h9 \. B+ L    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
. y* }/ z9 @  v" ]% \9 |: t% U9 E1 z    pSurface->Release(); . |! V! O) k) l8 l4 P
}; a% }2 ]+ d2 w4 v
上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:
( c6 F$ U2 R' J  ~+ v  d, u5 o
6 d8 l7 h2 P' o  Hextern void* pBits;- q: J; x8 c! z2 n# @
extern IDirect3DDevice9* g_pd3dDevice;
+ ]5 |5 F8 R: o! EIDirect3DSurface9* pSurface;
0 Q, `( w% x9 Q' f! F' fg_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
9 P0 P/ Y1 r3 ]8 V! T+ E) k                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH,
& Q" Z0 n# r, g3 [$ @& G                                          &pSurface, NULL);
) ?, K* z; i  S4 Rg_pd3dDevice->GetFrontBufferData(0, pSurface);/ L1 k% x3 }0 k! a% e
D3DLOCKED_RECT lockedRect;
" |# c9 e& j) G* l# m8 apSurface->LockRect(&lockedRect,NULL,6 G: ?3 W2 R% ?, v# x, n
                   D3DLOCK_NO_DIRTY_UPDATE|
3 L8 }7 a( |8 v( O6 c. W8 f; M( C                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
. p" l, P* Y1 Z) V1 p0 g. Q) b9 _for( int i=0 ; i < ScreenHeight ; i++)
5 o# S2 _. i: D& }/ f- ^{
) U- b8 h" h7 t2 W7 U- ]    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 ,
- H* W3 n! |, [        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 6 @2 G# M4 g# O: j, x9 i) y
        ScreenWidth * BITSPERPIXEL / 8);
: C# k& m$ k5 m0 v' e# v7 J}
. c" h9 e9 H# o  X+ k' u- z2 ig_pSurface->UnlockRect();& y6 q$ q, Q9 p$ T; n! X0 R( Y8 u6 i
pSurface->Release();& W" m2 ?" D) H9 u9 @8 w# i
- |. b2 y1 ^: R' x; n
6 G0 \8 Z! u7 M4 u3 o: f* n
上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:
/ E# y3 \/ T- @( H/ }$ K' M- o( z. a% a' w( D# d
for( int i=0 ; i < ScreenHeight ; i++)) f: g+ I( j" [) U) \+ L' m
{0 L. E) ?" [4 v3 j
    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
% _+ z  l3 Q/ Y- I9 M1 H        ScreenWidth * BITSPERPIXEL/8 , " Z1 N6 z& W0 x) ?5 p9 E
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , / |8 w4 P% {# `1 h1 [! `/ S* j9 c
        ScreenWidth* BITSPERPIXEL/8);
7 ~! h( `& @6 M/ s, Y3 [( T}
2 J; |4 A" `7 Y3 B0 H; U ) }$ M! E! R/ i; _7 ^5 |
' E) c$ B4 k8 k$ v5 x
这点代码用于转换自顶而下和自底而上的位图。' K8 m& V9 Q  ^. o3 a& V

8 b% t6 D& ?7 C' A- H. ]; Z& p上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。
( F% ]$ b! L0 u4 f/ C* U2 U+ l0 b
1 M1 \- T+ y0 t7 Y ( m& C, j# [5 s& a7 t2 ]

" I. P) i$ [5 a然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。
7 h; E1 E* v% p2 z  _" E
( m0 Y+ Y  t# E- S  k2 X: J
. w3 k* |3 W1 A* i1 n, x# `! {- p; v
捕获程序要点:4 X# P2 a! T) S
4 Z# S. h+ \; w* Z* r) Y1 ~
    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:% i6 p: x2 i+ [" D
) r+ c. i$ o+ S+ b
1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。! f* x" N( g9 K& _9 X- n7 K
7 `* ~0 v) q+ c" M0 C( d$ ]
2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。2 F/ K7 B: _5 i; i9 O
' r7 Y3 E) M( `$ X9 Y% z$ V2 k4 T: l9 L- q
3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。
! H4 M) n: ^# j) z5 q
1 T1 A- O2 k9 k( R8 P5 t/ d" _. f4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送
! O3 q: f2 U3 Q6 j  n' t" K7 m& U7 H7 A5 t! {5 L1 K2 L
5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标/ l6 E! L+ w8 K: w! |
: W& j$ N4 Q- R0 K; T
  g0 z( P. O/ U+ v; k% ?0 f

% f" m6 C$ T& s- B, Q, v4 [程序运行截图:) q1 H- |* J& V

' O5 `9 Z$ v: Q4 O1 n, a6 ?2 _/ k- h0 i0 m5 F. f
该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。1 h6 m8 M' D# c0 m- ?. e. m6 _: \
6 t- e3 Z4 P1 h# ]  @
一些可能的改进:8 N7 h+ N; D( L. x8 }5 ?

% o! y+ v# n& D) O5 C3 b1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦
$ s! [; f& F4 k# R) l
6 c  Q" _# _! W2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。
" D1 ]# ^$ ^. ?1 Z2 e2 U4 F
% J" ]8 v! _* z6 Y+ O6 `3.编程中的一些优化和保护措施没有做好,程序运行的时候占用的CPU资源比较大
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享分享 很美好很美好 很差劲很差劲
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

冒险解谜游戏中文网 ChinaAVG

官方微博官方微信号小黑屋 微信玩家群  

(C) ChinaAVG 2004 - 2019 All Right Reserved. Powered by Discuz! X3.2
辽ICP备11008827号 | 桂公网安备 45010702000051号

冒险,与你同在。 冒险解谜游戏中文网ChinaAVG诞生于2004年9月9日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

快速回复 返回顶部 返回列表