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

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

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

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

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

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

原文
/ R" I3 o. s* E8 A# fhttp://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx
4 d6 f- D. l6 f4 b) u7 e8 x& r2 m
       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。2 b! f, T9 P* f8 |
- W, Z# e# B8 H4 l5 X7 p
% ?) b+ m6 |9 J4 a" S/ O" Z

) J- E4 _- _& k用GDI方法捕获' }* w2 F$ \/ s4 ?

$ e; b" }9 r& V+ o$ B. g2 y8 T       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:, i& X5 K4 ]$ ], s- `

7 e* k( I  D; _! ]. @  b) K8 z1)  使用GetDesktopWindow获得桌面句柄  d8 r5 P) @2 ^5 G
- k( v0 `3 g; j0 Z3 v3 P9 }
2)  使用函数GetDC获得桌面窗口的DC- |9 y3 j$ x) p2 \& u: d6 w- |

0 w' U  b8 D9 e# y$ c) q3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC+ o1 W% ]" M- ]
) D7 h: x1 @. Q* e* k
4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容0 `2 V1 W6 s- x+ p4 t

4 M1 N  l+ ?, w% J+ l6 k& C5)  当完成捕获后,不要忘记释放对象
1 K; {' U2 p  u9 H5 ^5 F
. c5 u3 D$ u1 R2 d- v* g  l例子:
7 M$ u: T7 z5 O( f5 F- S6 E# n# C, p! T, n
Void CaptureScreen()
; S. |. k( i  z: A- P# S{
* m# a  L' i, M  i    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
5 @7 N$ v# \/ K- t, B) t) b    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);. v& X* v' p4 L1 X  |, h
    HWND hDesktopWnd = GetDesktopWindow();
& E: ]0 T9 ~1 Z8 n3 [0 f$ \6 ^    HDC hDesktopDC = GetDC(hDesktopWnd);% _! o) |6 Y$ `7 S" n7 b8 f% r
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
, T& D+ }% V* B2 h4 G: d1 O7 D    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,   Z; W( Z7 ]0 U; D+ j& C
                            nScreenWidth, nScreenHeight);* O6 a3 @1 w9 I  C4 _! g* T0 t
    SelectObject(hCaptureDC,hCaptureBitmap); 9 @4 i% j. z% n) }4 W
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,/ n/ R, Q6 q# c
           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); $ K- S  W1 ^/ i+ z, u) g5 R
    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码3 n6 E' I6 ^% w
    ReleaseDC(hDesktopWnd,hDesktopDC);* K: f* O6 @5 U" t5 ?8 H: z
    DeleteDC(hCaptureDC);
  o9 j  R+ |/ o, c    DeleteObject(hCaptureBitmap);8 V/ r  `2 C* U  F, y" L
}2 d1 D/ a8 a9 w' H9 g; [/ P
上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。$ \- p% T$ V- \( h% B, C0 _, H
$ @0 n0 y3 n* T) Q2 ^
- K+ ?' _7 ~4 S2 ~, d

5 b& C: W% [0 v* u3 D: Q$ q  T+ K, {采用DirectX的方法:
; k( a! Y; @# o1 i
) g- R  k  V1 U8 n2 a7 k8 X    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。
1 D5 r5 W" K( A
7 i. N* S: w& U5 @每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。, W6 t, O4 K9 Q* ~- E

0 O' q) U+ Y) ^
% _' V( y! l" R* U1 f
3 _7 i! k, X/ T. |通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:0 ~2 |+ p, W9 e7 r8 H, ~5 z" g# k- ?& h

& C) e: u$ L+ C) ~0 k5 V/ Nextern IDirect3DDevice9* g_pd3dDevice;. i' l2 g7 Y  \& @/ o+ E
Void CaptureScreen()
! G" p9 ]9 C6 g9 q! Y8 \{% K2 }4 ^- r; y5 }; |
    IDirect3DSurface9* pSurface;
6 u/ t, R8 p% j7 ]; w    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,3 V( s- n  }6 q8 }! x
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
6 o6 W+ \$ O6 O  o3 T7 v) P+ n    g_pd3dDevice->GetFrontBufferData(0, pSurface);
  S0 l" g& ^$ M; R% u9 N! F4 M    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
( a$ h. \3 G0 J6 G    pSurface->Release(); # r% m" A: g  m
}
9 K' d0 a* V( Y$ V2 @5 L上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:9 M) W( g# O2 y' _# G

( \* b4 P+ Q% }9 }extern void* pBits;
2 c2 M8 d" T* N/ t0 M9 F' S* b4 \extern IDirect3DDevice9* g_pd3dDevice;
' \* A6 t0 K' ]$ [IDirect3DSurface9* pSurface;7 ?) \0 h# \# D6 c( T
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
/ J5 ?1 |! o2 F- h                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH,
$ p: U- i& }( I5 n                                          &pSurface, NULL);0 t7 d6 w6 \0 z, l- i
g_pd3dDevice->GetFrontBufferData(0, pSurface);
$ f  e2 G8 d  }7 i- Q$ LD3DLOCKED_RECT lockedRect;
1 k, q" Y8 ?+ d# q8 @% m+ h# v5 zpSurface->LockRect(&lockedRect,NULL,
4 X  V  O# W. M) j% `                   D3DLOCK_NO_DIRTY_UPDATE|
3 F" j  l* R) `                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));7 t# ]2 j6 Z, M' m3 R; ^
for( int i=0 ; i < ScreenHeight ; i++)
6 a  n% Q! J" B$ z5 J1 b{" `4 U8 F- n6 u
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , / M+ ^$ b( G0 `; B6 X  w
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
9 B+ C1 Y/ u( m" B' ]2 l        ScreenWidth * BITSPERPIXEL / 8);! x, C3 s" k, Q
}  q  K; `# z5 p" a0 D8 u/ @
g_pSurface->UnlockRect();
0 e2 K7 Z9 t$ m. EpSurface->Release();
6 S4 v3 `. y# n- g
- x7 @7 U! w0 w6 M: `9 w
* T# [9 P( M/ Z7 `) e, h2 J上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:$ R0 H( j! o+ E: C% W
( x( h6 B3 S. U. c5 k: t; ?! c1 `. P
for( int i=0 ; i < ScreenHeight ; i++)
9 r( A6 c: J3 n( E2 E{
( e( A! h4 f' F6 x/ t2 I  x. \    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
3 |6 P- Y: Q  Z- h        ScreenWidth * BITSPERPIXEL/8 , ' i, p" ?, j: e9 d" \
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , % M( Y6 E" Q+ H( ?
        ScreenWidth* BITSPERPIXEL/8);
7 J6 q: E- X4 d}
9 W5 N7 B. Z* K, C% R 7 ?: W& I1 E* W, j' y
/ @" F# i; y) \* O; b
这点代码用于转换自顶而下和自底而上的位图。# S2 j7 ~2 c& f

/ {& t1 t3 \- b( g! s; d3 M7 e7 R3 U* x上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。
. ]! n& {! B6 Y& @, R# ?9 Q5 q
. s8 H% p9 m2 x4 S( R% F0 ]
' \& D2 g# ?! P" R
0 h. S& U9 E4 G然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。1 S: F! I+ e$ h

8 N7 s, o, K2 A2 o( H% i. ^
8 Q. Z# v2 h$ e6 ?% }# R$ [- ~9 Q# H) W1 L+ W& R" B( b/ C& S
捕获程序要点:8 t6 d6 f- {$ E- R9 K! n/ D

) y- m3 C1 r- B. V/ G2 M: L    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:# Z4 U2 {7 \. P4 O3 ?

" m: y8 X* K  |1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。
- H# Q* w4 l9 Z2 i' h# Y. R& }- g: P
2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。
  u5 n$ u) S8 Q, x
4 ^+ e$ s0 Z$ j+ O1 `3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。
! j7 e! ]1 f9 V0 j
9 m. {3 f' e; [5 J4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送- p3 U' l. Y$ B' l6 |& K$ p
1 L+ B% I. m4 u; @0 i  d
5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标; p3 C  H1 ?( T; S& \
* I8 j, S  J( m* e+ q9 s
, r1 m) Y" i. Q! I  j. [1 ~; t

/ H% y6 o. |: s5 F( _程序运行截图:
7 N& V% A$ {) ?- s  s2 o' q; Z' x( Y% L1 ?" }
- t* g7 W8 V( r" s( t$ y
该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。
. T" c  l- @, x4 w! q% f
* u( i+ R9 L  l& e5 y6 \/ J; C0 m一些可能的改进:
$ M4 s" w! G, y! f4 l" \, }4 z" p' F) d6 G# v& F, b8 M
1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦
. X4 W& X0 |/ d% I' @# x  U( Z* Z! g; }. K$ t$ N" p) b
2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。
) |  j5 Z6 B# f5 b6 t* r5 Y/ N6 P2 e. B% S1 m3 F! R8 U5 D" }
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日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

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