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

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

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

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

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

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

原文
1 k3 _! i  v) ehttp://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx
1 `4 s4 G9 p2 M$ a5 S+ p
8 z' R  F( P3 E9 V# X( F7 ?       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。
; l" S: D6 f1 T/ U* D! [7 N. @( ]$ c7 T# y6 g$ V- l* }

8 }/ q% A7 S; f* o+ F& z  l. @! m
; J9 f$ i$ S4 M: x, _5 t用GDI方法捕获* H4 d; T; l6 S3 l

/ |0 [) o; F0 }7 o% s4 @7 o* q1 E4 B       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:/ y! C, Q* U/ t. {; C

0 U! T: P' n8 s- U1 F1)  使用GetDesktopWindow获得桌面句柄+ g1 u) w2 Y" I+ ]
7 D3 n. B5 n7 I, ?, L
2)  使用函数GetDC获得桌面窗口的DC
) ~& t. X: v! X0 p
; X; F) K* Q9 M( k0 @- x3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC
" [% W0 F) b6 b: u
1 S& c& m5 d% L$ p2 S4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容
; N% ~9 g1 L  F5 {9 r2 R" a
, o1 _& I& v+ [5)  当完成捕获后,不要忘记释放对象- r$ n# H$ n1 u* u8 A6 l

3 a( `7 O/ U! `$ e4 A2 y( F例子:4 `* b: a  s& |) N- b) K* l
/ z) ?5 n4 Y5 r3 D8 l+ [
Void CaptureScreen()! G$ G* ^& n) ~% i
{
* f' v7 H5 p  |, r" O9 p    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
, I/ w% }# d3 r6 ~' a    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);6 N+ e7 k3 k: @6 a& \( G4 N- z% B
    HWND hDesktopWnd = GetDesktopWindow();9 J8 _  w  Z* C; }6 I
    HDC hDesktopDC = GetDC(hDesktopWnd);# J6 f6 J$ p3 a+ }1 y, s$ x* D
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
! B- R! U/ b  n. ?. ^! \2 ^    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
/ W- V( P: Y, T* w2 Y# D                            nScreenWidth, nScreenHeight);
/ s6 ~4 @, s. v' H7 ~' ~2 u    SelectObject(hCaptureDC,hCaptureBitmap); " B! h6 U" m7 x3 L4 m: y
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
  ?$ h9 k; l! i           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); 2 o- V, A% F) `  S2 e- ^6 k
    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码1 N# [5 j' `8 \7 @$ M4 y
    ReleaseDC(hDesktopWnd,hDesktopDC);: M8 R# T+ W1 o1 K" v4 C* Z4 c' e
    DeleteDC(hCaptureDC);9 r5 C6 v5 `# A6 a) Y$ |! z
    DeleteObject(hCaptureBitmap);6 p2 m- w7 r  n# C/ a
}
4 s  y! o- |- _, A! g2 Q1 l" A上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。2 g* R2 X. H" H& @; N) R* E) u- s7 U
8 g! U0 u" i$ O& E

$ T/ x1 ]  j+ a6 d% [4 Y& N* R7 n6 F! X" }
采用DirectX的方法:+ R3 a6 j- w& q  ^8 U
. Y5 \3 X: u$ H! D  j/ |  Q
    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。9 _2 A5 H* U# }" p3 k- ~( n
& D2 C' A$ ]' f  A7 \- i) u
每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。
. n/ N- ?: f' F, y& Y' @+ t# q1 @
/ H% p! j6 W  D& ]
- w" t/ h1 r0 \& z7 c
通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:
1 f- o9 L7 K% O8 a1 C; L' c& p4 n1 Z$ C5 a0 @" Q! ?
extern IDirect3DDevice9* g_pd3dDevice;
  y( J, t4 v* m" Z0 c* J- ^Void CaptureScreen()  j) `, w) j. }- q2 H
{  U  y) A! m* E' Q' o5 ?5 B
    IDirect3DSurface9* pSurface;
- t: U, j; W' r    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,9 a$ ~4 }0 z: c: l9 f6 v3 d0 U" U
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
7 o5 A7 r2 V3 I0 [, O. X    g_pd3dDevice->GetFrontBufferData(0, pSurface);$ L+ H; Q- ?4 E
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
6 F  R* @/ A; Z; M7 }    pSurface->Release();
) \# z8 A% s: q4 U+ R: o}
$ `  _, ?2 Q" ^. V! _上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:
( {6 R  n5 |- v$ _! O/ d  Q: |# ]2 Y) }" `
extern void* pBits;
# ?$ q$ l' @3 F% \5 ~6 {extern IDirect3DDevice9* g_pd3dDevice;
; h# ^/ |" e' `  {( zIDirect3DSurface9* pSurface;
3 R6 X  v/ @9 @g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
' I- ]2 F/ Q3 ~# ]                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH,
5 }6 T! N8 U; U* L1 ^& `/ u                                          &pSurface, NULL);
8 v$ V9 Q& k$ a4 u' ~( j* J0 k+ jg_pd3dDevice->GetFrontBufferData(0, pSurface);
( \) y. y* A9 u1 ~% S% n6 B1 cD3DLOCKED_RECT lockedRect;2 |+ D* h9 A" ^  G, `  x2 o
pSurface->LockRect(&lockedRect,NULL,
" a1 M, b, q; q8 d                   D3DLOCK_NO_DIRTY_UPDATE|
. N9 ?; k6 q1 W: a                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));6 ?6 c7 w7 Z: n, r2 r  Y/ L3 Q
for( int i=0 ; i < ScreenHeight ; i++)& e7 R# y! s3 T& h' b& B! d
{  x- L; U! G1 {9 @
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 4 x/ b8 j9 F) u9 |; ~4 @
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , ' U, |6 S" F) a* D& L( F
        ScreenWidth * BITSPERPIXEL / 8);* D/ c! f0 g; N  M8 C5 K
}
0 o/ g: _7 N* }, o! y' h! j1 Yg_pSurface->UnlockRect();
5 h2 ^  B+ ]% C4 Z) IpSurface->Release();5 U6 Z5 Z" [5 n+ p) ^

5 ]) h$ h" @( H6 G7 m- ~" W: W1 |$ S# R. d7 v" [- c8 Z9 z
上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:
& k' Z/ ^/ g' k5 Z; m4 \  U+ k6 ~
6 f9 D: e$ `" v! O5 v. E9 }for( int i=0 ; i < ScreenHeight ; i++)4 h$ U1 V: K6 I+ i  Q
{
  d( ^" T: C& G) F! E7 i    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
) }* P9 o" R* i        ScreenWidth * BITSPERPIXEL/8 ,
; y! ?( Y3 W8 g- B/ J        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,   P  \% g) f/ k, v
        ScreenWidth* BITSPERPIXEL/8);
4 I# e# H# }6 T1 d}# @5 u# C+ H9 E, T5 N) \1 W
* D2 K, Y- h+ w, n+ \- I' f% x9 v+ I
/ G9 u8 g  l' k9 U' w2 P! n. f
这点代码用于转换自顶而下和自底而上的位图。
/ N1 v2 j/ h) \0 I7 q' e, r* T
0 x$ }! k/ H1 c# C$ A% U0 Q; U上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。1 y/ }, q5 A: o+ K

* A0 o% U0 C6 t( F. n 2 C4 @0 I4 m/ }/ W" B

" \" ~" o& v" h( w然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。
* ?) ?2 H. q' y; v
. `8 p( T/ G, g4 i: Y8 J' H 1 w, z, i. X1 v( ]4 e+ ^

2 F; @2 L, I( n5 T0 ~- R捕获程序要点:
6 F# w, F- a: l2 i& n/ A, h
4 a8 `4 N# J9 B: f; B" D    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:
: F, X4 G, t; i1 y9 w8 p3 a, t$ \. f% N5 e, _
1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。9 r3 A9 M! k9 i2 q; K" \

7 E1 e0 ?* y6 H" `7 S( ?3 S8 p7 J2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。4 l. V3 l  n" Y. y2 l. h" s. g$ T" [
  ]7 J% G0 _# |, w# o
3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。
) S5 a$ v& f; t
( u' N) O; A# a7 r7 @) v: ]/ ~4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送, f& ^' L. U- u5 P

. X: [2 a: `5 J' y5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标2 D; Q2 C: _2 {4 @+ W% A* D  ~
9 e+ a' b( I! J# M6 O9 I% W

4 w1 r4 O2 h( j) ]$ C
6 W) b$ ?+ n( y% R$ I; a0 Y* a* o程序运行截图:/ ~1 ^  M2 {5 O4 {  H
7 R- l( }* U$ p/ _$ F, Y
  H2 L) M% [. `- _( F
该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。
% ?* ]/ ^5 y' i+ M0 o, V$ a/ _" p; r0 a0 g; k
一些可能的改进:  M  t8 N7 }6 K, G  B! u

% F% i7 e- T0 t  e+ a: u. ~- ~6 t1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦
. O4 {- n- G; E2 f' N" Y5 i$ n" s9 R6 ~: H
2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。4 ]$ h/ s. l; U6 X
) J# z5 f1 _, b5 \( a
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日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

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