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

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

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

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

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

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

原文
% X$ J( b% G6 r' H6 V4 B. @http://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx& K! _; B9 X# c% w0 ?% k1 {; O

. I1 p* X! b$ x% t! w, C0 p       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。) _6 N% T1 ]+ R7 p* D
' z4 W( K- x: k0 P6 p  z" `
7 v$ O/ z. d. r: p

2 u7 o2 w1 s; W9 b用GDI方法捕获( m0 K, e. g7 \

2 s5 A8 e: o* y, o0 ?7 D       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:
! B* D6 v  P4 o3 A( a9 ^! |& L
6 O5 x# V$ i: D* s1)  使用GetDesktopWindow获得桌面句柄; ^5 z6 \5 I$ h  q

% \! S+ `# G8 W( X8 I! N6 c2)  使用函数GetDC获得桌面窗口的DC$ E! Y! c8 u0 h& I1 S7 v
2 T) z) y5 s: [1 V# u
3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC
! C+ h/ O# {) S) a) E# }; P3 |: i, O* C  `* |
4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容& D7 J/ G% _& u* F: g
0 U% X# @3 {$ X& Y) j$ b! q( i
5)  当完成捕获后,不要忘记释放对象
# ~4 T8 _5 Q' E# N5 g0 `" U
$ P3 X7 T+ ~: \5 q; r9 J例子:
! G* H0 z1 w, q- G
, I" [$ ^' G- t$ u# j1 L6 ?Void CaptureScreen(). _0 }- I7 l: g" R8 O; [* L% W: }
{9 f) H, x# e. ?( K: G
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);6 s* D) Y* x( ~7 ]* K, S1 q3 {! P
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);/ \& h/ G2 S' [; d, s
    HWND hDesktopWnd = GetDesktopWindow();
1 |5 X# \% U( [    HDC hDesktopDC = GetDC(hDesktopWnd);
$ j1 t( D9 G! v# b+ G: a    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);3 D! \" ?% n9 b( z9 d) k
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, 9 m0 B% \( y% b& d
                            nScreenWidth, nScreenHeight);
0 [6 q' N  {4 e. u* A% t5 g+ x    SelectObject(hCaptureDC,hCaptureBitmap); 2 X. p$ n; W5 J& ~8 ]: |& N) ]0 |
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,+ `1 L8 [5 W5 o7 g6 i
           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT);
& D- J2 S" R, d* f    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码5 }8 N  n0 N: A$ z0 F4 V
    ReleaseDC(hDesktopWnd,hDesktopDC);
- m! i! o& J0 @( L: q    DeleteDC(hCaptureDC);
% U) L$ r0 c  n: c    DeleteObject(hCaptureBitmap);
( u# S" X6 C. P}; U/ n4 D% p* L3 T
上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。( f2 N% m& b( L* j( ]1 _
( b: ]9 `2 ]7 {, ?3 b! b- Z
0 n7 |7 B  r" i5 a5 a. L" }  P. x0 V

# b# F6 M- m! s采用DirectX的方法:
1 _2 d. I# s; O3 }- n( Y8 i, ?/ X- E5 I5 b# I1 f: r
    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。, P6 R- K* [' N. M& W
4 J" p' C) _- w
每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。3 T1 S2 k4 K. z0 h+ Q/ i, d$ ^
" |! z, X2 k" \3 A* p& q5 s
7 N/ u, R6 l8 W# K

1 ?& k- u( x$ W) t0 E通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:. |3 ?8 D" |$ [7 G  O( w+ [" F' ^7 B

' ^! b$ G  K9 q5 w" ^5 \/ k1 M  Eextern IDirect3DDevice9* g_pd3dDevice;
" P/ I& z9 A* g( G9 _Void CaptureScreen()3 h- E' |) ]- y9 f, u. p
{# d; v. M" i, A* A- }
    IDirect3DSurface9* pSurface;+ D5 W4 {) R5 f( W' O7 f. M
    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,9 `$ |: j) Z& r- K. F5 \0 V8 k
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
3 e. ?# ]/ T" L  n4 E: U    g_pd3dDevice->GetFrontBufferData(0, pSurface);  Y( ~& N9 e& P$ x+ f  e
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);6 P( z2 r/ U  }  d0 q2 l( e4 `
    pSurface->Release();
# j$ r/ c  S6 x}
/ @2 ~$ |5 _& C/ i* U, t0 }7 S上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:+ w; U2 ^8 p7 ~! J$ p4 W6 K

: D8 C2 H; f$ F  U# cextern void* pBits;9 v7 a7 Y0 O, Q# ?2 F
extern IDirect3DDevice9* g_pd3dDevice;# N: v$ S3 e0 \/ C  n, E; U! G
IDirect3DSurface9* pSurface;" v' |, E3 R; P2 Y8 P
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,5 b6 V. t& L1 |6 w, f8 a: M
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH,
0 D& J0 T) O) E4 a1 t; |                                          &pSurface, NULL);: s" B: i& s& K/ @6 N. i  \
g_pd3dDevice->GetFrontBufferData(0, pSurface);
8 d# ~+ O2 Z, G# e+ g" K; _" MD3DLOCKED_RECT lockedRect;% p: n- G. P  W; U& R+ H( x- f; L
pSurface->LockRect(&lockedRect,NULL,
& t; a+ ^7 [$ Z$ @3 o: C* b$ ~                   D3DLOCK_NO_DIRTY_UPDATE|
6 g- s. {" p* s  F% \& J                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));  P+ r0 _2 A7 r& _! z6 l! ?: T
for( int i=0 ; i < ScreenHeight ; i++)
7 M8 i! W/ s3 k5 Q) l{
" x, K9 |! D. t- ]' s' c    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 ,
+ Q+ b2 H# O3 X. R4 }* ^* t) _; L- g        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , " K6 `" f* ~  h5 i& G6 L1 C! b
        ScreenWidth * BITSPERPIXEL / 8);) [  `5 U. e' M6 e4 c
}2 ^) |/ V+ t3 A* B
g_pSurface->UnlockRect();; g9 c4 `( c, W' ^9 w
pSurface->Release();
! l! ]0 S2 B9 d! ?, }% S4 [
  g/ k4 m' J' ^+ m* ]) n6 w: q
2 E3 O3 k! q8 V! n3 G, R上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:  I( i# e  C5 w' @
8 D2 `& M5 x! `
for( int i=0 ; i < ScreenHeight ; i++)( P- t4 i% S) S2 J
{
; d: l' Y% N- M0 c/ ^' f4 G: i    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * 4 b1 W2 F8 p' L7 W. n6 q  S
        ScreenWidth * BITSPERPIXEL/8 , 3 Q/ l3 x, ?# e
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , $ A) z7 g! E4 T" |! \% D0 E
        ScreenWidth* BITSPERPIXEL/8);
2 k/ p5 M5 }# C0 q  y4 i" b7 r}
4 p( J, c) v/ F* ~6 x' f! G3 \
7 r- L/ I/ r# _- f3 s- X! Y8 N  q# S! ^0 L6 o  s& D1 ~1 `$ i
这点代码用于转换自顶而下和自底而上的位图。) _1 h: X2 v; ^8 {0 f
# w) Y, V* w3 z7 T) b/ B- o: Z
上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。
/ x9 }0 }6 ?- a( E, K* R! e2 P5 G4 Y0 {

* N* o5 i: v* X8 S3 I+ B% [/ {
' D! {4 D( M3 T+ @% W5 M! ~( _然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。" X: s; N; ^# h- P: \- ?( K3 W

* n/ R  B; R6 n2 x0 V " l0 J) t7 w0 z- z, M

0 I1 @. K: A/ c4 ?$ [捕获程序要点:0 |9 ?$ s, d% V) G

% i) j: O6 e( |) t( e( x    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:
3 j) c) H8 ?& A* H2 w' p
: d+ V/ c  y9 j8 p( i$ Q& y1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。/ V) S& z* T$ Y/ f- \% l; k

2 u/ X( y& j0 K8 s2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。% l7 F8 C$ P/ L: `9 S0 v/ x% C
) V1 S! F1 I  R4 m! q
3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。$ Q6 g- w( x2 V; {! C9 N9 A

: E7 _. \3 h+ \+ h& Z4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送4 r6 `& E& c/ S

! ~- x& }9 v- J$ v6 \' p5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标
# y( y/ P6 s( }9 `1 o5 f* }6 J% T* k- X+ O  d  w& C/ m

6 v9 J% Y/ X: t% \8 n, Z
5 @2 i8 J: P4 ~  u! A9 I程序运行截图:
8 v4 [5 B8 w/ [0 q" [- v  R% m! R- `4 m

* V" ^6 y5 V* a; E, N该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。
( Z8 U6 T: o" ^4 I
0 N( k1 \5 a% ~( O: Z6 @1 Q  D! L一些可能的改进:
1 T; C: L& c3 ~; f
1 Q1 b3 k' y& ?+ \2 W3 q1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦, `) U; D+ Q( Y" t2 {4 W0 H
1 ^# t) O9 a! {- h+ u9 L
2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。
( s+ o& {: g* t8 ]0 b5 L9 k
. a' q, D9 `$ [1 n/ _$ @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日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

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