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

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

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

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

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

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

原文
1 d3 H3 Q! }7 E; i" C% ]5 Thttp://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx
, h: s9 C' b! M9 G. F' j7 _# d) s2 R# u
       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。7 d& e! F0 H' i$ t. y

! j  d5 |, K; o: q' ]
4 u4 ~+ D; E0 v' t" b; i& u, r/ X! `+ H) L7 s9 v0 I
用GDI方法捕获
1 R* u$ Y7 c7 ^- J) |* m8 _# z: x8 J1 k" x
       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:7 G2 s5 I& m: }" P

* b. j: a) @% B) K. J1)  使用GetDesktopWindow获得桌面句柄; t. V4 }2 J0 q& l& `
0 ], ^1 M2 f) L8 h$ Z) w1 @
2)  使用函数GetDC获得桌面窗口的DC
5 U' l; ~0 S7 i$ S1 p/ B5 M
  h$ Y% k9 ?; m' w8 G8 e+ B, Q! H& @3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC
- m9 a. G5 o( A2 m/ q# h) X- R% c6 n! Z* v, `+ X2 `$ z
4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容
4 J, e3 `0 i6 x9 O6 p% K
9 \. D2 s- _: G; f' v0 {. A, f5 ]5)  当完成捕获后,不要忘记释放对象( p* C8 `, V0 N
, ^! C2 N1 u$ w5 x# F
例子:% ]$ W2 Q" x" g4 N

* p# e% J& y* ]% S; W: o% FVoid CaptureScreen()
1 c" o1 }% o% K8 `{
! C: Y6 r8 K% R* B* T) V6 d    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);& o/ w8 Q/ w) q/ J- Q1 f
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);" Q4 {5 z. z, W: m  Z% D
    HWND hDesktopWnd = GetDesktopWindow();' M6 p7 A8 l: ?3 V* K* ?) T
    HDC hDesktopDC = GetDC(hDesktopWnd);
6 x8 y( R" c! h: A0 j- {2 `6 [    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
2 E" W( X% g: {3 U2 B; G! M3 p    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
2 H( ~/ U9 K3 A1 N1 n3 S+ J                            nScreenWidth, nScreenHeight);; l1 Z- g5 F( f) ?9 _
    SelectObject(hCaptureDC,hCaptureBitmap);
# _$ J* z7 N4 k" a1 A4 y; }    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
5 ^" Q: y7 W9 Z2 H           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT);
: W/ g$ z5 Z: {) `    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码
; `& s0 {+ z3 q9 t% s    ReleaseDC(hDesktopWnd,hDesktopDC);
9 X3 i4 z6 m8 u: V# ]( O$ r    DeleteDC(hCaptureDC);
6 c2 i5 J# s4 W  P2 M9 {+ m$ T    DeleteObject(hCaptureBitmap);' b" z2 o; y; |4 ^
}
- n% n; s& N9 L& a* U上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。% g9 E9 X4 o% D
+ ?/ _! u& J: w! u( J' D# M! j6 W5 P
% `1 X/ c4 }1 ]

9 K) B  I& }" ^4 m0 N+ k4 `采用DirectX的方法:! w) C. h: m3 w
; Z; Y$ i+ ~2 O  z
    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。1 ^* c% N9 r0 o0 r; j
( c. L& p% o- w2 `" O
每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。' [1 W& `( E" o/ s2 A* D  F* d6 O
2 F4 ^( J2 A; D" R& I% G3 g
# ^1 _1 U% t* A$ Q4 B

7 U, l8 h* f- r/ }通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:
9 c5 ~( L) j% W+ |7 e) ^9 Y. z, l) i! _4 ~7 u  U
extern IDirect3DDevice9* g_pd3dDevice;) C4 t; t3 x4 M7 U9 T* D
Void CaptureScreen()
. G5 ?) j1 z/ ]1 h$ C- M{
- F! X5 M; S: Y& b' }    IDirect3DSurface9* pSurface;
4 ?3 n/ ~& D# T7 _    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
0 C9 k* {+ i& c3 I, f6 i- c        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);2 W  U$ f: w8 Z  z! Q
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
" }8 ^5 m" ]  [- d- \  t* ?: G    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
' U/ I; Q* |  N/ I' x    pSurface->Release();
, k5 b% r! P# E% P: L9 ?. B8 q3 a) v}' v' P  S- d/ K8 c& q3 [
上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:
" I5 \2 z- Q9 }! x# v# b. k7 I) c# k) ]* c; `
extern void* pBits;
- E) Z  Y7 @. ]' c1 ]extern IDirect3DDevice9* g_pd3dDevice;
" X) ], N* s$ x$ wIDirect3DSurface9* pSurface;
3 R# p& O3 Z2 ~* h0 b; hg_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,) S( E' Y% i' \0 h
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH,
+ A) E& t) L" p$ p6 {                                          &pSurface, NULL);1 c1 ^, X* K$ k7 Q; \1 F/ U, q- {
g_pd3dDevice->GetFrontBufferData(0, pSurface);
+ f  H# N" }; F, N/ b1 T- ?6 GD3DLOCKED_RECT lockedRect;* u* U/ z) |# o& n" J/ P
pSurface->LockRect(&lockedRect,NULL,
' T' e8 p( y! X! A1 |! B                   D3DLOCK_NO_DIRTY_UPDATE|
% \; E0 [# D& ~1 A1 I" @# ]                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
: N0 g( X* O& U, n2 N; Ofor( int i=0 ; i < ScreenHeight ; i++)
: b* t/ T' C) u' l{
- z. t8 A4 P* U. u* K( F    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 8 F; Q. T/ ?' y( w; Q; F
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , & B% h3 y5 s" @2 U6 w! C) a( v
        ScreenWidth * BITSPERPIXEL / 8);
8 I2 I4 |! t9 J6 f# K}
8 y$ M' h# q4 u& Lg_pSurface->UnlockRect();
$ ^+ m8 d9 [- s, `pSurface->Release();+ c( E' r- N0 E; W+ v) p

" V" v" J. R! V" A3 n1 J" F6 {
& X8 m; F# ^" V, C! q: S上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:
& U5 U+ o! T) d: c7 a6 }# ?$ D. m7 M
for( int i=0 ; i < ScreenHeight ; i++)5 n: ^5 m+ @8 O2 Z' F2 \
{
# }8 ~$ T' H6 A" j    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
2 T- K6 C5 u  ~& h: [& ]/ `        ScreenWidth * BITSPERPIXEL/8 ,
0 j8 i0 R* K' Y8 h* z+ K) Q: p/ B        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
2 w0 @6 \! X& V0 E" Y5 U2 s        ScreenWidth* BITSPERPIXEL/8);3 F  U+ M: ~" t) I
}0 P" Z" s+ I/ z2 B* g
, c1 ?1 G2 j4 J& E
) L- f- H3 r/ a7 o  w
这点代码用于转换自顶而下和自底而上的位图。) S; }+ P3 v' f2 k: Y4 V5 K
" @/ s6 {! ~$ R0 u* r, ?1 Y) m
上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。
7 G5 o- z# z+ d' T( B' K' V  L# _0 V  W* i
3 j$ R) ?) P& F. Y2 ^* `

2 y: B' q' @# y' b然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。
! ~, l+ l& _) E, E% T' O) c. X+ r5 s6 R' [$ f+ O; b2 P

) O1 g+ \% W4 y6 E0 d$ ~4 o: g& p  j
捕获程序要点:
' l& [6 N7 |/ h* c5 J& H$ r0 z! A% K5 w( }- j+ p
    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:
$ N( E; l9 j* \$ W
. B% z% I6 v/ I$ g1 V1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。
, r& ?* C. [. y- W* \, S4 X
; v* K5 i3 P9 O, c: U2 E6 g6 p$ t2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。
# Z2 F% g. Q  l/ N+ c  c$ _
8 O/ x% `# e/ @# ?6 i4 h- S3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。+ ]* F# R) I, X" ^7 O7 F; g

. x3 [6 X) d" |  u" f! `4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送
* {: f0 D- z" g' t4 M* v9 I/ e
8 w/ V8 V8 V- k; A7 E: @/ G3 ~5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标' V, Q% |0 G- @0 ~5 e, A
& N! L9 B) B3 R) D+ I" V
0 W' ~, M6 t, t4 D+ q  K3 H

& Y& p; ]# q- |0 T7 x程序运行截图:/ a2 B- Q3 }- ]

8 u6 @( c0 w& ?1 f2 j% s* l& H! e5 b
该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。
, S+ m2 I, K1 i# \; ]! S6 K
& a2 s3 o( D% H9 c- G& X& e一些可能的改进:% @, y% U5 N8 p6 T% ?- v% F3 S
& l0 y$ C3 z. J/ A& m. t
1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦+ L5 x$ y( B; D+ t3 R
2 J; w' N2 E: D+ g( \
2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。
) B" }0 I8 H# \+ K1 e; v: o, f# Z& `  B/ {# J
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日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

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