冒险解谜游戏中文网 ChinaAVG

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

作者: shane007    时间: 2009-5-28 00:22
标题: 【汉化资料】电脑屏幕捕获编码设计案例
原文5 U( |1 M; e) |, i% y6 Z  y
http://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx3 A$ E  A$ b  v2 \( P, M

# M( U+ _$ j9 X/ L2 g' s5 {  k% g       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。5 \8 v7 j7 H9 r& M: s
4 G) n/ A" w$ H6 }# I6 u
, G7 u, I4 U+ q6 s& e. b* |" N/ c) a

! c6 k1 u* w  I& T6 w用GDI方法捕获
$ y, E9 t  Q7 u) |& |& u
. }* V- j, O; ^2 v6 Z8 ?6 e: g9 p' M       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:. L! e/ y" t' r8 S  p# ^
0 o; {; R1 _. K* F$ [. Q
1)  使用GetDesktopWindow获得桌面句柄+ y# T% K: _4 [1 u* I* d1 T

2 r$ B* M6 K% k1 L9 M" o2)  使用函数GetDC获得桌面窗口的DC: S4 k( X5 J. D3 ~' w

+ E( J4 s' P- k) M' k9 o7 x6 }3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC
) _+ \5 ]- _. y3 S  u
; m& O1 M9 H9 |1 s4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容/ @6 L1 O  N" B1 B3 U
) i2 Z' V3 Q& l* C! x! s
5)  当完成捕获后,不要忘记释放对象9 Q, w5 L& x0 a" C3 L

3 z) O3 M% H$ {; a例子:
0 x1 G$ @0 @% `9 r( b! a6 Z
+ y3 O" R0 I3 g( U: [* sVoid CaptureScreen()( l  ~; s8 ~* c- ^
{
  C1 Y1 S+ X" U( Q9 H6 N    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
. L. P  T6 v0 u% _, O2 S2 W+ u    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);7 E" h0 ^0 \! f9 ~
    HWND hDesktopWnd = GetDesktopWindow();
. H3 y5 o4 U- r7 i    HDC hDesktopDC = GetDC(hDesktopWnd);& i; e6 Q4 O% y
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);# j; W5 _( ]8 Y  T8 T
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
. Y. D0 s/ }  ?# Y                            nScreenWidth, nScreenHeight);
0 |8 j, b5 s4 T; g5 A; x1 M    SelectObject(hCaptureDC,hCaptureBitmap);
" F, l% u$ c: ^5 _4 m    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,! k1 p0 E# e+ S, M4 t, G$ F* e0 C
           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT);
2 c7 d: k$ l+ L$ i; I) v" q    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码
4 A+ Q8 G8 d: x  c  I( G    ReleaseDC(hDesktopWnd,hDesktopDC);
# @/ ^6 O: O2 Z. K2 [    DeleteDC(hCaptureDC);9 D2 w* K1 s* m6 @% ^$ M
    DeleteObject(hCaptureBitmap);
( \5 C) E, M' |* s}
: A6 I+ n" z9 s4 q上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。
0 Y: s( f) Y/ |
3 P4 l/ {) V1 s- U* u6 A4 K
2 ^6 R( R; o1 r/ X3 w% P- B& {& s& V+ g- E% H
采用DirectX的方法:' I; K0 D9 s2 l4 h7 R. J8 s8 O

3 J# h/ s4 C; R# G    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。
9 u8 i/ z2 e4 S: i$ U
% C4 H1 P& L* V) P每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。4 @7 K. g+ O. e% Z
3 Z4 O. H, G9 m0 C2 V4 D

* h  N; }% a# ]
6 S0 E5 X, E$ X) D' c7 e通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:
. S3 s, D/ j! r$ H
3 b8 T: r6 Z; R5 oextern IDirect3DDevice9* g_pd3dDevice;: Q! Y  Z7 _0 |7 f
Void CaptureScreen()
$ W5 A4 u9 O. Y0 |1 ~{9 p- u8 D! \1 X" k" Q
    IDirect3DSurface9* pSurface;
( P1 t  F( }7 ]& E' f5 a* l, z0 d8 i    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,1 D# N" E7 p% L% u# Z7 x
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);. r: G% ]0 f: n2 W! _/ \
    g_pd3dDevice->GetFrontBufferData(0, pSurface);& _/ x* }' G( M$ E$ t% `) ^) z1 a
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
7 @3 P- ]6 b* p    pSurface->Release(); " C: V5 y& N! F5 n
}; N: c8 d# z! Y
上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:
# N( o8 s- B- Y( T7 S8 P
1 k7 X* H8 |+ o6 fextern void* pBits;; |4 }) [% _: I$ Q6 W0 `6 _
extern IDirect3DDevice9* g_pd3dDevice;
) y& v1 K1 B( _( pIDirect3DSurface9* pSurface;2 c+ ^! b! V% t. i0 p
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
$ P+ e. o+ ?9 i' ]                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, 8 ?# m2 q# r+ M2 M  e
                                          &pSurface, NULL);
! D) z% F3 U) bg_pd3dDevice->GetFrontBufferData(0, pSurface);/ i( t3 p4 A/ u$ |
D3DLOCKED_RECT lockedRect;
! L9 O/ t, e; J8 B7 ?pSurface->LockRect(&lockedRect,NULL,# |. l1 K/ A% [! r) e
                   D3DLOCK_NO_DIRTY_UPDATE|% K+ Y; N! E9 r, L0 H2 h
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
% }: A# {( S% Y5 _for( int i=0 ; i < ScreenHeight ; i++)6 B4 i9 P! y8 z  b9 b) j# q
{
9 }/ J, F( O7 o6 f- i    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 ,
5 X4 e: F/ ]9 _0 a        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 5 a% p9 |9 O, w. [# R' V
        ScreenWidth * BITSPERPIXEL / 8);
3 C8 a0 J  c3 s7 Z. B$ H}
9 X& X$ c- y4 c# K4 }g_pSurface->UnlockRect();
+ ?7 D! g$ M" q; f% n0 k9 d% kpSurface->Release();
1 b* a4 c* H: @7 f! r 0 f8 W0 a$ q" t- T
  \* ?  }2 O- K+ [; Y0 s4 K. q
上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:
# r3 E2 i: I9 _( D
1 p" p# Y* E' ~" G# Y: m1 nfor( int i=0 ; i < ScreenHeight ; i++)
0 Y0 f- [9 \7 ^# A" |, E- P{
. V* O5 w  Y+ ]4 s5 W8 |+ U( z    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
( D8 M7 _3 s) V1 Q        ScreenWidth * BITSPERPIXEL/8 , . B# J" f4 Q9 t0 b3 l
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
- z' Z  h( z3 N- g1 M9 V9 V" t# P' P        ScreenWidth* BITSPERPIXEL/8);
% z4 f; U1 n7 T# ^% R- E}
9 O9 J3 j6 T  Y, u9 p+ b
% n/ Q3 @1 O6 _0 X
* _5 ^- ~- Q" v9 s) F这点代码用于转换自顶而下和自底而上的位图。
+ k3 H+ K' I0 N1 \! k
* V' @1 w! J/ J" r1 Z上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。
' |* Z4 p3 G" n& w( b1 W/ D5 D9 J5 T7 I7 O& s! @# M' u
3 `: O4 J) p+ I* S% N! U" ]$ v6 V

) C+ X& J9 d( D! D然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。6 {' k* L9 L! p: x% l; E2 n5 o

; O: p' ?6 Z+ S
: b1 S: x8 B3 m& t
) U! ]! F' l" ?3 ~捕获程序要点:+ M+ v* Z; s) ?! P( H

9 J. Z( N0 G9 S  U' u3 H    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:2 B' ^0 k7 F3 j* J5 H

  }5 s9 ~0 ]0 S4 f  k3 N; h1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。
9 F0 k3 t; |% ?4 H# `0 V/ t3 o1 V: e. C# \; m* R6 V* X
2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。
# S( S1 H  Z; X
8 f1 Y% r& [/ [) E, i3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。5 K7 F7 u4 i" z1 x, A0 {

; D, K9 U1 X! o1 T4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送
5 T4 J3 i, Z9 }+ {4 q: R. _7 A" T1 k
5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标5 X+ z/ n# p* k; @, U# e! i
& j0 \- s3 @2 y0 W: W( j0 t& a
# C4 j: b$ g' i8 m2 Q: A$ _
' r+ m( K4 Z8 E0 S4 ^% ]  I& i
程序运行截图:
4 o& z! h9 g/ @1 g7 \! L5 Z$ G7 ?& v# q" u1 s: ]

- u! z+ Q9 C) A$ O1 f; }% {该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。
2 i) W) o$ V* d! }0 J) d# w+ I
( b0 z2 p9 N" i  s一些可能的改进:
( W- e: C+ k. X4 k/ i: b2 q  i1 k0 ]# f1 D9 k
1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦" r, C" z3 p& V$ x3 p7 E3 I
' l( O0 f, v* D2 g
2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。
' E3 F9 t4 h) X& C2 L1 f6 A" Z% W
/ l) e& a+ _5 {. x( n6 O; v3.编程中的一些优化和保护措施没有做好,程序运行的时候占用的CPU资源比较大




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