捕捉屏幕的各种方法
内容 介绍 用GDI方式捕获它 还有DirectX方法 用Windows Media API捕获屏幕 介绍 有时,我们希望通过编程捕获整个屏幕的内容。下面解释如何做到这一点。通常,我们可以直接使用GDI和/或DirectX。另一个值得考虑的选择是Windows Media API。在这里,我们将分别考虑它们,看看如何将它们用于我们的目的。在每种方法中,一旦我们将屏幕截图放入应用程序定义的内存或位图中,我们就可以使用它来生成电影。有关以编程方式从位图序列创建电影的详细信息,请参阅文章“从HBitmap创建电影”。 用GDI方式捕获它 当性能不是问题,当我们想要的只是桌面快照时,我们可以考虑GDI选项。这个机制基于桌面也是一个窗口的简单原理——即它有一个窗口句柄(HWND)和一个设备上下文(DC)。如果我们可以捕获桌面的设备上下文,我们可以用常规的方式将这些内容blit到我们的应用程序定义的设备上下文。如果我们知道它的窗口句柄,那么获取桌面的设备上下文非常简单——可以通过函数GetDesktopWindow()来实现。因此,涉及的步骤是: 使用函数GetDesktopWindow()获取桌面窗口句柄; 使用函数GetDC()获取桌面窗口的DC; 为桌面DC创建一个兼容的DC和一个兼容的位图,以选择到该兼容的DC。这些可以通过使用CreateCompatibleDC()和CreateCompatibleBitmap()来完成;选择位图到我们的DC可以通过SelectObject()完成; 当您准备捕捉屏幕时,只需将桌面DC的内容blit到已创建的兼容DC—这就是全部—您就完成了。我们现在创建的兼容位图包含了捕获时屏幕的内容。 不要忘记在完成操作后释放对象。内存是宝贵的(对于其他应用程序)。 ExampleHide,复制Code
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|CAPTUREBLT); 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方法 用DirectX捕捉屏幕截图是一项非常简单的任务。DirectX提供了一种简单的方法。 每个DirectX应用程序都包含所谓的缓冲区或表面,用于保存与该应用程序相关的视频内存的内容。这被称为应用程序的回缓冲。一些应用程序可能有多个回缓冲。另外,每个应用程序默认都可以访问另一个缓冲区——前端缓冲区。这个缓冲区,即前缓冲区,保存与桌面内容相关的视频内存,因此本质上是屏幕图像。 通过从DirectX应用程序访问前端缓冲区,我们可以捕获此时屏幕的内容。 从DirectX应用程序访问前端缓冲区非常简单和直接。接口IDirect3DDevice9提供了GetFrontBufferData()方法,该方法采用IDirect3DSurface9对象指针并将前端缓冲区的内容复制到该表面上。可以使用方法IDirect3DDevice8::CreateOffscreenPlainSurface()来生成IDirect3DSurfce9对象。一旦屏幕被捕获到图面上,我们就可以使用D3DXSaveSurfaceToFile()函数以位图格式将图面直接保存到磁盘上。因此,捕捉屏幕的代码如下所示:复制Code
extern IDirect3DDevice9* g_pd3dDevice; Void CaptureScreen() { IDirect3DSurface9* pSurface; g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL); g_pd3dDevice->GetFrontBufferData(0, pSurface); D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL); pSurface->Release(); }
在上面的示例中,g_pd3dDevice是一个IDirect3DDevice9对象,并假设已正确初始化。此代码片段将捕获的映像直接保存到磁盘上。但是,如果我们只是想直接操作图像位,我们可以使用方法IDirect3DSurface9::LockRect(),而不是保存到磁盘。这提供了一个指向表面内存的指针——本质上是一个指向捕获图像的位的指针。我们可以将比特复制到应用程序定义的内存中,并对其进行操作。下面的代码片段展示了如何将surface内容复制到应用程序定义的内存中:复制Code
extern void* pBits; extern IDirect3DDevice9* g_pd3dDevice; IDirect3DSurface9* pSurface; g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL); g_pd3dDevice->GetFrontBufferData(0, 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*。在复制到pBits之前,确保我们已经分配了足够的内存。BITSPERPIXEL的典型值是每像素32位。但是,它可能会根据您当前的监视器设置而有所不同。这里要注意的重要一点是曲面的宽度是n与捕获的屏幕图像宽度不同。由于涉及到内存对齐的问题(假定与字边界对齐的内存访问速度比非对齐的内存更快),surface可能会在每一行的末尾添加额外的内容,以使它们完全对齐到字边界。lockedRect。Pitch表示连续两行的起始点之间的字节数。也就是说,要前进到下一行的正确位置,我们应该按音高前进,而不是按宽度前进。你可以用下面的方法反向复制表面位:隐藏复制Code
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); }
当您在自顶向下和自底向上位图之间进行转换时,这可能非常方便。 虽然上面的LockRect()技术是访问IDirect3DSurface9上捕获的图像内容的一种方法,但是我们有另一个为IDirect3DSurface9定义的更复杂的方法,即GetDC()方法。我们可以使用IDirect3DSurface9::GetDC()方法为DirectX图像表面获得一个GDI兼容的设备上下文,这使得直接将表面内容blit到我们的应用程序定义的DC成为可能。有兴趣的读者可以探索这种选择。 本文提供的示例源代码实现了将屏幕外平面的内容复制到用户创建的位图上的技术,以便定期捕获屏幕内容,并根据捕获的图像序列创建影片。 但是,在使用此技术进行屏幕捕获时,值得注意的一点是文档中提到的注意事项:GetFrontBufferData()从设计上来说是一个很慢的操作,在性能关键的应用程序中不应该考虑使用它。因此,在这种情况下,GDI方法比DirectX方法更可取。 用于捕捉屏幕的Windows Media API Windows Media 9.0使用Windows Media Encoder 9 API支持屏幕截图。它包括一个名为Windows Media Video 9 Screen的编解码器,该编解码器经过特别优化,可以对通过屏幕截图生成的内容进行操作。Windows Media Encoder API提供了接口IWMEncoder2,可以使用该接口有效地捕获屏幕内容。 使用Windows Media Encoder API进行屏幕捕获非常简单。首先,我们需要使用CoCreateInstance()函数创建IWMEncoder2对象。可以这样做:隐藏复制Code
IWMEncoder2* g_pEncoder=NULL; CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER, IID_IWMEncoder2,(void**)&g_pEncoder);
因此创建的Encoder对象包含处理捕获的屏幕数据的所有操作。然而,为了正确地执行它的操作,encoder对象依赖于在所谓的概要文件中定义的设置。配置文件只是一个包含控制编码操作的所有设置的文件。我们还可以在运行时使用各种定制选项创建自定义配置文件,如编解码器选项等,这取决于捕获数据的性质。为了在屏幕捕获应用程序中使用配置文件,我们创建了一个基于Windows Media Video 9屏幕编解码器的自定义配置文件。IWMEncProfile2接口支持自定义概要文件对象。我们可以使用CoCreateInstance()函数来创建一个定制的配置文件对象,如下所示:复制Code
IWMEncProfile2* g_pProfile=NULL; CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER, IID_IWMEncProfile2,(void**)&g_pProfile);
我们需要在配置文件中为编码器指定目标受众。每个配置文件可以保存多个受众配置,它们是接口IWMEncAudienceObj的对象。在这里,我们使用一个受众对象作为配置文件。我们为概要文件创建观众对象使用方法IWMEncProfile:: AddAudience(),它会返回一个指向IWMEncAudienceObj然后可以用于配置如视频编解码器设置(IWMEncAudienceObj: put_VideoCodec()),视频帧大小设置(IWMEncAudienceObj: put_VideoHeight()和IWMEncAudienceObj:: put_VideoWidth())等。例如,我们将视频编解码器设置为Windows Media video 9屏幕编解码器:Hide 复制Code
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屏幕编解码器的fourcc是MSS2。IWMEncAudienceObj::put_VideoCodec()接受概要文件索引作为输入,以识别特定的概要文件——可以通过使用方法IWMEncProfile::GetCodecIndexFromFourCC()来获得。 一旦完成配置概要文件对象,我们就可以通过使用在编码器的源组对象上定义的方法IWMEncSourceGroup:: put_Profile()来选择这个概要文件到我们的编码器中。源组是源的集合,其中每个源可能是视频流、音频流或HTML流等。每个encoder对象都可以与许多源组一起工作,从这些源组中获取输入数据。因为我们的屏幕捕获应用程序只使用一个视流,所以我们的编码器对象需要有一个源组,其中包含一个单独的源,即视频源。这个单一的视频源需要配置为使用屏幕设备作为输入源,这可以通过使用IWMEn方法完成cVideoSource2: SetInput(型):隐藏,复制Code
extern IWMEncVideoSource2* pSrcVid; pSrcVid->SetInput(CComBSTR("ScreenCap://ScreenCapture1");
通过使用IWMEncFile::put_LocalFileName()方法,可以将目标输出配置为保存到视频文件(wmv movie)中,该方法需要一个IWMEncFile对象。这个IWMEncFile对象可以通过使用方法IWMEncoder::get_File()来获得:Hide 复制Code
IWMEncFile* pOutFile=NULL; g_pEncoder->get_File(&pOutFile); pOutFile->put_LocalFileName(CComBSTR(szOutputFileName);
现在,在对encoder对象完成所有必要的配置之后,我们可以使用IWMEncoder::Start()方法开始捕捉屏幕。方法IWMEncoder::Stop()和IWMEncoder::Pause可以用于停止和暂停捕获。 在处理全屏捕获时,我们可以通过调整输入视频源流的属性来交替地选择捕获区域。为此,我们需要使用IWmEnVideoSource2对象的IPropertyBag接口,如下所示:复制Code
#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不太可能捕获鼠标光标)。 注意,您的系统需要安装Windows Media 9.0 SDK组件来创建使用Windows Media 9.0 API的应用程序。 要运行应用程序,终端用户必须安装Windows Media Encoder 9系列。当您发布基于Windows Media Encoder SDK的应用程序时,您还必须包括Windows Media Encoder软件,通过在您的设置中重新发布Windows Media Encoder,或者要求您的用户自己安装Windows Media Encoder。 windowsmediaencoder 9.0可从以下网站下载: Windows媒体编码器 结论 上面讨论的所有技术都针对一个目标——捕捉屏幕的内容。然而,正如很容易猜到的那样,结果会因程序中使用的特定技术而有所不同。如果我们想要的只是一个偶然的随机快照,那么GDI方法是一个不错的选择,因为它很简单。然而,如果我们想要更专业的结果,使用Windows Media会是一个更好的选择。值得注意的一点是,通过这些机制捕获的内容的质量可能取决于系统的设置。例如,禁用硬件加速(桌面属性|设置|高级|故障排除)可能会极大地提高捕获应用程序的总体质量和性能。 本文转载于:http://www.diyabc.com/frontweb/news5251.html