冒险解谜游戏中文网 ChinaAVG

标题: 【汉化资料】电脑屏幕捕获编码设计案例 [打印本页]

作者: shane007    时间: 2009-5-28 00:22
标题: 【汉化资料】电脑屏幕捕获编码设计案例
原文3 M3 S/ F& I  X
http://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx
  m& G# t4 f7 v* u5 t! ?1 e4 V+ n1 v: A! m
       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。
8 e! D% b/ i2 R  M( ~1 S% L6 t
! X6 l! P4 H% S
$ V0 {& u$ g; R/ R
用GDI方法捕获$ X- K1 k/ T# b  p' j3 U, i
" y( h, k/ g# K$ q7 h
       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:
1 C) {8 y9 A( k6 D" x4 D
  p1 G8 r: F+ w1)  使用GetDesktopWindow获得桌面句柄" L$ z. P  R4 {# Q) p% v

' u$ E6 j3 C0 ?" j. c2)  使用函数GetDC获得桌面窗口的DC* O" |  Y" W$ ~+ x7 w* N& |" D

5 O& \6 p# X8 n7 I, g$ s3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC
  U5 k# h  b) L+ G' G- z, J, S* ?& O' a
4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容* ^, [; g2 W5 q/ E

5 Y+ d$ Q& A3 D  b4 g1 x: ?8 @5)  当完成捕获后,不要忘记释放对象
# c0 q% f) U8 s4 N
- U+ ?/ E6 U/ I' V( L例子:
; Y  ?: @6 |# @% v1 y; n  x, N$ T/ \( Y7 ^7 V  c# d
Void CaptureScreen(), J* C, y$ T6 l% d" h3 r
{' ^. q& Q8 c( j
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);/ i% g0 D; X- b' _+ F1 J
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);9 I4 \+ I$ q* ^! Z$ h0 B  O! p
    HWND hDesktopWnd = GetDesktopWindow();) r- `* W# b4 r2 h& `
    HDC hDesktopDC = GetDC(hDesktopWnd);( ^, o5 Z; W1 u, J9 T. j! ]
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);; g0 E6 m2 \3 A9 v
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
8 R5 e9 g2 f- J- ]1 c; m; G                            nScreenWidth, nScreenHeight);
8 g0 v# K* b- Q& d4 q2 |2 X! f- y4 c    SelectObject(hCaptureDC,hCaptureBitmap);
# k9 l6 d; K' ~/ [) `' {4 c& e    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
0 c2 g- |. X  s$ `, C' P" K           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT);
8 Q+ n7 W8 e; S8 q( F( x    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码
: _1 z- i2 V1 r* ]3 e' {    ReleaseDC(hDesktopWnd,hDesktopDC);! A1 [7 m3 A. k/ u- x
    DeleteDC(hCaptureDC);/ r4 [% A9 G+ `
    DeleteObject(hCaptureBitmap);
" L& s" S! q6 H! x" n}7 r1 `5 |9 l  f8 ]: L7 O
上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。
4 \9 {1 i0 B# J+ v' b
! @! M" j) Z7 ~. }4 e
5 i4 I( q* ^! s" @
, x2 V1 s1 f# H; e1 ]1 }  R采用DirectX的方法:
1 L, n+ I4 g. _- u
7 n" y4 }) l% Z- {1 V6 X9 \    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。# x6 P: a1 N8 y. R$ u4 H
+ l6 Z, e: p+ p, G1 u  o4 u
每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。
5 e; a3 R  d1 w7 [: Q, |
; A9 m0 [1 u1 `" W, g
  t/ m  |/ o2 b1 Y3 W3 e  i3 X% n5 J6 b0 u, s$ w  l. [
通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:4 n, K4 Z2 f' k, c# W

9 Q# o7 ~) p8 }% ~2 Kextern IDirect3DDevice9* g_pd3dDevice;5 `' B5 c% a( D( Z8 h
Void CaptureScreen()/ E6 D% G: z/ \8 s: @# |
{& T7 j; i$ f& e) F) K% H, F: W9 n) Z
    IDirect3DSurface9* pSurface;
9 u1 H& w5 ?( a3 i    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
' K4 B# f7 D7 _4 X/ N- O6 ?        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);, K6 F  j! @* m0 }" C  x
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
2 h9 K7 w* }) n    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);4 ]1 L8 Y0 ^' @3 V; Y
    pSurface->Release();
' v7 L. l! j4 y3 n6 K! ^}
  o8 }1 ~$ ?" W) ~  r1 `上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:- o) q! }9 V6 u7 j
3 {  P/ s1 t" l+ w' [: `
extern void* pBits;
4 M) L/ ^- x' V; L5 c& w* V% u2 Dextern IDirect3DDevice9* g_pd3dDevice;
$ C; \& O9 `; j) p0 L4 ^IDirect3DSurface9* pSurface;, ]0 S* v2 Q1 M& J4 S3 K& V/ t
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,- d7 j$ y  W6 Z/ i
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH,
4 U7 c4 m) Y& e+ j6 }                                          &pSurface, NULL);
! A6 c- i; h7 {, r+ Wg_pd3dDevice->GetFrontBufferData(0, pSurface);
4 O* z4 q% c* UD3DLOCKED_RECT lockedRect;
, I1 w6 l; N7 X) G* k* n! q, F, ^- kpSurface->LockRect(&lockedRect,NULL,
$ b+ S& H2 W$ _3 @/ c6 i9 M                   D3DLOCK_NO_DIRTY_UPDATE|1 l( _, f! u9 C, `& m0 t
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));! S0 k" V/ i1 o
for( int i=0 ; i < ScreenHeight ; i++)
9 ~7 J# x% G3 H: |( c% p( F$ ^{
7 N5 G& U1 V: b  Z. G% F) ?    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 ,
& S2 N; f" M7 n# J) L* ~& l  @. Z        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
4 \+ M/ P) X9 j* N        ScreenWidth * BITSPERPIXEL / 8);- ?7 N% f9 z0 W
}
: [6 e5 o, T9 [1 D% K  L0 cg_pSurface->UnlockRect();
4 ]5 }2 q3 I. ~. L6 R+ r+ \pSurface->Release();
5 i( d5 Q' J8 ^& k
1 E( [$ D9 H1 L! s9 c
! U* f( S% P" ]1 g# z( h" w上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:
( \5 g# {. b' |+ P7 i0 O6 g! T
* i  y  S% \# O8 [  wfor( int i=0 ; i < ScreenHeight ; i++)
' \. H7 j6 ~: T, ^9 W( I: k{# B9 R" y: \, a0 F
    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
/ F* z# S# H# b9 `' _5 b" D; x        ScreenWidth * BITSPERPIXEL/8 , 6 e  D3 ~* U( r/ v2 N+ ?- s
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
1 q5 ]& s. G4 P, z        ScreenWidth* BITSPERPIXEL/8);* R- ~9 k; b9 r' p, u
}8 f& a" C/ _& d
) v2 W7 a1 G" I, s) c

. p8 Y& R+ Z" W  z这点代码用于转换自顶而下和自底而上的位图。
, k" d: W3 f" x0 O  j% `8 g$ Q2 B; _
上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。
5 h% B! y6 C9 y+ S1 e
2 |3 o1 g& W- |6 N! A5 p: k) F 2 m: }$ G% z+ Y# ?9 P8 y
6 [7 r/ C/ N5 j9 _% H: v( L+ b& G
然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。2 d2 Q$ @, J  B# p# k: W

( D) [! x! S2 J  U4 E1 z * n. V/ v( J- J) C. H1 E& D& n0 I1 W0 V

4 ]7 [' Z( `" u: a- _捕获程序要点:
/ S+ N8 g: t) w- T9 k5 k+ @3 h# z/ e& A/ f4 J6 \" c" A7 O5 q
    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:4 N* q$ e; Y. Q9 M4 i0 |  L" F
, o' o4 t% `4 M3 Z) N9 q# h
1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。9 J% D( ~- Q- M( J& V* b' H3 c& ]
% b& v" ]6 P& u' X: X# [3 R6 ?3 y8 V
2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。) y9 b" U8 I( O4 G- F+ Z5 R9 M$ c

3 n0 f1 O7 F2 }6 X" o( Y) h# I4 W" C0 Q3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。
9 a( j% v# i5 E% {
9 h; d9 Y$ ~& t' M" A6 r% V  W4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送
7 y: V6 R, I, ^" H, h
5 n3 ^9 e3 L7 T5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标% Y. b' A) O) v1 P8 k7 {) t
) a% `  D& b  J2 t
* e/ n# i8 M0 x
6 Y. D- R- {  J" x
程序运行截图:  d8 [$ y: ^8 p# p3 ]' ?1 h* b* u
3 R6 ~$ _! D8 N( e

: p1 f4 ^- Y6 Q该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。
7 w# ?  V( y9 h6 Y; b
2 J7 u0 L( j, l( B. M/ T) l一些可能的改进:
% k3 a/ K; j6 P) H* p; F
3 T- o* e. _( m) S& s: l  j' e1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦, ]. o; F5 t+ Y
4 s! f) x! V  w4 N! @: R+ |& Q1 Q
2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。
: G9 K- w6 |2 ~9 \
) X! O3 R  l7 ]% t) K1 N3.编程中的一些优化和保护措施没有做好,程序运行的时候占用的CPU资源比较大




欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/) Powered by Discuz! X3.2