文章翻譯自 P.GopalaKrishna 的 Various methods for capturing the screen 一文,原版地址見下面。本文章版權歸原作者所有。
??? 如果轉載該譯文 , 請保證文章的完整性,并注明來自 www.farproc.com
袁曉輝 ??
2005/6/12
原版地址: http://www.codeproject.com/dialog/screencap.asp#Windows%20Media%20API%20for%20Capturing%20the%20Screen%20:
?
本文附帶源碼 1 下載 39K
本文附帶源碼 2 下載 135.5K
本文附帶源碼 3 下載 59.8K
?
目錄:
?
l????????? 導言
l????????? 用 GID 函數抓屏
l????????? 用 DirectX 方式抓屏
l????????? 用 Windows Media API 抓屏
?
導言
?
有時候我們需要編程抓取整個屏幕上的內容,下面我將介紹抓屏是如何實現的。典型地,我們可以用 GID 和 DirectX 來完成,另外一個選擇是 Windows Media API ,在這篇文章我會逐一加以分析。在每一種方法里,一旦我們把屏幕的內容保存到了程序定義的內存塊或 bitmap 文件里,我們就可以進一步利用它們來生成動畫和電影,這個過程你可以參考“ 從 HBitmap 創建電影 ”一文中,以獲得更多的幫助。
?
用 GDI 函數抓屏
如果我們不太在意抓屏的效率,并且我們想要的只是一個屏幕快照的話,可以考慮使用 GDI 方式。這種抓屏機制是以“桌面也是一個窗口,桌面也有一個窗口句柄( HWND )”這個簡單的常識為基礎的,如果我們得到了桌面的設備上下文( DC ),就可以利用 blit (復制)它的內容到我們創建的 DC 中。我們可以用 GetDeskWindow ()得到桌面的窗口句柄,從句柄得到 DC 也是很容易的。具體的實現步驟為:
?
1.????????? 通過 GetDesktopWindow ()函數得到桌面的窗口句柄
2.????????? 用 GetDC ()取得桌面窗口的 DC
3.????????? 創建和屏幕 DC 兼容的位圖和 DC ( CreateCompatibleBitmap ()和 CreateCompatibleDC ()),并把這個位圖選進該 DC ( SelectObject ())
4.????????? 當你準備好抓屏時,就復制桌面窗口 DC 的內容到兼容 DC ,你就完成的抓屏過程,兼容位圖中就是抓屏時刻的屏幕內容
5.????????? 完成后別忘了釋放你創建的對象,內存是寶貴的(對別的程序來說)
?
示例代碼:
void CaptureScreen()
{
??? int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
??? int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
??? HWND hDesktopWnd = GetDesktopWindow();
??? HDC hDesktopDC = GetDC(hDesktopWnd);
??? HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
??? HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
??????? nScreenWidth, nScreenHeight);
??? SelectObject(hCaptureDC,hCaptureBitmap);
??? BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,hDesktopDC,0,0,SRCCOPY);
??? SaveCapturedBitmap(hCaptureBitmap); //Place holder - Put your code
??????????????????????????????? //here to save the captured image to disk
??? ReleaseDC(hDesktopWnd,hDesktopDC);
??? DeleteDC(hCaptureDC);
??? DeleteObject(hCaptureBitmap);
}
?
上面代碼段中, GetSystemMetrics ()返回屏幕的寬度( SM_CXSCREEN
)和高度( SM_CYSCREEN
)。關于如何保存抓到的位圖到文件和如何置到剪貼板,請參看附帶的源代碼,很簡單的。示例代碼每隔一段時間就通過上述技術抓屏,并把圖像序列保存到動畫。
?
DirectX 方式
用 DreictX 進行抓屏也是很簡單的, DirectX 提供了很優雅的實現。
每個 DirectX 程序都包含一個被我們稱作緩沖的內存區域,其中保存了和該程序有關的顯存內容,這在程序中被稱作后臺緩沖( Back Buffer ),有些程序有不止一個的后臺緩沖。還有一個緩沖,在默認情況下每個程序都可以訪問-前臺緩沖。前臺緩沖保存了和桌面相關的顯存內容,實質上就是屏幕圖像。
我們的程序通過訪問前臺緩沖就可以捕捉到當前屏幕的內容。由 DirectX 的底層優化機制做保證,我們的抓屏效率是很高的,至少比 GDI 方式高。
在 DirectX 程序中訪問前臺緩沖是很簡單的, IDirect3DDevice8
接口提供了
GetFrontBuffer()
方法,它接收一個
IDirect3DSurface8
對象指針做參數,并復制前臺緩沖的內容到該
Surface
。
IDirect3DSurfce8
對象可以用
IDirect3DDevice8::CreateImageSurface()
得到。一旦屏幕內容被保存到了這個 surface ,我們就可以用 D3DXSaveSurfaceToFile()
方法直接把內容保存到磁盤
bmp
文件。示例代碼如下:
extern IDirect3DDevice8* g_pd3dDevice;
Void CaptureScreen()
{
??? IDirect3DSurface8 * pSurface;
??? g_pd3dDeviceàCreateImageSurface(ScreenWidth,ScreenHeight,
??????? D3DFMT_A8R8G8B8,&pSurface);
??? g_pd3dDevice->GetFrontBuffer(pSurface);
??? D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,
??????? NULL,NULL);
??? pSurface->Release();
}
上面, g_pd3dDevice
是一個初始化好的 IDirect3DDevice
對象,這個例子直接把捕捉到的圖像保存到文件。然而,有時候我們想訪問直接這個圖像中的各個位,我們可以使用
IDirect3DSurface8::LockRect()
,它給我們一個執行
surface
內存的指針,也就是捕捉到的圖像的數據。我們復制這些數據到程序定義的內存中就可以操作它了。看下面的代碼:
extern void* pBits;
extern IDirect3DDevice8* g_pd3dDevice;
IDirect3DSurface8 * pSurface;
g_pd3dDeviceàCreateImageSurface(ScreenWidth,ScreenHeight,
??????????????????????????????? D3DFMT_A8R8G8B8,&pSurface);
g_pd3dDevice->GetFrontBuffer(pSurface);
D3DLOCKED_RECT lockedRect;
pSurfaceàLockRect(&lockedRect,NULL,
????????????????? D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_NOSYSLOCK|
????????????????? D3DLOCK_READONLY)));
for( int i=0 ; i < ScreenHeight ; i++)
{
??? memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 ,
??????? (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
??????? ScreenWidth * BITSPERPIXEL / 8);
}
g_pSurface->UnlockRect();
pSurface->Release();
?
上面的 pBits 是一個 void* ,請保證為先為它分配組足夠的內存空間。 BITSPERPIXEL 一般用 32 位色即可,它也取決于你的顯示器當前配置。一個需要注意的是, surface 的寬度和被捕捉的屏幕寬度不一樣。由于內存對齊的原因 ( 按 WORD 對齊的內存通常在訪問時效率較高 ) , surface 在每行結尾處可能會有多余的 bits 以使它對齊到 word 邊界上。 lockedRect.Pitch
給我們提供了兩個連續行的開端之間的字節數。也就是說我們在讀取一行時要向后移動指針
Pitch
字節而不是
Width
字節。你可以用下面的代碼反序復制
surface
:
for( int i=0 ; i < ScreenHeight ; i++)
{
??? memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
??????? ScreenWidth * BITSPERPIXEL/8 ,
??????? (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
??????? ScreenWidth* BITSPERPIXEL/8);
}
這對于從 top-down 位圖到 bottom-up 位圖很有用。
?
我們還可以使用 IDirect3DSurface9
的 GetDC()
方法取得
DirectX surface
的
GDI
兼容
DC
,然后復制它的內容到我們的兼容
DC
。如果你用的是
DirectX9
,試試吧。
最后,需要注意的一點,文檔提到:
FrontBuffer
是一個比較慢的操作,設計就是如此,所以在效率很關鍵的程序中應避免使用。已經警告你了!本文附帶的源代碼用這種技術定時捕捉屏幕,并保存為動畫。
用 Windows Media API 抓屏
Windows Media 9.0 支持用 Windows Media Encoder 9 API 來抓屏。它有一個編碼器叫 Windows Media Video 9 Screen codec ,特別為抓屏優化過。 Windows Media Encoder API 提供了一個 IWMEncoder2 接口可以用來高效地捕捉屏幕圖像。
?
用這種技術進行抓屏也很簡單,首先我們用 CoCreateInstance()
創建一個 IWMEncoder2 對象:
IWMEncoder2* g_pEncoder=NULL;
CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,
??????? IID_IWMEncoder2,(void**)&g_pEncoder);
這個 Encoder 對象包含了捕捉屏幕的所需的全部操作,然而為了正確地工作,編碼器對象的行為取決于被稱作 profile 的設置。一個 profile 只是一個包含了所有控制編碼操作設置的文件,我們可以根據被捕捉的數據的特性在運行時創建包含自定義設置的 profile 。為了在你的抓屏程序中使用 profile ,我們基于 Windows Media Video 9 Screen codec 來創建自定義的 profile 。自定義的 profile 對象從 IWMEncProfile2 開始就被支持了。我們可以用 CoCreateInstance 來創建自定義 profile
IWMEncProfile2* g_pProfile=NULL;
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
??????? IID_IWMEncProfile2,(void**)&g_pProfile);
我需要在 profile 里指定編碼器的聽眾( audience )。每個 profile 可以包含多個聽眾配置,它們是 IWMEncAudienceObj
接口對象。這里我們為
profile
使用一個聽眾。我們可以通過
IWMEncProfile::AddAudience()
為我們的
profile
創建聽眾,這個函數返回一個
IWMEncAudienceObj
指針,可以用來配置視頻編碼器
( IWMEncAudienceObj::put_VideoCodec()
) ,視頻幀對象
( IWMEncAudienceObj::put_VideoHeight()
和 IWMEncAudienceObj::put_VideoWidth()
) 我們用下面的代碼來配置視頻編碼器:
extern IWMEncAudienceObj* pAudience;
#define VIDEOCODEC MAKEFOURCC('M','S','S','2')
??? //MSS2 is the fourcc for the screen codec
?
long lCodecIndex=-1;
g_pProfile->GetCodecIndexFromFourCC(WMENC_VIDEO,VIDEOCODEC,
??? &lCodecIndex); //Get the Index of the Codec
pAudience->put_VideoCodec(0,lCodecIndex);
?
fourcc 是針對每個編碼器的唯一的標識, Windows Media Video 9 Screen codec 的 fourcc 為 MSS2 。 IWMEncAudienceObj::put_VideoCodec()
接受
profile
索引來組織一個
profile
,索引可以用
IWMEncProfile::GetCodecIndexFromFourCC()
取得。
?
一旦我們配置完畢一個 profile 對象,我們就可以用 IWMEncSourceGroup :: put_Profile()
選擇這個 profile 到我們的編碼器。一個源組( SourceGruop )是一組視頻流來源或音頻流來源,或 html 來源。每個編碼器可以使用許多源組,并從中取得輸入數據。由于我們的程序僅僅使用視頻流中是視頻來源。這個視頻來源需要用 IWMEncVideoSource2::SetInput(BSTR)
Screen Device 來配置為輸入來源:
extern IWMEncVideoSource2* pSrcVid;
pSrcVid->SetInput(CComBSTR("ScreenCap://ScreenCapture1");
?
目的輸出可以用 IWMEncFile::put_LocalFileName()
配置為保存到視頻文件( wmv 文件)。 IWMEncFile 對象可以用 IWMEncoder::get_File()
得到:
IWMEncFile* pOutFile=NULL;
g_pEncoder->get_File(&pOutFile);
pOutFile->put_LocalFileName(CComBSTR(szOutputFileName);
?
現在,一旦編碼器對象的一切所需配置都完成后,我們就可以用 IWMEncoder::Start()
開始抓屏。
IWMEncoder::Stop()
和 IWMEncoder::Pause
可以用來停止和暫停捕捉。
這些適用于全屏捕捉,我們也可以通過調整輸入視頻來源流的屬性來選擇一個區域進行捕捉。我們可以用
IWmEnVideoSource2
的
I
PropertyBag
接口來實現:
#define WMSCRNCAP_WINDOWLEFT CComBSTR("Left")
#define WMSCRNCAP_WINDOWTOP CComBSTR("Top")
#define WMSCRNCAP_WINDOWRIGHT CComBSTR("Right")
#define WMSCRNCAP_WINDOWBOTTOM CComBSTR("Bottom")
#define WMSCRNCAP_FLASHRECT CComBSTR("FlashRect")
#define WMSCRNCAP_ENTIRESCREEN CComBSTR("Screen")
#define WMSCRNCAP_WINDOWTITLE CComBSTR("WindowTitle")
extern IWMEncVideoSource2* pSrcVid;
int nLeft, nRight, nTop, nBottom;
pSrcVid->QueryInterface(IID_IPropertyBag,(void**)&pPropertyBag);
CComVariant varValue = false;
pPropertyBag->Write(WMSCRNCAP_ENTIRESCREEN,&varValue);
varValue = nLeft;
pPropertyBag->Write( WMSCRNCAP_WINDOWLEFT, &varValue );
varValue = nRight;
pPropertyBag->Write( WMSCRNCAP_WINDOWRIGHT, &varValue );
varValue = nTop;
pPropertyBag->Write( WMSCRNCAP_WINDOWTOP, &varValue );
varValue = nBottom;
pPropertyBag->Write( WMSCRNCAP_WINDOWBOTTOM, &varValue );
?
本文附帶的源碼實現此中技術的抓屏。除去生成的動畫質量很好外,一個有意思的地方是鼠標指針也被抓到了( GDI 和 DirectX 默認是不抓取鼠標指針的)。
?
注意,為了適用 WindowMedia9.0 API ,你的電腦必須安裝 Windows Media9.0 SDK ,你可以用下面地址下載:
- http://msdn.microsoft.com/library/default.asp?url=/downloads/list/winmedia.asp
最終用戶必須安裝 Windows Media Encoder 9 系列才能運行你的程序。在發布基于 Windows Media Encoder SDK 的程序時, Windows Media Encoder 軟件也必須附帶上去,要么在你的軟件安裝時自動安裝 Windows Media Encoder 要么讓用戶自己下載安裝。
?
Windows Encoder 9.0 可以從下面地址下載:
http://www.microsoft.com/windows/windowsmedia/ 9series/encoder/default.aspx
?
結論
上面討論的各種方法都是基于一個目標-抓取屏幕的內容。然而適用不同的技術,得到的結果也不一樣。如果我們需要的只是偶爾的抓屏, GDI 方式是個好的選擇,因為它簡單。然而如果你想得到更專業的結果,可以使用 Windows Media 。一個可能沒有意義的要點是,這些技術捕捉到的內容的質量很大程度上決于你的系統設置,比如進制硬件加速會大大提高抓屏的質量和程序的運行效率。 ?
?
?
?
?
?
?
?
?
?
?
?
?
?