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

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

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

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

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

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

原文
6 _7 B$ q9 Z3 U0 ~+ Jhttp://blog.csdn.net/matlab2000/archive/2006/10/30/1356752.aspx9 ]( [9 J: ]+ f7 m2 i' ]7 O$ k* F$ X
( u9 v+ u2 i# y$ R9 i' J
       常常我们想要编程捕获整个屏幕的内容,下面将解释如何做到这一点。典型的,我们的选择是使用GDI或者DirectX。另外一个值得考虑的方法是Windows Media API,直接将捕获的屏幕图像编码成码流输出,不过本文中不讨论该方法,主要讨论前两种技术。每一种方法中,我们得到了屏幕快照后,就可以用于保存或者编码输出。下面讨论了两种方法,并给出程序的部分代码和运行截图。
, L$ E" w. G" e  K
/ g4 J# L( z4 U- r+ L - C- J  M, q) ], [
3 o% Z1 Y) \4 y+ O( W6 A
用GDI方法捕获
% ]' Y3 T8 I7 T$ d; g, K
& g  m7 l+ Q- u+ P5 S       如果性能不是问题,并且我们需要的只是屏幕快照,就可以考虑采用GDI方法。这种方法基于桌面也是一个窗口的基本思想(桌面也有窗口句柄和设备上下文),只要得到被捕获桌面的上下文,就可以用普通的方法把内容blit到我们应用定义的上下文中。如果知道桌面句柄(可以用函数GetDesktopWindow()函数得到),得到上下文是很直接的。步骤如下:5 `7 Z% b' G2 ]

0 J# R+ E) O7 r+ M1)  使用GetDesktopWindow获得桌面句柄) I( z5 d7 n" w

: ]0 B2 o  X  ?; ^$ ~2)  使用函数GetDC获得桌面窗口的DC
8 G/ v* A2 u/ X4 f- l/ Z6 e5 F5 x' T& z. F
3)  为桌面DC创建一个兼容的DC和一个用于选入兼容DC的兼容位图。可以用函数CreateCompatibleDC和CreateCompatibleBitmap;可以用SelectObject将位图选入我们的DC
  X1 a. T2 e! B; R) z& v- [1 \$ u- o% f$ L1 x
4)  不管何时准备捕获屏幕,blit桌面DC内容到创建的兼容的DC,我们创建的兼容位图现在包含了捕获时刻的屏幕内容
5 P$ O6 X& n/ v( R# R/ q
# @- Z* X& B% Y/ T; W. ]5)  当完成捕获后,不要忘记释放对象* j& j0 I! v6 \3 B1 `9 i' M

: ~. i( J: W- g" C例子:! @$ i. }8 f- q$ s: p- t, G+ |

6 s( N- t. X, r2 P, KVoid CaptureScreen()
. C) u& q$ I2 b) Y. H$ e" e{
2 ?& m9 x# Z, i: i    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);  J9 u% g, a+ R9 O( K* n9 A
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);  f0 x  z2 x0 u6 H) B; z" i" f
    HWND hDesktopWnd = GetDesktopWindow();
) p2 n! X$ n: [; Q    HDC hDesktopDC = GetDC(hDesktopWnd);3 \, P; B1 X; s9 o7 ~6 V$ Z
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
- _9 ^6 s5 k# [8 \    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
  R. ^4 j: h7 d$ F3 U7 [8 U                            nScreenWidth, nScreenHeight);1 Z" e" m& N, r4 k7 A! m) c
    SelectObject(hCaptureDC,hCaptureBitmap);
7 B* x8 h. |, x. X. Y7 A    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
" }& }) b# O! C9 M% E           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); . S3 \, K# @- f% }5 V2 m3 c* r
    SaveCapturedBitmap(hCaptureBitmap); //占位符号,可以在此处放入自己的代码% }4 R( ~9 Y; }  q4 J8 b
    ReleaseDC(hDesktopWnd,hDesktopDC);- O5 r' ^& t) X3 ]  `
    DeleteDC(hCaptureDC);
; ~* o9 L( ?9 _- O    DeleteObject(hCaptureBitmap);  L, H; K! I2 T( U0 o* p
}. B7 F5 ~% V. h
上面的例子中,函数GetSystemMetrics()如果使用SM_CXSCREEN则返回屏幕宽,如果使用SM_CYSCREEN则返回屏幕的高。参考相关代码细节,我们很容易创建文件或者影片。
9 ^2 T6 c0 z& b! l9 P. c" c, G$ O, W* x) ~9 v- U& D/ J8 O* i
# F4 S+ b& W, b% u0 K

& d. ~% ?+ q, U- |$ A$ I: A7 s2 J采用DirectX的方法:
0 H. C6 x# F2 x  j
) n9 E9 d# @- ^9 ?+ C    由于DirectX提供了一个干净的方法,所以捕获屏幕快照在DirectX下非常容易。) L8 |% m' K- R4 h8 f  ]  H
6 s9 a, l0 T' v
每一个DirectX应用都包含了我们称为buffer或者surface的包含了相应于应用的视频内存,被称为后备缓冲。一些程序可能有超过一个后备缓冲。另外有一个所有程序能够存取的前端缓冲,该前端缓冲包含了桌面内容相关的视频内存,所以就是屏幕图像。2 ~" K* Q( W: K- V  }

4 n3 }3 [) M. |; n; `% ^; I' {% O
9 ^) G2 u# b6 Q- t, o9 b/ \1 w3 ^4 J4 [
通过存取我们应用程序的前端缓冲,我们就可以捕获此刻的屏幕内容。存取应用的前端缓冲是简单直接的。接口IDirect3DDevice9提供了GetFrontBufferData()方法,带一个IDirect3DSurface9对象指针,拷贝前端缓冲的数据进入surface. IDirect3DSurfce9对象能够通过方法IDirect3DDevice8::CreateOffscreenPlainSurface()获得。一旦屏幕被捕获到surface,我们可以使用D3DXSaveSurfaceToFile()以位图方式保存表面到磁盘。捕获代码如下:0 g$ W. r5 |/ Y& }

: F2 g! n5 ~- _extern IDirect3DDevice9* g_pd3dDevice;+ E8 n  E9 p) L/ L" O0 P8 {
Void CaptureScreen(), A: Y. W6 h8 A# z9 c' [
{7 o- M; Z" E8 f/ J
    IDirect3DSurface9* pSurface;
- J! x8 \- {8 W0 l2 Z% A    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
0 g* g2 F, V0 U- T* @        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
* ^+ f  N9 T& ^: N/ ~# I4 j; d+ f    g_pd3dDevice->GetFrontBufferData(0, pSurface);
5 q6 ^3 i8 w+ {  I    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);0 `! T$ X; [- g' Y7 P6 ~9 y  C
    pSurface->Release();
/ ~( Z- V2 v& |2 E9 t/ c' a}
0 Q/ ~" F5 w& U7 f/ Y上面代码中的g_pd3dDevice是一个IDirect3DDevice9对象,假定已经正确的初始化了。代码片断直接保存捕获图像到磁盘。然而,代替保存到磁盘,我们可能想要直接操作这个位图。我们可以通过使用方法IDirect3DSurface9::LockRect()来做到这点,获得一个指向表面的指针。我们可以拷贝图像到应用内存然后操作他们。接下来的代码片断表明了如何拷贝表面到我们的应用内存:) W0 n/ X. @9 D) ^4 O1 J0 V9 |% H
% `3 Q  k9 S1 ?% D  ?: ~
extern void* pBits;9 _, V- ~* i  `+ [% c
extern IDirect3DDevice9* g_pd3dDevice;
5 x0 a4 P. u. y- L* a* I" z: `0 d" m* uIDirect3DSurface9* pSurface;
* J: Y! M; q  D  t3 {g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,0 ~' S6 z6 n( b$ e& P, x
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, 7 q4 M# u: c$ h+ n, v7 E; c8 [
                                          &pSurface, NULL);
4 k) w5 ~9 W0 S% ~1 |3 mg_pd3dDevice->GetFrontBufferData(0, pSurface);
7 H3 W0 Q* [1 T" `, ^( ^D3DLOCKED_RECT lockedRect;
" j. Y  j$ _0 f7 z5 z$ J/ hpSurface->LockRect(&lockedRect,NULL,
& E  M/ Y3 P! i( ]6 P! W                   D3DLOCK_NO_DIRTY_UPDATE|0 N  @% H5 Q$ E, F( i9 V% ~
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
7 z6 h9 B! Q- W) T7 D  Gfor( int i=0 ; i < ScreenHeight ; i++)
$ z2 E5 `. z4 ?. x- s{$ G% e% z7 b: S( J( P. q
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , ( F" D1 J! G7 E  {; e7 o
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , . ]4 U) ^& k% y: U1 o9 \9 Z. F
        ScreenWidth * BITSPERPIXEL / 8);
) C5 L( @3 m9 e/ |# ]}4 Z1 \0 W8 N# p; e$ Q  Z; Y1 b4 n! K
g_pSurface->UnlockRect();
% i/ v& X6 q! mpSurface->Release();5 s. Q7 t- C. b& H1 e. }4 C( ?

' y' f. _. D# T* k0 p+ z3 s- Z+ v* ~8 A( P4 I
上面的pBits是一个void *,在拷贝之前要确认我们已经分配了足够的内存。一个BITSPERPIXEL的典型值是32位,然后,依赖于你的显示器设置。重要的是注意到表面的宽度与捕获屏幕的宽度并不相同,由于这个问题涉及到内存对齐,表面可能加入附加的部分到每行的结束来对齐到字边界。lockedRect.Pitch给出两个行之间的字节数目,也就是说,为了前进到下一行正确的点,我们应当推进Pitch,而不是Width.下面的代码反向的拷贝表面的字节:
+ t- N5 C, E) P% J  u7 y5 X3 i
: |  [, o* Y% Y# Kfor( int i=0 ; i < ScreenHeight ; i++)
) P/ @  Q8 F5 I{
% m$ B) X% r( m; N- `6 {2 h1 S2 }# E4 F    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
& ^; [% |& H: v# B  q+ t        ScreenWidth * BITSPERPIXEL/8 , 4 K1 l9 P- \! O6 W8 D3 N/ ]2 ~, Q; u
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
8 H6 f% a4 N0 }  y) U/ j' b        ScreenWidth* BITSPERPIXEL/8);5 _- t. Y; I" [8 W. q$ \7 f
}+ d; S( Y1 x6 [# |5 w

  I- N; T! b( F" f* O8 m! a* u7 l1 M8 _6 |! k4 F7 P( j
这点代码用于转换自顶而下和自底而上的位图。
4 _3 R- [& C0 B8 a: d* ?% ^5 q$ i2 X! L& |1 M7 f+ `) j3 o$ y
上面的技术中LockRect是IDirect3DSurface9中存取捕获图像的一个方法,我们还有另外一个更加复杂的方法,GetDC方法。IDirect3DSurface9::GetDC()方法用来获得GDI兼容的设备上下文,这样就可以直接blit表面内容到我们应用定义的DC。" f% R& ~2 o8 [6 _
9 J# y( Y/ m( A+ C. C7 i. \
$ V; }- c2 x2 ~
5 r) r# }# U4 U# f
然而,GetFrontBufferData是一个慢的方法,不应当用于性能临界的应用,GDI方法是更快的方法。
  [. |. P* @7 ~' }& l2 N* `& w
3 B) g2 w2 ~2 |
+ @) U  Y" `  p) l8 m5 B' A) t
5 D7 s, w% M: F! A4 C捕获程序要点:/ c: @) x8 Y6 p' g1 w, P7 ], X

% g  c6 a; J; Y% o+ t  O! V1 L; q    程序初始化的时候需要初始化相关资源,在WM_CREATE消息中初始化D3D,然后初始化FFMPEG,同时启动定时器;当用户选择开始捕获的时候,每次定时器到,处理WM_TIMER消息,锁定前端缓冲平面,然后拷贝数据到缓冲区,将该缓冲区传递给FFMPEG编码并输出;当用户停止的时候,停止定时器,清理D3D和FFMPEG,然后退出。下面是几个需要注意的技术点:
1 M6 P4 [# C0 L, s! b3 v* P2 V- x8 r3 U
1)屏幕的颜色深度,由于屏幕可能处于不同的颜色深度下,所以需要仔细区分,当GetDeviceCaps函数返回颜色深度为16的时候,需要区分是否RGB555或者RGB565,根据颜色深度来设定传递给FFMPEG的像素格式。2 L6 t6 A- y# `4 \. P
6 c- D8 J& E$ j& @
2)编码参数的设定,由于编码库支持的图像大小不是任意的,所以需要仔细的设置编码参数,否则不能初始化编码器。
7 N: B9 G4 ]" `3 C7 [' G* \' K* P3 h" _7 N& m
3)图像转换的设定,由于原图像和目的图像的色彩空间和大小都不一样,所以需要仔细考虑图像转换,色彩空间的转换可以用img_convert,大小的转换可以用img_resample,注意到大小转换只针对YUV420P,所以在本案例中是先转换色彩空间,从RGBA32转换到YUV420P,然后调用重采样函数改变大小。6 Y% ]. A% x  p( C. R/ ]
' k1 x, Y# \0 d. Z9 A" Q
4)编码图像的保存,调用avcodec_encode_video编码图像,然后直接写入文件即可,如果想要从网络输出,那么考虑rtp_callback函数回调,由库来做合适的分包,然后加上合适的头部发送
' {  `8 o! U; k+ a9 C
+ z# e6 x) L6 x; c7 f1 ~8 `5)抓屏过程中,程序需要转换成托盘图标。保存为bmp的时候图像行是颠倒的,要把表面的第一行作为bmp的最后一样。传给解码器的是正常坐标9 e' T. ]& I7 u( Y

% b# t" f& t& w3 e: N
0 L4 g7 Q8 g! l* Y  p% m! Z! f4 n0 f1 h' k- M! U
程序运行截图:
8 k' c" D5 k9 ^9 F& \( {8 ~: d1 \0 m$ \" o

3 Y3 ^& n+ v0 l* B$ h* t该图是采用的捕获工具所截获的H263纯码流通过VLC播放然后用快照功能获得,是CIF的分辨率。* ?  o) B! q5 A' E. r

" j" j+ v3 @7 V6 B6 M一些可能的改进:
  O9 D; P( a: m/ F" `* H# A8 m; z0 s# Y
1.针对DirectX和OpenGL的窗口捕获,这个需要采用hook技术,比较麻烦
; ?* q, N5 d* r; d" r  D" x  j0 m7 L3 e
2.针对不同图像尺寸的编码,目前的库不能支持XGA的H263编码,4cif的编码出现了不规则捕获的问题,问题还没有查清楚,也许是能力问题,也许是程序设计问题。
, m/ Y% l+ j5 `! T8 S
1 `5 w) Q, I, |, ?8 S1 L6 T3.编程中的一些优化和保护措施没有做好,程序运行的时候占用的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日,是全球华人共同的冒险解谜类游戏家园。我们致力于提供各类冒险游戏资讯供大家学习交流。本站所有资源均不用于商业用途。

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