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

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

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

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

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

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

原文
* c, e4 U5 B: ~' Thttp://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx
1 m" A8 B" Y. r" b$ c' [5 \9 }, x( p  _( Y3 K( b' k
       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。
7 p. W/ x" @* M& V3 D( F6 m% N, k/ Y0 T6 x
# p5 `/ A5 B) C: x
% L) Z; _( ?( k% V% `. j
用GDI方法捕获
  d3 f& j( a+ P7 ]/ T; N8 g4 r: }; Q$ M
       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:; d8 P% x, [# a$ P& v' C! I
) Z- l- Y) D+ ]- ^2 J4 }1 f9 v
1)  使用GetDesktopWindow获得桌面句柄- b  _) M4 Q% n  S! j
3 p7 x( T) Q  J% C0 u
2)  使用函数GetDC获得桌面窗口的DC5 g; m; z3 {/ B& q9 E( L. s0 e
4 ~9 N. C4 k( o% f4 x# b6 `
3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC. g7 {7 u+ Q/ D- O; c/ H, J

- p5 h# W! S/ x. ~4 F$ L4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容
& S' \" ?- Z. k8 J: Z' v
8 M) [9 U- X' V# U9 s5)  当完成捕获后,不要忘记释放对象$ n0 B' I* x6 p$ o0 ^5 _

. O% K6 w4 q4 S! `7 L; M9 H7 ~例子:$ o! \6 q4 @, w- o
+ V. b  ~- |8 p
Void CaptureScreen()/ g5 L& l9 C6 p
{- O# Q9 `0 v) I
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
( ]! f3 ?3 \, Y9 N) L1 A; C    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);6 W1 w+ \5 m, V7 ?3 i+ u
    HWND hDesktopWnd = GetDesktopWindow();
9 @6 Y) f4 M) M- k8 a) h  O    HDC hDesktopDC = GetDC(hDesktopWnd);0 B$ |# B! e# Z
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
, d: t. d! w5 w6 G3 |    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, $ K8 x2 l2 k: o, ^0 m
                            nScreenWidth, nScreenHeight);, q7 p: L9 i) A2 i! o* r+ \
    SelectObject(hCaptureDC,hCaptureBitmap);
- ~/ V# V+ }( R5 V* W3 U    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
3 [+ Z/ b  e" ~9 X           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT);
! s3 x7 q# \. z! N7 b# ~    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码1 `0 Q/ L$ C/ D
    ReleaseDC(hDesktopWnd,hDesktopDC);
+ ?6 h8 Q! y& B( r    DeleteDC(hCaptureDC);
% }" p  G) z" k. r/ p9 f    DeleteObject(hCaptureBitmap);
$ [5 h  p- i  N9 m& p8 X}. c. u# g- a4 c* l  A# q
上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。, Z- B8 w$ q- i% y2 }

3 n. U6 f% p$ s) H; G& Y2 e6 e ' @2 W2 |) A5 W( p8 x
7 w$ j6 D" p! V3 H( S: F7 y# v
采用DirectX的方法:+ I, y* o, p% j6 o6 g- W

* }- w, z) p3 F" f0 b    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。
2 R) H1 F1 n9 e
* M+ |) R( Z, m) V! i. X每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。& D  m' A4 d/ r& o

( x) F8 M2 D% \( T, R3 d( l- \ + ~( Q6 r: q/ ]$ S6 M7 J
( _7 p7 g8 |+ t5 s- k. g/ s
通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:: v6 M, l; G9 z. w3 u

* J' Q) K/ Q* r8 |extern IDirect3DDevice9* g_pd3dDevice;
" ?( o8 R' i* A9 NVoid CaptureScreen()8 L2 u9 e* I; D' \; @1 o
{) `! }4 r7 W$ E* F2 l1 q: w+ ~  v
    IDirect3DSurface9* pSurface;
! O" C( C; I8 P* N. Y3 Z4 Q" Z    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
/ D  v" i9 I% k1 z        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
8 O6 l% A& J% d' ^6 N3 i    g_pd3dDevice->GetFrontBufferData(0, pSurface);7 w; K# C+ T7 F  g5 e/ e
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);5 g2 h- o7 A* F. |& w6 c
    pSurface->Release(); 9 O$ y! C0 ^; l
}
, w- A0 W) i: z1 c& ^上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:9 V/ [" q" R6 h! ?
5 n3 c2 d) {5 ~/ @
extern void* pBits;
0 W' O, e+ F& v* Bextern IDirect3DDevice9* g_pd3dDevice;" \; G( K' K5 K+ \* G
IDirect3DSurface9* pSurface;2 E8 C. I. ?- X) Y. S  ?
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
, i# K4 o( [0 {+ z& l" [: c! h                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH,
& B7 c' L0 B+ v: n! _                                          &pSurface, NULL);1 g1 N! E4 a8 z
g_pd3dDevice->GetFrontBufferData(0, pSurface);
; N1 F  s0 c5 Q2 r: c2 ]D3DLOCKED_RECT lockedRect;
* o7 D! @+ V  \4 m6 IpSurface->LockRect(&lockedRect,NULL," P1 ?( c4 C3 C1 c" [
                   D3DLOCK_NO_DIRTY_UPDATE|! Z: d6 R) P8 y% p! `9 X: y
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));- r  W& E) e* D) {7 s+ b5 M
for( int i=0 ; i < ScreenHeight ; i++)
, d: m, H  j4 n! M{' B* A' ^) M" t- S( X( w2 e. w
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 4 I" u+ ]/ w( j8 H6 x' h
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
, B6 t. v) u! e! U! w' v        ScreenWidth * BITSPERPIXEL / 8);4 o! n2 T  Q4 ~/ q, O
}
, H* l' W/ d0 f8 j2 P* L! Rg_pSurface->UnlockRect();& O& j5 `! E  E# {! j" z. F5 B& n
pSurface->Release();( H. y3 v7 f( E) F# A

* a% I' N" r: Q( D" L( @! {. k  |6 l6 X: @/ A, B, u
上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:. A. e3 O& N) L, u% f! f( k# J5 b' F$ `
" _: {) T) d9 a2 w6 |5 |% \) t
for( int i=0 ; i < ScreenHeight ; i++)% N  v, s3 ^: \4 c
{* v# c6 y: i9 `6 l4 \( ]' `1 h% [
    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * ' N3 Z" m# ?6 E
        ScreenWidth * BITSPERPIXEL/8 , 5 n7 s% c# W* t, _, Y9 X# E# H
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , , z% }" \. d1 G6 `' K
        ScreenWidth* BITSPERPIXEL/8);" l( }( D5 ?& E$ {' _7 a
}
* X/ W. A2 i8 r' S3 G' d & @% u3 a# `& E9 e+ G2 Q8 x; @& q8 B

. g% m% T  B! s, ?0 ~0 C6 H这点代码用于转换自顶而下和自底而上的位图。8 P, a1 F3 N; {& g) h
3 Z# ~* p8 L3 M' g4 \% T4 Y5 M
上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。2 n9 `$ S/ p% B- @# ^* J  c

; |, {' T+ l7 f# [ , ^3 v/ @5 O- j! p" V; t
, d$ {% o& m4 d# f* d
然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。+ H) x; x. r/ Y  I* d" R  O
; m4 k. w% c" n4 _1 [! i2 t
% H& ~/ @5 c  b
& t& ^6 Z9 `6 `+ L
捕获程序要点:
: {. R7 U5 @+ R8 \, O" y* A
/ F$ K/ F, B$ E  u3 X    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:5 R2 c( c; z" I* b# o! I) }% r/ I+ }3 d" b

: e* t6 h2 q; V: a1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。, r( I/ O( D. b; I$ I- R2 F

$ h/ Z) g& M- ?& {5 ^. V1 ]# _% U2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。
( ^( b$ U3 f. x
) U5 k' p6 S7 P% Y3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。
/ }6 C( P2 @! c
6 z+ G5 y+ A7 u% `/ F# r0 y4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送, t& b* G5 x8 s& J- f7 q5 R  X2 n

! V. P4 d, W+ A( Q5 C. A5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标5 s& V% @# B/ G: {

' @& j5 y9 S5 F/ Z9 }: B 6 v% s% ~% [4 s; d8 x

; Q! w) {- J* I( c1 ?$ r程序运行截图:
3 e" e0 H2 I4 y; V: G
7 l: K* \& V! |3 H3 R2 L
' s0 B; ^: |, T0 @该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。
2 L, n* l; y  E$ I" p* f) p! ~" ], K( p" b5 C/ y# @' ]$ _. m
一些可能的改进:
- L8 R' c4 y) r" O0 y5 y! y5 }2 J1 E4 }6 ]% {
1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦( u* X: o( P3 c- O

& I3 z: f3 Q* b  s7 L6 Z5 d2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。/ {( `. s% b& j# `4 \. b6 }! w- y- z

1 e8 S) u' `; x& ~( {! y3.编程中的一些优化和保护措施没有做好,程序运行的时候占用的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日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

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