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

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

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

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

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

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

原文  }3 d0 x! O  s0 I4 B, @  S7 [& F
http://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx/ ~! L  ^: W: `9 O+ p

7 C$ Z+ [' _$ Z: M$ \       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。
7 l1 T; v/ V$ S: L/ D$ g3 D, g& ?7 x7 E$ e1 }

- T: s1 K* c- A/ n+ O) J  p) C' @& g! d- K# G3 F8 @* v5 Q' X0 L
用GDI方法捕获
* O+ p) w4 b/ n, A/ b# k( ^1 m9 p& ]' [
       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:% k  j+ h( B* |# ?0 s+ n& @" |. B

1 \7 \6 @2 f4 T( L. w4 Z. i3 `; o1)  使用GetDesktopWindow获得桌面句柄8 e) F2 y2 [9 [8 O( ?# l+ k. K2 A

; p: [; P8 e* q) [9 q/ P6 a: H2 q2)  使用函数GetDC获得桌面窗口的DC& g# v" ]( ]& |1 r

7 V2 G. l. }  |; g5 n, X' O3 L; X3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC2 c, a' i- |' r* G$ x
9 e9 b6 j' h( v' L6 `9 ^# \  z
4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容
# Q. x* [* {) Q( Z% ^. |8 T1 g( {' c$ F7 x, [
5)  当完成捕获后,不要忘记释放对象
5 j2 I/ d9 `1 L+ k/ j$ m: s7 c3 G2 h. i0 n+ t
例子:
! q& c* H) E8 u& w
! h9 p* m2 C9 hVoid CaptureScreen()
3 J$ m$ i5 i% m0 m3 h! J- z/ {+ u  ?{
( P& t: _, U+ i( S    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);& K7 Y- z1 Z6 Z5 N
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
' P' V9 g* X& u8 `' c    HWND hDesktopWnd = GetDesktopWindow();
! g' B, P& O( z    HDC hDesktopDC = GetDC(hDesktopWnd);1 H" w3 i# v1 i1 s3 T$ v
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);) V) R3 [) \" x  a% F4 f$ C/ O
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, ( f4 \7 s8 A4 A# ^& Q0 M8 I0 U7 N* b
                            nScreenWidth, nScreenHeight);
) E. r8 a$ [% J* D+ S% s2 G) N    SelectObject(hCaptureDC,hCaptureBitmap); & {) o* ?7 L# V. `& b
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
/ h) @. n) J/ m) h8 ]  W           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT);
) K7 m" s- T# Z    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码
8 m8 B$ e6 {* F6 b4 U    ReleaseDC(hDesktopWnd,hDesktopDC);
) }. n7 D; v( `7 @' A( ]: |    DeleteDC(hCaptureDC);
" _, M+ z0 Q: Y0 V+ E    DeleteObject(hCaptureBitmap);; G7 v$ L3 l; s+ d- `
}
& _" C+ i9 @/ V9 ~0 _8 o  \3 o" Y* u上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。
! [- q8 h) ?8 U0 d+ o) n3 N( _+ H" S. d1 w# ^7 Y
1 T3 e* U8 E& q1 Z
- e+ w+ A% V7 O9 d! v0 f1 o" j  z% ~; I
采用DirectX的方法:+ m2 q9 B! S! t& N4 r# w: }' e

. U+ e& O* |3 @8 [    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。
7 ~) C, f- {6 ]3 o
7 Q+ Q; [" O+ Z. Q: [1 Q' B每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。) Y! g2 Z) Y& J- @
7 ^4 ~9 H7 H! O$ I' r! [+ B) D

' V/ c$ P) M! H0 M; M7 W; x7 q; j: H( \( b( g( e$ _
通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:
4 C+ h" A  L. P- Q+ c- Y2 `' Y+ K0 k
extern IDirect3DDevice9* g_pd3dDevice;+ _2 F+ N1 @/ {$ C2 B( Z" B. T
Void CaptureScreen()
* f& b. r, A5 e* g! T8 u0 q# T{
9 b( T" K: ^! V4 R- e  F    IDirect3DSurface9* pSurface;7 I9 L# ^# I4 I, [: @9 B- i# L
    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
+ K4 N: l1 ~7 j  L6 L        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);& R3 R: ^8 @, }6 b# C
    g_pd3dDevice->GetFrontBufferData(0, pSurface);4 K& ]; u) X7 l% G3 \) h! O9 V
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
) h, y' K0 v2 F% J1 l+ M0 ^+ L    pSurface->Release();
% }4 R( Q  f# n}
5 C' T. L, u/ p* @! i6 _1 w上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:, j; \( T! `  l3 n  _" \2 H
  ~* d* M8 }( F% h. p, q
extern void* pBits;
6 O$ O# |4 T  }- n2 pextern IDirect3DDevice9* g_pd3dDevice;! O; `# y8 l9 M; p# n
IDirect3DSurface9* pSurface;
0 `* o" M7 X& G5 C$ C/ Vg_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
2 q/ O% ^$ o+ }, K! c' ]; G1 v7 }                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH,
4 q* r% O$ `/ h3 a$ ]: S8 o                                          &pSurface, NULL);
; T* R0 k$ G1 j* B, kg_pd3dDevice->GetFrontBufferData(0, pSurface);
" D) o* q) A6 c+ gD3DLOCKED_RECT lockedRect;/ N3 ]1 W4 g9 r, P# W) q
pSurface->LockRect(&lockedRect,NULL," a: F: [7 q$ E7 Q+ s" l5 I9 w: m
                   D3DLOCK_NO_DIRTY_UPDATE|; j" l3 F7 ?4 `$ u  Q
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
0 n: K0 w9 W, p" l) b( t; lfor( int i=0 ; i < ScreenHeight ; i++)0 \; g- h9 b2 m& J( M
{6 E, Z9 \: m$ k% X7 S
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 ,
& H5 u- ~6 t0 N. q- f' C        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 7 x. ~4 g9 M9 j
        ScreenWidth * BITSPERPIXEL / 8);9 G7 G) B. W& C% M2 _
}
, w4 @& {6 f- U9 Lg_pSurface->UnlockRect();
' S, d; L  A7 v% J( MpSurface->Release();) X5 p: X, S9 I4 ~% o7 x- X
/ ?# x" ]7 c2 B9 N2 E+ D* }
* t5 {& X4 u# W7 b$ G1 r5 D
上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:
. A; H" Y: b8 R5 t8 Z0 z
- s- l8 b0 @2 d6 S8 {& {for( int i=0 ; i < ScreenHeight ; i++)
8 t; q2 |' {% n0 \6 y$ B{
5 {1 z8 L" x/ w; z  j8 F    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * ) R% Y5 M4 F' F4 U! I7 U. r
        ScreenWidth * BITSPERPIXEL/8 , - l; @9 y4 |: l
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , . K9 G9 q6 G  ]1 C- L1 A
        ScreenWidth* BITSPERPIXEL/8);
5 O5 F* }- Z  w1 V! `# q; e- X}0 Y# h1 n6 y5 j9 l* d& l
! I. R: P  {0 E+ d" l
5 y' G7 c; l; ~) H+ y9 |+ `
这点代码用于转换自顶而下和自底而上的位图。6 V# w2 d; L" s) c
. s, q" R0 w7 F+ c  L/ f# V
上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。; `# l! g6 T% _- R4 o; h

+ v- ~7 z" E; y' P * f2 Q& D2 p5 J8 B  p' c8 o9 [1 s

( ^& B, p; g- R: u然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。
$ Y/ J$ D: D4 X6 O# _$ F3 V
/ x; F4 a% q: j1 l9 {* M! i) ~5 Q
" }) D7 J3 f! _8 {- W9 z$ r& A2 Q5 U5 b% J5 l8 |! J
捕获程序要点:  T$ `, b" Y1 L) x0 b3 \
1 h, J) T( I- o$ h& @
    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:
/ a, X* I; c; s/ t9 ~7 a( }
! X+ l* {( [' ~( k- e3 C1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。, I1 j1 }6 E- R5 k/ @; }
( @4 X% I2 A: }! I
2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。
" O  ^6 q5 ]. h* H' C: j* V
5 ?- B0 f3 m9 [1 T' Z3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。
. T6 o' o7 r# ?/ p: c8 h4 R$ y  G  @! P( j! |' L7 g
4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送  \( Y6 A+ t+ C! L1 ]; ?

) m5 X/ p, o$ W) O* S5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标, F, V: ~2 ]- r; @, A$ c- t

: F, x( N$ s& s- w
* U- p( i- M4 L0 J1 A
) z1 l# G" _* [, k: L% p) {) e* y程序运行截图:3 c0 N3 o! H( f# r

, A0 ~$ Z$ f9 J, H6 [, p! a+ `7 e: y2 L2 p  Z3 F& x& |
该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。
# S4 @# W: x0 e4 E6 M; O
4 g5 O/ E* m( @7 E一些可能的改进:
. x+ m- M% f% ]
) z  [6 E: v7 K+ W6 @1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦
: a8 @  a5 [$ Y6 M) Q6 I7 ?. c( K4 i  N( ~% u3 g8 t
2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。
4 k4 e; e% l2 A6 I$ l5 v$ [' |$ x2 G0 p. |6 [8 k; y7 J7 }
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日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

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