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

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

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

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

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

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

原文
3 }7 Q+ }- Z) |; Jhttp://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx
' E8 J% i. ]1 K% Z1 ?* K" ~9 L6 `8 n2 U  c$ z
       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。& B1 p; y; |$ S* P! `0 F/ x$ C7 A
" j4 H2 s- }0 U  o; D2 X

% I1 {" J1 i  R, Q* O( H
) d& g' Z! r2 v# Z' h. q$ y用GDI方法捕获
2 Y: ^. }4 w: D' h7 k+ @! b, F  I: e$ i" r
       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:
/ r$ F& M. p1 ^: a- n
  m/ u. v: G; P9 o1)  使用GetDesktopWindow获得桌面句柄1 s' r! q6 H: a% a) Z# S; S$ r; S
0 B# l3 F( b$ L: D
2)  使用函数GetDC获得桌面窗口的DC
6 U* @8 F, N- q* A. Q% ?
5 @+ F& Y$ q/ T) Z" {7 q: g( f3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC- [2 F$ ?  c# }! l! |* s# J6 u
5 c& Z3 X* G+ ^0 ]$ i' L& F6 Z& Y
4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容' m5 m. R1 F" H) ?  P; ?/ ]8 M
( W) W1 d/ O) w0 D' |0 Q3 H
5)  当完成捕获后,不要忘记释放对象' h( R- K7 H* V# K# X
7 W# R. V/ L( T0 A! e- [
例子:' Y* j) E# S8 \( k( D: e8 B/ @

) @- u5 B3 S5 c% W- z3 MVoid CaptureScreen()
6 I: w) J# x# ?/ i{
8 y4 g  e8 C& b! I3 {9 d5 i    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
8 R; O' Z* J3 W3 K( N1 d# `    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);2 J$ V, o. I2 X" M' q
    HWND hDesktopWnd = GetDesktopWindow();! _: }8 a; A. }0 N' ~$ `: h7 N/ u
    HDC hDesktopDC = GetDC(hDesktopWnd);
4 m  ]' y) v) g4 f  G    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
1 w# ~% W3 z. W( j3 `" |8 ^7 }' R    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
6 L- p5 V+ x% v& Z2 s$ L, k                            nScreenWidth, nScreenHeight);% t% B+ W# R. c  S9 m8 W+ e0 {) P
    SelectObject(hCaptureDC,hCaptureBitmap);
. F9 j$ W2 N+ X" m, v: ?    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
8 V. N; ?, _$ A1 R1 G! P0 b2 E0 q           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); : j0 V3 ?7 o" W/ ]
    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码" p3 v+ |' ^5 i4 j; s& i/ K
    ReleaseDC(hDesktopWnd,hDesktopDC);. i$ s! _* v( O; B2 X3 f7 S
    DeleteDC(hCaptureDC);0 z/ n1 x3 l+ I8 F1 |% q
    DeleteObject(hCaptureBitmap);0 x7 X, k! @7 {! j* r8 i7 E
}
3 b. _# A+ Y3 H; P上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。
7 k, N" R+ _% ]& N  S  E
1 v1 `" f+ Y. J6 e4 V# e& o   n8 I; K3 ]- T3 J7 j2 r6 W

3 p2 M+ u  E6 e4 o1 b采用DirectX的方法:
9 w! s- {5 y6 ~, ~1 H
4 V  i2 B7 w0 X' O    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。
+ s& p+ b4 F1 T( _$ U6 x4 M% l5 g8 c- w1 n0 ]6 `8 T
每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。, G9 m$ v) Q- ?/ ?- {/ d

/ m' X# i) _: \( k" a
" s4 g: A0 C) p$ a
  G" G4 S0 O) v! q9 P通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:* h$ I# b: G1 U  y/ R  [
( {" `+ K0 z  _6 @3 W
extern IDirect3DDevice9* g_pd3dDevice;
& K$ `8 Z& b& A$ r: V; O/ Y% o5 vVoid CaptureScreen()
+ L% R: |8 h" T% c! G' I{& c; K/ |3 j9 g. u& i* c1 O* n) j
    IDirect3DSurface9* pSurface;
# R$ t* J* r9 T1 ^/ f    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
* H- B0 g5 F# r0 j( G        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
$ o, a. Q% f; Q2 h- \, c    g_pd3dDevice->GetFrontBufferData(0, pSurface);$ L9 N5 u7 z+ U5 V% B' ~
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
. o( w: n; A" K9 y    pSurface->Release(); ' V) \: |1 s: F* E- z
}
+ U" H, m9 L! P) W6 n& X" T- p上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:! c  H' `: r: N; R- i, F" @- S8 N

/ I8 u! ?7 X0 sextern void* pBits;
/ K7 ~1 P) C+ o" Bextern IDirect3DDevice9* g_pd3dDevice;
3 a+ d: I. i  N! ?IDirect3DSurface9* pSurface;, |/ N6 r7 n: `- f) H- H, o1 f
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
! x+ @1 h6 [7 i, a7 M' E                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, - m" ]' M+ M+ Z, ^
                                          &pSurface, NULL);
4 [. H7 Q$ M! W; t8 N7 [( og_pd3dDevice->GetFrontBufferData(0, pSurface);1 @2 K9 I( y- |. {* S
D3DLOCKED_RECT lockedRect;: o! O' p, l7 Q& X7 ?: Q
pSurface->LockRect(&lockedRect,NULL,/ v$ `: P2 m/ L3 U
                   D3DLOCK_NO_DIRTY_UPDATE|( Y: h. C# N3 p0 T$ h
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
8 T2 }# S$ H7 ]: w* D3 C( R; `; Bfor( int i=0 ; i < ScreenHeight ; i++)
; \6 q% p# K  v; j{! \7 g5 w# O) K0 \( f+ n
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 ,
; U. g7 ^, d* i# _        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , . ]; L. M% ]& m! U* h- O
        ScreenWidth * BITSPERPIXEL / 8);
3 I* ?6 O( {& i, c* O}0 B; S5 ?3 @. T) p' V0 e1 E
g_pSurface->UnlockRect();
& c$ @- F9 C4 \9 K# e7 zpSurface->Release();% j5 J9 J( r0 ~
) t0 w7 U% d: L% i& r9 Y! A, {. w
7 J7 Y- _7 z% Z% d( D2 e+ X
上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:
" J% U" y7 T( F, b: V1 ~9 A; w2 e  W
for( int i=0 ; i < ScreenHeight ; i++)4 U0 J- r* y0 Q# [
{
; M- a9 L5 e, G    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * 7 }" ^) L4 o5 Z& s9 x* }# t' v/ h1 {
        ScreenWidth * BITSPERPIXEL/8 , + z; j4 k; F, ?& Q
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
8 T* e8 I* ^: Q) C        ScreenWidth* BITSPERPIXEL/8);
$ J  K8 Q1 y9 P% n8 H}0 v% j$ E; W" T
7 j9 d, l5 X) Z9 f6 D
( ?5 [9 G: O( S( }" i
这点代码用于转换自顶而下和自底而上的位图。
0 h2 R6 _# U) f& e8 ?# \
5 o( A8 t) f. H& d! T6 ~上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。
8 e" K  z* |+ X0 |, m$ q& `6 z0 v2 `3 l

- x( H- y! H3 |- `2 D1 i3 z' ]; j0 \; `! j! N$ |1 \! R) @
然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。9 |0 f- U+ D# A

- D4 c! i: \8 \* I* l9 P & C, o- h  q1 v* S: R2 D3 n2 x

/ R' b( O8 O( O8 X, R捕获程序要点:
1 }. ?$ J& p; _; r! M0 V: c% k( K$ b! g. j! @. l: O
    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:# D# e- d( ^% w9 n/ i
: t& g' e4 x5 U
1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。/ a* }: D  N/ m0 |' P' d: b
! B$ g! t: x$ R- B" f
2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。
+ [  A1 n3 K7 }1 B9 A& r7 j" z9 O
; O. q; ]2 M3 }3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。
) y2 E" z0 u4 n5 C$ o: e3 T& {- z
, P4 `/ J. q+ s. C5 S3 k! N" B9 _$ x4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送1 B9 b) R* s$ a" V3 B( |

8 z; o9 Y1 y( L9 Z  a8 a+ g% v- w4 x: j5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标
' P: N( y$ f* j3 C8 \' ]: ^1 t) P* T
- d# x' h/ [* u
# T) a( L1 i' P5 G
% I4 _! l9 g. n& {: K! d程序运行截图:/ c" I* b" J5 {) ]. \
, F4 e2 Q/ {) ?0 q6 X

+ ?+ g6 Z0 S$ ?+ X2 K该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。' y, V, R7 {0 b, P& }4 d
" L4 r/ @- ?5 G3 |
一些可能的改进:
! Q, @9 D7 s+ x% r$ c1 e1 k2 W% z% P0 @1 f* \5 S. @" I
1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦
; [7 f& L+ m; x4 x
+ n' }8 `* K" L0 F2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。3 D) {/ u- E7 N# N" y& A

9 ]: q. t. F1 Q; G7 |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日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

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