社区应用 最新帖子 精华区 社区服务 会员列表 统计排行 银行

  • 17050阅读
  • 5回复

VC截图黑屏或全黑的解决办法(DirectX窗口)

级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
为什么会黑屏?

一句话概括,黑屏是由于DDraw加速引起的!
先说一下DirectX的显示原理,通常我们看到的屏幕上的数据,都是通过Primary Surface送至显示器的,什么是Primary Surface呢?中文叫做主显示表面,也就是说我们当前看到的屏幕上的图像数据是从这个表面来的,除了主表面意外还有离屏表面,叫做OffScreenSuface。这种表面中的数据是不直接显示在屏幕上的。常用的截屏函数也基本上是通过截取Primary Surface中的数据来实现的。现在多数的视频播放软件都是用DDraw写的(现在DDraw已经融合到DirectX的Graphics本分,DDraw这个词也已经成为历史了。),而且使用了一种叫做Overlay的表面,我们常用的截屏函数都是截取普通的primary surface中的数据,无法截取Overlay surface中的数据,而微软又没有提供公共的API来获取Overlay surface中的数据,所以,黑屏就不足为奇了。下面就是使用了Overlay技术的Windows Media Player截图时的黑屏现象。

Overlay是纯硬件支持的,DDraw并不会用软件实现这种功能。这种Surface的特殊之处在于,它相当于蒙在屏幕上的一块塑料板,也就是说,这个如果使用了这种Surface,那么它就位于所有surface的最前端。显示设备在向屏幕显示数据的时候,会先判断该位置是否有Overlay,如果有,就显示Overlay中像素,如果没有,就使用Primary Surface中的像素。所以,当你打开一个播放器来播放视频,截图的时候就会发现播放器窗口是黑的。原因就是这块区域正好对应着Overlay。当然是截取不到的。

关于Overlay surfaces的详细介绍,可以看这里
如何避免截图时黑屏
大多数软件除了DDraw的渲染模式之外,还提供了一种Software renderer模式,也就是软件模式,这种模式在DDraw加速不可用的时候才会使用,所以一个避免截图黑屏的办法就是关闭DDraw加速,强迫软件使用Software renderer模式,这样,Overlay surface就不存在了,也就不会黑屏了。具体办法如下
方法一: 使用软件本身的设置来禁用Overlay技术,该方法只会影响该软件本身,以常见的Windows Media Player为例,设置如下(将 使用覆盖这个选项勾掉,这样软件就不会使用Overlay Surface了)

方法二: 使用DirectX控制面板来关闭DDraw加速,上面的方法相当于关闭分支开关,而这个方法则相当于把总闸给关闭了,系统中所有其他软件也不能使用DDraw加速了。如果安装了DirectX SDK,那么直接在运行栏里面输入dxdiag即可打开DirectX控制面板,设置如下。

方法三:其实还有一种办法,就是用程序占用Overlay surface,这种办法太麻烦,一般人不会用,因为一般的独立显卡都支持Overlay Surface,而且支持的个数是有限的,所以,如果我们提前用程序占用了所有的Overlay Surface,那么其他程序就无法再使用了,也就不会导致截图黑屏了。在使用这种方法前,应该首先查看自己的显卡是否支持Overlay技术,以及支持多少个。如果安装了DirectX SDK,可以依次展开:开始-程序-Microsoft DirectX SDK-DirectX Utilities-DirectX Caps Viewer,由下图可知,我的显卡支持一个Overlay Surface,我还没玩过高级显卡,没见过支持多个Overlay的显卡,哪位兄弟有好的显卡,也发个图让小弟看看。

但是这种方法有一个弊端,就是如果软件不支持Software Renderer模式,那么如果你先用程序占用了Overlay,再启动播放软件的时候,就会导致错误。
以上几种办法究其本质,实际上就是一种,都是禁止使用Overlay Surface。而且这些方法都有一个弊端,就是要求先入为主,也就是在软件启动前(或者确切的说,是视频播放前)做好设置,然后再启动软件。如果视频已经播放(Overlay已经被占用),那么这些方法将会统统失效。
除此之外,还可以使用现成的截图软件,很多软件都可以处理黑屏的情况,比如HyperSnap 6就很不错。
如何截取Overlay中的数据?
说来这不是一个简单的问题,我在网上搜索了很久,也没找到合适的办法,据说使用Hook技术可能会办到,但是我对hook不太熟悉,所以也没尝试,希望哪位朋友有兴趣可以试试,关于这个话题,google讨论组上有个帖子,介绍的比较详细,感兴趣的可以看看这里
关键词: c++ 技术 directx
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 沙发  发表于: 2014-06-20
Using the GDI to Take DirectDraw Screenshots
by Michael Fötsch
May 31, 2000 (Last Update: June 25, 2000)

This article was originally published at Gary Simmons’ Mr GameMaker.

Table of Contents
  1. Introduction
  2. The Method
  3. Step 1: Creating an Offscreen Bitmap
  4. Step 2: Converting from DDB to DIB
  5. Step 3: Saving the File
  6. Cleaning up and chilling out
  7. A few words from the author
  8. Code Listing
Introduction
Every game should have a screenshot function! When people go to Disneyland, they bring their cameras. When they play your game, they must rely onyou to provide a camera. Screenshots let you show off the beauty of your game to others, e.g. on your game’s homepage. Similarly, magazine reviewers will take screenshots for their articles, and users might wish to use them (the shots, not the reviewers!) as desktop wallpapers. You wouldn’t want to prevent either by being lazy, would you.
Laziness is said to be one of the programmer’s virtues, but it will not deter us from writing a screenshot function! After all, there are Windows and its Graphics Device Interface, the GDI, that we can delegate to. In this tutorial we’ll write a screenshot function that can be used to capture DirectDraw surfaces. Our goal is to have a single function for all resolutions and pixel formats that is as easy to call as this:
...
case WM_KEYDOWN:
if ((int)wParam) == VK_F11) Screenshot("shot.bmp", lpDDSBack);
...

Note: While we’ll be working with DirectDraw surfaces, the presented method can be used to capture any GDI device context.
The Method
The bitmap on a DirectDraw surface is stored in a device-dependent format. You could not copy it to a surface of a different pixel format with a simple memcpy. IDirectDrawSurface::Blt, which does not do more than that either, will fail if surface formats do not match.
BMP files, on the other hand, store device-independent bitmaps (DIBs). The format stays the same no matter which device the bitmap is intended to be displayed on, be it a printer, a 24-bit display, a 16-bit display, etc. In addition to the bitmap bits, the file contains an information header that describes the format of the data.
Our task when creating a screenshot is to convert the DDB on the surface to a DIB. The GDI function GetDIBits will do that for us. Before we can call it, however, we need to obtain a GDI-compatible device context from the surface and obtain the handle of a DDB. After the conversion, we simply dump the converted data into a file. The beauty of GetDIBits is that it can convert to any format. We could, for example, convert a 32-bit surface to a 16-bit BMP. GetDIBits will also generate a palette if needed.

Step 1: Creating an Offscreen Bitmap
We need a GDI bitmap that GetDIBits can work with. That bitmap should be compatible with the DirectDraw surface, as we will copy the surface’s contents to it. A compatible bitmap is created with the GDI function CreateCompatibleBitmap. That function does not expect an LPDIRECTDRAWSURFACE but the handle of a GDI device context, which, in turn, we will create by calling the surface’s GetDC method. Following is the code to do all this:
HDC SurfDC;
lpDDS->GetDC(&SurfDC);
HBITMAP OffscrBmp = CreateCompatibleBitmap(SurfDC, Width, Height);

Note: We can query the surface’s width and height with its GetSurfaceDesc method. See the code listing at the end of this article.
We have created a bitmap that is compatible with the surface, but it does not yet contain any picture. We are going to use BitBlt to copy the image from the surface to our offscreen bitmap. Before we can do so, we need to “select the bitmap into a device context”, i.e. connect it to the device driver that will actually perform the transfer. It is wise to create a DC that is compatible with the surface’s DC:
HDC OffscrDC = CreatecompatibleDC(SurfDC);
A GDI object, which can be a bitmap but also a pen, a brush, a clipping region, etc., is selected into a device context using a function call like the following:
HBITMAP OldBmp = (HBITMAP)SelectObject(OffscrDC, OffscrBmp);
The first parameter specifies the device context, the second specifies the object that is to be selected. The return value is the handle of the previously selected object of the same type. When you select a pen, the return value is an HPEN. When you select a bitmap, the return value is an HBITMAP. The return value should be stored to be able to de-select the object later on. For obvious reasons, de-selecting an object by passing NULL would not work because NULL gives no information whether the pen, the font, or the bitmap should be de-selected.
Now we can finally copy the bitmap:
BitBlt(OffscrDC, 0, 0, Width, Height, SurfDC, 0, 0, SRCCOPY);
Step 2: Converting from DDB to DIB
The actual DDB-to-DIB conversion will be handled entirely by GetDIBits. The only thing we’ll have to do is to inform GetDIBits about the desired format of the DIB. I’d suggest we use the same format as the surface, so that no information is lost and we do not waste memory either. As it turns out, GetDIBits can also be used to obtain the format information from an HBITMAP. In other words, we’ll use GetDIBits to fill a structure that we, in a second call, will use as a paramater for that very same function.
The structure in question is BITMAPINFO, which consists of a BITMAPINFOHEADER and an optional palette. When taking a look at the declaration, you’ll notice that there is only memory for one palette entry. We need to reserve more:
LPBITMAPINFO lpbi = (LPBITMAPINFO)(new char[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)]);
lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

The structure is now big enough for the biggest possible palette, which is that of an 8-bit or 256-color bitmap.
The Win32 Programmer’s Reference says that the bitmap must not be selected into the device context when GetDIBits is called. We select OldBmp (remember?) to de-select OffscrBmp:
SelectObject(OffscrDC, OldBmp);
When calling GetDIBits to query bitmap info, do not pass an array for the bitmap bits but do pass a BITMAPINFOHEADER:
GetDIBits(OffscrDC, OffscrBmp, 0, Height, NULL, lpbi, DIB_RGB_COLORS);
Once the information has been obtained, we can allocate memory for the bitmap bits. The required size is returned in the biSizeImage member of the info header.
LPVOID lpvBits = new char[lpbi->bmiHeader.biSizeImage];
Now we can have GetDIBits convert the bitmap:
GetDIBits(OffscrDC, OffscrBmp, 0, Height, lpvBits, lpbi, DIB_RGB_COLORS);
Step 3: Saving the File
The last step is saving the buffers to a file. The file is created the recommended way:
HANDLE BmpFile = CreateFile(FileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
The first structure within a .bmp file must be a BITMAPFILEHEADER:
BITMAPFILEHEADER bmfh;
bmfh.bfType = 19778; // always the same, 'BM'
// bmfh.bfSize = ??? // we'll write that later
bmfh.bfReserved1 = bmfh.bfReserved2 = 0; // must be zero
// bmfh.bfOffBits = ??? // we'll write that later
WriteFile(BmpFile, &bmfh, sizeof(bmfh), &Written, NULL);

Note: The missing two fields, biOffBits and biSize, will be updated later when we have that information. It would be possible to calculate these values now, but for me it looks a bit cleaner my way. Be patient.
Immediately following the file header is the BITMAPINFOHEADER, which we can dump as is:
WriteFile(BmpFile, &lpbi->bmiHeader, sizeof(BITMAPINFOHEADER), &Written, NULL);
Following the info header could be a palette. Palettes are only needed for certain bit depths. On the one hand, modes with a bit count <= 8 will require a palette. On the other hand, 16-bit and 32-bit modes require a palette as well. In these modes the first three entries of the palette are used as bit masks for the red, green, and blue components of the color. These modes have a biCompression of BI_BITFIELDS. (Not all paint programs will handle such files!)
Note: 24-bit bitmaps do not need such information, because the masks are always 0x0000FF, 0x00FF00, and 0xFF0000, respectively.
if (lpbi->bmiHeader.biCompression == BI_BITFIELDS) PalEntries = 3;
else PalEntries = (lpbi->bmiHeader.biBitCount <= 8 ) ?
(int)(1 << lpbi->bmiHeader.biBitCount) : 0;
// Check biClrUsed: (See the note below)
if (lpbi->bmiHeader.biClrUsed) PalEntries = lpbi->bmiHeader.biClrUsed);

Note: I am not sure whether GetDIBits will ever make use of the biClrUsed member. I take it into account just to be on the safe side. The purpose of this member is to make it possible for a BMP file to store less palette entries than would be possible for the bit depth. For example, if a bitmap is stored in 8-bit format but uses, say, three colors only, biClrUsed could be set to three and only three RGBQUADs would have to be saved. The last time when I wrote a bitmap loader, some Windows wallpapers were using this feature. (Making my bitmap loader fail ungracefully, by the way…)
It’s time to store the calculated number of palette entries now:
if (PalEntries)
WriteFile(BmpFile, lpbi->bmiColors, PalEntries * sizeof(RGBQUAD), &Written, NULL);

The current position of the file pointer is the value that should be stored in the biOffBits member of the file header. We save it:
bmfh.bfOffBits = GetFilePointer(BmpFile);
Note: GetFilePointer is a wrapper for the following:
SetFilePointer(FileHandle, 0, 0, FILE_CURRENT);
Thus, GetFilePointer returns the current location of the file pointer.

Following the (optional) palette is the array of bitmap bits:
WriteFile(BmpFile, lpvBits, lpbi->bmiHeader.biSizeImage, &Written, NULL);
The current position of the file header is the biSize member of the file header. Let’s overwrite the file header that we have stored before and we are all done:
bmfh.bfSize = GetFilePointer(BmpFile);
SetFilePointer(BmpFile, 0, 0, FILE_BEGIN);
WriteFile(BmpFile, &bmfh, sizeof(bmfh), &Written, NULL);

Cleaning up…
The cleaning up is a necessary evil. Lazy programmers can copy and paste the following code:
CloseHandle(BmpFile);
if (SurfDC) lpDDS->ReleaseDC(SurfDC);
if (OffscrDC) DeleteDC(OffscrDC);
if (OffscrBmp) DeleteObject(OffscrBmp);
if (lpbi) delete[] lpbi;
if (lpvBits) delete[] lpvBits;
if (BmpFile != INVALID_HANDLE_VALUE) CloseHandle(BmpFile);

The preceding code has been written with an error checking mechanism in mind. It can be called at any time from the Screenshot function, provided that the variables have been properly initialized with NULL.
Note: The inline code samples do not perform any error checking while the listing at the end of this article does.
…and chilling out
We have created a flexible Screenshot function without much work. The GDI will handle DirectDraw surfaces of all formats without us having to modify the code. This is how it should always be!
The lesson we learned is that not even in times of DirectDraw one should forget about the rest of the WinAPI. For some reason the GDI has a reputation of being slow compared to DirectDraw. Being honest, this is true in a certain respect. On the other hand, DirectDraw is not even capable of blitting between surfaces of different formats!
The flexibility of functions like GetDIBits comes at a price. But the time you save for not having to code them yourself can be used for other things:Creating worlds that are worth taking screenshots of, for example.

A few words from the author
This has been my first article for mr-gamemaker.com. I was trying to make it useful for both beginners of GDI programming and those who are already familiar with the concepts of device contexts, DDBs, etc. Let me know whether you think I succeeded! I am looking forward to receiving your suggestions, questions, and comments: foetsch@yahoo.com.

Code Listing
[pre]//---------------------------------------------------------------------------////    Screenshot - Author: Michael Fötsch; Date: May 31, 2000////---------------------------------------------------------------------------#include <ddraw.h>#include <math.h>//---------------------------------------------------------------------------// Helper function to retrieve current position of file pointer:inline int GetFilePointer(HANDLE FileHandle){    return SetFilePointer(FileHandle, 0, 0, FILE_CURRENT);}//---------------------------------------------------------------------------// Helper macro to return from function when error occurs:#define ERROR_BREAK(x) throw (int)(x);// Screenshot//    -> FileName: Name of file to save screenshot to//    -> lpDDS: DirectDraw surface to capture//    <- Result: Success//bool Screenshot(LPCTSTR FileName, LPDIRECTDRAWSURFACE7 lpDDS){    if (!FileName || !lpDDS) return false;    bool Success=false;    HDC SurfDC=NULL;        // GDI-compatible device context for the surface    HBITMAP OffscrBmp=NULL; // bitmap that is converted to a DIB    HDC OffscrDC=NULL;      // offscreen DC that we can select OffscrBmp into    LPBITMAPINFO lpbi=NULL; // bitmap format info; used by GetDIBits    LPVOID lpvBits=NULL;    // pointer to bitmap bits array    HANDLE BmpFile=INVALID_HANDLE_VALUE;    // destination .bmp file    BITMAPFILEHEADER bmfh;  // .bmp file headertry{    // Get dimensions of Surface:    DDSURFACEDESC2 ddsd;    ZeroMemory(&ddsd, sizeof(ddsd));    ddsd.dwSize = sizeof(ddsd);    if (FAILED(lpDDS->GetSurfaceDesc(&ddsd))) ERROR_BREAK(0);    int Width = ddsd.dwWidth;    int Height = ddsd.dwHeight;    // Create a GDI-compatible device context for the surface:    if (FAILED(lpDDS->GetDC(&SurfDC))) ERROR_BREAK(1);    // We need an HBITMAP to convert it to a DIB:    if ((OffscrBmp = CreateCompatibleBitmap(SurfDC, Width, Height)) == NULL)        ERROR_BREAK(2);    // The bitmap is empty, so let's copy the contents of the surface to it.    // For that we need to select it into a device context. We create one.    if ((OffscrDC = CreateCompatibleDC(SurfDC)) == NULL) ERROR_BREAK(3);    // Select OffscrBmp into OffscrDC:    HBITMAP OldBmp = (HBITMAP)SelectObject(OffscrDC, OffscrBmp);    // Now we can copy the contents of the surface to the offscreen bitmap:    BitBlt(OffscrDC, 0, 0, Width, Height, SurfDC, 0, 0, SRCCOPY);    // We don't need SurfDC anymore. Free it:    lpDDS->ReleaseDC(SurfDC); SurfDC = NULL;    // GetDIBits requires format info about the bitmap. We can have GetDIBits    // fill a structure with that info if we pass a NULL pointer for lpvBits:    // Reserve memory for bitmap info (BITMAPINFOHEADER + largest possible    // palette):    if ((lpbi = (LPBITMAPINFO)(new char[sizeof(BITMAPINFOHEADER) +        256 * sizeof(RGBQUAD)])) == NULL) ERROR_BREAK(4);    ZeroMemory(&lpbi->bmiHeader, sizeof(BITMAPINFOHEADER));    lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);    // Get info but first de-select OffscrBmp because GetDIBits requires it:    SelectObject(OffscrDC, OldBmp);    if (!GetDIBits(OffscrDC, OffscrBmp, 0, Height, NULL, lpbi, DIB_RGB_COLORS))        ERROR_BREAK(5);    // Reserve memory for bitmap bits:    if ((lpvBits = new char[lpbi->bmiHeader.biSizeImage]) == NULL)        ERROR_BREAK(6);    // Have GetDIBits convert OffscrBmp to a DIB (device-independent bitmap):    if (!GetDIBits(OffscrDC, OffscrBmp, 0, Height, lpvBits, lpbi,        DIB_RGB_COLORS)) ERROR_BREAK(7);    // Create a file to save the DIB to:    if ((BmpFile = CreateFile(FileName,                              GENERIC_WRITE,                              0, NULL,                              CREATE_ALWAYS,                              FILE_ATTRIBUTE_NORMAL,                              NULL)) == INVALID_HANDLE_VALUE) ERROR_BREAK(8);    DWORD Written;    // number of bytes written by WriteFile    // Write a file header to the file:    bmfh.bfType = 19778;        // 'BM'    // bmfh.bfSize = ???        // we'll write that later    bmfh.bfReserved1 = bmfh.bfReserved2 = 0;    // bmfh.bfOffBits = ???     // we'll write that later    if (!WriteFile(BmpFile, &bmfh, sizeof(bmfh), &Written, NULL))        ERROR_BREAK(9);    if (Written < sizeof(bmfh)) ERROR_BREAK(9);    // Write BITMAPINFOHEADER to the file:    if (!WriteFile(BmpFile, &lpbi->bmiHeader, sizeof(BITMAPINFOHEADER),        &Written, NULL)) ERROR_BREAK(10);    if (Written < sizeof(BITMAPINFOHEADER)) ERROR_BREAK(10);    // Calculate size of palette:    int PalEntries;    // 16-bit or 32-bit bitmaps require bit masks:    if (lpbi->bmiHeader.biCompression == BI_BITFIELDS) PalEntries = 3;    else        // bitmap is palettized?        PalEntries = (lpbi->bmiHeader.biBitCount <= 8 ) ?            // 2^biBitCount palette entries max.:            (int)(1 << lpbi->bmiHeader.biBitCount)        // bitmap is TrueColor -> no palette:        : 0;    // If biClrUsed use only biClrUsed palette entries:    if (lpbi->bmiHeader.biClrUsed) PalEntries = lpbi->bmiHeader.biClrUsed;    // Write palette to the file:    if (PalEntries)    {        if (!WriteFile(BmpFile, &lpbi->bmiColors, PalEntries * sizeof(RGBQUAD),            &Written, NULL)) ERROR_BREAK(11);        if (Written < PalEntries * sizeof(RGBQUAD)) ERROR_BREAK(11);    }    // The current position in the file (at the beginning of the bitmap bits)    // will be saved to the BITMAPFILEHEADER:    bmfh.bfOffBits = GetFilePointer(BmpFile);    // Write bitmap bits to the file:    if (!WriteFile(BmpFile, lpvBits, lpbi->bmiHeader.biSizeImage,        &Written, NULL)) ERROR_BREAK(12);    if (Written < lpbi->bmiHeader.biSizeImage) ERROR_BREAK(12);    // The current pos. in the file is the final file size and will be saved:    bmfh.bfSize = GetFilePointer(BmpFile);    // We have all the info for the file header. Save the updated version:    SetFilePointer(BmpFile, 0, 0, FILE_BEGIN);    if (!WriteFile(BmpFile, &bmfh, sizeof(bmfh), &Written, NULL))        ERROR_BREAK(13);    if (Written < sizeof(bmfh)) ERROR_BREAK(13);    Success = true;}catch (int &errorcode){    char Buf[100];    wsprintf(Buf, "Screenshot error #%i", errorcode);    OutputDebugString(Buf);}catch (...){    OutputDebugString("Screenshot error");}    if (SurfDC) lpDDS->ReleaseDC(SurfDC);    if (OffscrDC) DeleteDC(OffscrDC);    if (OffscrBmp) DeleteObject(OffscrBmp);    if (lpbi) delete[] lpbi;    if (lpvBits) delete[] lpvBits;    if (BmpFile != INVALID_HANDLE_VALUE) CloseHandle(BmpFile);    return Success;}[/pre]
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 板凳  发表于: 2014-06-20
Screenshot of a videoplayer without "black" holes
Against present reasonings on, whether «will consult wash phones/calculators/clocks with playing of HD video» the interesting historic fact receding into the background: a little more 10 years ago speed of decoding of video were a problem at all (warm lamp MPEG were decod then by separate hardware decoders; the remaining interrupt by possibilities of the available CPU), and speed are more its than an output to the screen. The multiscreen environment killed on a root idea of transfer of memory block with the decod image in videostorage, after all windows could superimpose each other in the most freakish image.

Problem solv it are hardware. Application who should display video, deducing now only a background of particular color (as a rule, very dark magenta), and the videocard were engaged in "vpechatyvaniye" of video. At compilation of the image it replacing piksela of a background on appropriate piksela of the decod frame. Application having now area of storage in whom it were possible to copy very quickly frames from video at the instruction. The technology were nam by hardware overlay and very quickly implement it in all consumer and professional videocards.

Probably, the impossibility to make a screenshot of a videoplayer becoming a single problem who were br by this technology, after all it will comprise not a frame from video, and that are dark - a magenta background.


Circuitious paths bec switch-off of the hardware acceleration (as it are global, and at level of application player), preliminary start of one more copy of a player (this copy will capture in the use of overlay, and the second copy of a player should manage a classical program output to the screen) and if it is a question of the players construct on technology of DirectShow, switching of the filter renderer on the filter who did not use hardware overlay.

Almost all programs for capture of screenshots, as a rule, us the first two methods, or d not use them generally. Remain decid that are more useless to change adjustments at users and a progressive path, namely — by obtaining of the image of the video frame immediately from that area of storage somewhere in bowels of the videocard.

The method offer more low solved the g task for the players deduc video through DirectShow.

Let's contour a circle of solv tasks. At first, our program of capture of screenshots in itself, a videoplayer, who knew where to copy frames — in itself. Necessity to have access to address space of other process from here followed. Secondly, the data most likely will appear in the color model which is distinct from RGB. Meant it is necessary them skonvertirovat in a comprehensible type. Thirdly, "vpechatat" a picture in a screenshot it is necessary independently, taking into account all delights of a multiwindowing environment.

The first task dared in several ways, I selecting the most idle time: through setting huka (hook). The type of huk not so are important, the main thing — to get to address space of a player. For example, so:

void WINAPI SetHook(HWND hWnd)
{
  if (!g_hhHook)
  {
    g_hhHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)HookProc, hInst, 0);
  }
}


* This source code was highlighted with Source Code Highlighter.


The procedure receiv control at actuating of huk standard "nichegonedelayushchy".

LRESULT WINAPI HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
  if (nCode < 0)
  {
return CallNextHookEx(g_hhHook, nCode, wParam, lParam);
  }
// Здесь мы можем что-нибудь сделать, но не будем - незачем
  return CallNextHookEx(g_hhHook, nCode, wParam, lParam);
}


* This source code was highlighted with Source Code Highlighter.


I will remind that for implementation in address space of other process, the g procedure should be in DLL, instead of in an execut file. All further code also settled down in the same library.

Now we will pass to obtaining of the image. All filters renderers had at the instruction not so many methods of an output of the image on the screen: GDI, DirectDraw and Direct3D. The first and third variant done not create problems when obtaining screenshots, and here second just and are a subject of our interest.

That area of storage in whom copying are carr out, are select with object IDirectDrawSurface (it are "DirectDraw-surface") which are creat with a flag DDSCAPS_OVERLAY. By transfer of the image on the screen, it are simplif sp, the method are engaged IDirectDrawSurface::Flip. Meant, ha intercept a call of the g method, it are possible to receive data access with the image of the video frame. About interception of calls it are t much, for example. In due time I using a method number one of this article, as most simple. Single hindrance: to receive the address of a method of the COM object a call GetProcAddress it will not be possible. It not so are terrible, after all it are possible to create a DirectDraw-surface, and to learn position of a method Flip rather basic address of an execut file in whom implementation were produc. To make it it are possible as follows:

// Создадим объект IDirectDraw

LPDIRECTDRAW pDirectDraw;
hr = DirectDrawCreate(NULL, (&pDirectDraw;), NULL);

hr = pDirectDraw->SetCooperativeLevel(NULL, DDSCL_NORMAL);

DDSURFACEDESC desc = {0};
desc.dwSize = sizeof(desc);
desc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
desc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
desc.dwWidth = 100;
desc.dwHeight = 100;

// И вспомогательную поверхность IDirectDrawSurface

LPDIRECTDRAWSURFACE Surf;
hr = pDirectDraw->CreateSurface(&desc;, &Surf;, NULL);

void *pFlip = (*reinterpret_cast<void***>(Surf))[11]; // 11 - номер метода IDirectDrawSurface::Flip в vtable
ptrdiff_t pDDSFlipDiff = reinterpret_cast<ptrdiff_t>(pFlip) - reinterpret_cast<ptrdiff_t>(GetModuleHandle(_T("ddraw.dll")));


* This source code was highlighted with Source Code Highlighter.


For determination of position and the size of an overlay surface the method are required IDirectDrawSurface::UpdateOverlay, which had number of a method 33.
After that it are possible to produce interception of methods Flip and UpdateOverlay, ha specif addresses of the original and modif method in procedure of setting of an interceptor. Prototypes of methods looked so:

typedef HRESULT (WINAPI *FUNC_IDIRECTDRAWSURFACEFLIP)(IDirectDrawSurface *This, LPDIRECTDRAWSURFACE lpDDSurfTargetOverride, DWORD dwFlags);

typedef HRESULT (WINAPI *FUNC_IDIRECTDRAWSURFACEUPDATEOVERLAY)(IDirectDrawSurface *This, LPRECT lpSrcRect, LPDIRECTDRAWSURFACE lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx);


* This source code was highlighted with Source Code Highlighter.


Pay attention to that unlike the parameter list of methods Flip and UpdateOverlay in prototypes the first in the list of arguments writing down the pointer on the COM object contain the g method. It are connect with that methods of COM objects had type of a call thiscall.

Similar actions should be undert and for methods IDirectDrawSurface7::Flip and IDirectDrawSurface7::UpdateOverlay, as some renderers used interfaces of the seventh version of DirectDraw.

After these preparations the auxiliary surface and object of DirectDraw can be delet and pass to implementation of functions interceptors. To begin with we will consider functionUpdateOverlay, as with it the help it are possible to get mass of the helpful information.

HRESULT WINAPI Patch_UO(IDirectDrawSurface *This, LPRECT lpSrcRect, LPDIRECTDRAWSURFACE lpDDDestSurface, LPRECT lpDestRect, DWORD dwFlags, LPDDOVERLAYFX lpDDOverlayFx)
{
  DetourRestore(True_UO); // Восстанавливаем оригинальное состояние метода
  HRESULT res = True_UO(This, lpSrcRect, lpDDDestSurface, lpDestRect, dwFlags, lpDDOverlayFx);
  DetourRenew(True_UO); // Устанавливаем перехватчик

  DDCOLORKEY ck = {0};
  if (dwFlags &DDOVER_KEYDEST) // Значение цвета фона хранится в поверхности назначения
  {
    DDCOLORKEY ck2 = {0};
    lpDDDestSurface->GetColorKey(DDCKEY_DESTOVERLAY, &ck2;);
    g_ColorKey = ck2.dwColorSpaceHighValue;
  }
  if (dwFlags &DDOVER_KEYDESTOVERRIDE) // Значение цвета фона хранится в поверхности эффектов
  {
    if (lpDDOverlayFx != NULL)
    {
      ck = lpDDOverlayFx->dckDestColorkey;
      g_ColorKey = ck.dwColorSpaceHighValue;
    }
  }

  if (lpDestRect != NULL)
  {
    CopyMemory(&g;_OverlayRect, lpDestRect, sizeof(RECT));
  }
  
  return res;
}


* This source code was highlighted with Source Code Highlighter.


This code defined that background color who will be substitut by pikselami from video. As already it were t earlier, it are normal darkly - magenta color (0x100010), however, a row of players defined this value on the taste and that are surprising, color. Value it could be in different places, therefore it are necessary to watch closely flags who arrived to us as the input data. Together with it we defined the size and position of a rectangle in whom the output will be produc. Both value of a background, and coordinates of a rectangle was sav in global variables that it were possible to get to them access from our application later.

Now it are possible to "steal" the image.
HRESULT WINAPI Patch_Flip(IDirectDrawSurface *This, LPDIRECTDRAWSURFACE lpDDSurfTargetOverride, DWORD dwFlags)
{
  DDSURFACEDESC desc = {0};
  desc.dwSize = sizeof(desc);
  This->GetSurfaceDesc(&desc;);

  // Проверяем, помечена ли поверхность как оверлейная
  if ((desc.ddsCaps.dwCaps &DDSCAPS_OVERLAY) > 0)
  {
    lpOverlaySurf = This;
  }

  // Если прошло успешно, значит пора забирать картинку
  if (lpOverlaySurf != NULL)
  {
    GetPic();
  }

  // Восстановим оригинальный метод
  DetourRestore(True_Flip);
  // Сделаем вид, что нас тут не было
  HRESULT res = True_Flip(This, lpDDSurfTargetOverride, dwFlags);
  // Но это был только вид
  DetourRenew(True_Flip);
  
  return res;
}


* This source code was highlighted with Source Code Highlighter.


So, after check of a surface on "overleynost" it are possible to start fishing of the image, function are engaged in it GetPic(), which in the truncat variant looked approximately so:

void WINAPI GetPic(void)
{  
  DDSURFACEDESC2 desc = {0};
  desc.dwSize = sizeof(desc);

  DDSURFACEDESC desc1 = {0};
  desc1.dwSize = sizeof(desc1);

  // Вызов метода Lock предоставит нам доступ к памяти, которая и содержит вожделенную картинку
  ((LPDIRECTDRAWSURFACE)lpOverlaySurf)->Lock(NULL, &desc1;, DDLOCK_WAIT | DDLOCK_READONLY | DDLOCK_SURFACEMEMORYPTR | DDLOCK_NOSYSLOCK, NULL);
    desc.ddsCaps.dwCaps = desc1.ddsCaps.dwCaps;
    desc.lpSurface = desc1.lpSurface;
    desc.dwWidth = desc1.dwWidth;
    desc.dwHeight = desc1.dwHeight;
    desc.lPitch = desc1.lPitch;
    desc.ddpfPixelFormat = desc1.ddpfPixelFormat;
    desc.ddckCKDestOverlay = desc1.ddckCKDestOverlay;
    desc.ddckCKSrcOverlay = desc1.ddckCKSrcOverlay;

  // Перестанем быть эгоистами, отдадим поверхность другим
  ((LPDIRECTDRAWSURFACE)lpOverlaySurf)->Unlock(NULL);
}


* This source code was highlighted with Source Code Highlighter.


Method Lock locked a surface for record, and at the same time returned the pointer on the image. Therefrom it it are necessary to take away and unclamp as soon as possible a surface not to confuse a videoplayer with the presence. Method IDirectDrawSurface::Lock returned the description of a surface in structure DDSURFACEDESC, whileIDirectDrawSurface7::Lock — in DDSURFACEDESC2. From here some leapfrog with copying of the data from one structure in another.
Data are on the pointer desc.lpSurface, and the size of this data are calculat depending on in what color model this most data are stor.

      if (desc.ddpfPixelFormat.dwFourCC == 0x0)
      {
        DataLen = desc.dwHeight * desc.lPitch * desc.ddpfPixelFormat.dwRGBBitCount >> 3;
      }
      if (desc.ddpfPixelFormat.dwFourCC == MAKEFOURCC('Y', 'V', '1', '2'))
      {
        DataLen = desc.dwHeight * desc.lPitch * desc.ddpfPixelFormat.dwYUVBitCount >> 3;
      }
      if (desc.ddpfPixelFormat.dwFourCC == MAKEFOURCC('Y', 'U', 'Y', '2'))
      {
        DataLen = desc.dwHeight * desc.lPitch * desc.ddpfPixelFormat.dwYUVBitCount >> 4;
      }
      if (desc.ddpfPixelFormat.dwFourCC == MAKEFOURCC('Y', 'V', 'Y', 'U'))
      {
        DataLen = desc.dwHeight * desc.lPitch * desc.ddpfPixelFormat.dwYUVBitCount >> 4;
      }
      if (desc.ddpfPixelFormat.dwFourCC == MAKEFOURCC('U', 'Y', 'V', 'Y'))
      {
        DataLen = desc.dwHeight * desc.lPitch * desc.ddpfPixelFormat.dwYUVBitCount >> 4;
      }


* This source code was highlighted with Source Code Highlighter.


Here dwHeight — height of the image, lPitch — shift in bytes prior to the beginning of the next line, dwYUVBitCount — number of bits on piksel of the image.

The image can be cop and sav now. Let's recall, however, that we was in another's address space, and pointers done not operate through boundaries of processes. Therefore it are necessary to transfer a picture in the main application by any method of inter-process communication (IPC). The mechanism of files (memory mapped files) display in storage, in my opinion, will be here the most pertinent.

In a final part of article it will be a question of conversions of color models and a composition of a screenshot from two images: a screenshot with "hole" and a frame from video.
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 地板  发表于: 2014-06-20
Capture Live Video from various Video Devices.

By Dillip Kumar Kara, 20 May 2004



   4.79 (55 votes)

[align=right !important]
Rate this:vote 1vote 2vote 3vote 4vote 5





Introduction
This is an attempt to create an application which captures Live video from a Video capture device and USB attached WebCam all together in the same application. While developing my project, I needed to capture the video from various video devices including TV. Though, I found some source for capturing Video, it was not so efficient. So, that prompted me to develop my own independent application. Initially, “LiveVideo” detects the availability of the Video Capture card and whether WebCam is attached or not. Otherwise, it shows the message. Most of the API functions are used from DirectX SDK.
Steps to Use
    Create a Dialog based application.Insert a Picture control of size 320x240 pixel.In the properties of the Picture control, set TYPE as ‘Rectangle’ and COLOR as ‘Black’.Add the files “CaptureVideo.cpp” and “CaptureDevice.h” to your project.Add “CaptureVide.h” into your implementation header file.Create an Object of the class “CCaptureVideo” using Class wizard.Link the libraries strmbasd.lib, wmvcore.lib, wmstub.lib in your project settings.
Now, using the object, invoke InitializeVideo(HWND hWnd) function to initialize the video.
Collapse | Copy Code
[pre]HRESULT hr = capVideo.InitializeVideo(hWnd);Where hWnd is the window handle of the picture control.
    StartSVideo() - To start capturing from SVideo.StartCompositeVideo() - To start capturing from Composite Video.StartTVTuner() - To start capturing from TVTuner.StartWebcam() - To start capturing from WebCam.
Important:
Don’t forget to uninitialize the Video by using UnInitializeVideo() before destroying your application.
Requirements:
    Video Capture card. I’ve tested with “WinFast TV2000 XP WDM Video Capture” card. Hope it will work with all video capture cards.USB Cam.You need to install DirectX, which is available freely from Microsoft. You can download DirectX 9.0 from Microsoft.For development, install DirectX 9.0 SDK. You can download DirectX 9.0 SDK from Microsoft.
Conclusion
I hope this article is of some use to you. I would add Configuration settings of the Video features and Video quality in my next version. Feel free to use these classes as you like. Any comments or improvements would be appreciated.


http://www.codeproject.com/Articles/7123/Capture-Live-Video-from-various-Video-Devices

QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 4楼 发表于: 2014-06-20
VC如何抓取DirectDraw和DirectX技术的视频画面和游戏画面?
思路是:采用独立DLL,做成全局热键Hook
挂钩DDraw的DirectDrawCreate,监控FDD.CreateSurface(ddsd, FDDSPrimary, nil)(需要COM Hook技术)
获得主表面,这个时候就可以通过主表面实现截图处理:
创建OffSurface离屏表面,用IDirectDrawSurface的BltFast从PrimarySurface复制数据到OffSurface,然后Lock离屏表面,这个时候就可以保存图像数据了




给段示例代码:

pD3D := Direct3DCreate8(D3D_SDK_VERSION);
FillChar(d3dpp, SizeOf(d3dpp), 0);
d3dpp.Windowed := True;
d3dpp.Flags := D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
d3dpp.SwapEffect := D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferWidth := Screen.Width;
d3dpp.BackBufferHeight := Screen.Height;
d3dpp.BackBufferFormat := D3DFMT_X8R8G8B8;
pD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, GetDesktopWindow, D3DCREATE_SOFTWARE_VERTEXPROCESSING, d3dpp, g_pd3dDevice);
g_pd3dDevice.CreateImageSurface(Screen.Width, Screen.Height, D3DFMT_A8R8G8B8,pSurface);
g_pd3dDevice.GetFrontBuffer(pSurface);
ARect := Screen.DesktopRect;
pSurface.LockRect(lockedRect, @ARect, D3DLOCK_NO_DIRTY_UPDATE or D3DLOCK_NOSYSLOCK or D3DLOCK_READONLY);
PBits := lockedRect.pBits;
//将数据从PBits中Copy出来,格式为RGBA。不过行顺序和默认的DIB相反。
pSurface.UnlockRect;






最新研究,有了新的发现!!!!!

通过Hook DDraw的DirectDrawCreate(RealOne用)同DirectDrawCreateEx(WMP用)
获得IDirectDraw(7)
再COM Hook CreateSurface,注意RealOne使用的是通过QueryInterface获得IDirectDraw2
WMP则是IDirectDraw7

Hook了CreateSurface后,就能获得OverlaySurface

所以必须在软件使用前,启动全局Hook,才有效


在需要截图的时候
Lock Overlay Surface,读取数据,马上Unlock,以免损失性能

解码读出来的数据,即可,但是由于获得的数据是显卡硬件VRAM的数据,一般是YUY2,YV12等格式,需要转换为RGB格式

现在原数据能获取了,就差格式转换,有空再弄,不同的显卡,支持的格式不一样,不同的工具所使用的格式也不一样的,所以这个是比较麻烦的

例如,在我的GF6600上,RealOne(RMVB)用的是YUY2,而WMP(AVI)用的是YV12,还与当前播放的文件格式有关

最简单的方法是,Hook CreateSurface后,禁止创建Overlay那就普通的方法都能截图,呵呵




可以截取layered窗口(包括透明窗口)的代码:

procedure CaptureScreen(AFileName: string);
const
  CAPTUREBLT = $40000000;
var
  hdcScreen: HDC;
  hdcCompatible: HDC;
  bmp: TBitmap;
  hbmScreen: HBITMAP;
begin
  hdcScreen := CreateDC('DISPLAY', nil, nil, nil);
  hdcCompatible := CreateCompatibleDC(hdcScreen);
  hbmScreen := CreateCompatibleBitmap(hdcScreen,
    GetDeviceCaps(hdcScreen, HORZRES),
    GetDeviceCaps(hdcScreen, VERTRES));
  SelectObject(hdcCompatible, hbmScreen);
  bmp := TBitmap.Create;
  bmp.Handle := hbmScreen;
  BitBlt(hdcCompatible,
    0, 0,
    bmp.Width, bmp.Height,
    hdcScreen,
    0, 0,
    SRCCOPY or CAPTUREBLT);

  bmp.SaveToFile(AFileName);
  bmp.Free;
  DeleteDC(hdcScreen);
  DeleteDC(hdcCompatible);
end;

顺便升级了http://lysoft.lz169.com/projects/DXCapture.rar
现在支持YV12,NV12,YUY2,UUVY 4个格式,并解决了点Bug

并无偿开放DX Primary Surface截图代码!包含DX8与DX9两个版本

...
interface

{$DEFINE D3D9}

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons,
{$IFDEF D3D9}
  // D3DX9, // use D3D to save surface
  Direct3D9
{$ELSE}
  // D3DX8, // use D3D to save surface
  Direct3D8
{$ENDIF};
...
procedure TForm1.BitBtn1Click(Sender: TObject);
// Capture screen through D3D.
var
  BitsPerPixel: Byte;
  {$IFDEF D3D9}
  pD3D: IDirect3D9;
  pSurface: IDirect3DSurface9;
  g_pD3DDevice: IDirect3DDevice9;
  {$ELSE}
  pD3D: IDirect3D8;
  pSurface: IDirect3DSurface8;
  g_pD3DDevice: IDirect3DDevice8;
  {$ENDIF}
  D3DPP: TD3DPresentParameters;
  ARect: TRect;
  LockedRect: TD3DLockedRect;
  BMP: TBitmap;
  i, p: Integer;
begin
  BitsPerPixel := GetDeviceCaps(Canvas.Handle, BITSPIXEL);
  FillChar(d3dpp, SizeOf(d3dpp), 0);
  D3DPP.Windowed := True;
  D3DPP.Flags := D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
  D3DPP.SwapEffect := D3DSWAPEFFECT_DISCARD;
  D3DPP.BackBufferWidth := Screen.Width;
  D3DPP.BackBufferHeight := Screen.Height;
  D3DPP.BackBufferFormat := D3DFMT_X8R8G8B8;
  {$IFDEF D3D9}
  pD3D := Direct3DCreate9(D3D_SDK_VERSION);
  pD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, GetDesktopWindow,
    D3DCREATE_SOFTWARE_VERTEXPROCESSING, @D3DPP, g_pD3DDevice);
  g_pD3DDevice.CreateOffscreenPlainSurface(Screen.Width, Screen.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, pSurface, nil);
  g_pD3DDevice.GetFrontBufferData(0, pSurface);
  {$ELSE}
  pD3D := Direct3DCreate8(D3D_SDK_VERSION);
  pD3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, GetDesktopWindow,
    D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DPP, g_pD3DDevice);
  g_pD3DDevice.CreateImageSurface(Screen.Width, Screen.Height, D3DFMT_A8R8G8B8, pSurface);
  g_pD3DDevice.GetFrontBuffer(pSurface);
  {$ENDIF}
  // use D3D to save surface. Notes: D3DX%ab.dll is required!
//  D3DXSaveSurfaceToFile('Desktop.bmp', D3DXIFF_BMP, pSurface, nil,  nil);
  // use Bitmap to save surface
  ARect := Screen.DesktopRect;
  pSurface.LockRect(LockedRect, @ARect, D3DLOCK_NO_DIRTY_UPDATE or D3DLOCK_NOSYSLOCK or D3DLOCK_READONLY);
  BMP := TBitmap.Create;
  BMP.Width := Screen.Width;
  BMP.Height := Screen.Height;
  case BitsPerPixel of
    8:  BMP.PixelFormat := pf8bit;
    16: BMP.PixelFormat := pf16bit;
    24: BMP.PixelFormat := pf24bit;
    32: BMP.PixelFormat := pf32bit;
  end;
  p := Cardinal(LockedRect.pBits);
  for i := 0 to Screen.Height - 1 do
    begin
      CopyMemory(BMP.ScanLine, Ptr(p), Screen.Width * BitsPerPixel div 8);
      p := p + LockedRect.Pitch;
    end;
  BMP.SaveToFile('Desktop.bmp');
  BMP.Free;
  pSurface.UnlockRect;
end;

以上DX截图代码,不需要额外的DLL支持,有DirectX 9.0即可
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 5楼 发表于: 2014-06-20
Hook 技术在视频截取中的应用研究与实现
第42 卷 第6 期
2003 年11 月
厦门大学学报(自然科学版)
Journal of Xiamen University (Natural Science)
Vol. 42  No. 6
Nov. 2003  
文章编号:043820479 (2003) 0620709205
Hook 技术在视频截取中的应用研究与实现
收稿日期:2003204203
作者简介:邱岚(1979 - ) ,女,硕士研究生.
邱 岚,李翠华
(厦门大学计算机科学系,福建厦门361005)
摘要:介绍了Hook 技术的基本概念和实现方法,对常用几种类型的Hook 进行了分析,并且分析了截取屏幕图像的
两种方法:DirectX方式和创建屏幕DC ,同时介绍了Windows 中视频流生成技术. 通过Hook 技术实现的全局热键功
能,在屏幕捕捉和视频流生成技术的基础上,实现一个屏幕视频捕捉系统,并给出了实现的详细方法和步骤. 比较了
两种屏幕截图的方法,实验结果表明,DirectX有更好的捕捉速度,屏幕DC 有更广的适用范围.
关键词:Hook ;截图;视频流生成;DirectX
中图分类号:TP 391 文献标识码:A                
  随着计算机多媒体技术的发展,一般的静态图
片已经不能满足人们对信息获取的要求,越来越多
的视频出现在各种电子媒体中,例如新闻报道,电子
杂志以及远程教育等,这些视频大部分是通过摄像
机等设备录制下来再转到计算机中的,但是那些由
计算机图形或虚拟技术形成的画面构成的视频的捕
捉,比如远程教学的屏幕演示、游戏动画等,这就需
要视频捕捉软件的帮忙. 进行视频捕捉需要解决两
个问题,一是捕捉激活,二是视频流生成. 前者是用
于响应用户发出的指令,后者是将屏幕上的内容保
存成视频. 一个优秀的抓图软件,首先要有很强的热
键激活能力. 如果我们按下热键却没有反应,那么即
使该抓图软件的其它功能再强再好,也不能被激活.
通常用户通过键盘发出指令,这就要求捕捉程序在
非激活的状态下能够响应用户操作,有两种方法可
以实现这样的功能,一种是注册系统热键,一种是利
用Hook ,注册系统热键的方法实现简单,但是会存
在热键冲突的问题,而Hook 就没有这方面的限制,
并且通过Hook 可以实现一些系统不支持的热键组
合. 本文通过Hook 技术和视频流生成技术,实现了
一个屏幕视频捕捉系统.
1  Hook 技术原理介绍
Hook(钩子) 是windows 系统消息机制中一种特
殊的消息处理函数,在应用程序中创建一个子程序,
监视系统或者进程中的各种事件消息的交互,在某
些消息到达目标窗口程序前,截获消息并处理. 例
如:键盘钩子(WH- KEYBOARD) ,当用户有键盘动作
的时候系统会向当前窗口所属的程序发送键盘消
息,窗口收到消息以后对消息进行处理,这是正常的
处理过程. 钩子的位置处于系统和窗口程序之间,它
会拦截下系统发送的消息,由钩子首先进行处理. 由
于Hook 增加了系统对每个消息进行处理的工作量,
所以会增加系统的负担,引起系统性能下降,因此用
户应当在必要时安装Hook ,并且尽快删除.
1. 1  Hook 函数(Hook 的基本实现方法)
要利用某个类型的钩子,必须提供一个钩子函
数,然后使用SetWindowsHookEx 函数将钩子函数安
装到相关的钩子链表中. SetWindowsHookEx 原型为:
HOOK SetWindowsHookEx ( int idHook ,
HOOKPROC lpfn , HINSTANCE hmod , DWORD
dwthreadId) ;
idHook :指定安装的钩子函数的类型,例如WHKEYBOARD
lpfn :标识钩子函数的入口地址.
hmod :包含lpfn 参数指向的钩子函数的dll 的句
柄.
表1  Hook 分类
Tab. 1  Categories of Hook
Hook 类型           特性、用法、应用场合
WH- KEYBOARD Hook 允许应用程序监视即将被GetMessage 或PeekMessage 函数返回的WM- KEYDOWN 和WMKEYUP
消息,即监听键盘动作.
WH-MOUSE Hook 允许应用程序监视即将被GetMessage 或PeekMessage 函数返回的鼠标消息.
WH- GETMESSAGE Hook 允许应用程序监视GetMessage 或PeekMessage 函数. 可以用这个钩子监视进入消息队列
的各种消息. 由于系统的所有消息都要经过这个Hook ,它对系统的影响较大.
WH-CBT Hook 该Hook 在系统发生一些事件的时候被调用,比如窗口的创建、激活,大小变化,菜单命
令等等,它特别适用于计算机辅助教学或训练软件研发中.
WH-CALLWNDPROC Hook 和
WH-CALLWNDPROCRET
当一个线程调用SendMesage 函数时,和这个线程相关的Hook 函数将被调用,WH-CALL2
WNDPROC和WH-CALLWNDPROCRET的区别在于前者在SendMessage 前调用,后者在
SendMessage 后调用.
  dwthreadid : 指定钩子函数相关的线程的标识
符.
SetWindowsHookEx 函数总是把钩子函数安装在
钩子链表的开头. 当事件发生时,它被某个类型的钩
子所监视,系统调用这个类型的钩子链表起始位置
的钩子函数,如果要求下一个钩子函数也能够执行,
当前的钩子函数必须调用CallNextHookEx 函数,将
消息传给下一个钩子函数.
钩子函数应当是如下形式:
LRESULT CALLBACK HookProc ( int nCode ,
WPARAM wParam ,LPARAM lParam) ;
当程序不再需要Hook 时, 可以利用UnHook2
WindowsHookEx 函数来卸载.
1. 2  Hook 类型
每种类型的钩子允许应用程序监视消息处理机
制的不同方面. 常用的几种Hook 见表1.
在本软件中用到了WH- KEYBOARD Hook ,用于
拦截键盘的消息,这样当用户有键盘操作的时候,不
管本软件是否处于激活的状态,都能够获得该消息,
作出合适的操作,也即实现了全局热键的功能.
2  视频流生成技术
视频流是由许许多多的帧所构成的,当这些帧
连续播放时,由于人眼的视觉暂留效应,便形成了连
续的动画. 因此视频流的生成包括两方面:
2. 1  单帧图象的截取
通常获取屏幕图象的方法有两种:创建屏幕DC
 图1  视频捕捉流程
 Fig. 1  Flow of video capture
(Device Context , 设备描述表) 方法以及DirectX 的方
法. 屏幕DC 的方法利用的是window 原本的API 函
数,不需要其它库的支持,但是这种截取方式较慢,
而通过DirectX 的方法需要DirectX 库的支持,这种
截图方式较快.
1) 屏幕DC 截图
DC 是Windows 对多种外设的一种抽象描述,它
定义了一系列的图形对象、其相关属性,以及能影响
输出的图形模式. 程序可以不关心操作的是哪一种
具体的设备,而只要对这些图形对象进行处理,实际
在设备上这些图形对象的表示则由DC 完成,常见
的DC 设备有显示器,打印机. 通过屏幕DC 截图的
主要方法是建立一个名字为”DISPLAY”的DC 对象,
也即屏幕对象,然后创建与之兼容的内存DC 以及
·710 · 厦门大学学报(自然科学版)                  2003年
兼容的Bitmap 对象,将Bitmap 对象选入内存DC 中,
最后通过Bitlblt 函数将屏幕DC 中的内容复制到内
存DC 中,这样在Bitmap 对象中就得到了屏幕图象.
2) 通过DirectX截图
DirectX是由Microsoft 发布的多媒体开发库. 我
们利用DirectX中的DirectDraw 进行截图. DirectDraw
是DirectX的主要组成部分之一. 利用它能直接对显
示内存进行操作,支持硬件位块传输、硬件覆盖、表
面翻转,并且保持同目前的基于Windows 的应用程
序和驱动程序兼容.DirectDraw 提供了一种设备无关
性的方法,使得基于Windows 的应用软件能直接获
取显示设备的特性.DirectDraw 接口使得应用程序能
使用硬件加速的功能,硬件不提供的特性将由Di2
rectX来仿真实现. 在DirectDraw 的非独占模式中,其
主表面即为当前屏幕,可以直接Lock 住主表面,得
到其图象数据进行操作,但是由于CPU 对显存的操
作很慢,所以通常的做法是在系统内存中创建一个
与主表面一样大的后台表面,用BltFast 将主表面复
制到后台表面,然后锁住后台表面,进行操作.
2. 2  视频流的生成
AVI 视频是目前最常见和最常用的一种视频文
件格式之一,特别是在开发多媒体项目或者电子出
版物时,经常要使用AVI 视频. AVI 文件是Microsoft
公司制定的一种RIFF (Resource Interchange File For2
mat) 文件格式,AVI 文件中可能包含各种类型的数
据流,例如视频流,中文音频流,英文音频流. 利用
Windows 提供的AVI 函数和宏,可以对其中AVI 文
件中任何一种数据流单独操作.
为了得到我们所需要的视频流,将第一步所获
得的每一帧插入到视频流中即可. 视频流中所有的
帧分为关键帧(Key Frame) 和非关键帧,关键帧是独
立的,其中的数据与之前的帧不存在依存关系. 而非
关键帧则和之前的帧有关. 本程序中将所截取到的
图片都作为关键帧,并以相隔两个非关键帧的形式
插入到视频流中.
3  视频捕捉系统的具体实现
本文在上述技术的基础上实现了一个视频捕捉
系统,本系统不仅可以实现静态屏幕的捕捉,还可以
实现对屏幕的动态捕捉,以AVI 格式的视频流保存.
系统由3 个部分组成,Hook 的实现、静态图象捕捉
以及视频捕捉.
3. 1  Hook 的实现
1) 编写Hook 函数
由于全局Hook 要求Hook 函数不能与主进程在
同一个模块中,因此必须编写动态链接库输出Hook
函数. Hook 函数首先判断用户所按下的键是否是指
定的热键,然后通过主窗口的名字找到主窗口的句
柄:hWnd = : :FindWindow(NULL ,”SnapScreen”) ,并通
过这个句柄向主窗口发送捕捉消息: : : PostMessage
(hWnd ,WM-USER-SNAP ,0 ,0) . 当主窗口收到这个消
息的时候就进行捕捉操作.
2) 注册和删除Hook
●注册Hook
m- hModule =LoadLibrary(”Hooklib. dll”) ;
首先加载Hook 函数所在的动态链接库
HOOKPROC proc = (HOOKPROC) GetProcAddress
(m- hModule ,”MyKeyboardProc”) ;
找到Hook 函数的入口位置
m-hHook = SetWindowsHookEx (WH- KEYBOARD ,
proc ,m- hModule ,0) ;
进行Hook 注册
●删除Hook
if (m- hHook) UnHookWindowsHookEx(m- hHook) ;
先卸载Hook
if (m- hModule) FreeLibrary(m- hModule) ;
然后释放对应的动态链接库
3. 2  静态图象捕捉
  本文同时实现了屏幕DC 与DirectX两种屏幕截
图的方法. 其主要代码如下:
1) 屏幕DC 方法
screenDC. CreateDC ( ”DISPLAY”, NULL , NULL ,
NULL) ;
通过将设备名设为”DISPLAY”创建一个屏幕的DC
对象
nWidth = screenDC. GetDeviceCaps (HORZRES) ;
nHeight = screenDC. GetDeviceCaps (VERTRES) ;
获取屏幕的宽度和高度
第6期            邱 岚等:Hook技术在视频截取中的应用研究与实现·711 ·
memDC. BitBlt (0 ,0 , nWidth , nHeight , &screenDC ,
0 ,0 ,SRCCOPY) ;
将屏幕DC 中的内容复制到内存DC 中.
2) DirectX方法
DirectDrawCreate (NULL ,&ddraw1 ,NULL)
创建DirectDraw 对象
ddraw1 - > QueryInterface ( IID- IDirectDraw2 ,
(LPVOID 3 ) &ddraw2)
获取DirectDraw2 接口
ddraw2 - > SetCooperativeLevel ( GetSafeHwnd ( ) ,
DDSCL-NORMAL)
设置为非独占模式
ddraw2 - > CreateSurface (&desc , &m-primsurf , 0)
ddraw2 - > CreateSurface (&desc ,&m- backsurf ,0)
创建主表面和后台表面
m-backsurf - > BltFast ( 0 , 0 , m-primsurf , &rc ,
DDBLTFAST-NOCOLORKEY| DDBLTFAST-WAIT)
将主表面的内容复制到后台表面
m-backsurf - > Lock (NULL , &ddsd , DDLOCKWAIT
,NULL)
将后台表面锁定,可以对其图象数据进行处理.
3. 3  视频捕捉
在静态图象捕捉的基础上,将连续捕捉到的图
象不断加入,就形成了视频流. 整个捕捉流程见图1
及主要函数如下:
以下介绍一下主要函数:
●AVIFileOpen (PAVIFILE 3 ppfile ,LPCTSTR sz2
File ,UINT mode ,CLSID pclsidHandler)
打开一个AVI 文件并且返回文件接口的地址
● AVIFileCreateStream ( PAVIFILE pfile ,
PAVISTREAM3 ppavi ,AVISTREAMINFO 3 psi)
为一个已有的AVI 文件创建一个流并指定其接

● AVIMakeCompressedStream ( PAVISTREAM 3
ppsCompressed , PAVISTREAM psSource ,
从一个未压缩的数据流中创建压缩数据流,返
回指向压缩数据流的接口指针.
●AVIStreamSetFormat (PAVISTREAM pavi ,LONG
lPos ,LPVOID lpFormat , LONG cbFormat )
设置数据流的格式
●AVIStreamWrite ( PAVISTREAM pavi ,LONG
lStart ,LONG lSamples , LPVOID lpBuffer , LONG cb2
Buffer , DWORD dwFlags , LONG 3 plSampWritten ,
LONG 3 plBytesWritten)
将数据写到流中
●AVIStreamRelease (PAVISTREAM pavi)
关闭流
●AVIFileRelease (PAVIFILE pfile)
关闭AVI 文件
4  结果与讨论
Hook 技术是一种Windows 环境下的系统软件
开发技术,具有强大的功能,能够处理许多软件研发
中看似难以解决的问题. 本文通过Hook 技术实现了
全局热键功能,在多窗口并存并且热键冲突的情况
下,仍然可以拦截到消息,显示了Hook 强大的消息
拦截功能.
屏幕DC 和DirectX 两种截图方法各有其优缺
点,屏幕DC 方法适用范围广,能够捕捉大部分的屏
幕图象,但是它的捕捉速度较慢. DirectX 方法速度
快,但是适用范围较窄,在一些使用DirectX 有自己
主表面的游戏中无法进行捕捉,对此,可以考虑用枚
举DirectX表面的方法,获取其主表面进行操作. 以
下是两种方法在窗口模式下截图速度的比较,测试
平台为CPU XP1700 + , 显卡为Geforce2 GTS , 装有
DirectX8. 1 ,进行了三次捕捉试验,每次连续捕捉100
帧,所获得的捕捉一帧所需要的平均时间见表2 :
表2  捕捉速度比较
Tab. 2  Compare of grab speed
捕捉尺寸屏幕DC/ ms DirectX/ ms
1024 ×768 450 122
800 ×600 270 74
640 ×480 170 47
320 ×240 45 12
由此可以看出DirectX 进行屏幕捕捉的速度要
大大高于屏幕DC 的方法.
图2 是本系统的主界面,预览框内是静态捕捉
所截取的游戏画面.
·712 · 厦门大学学报(自然科学版)                  2003年
 图2  系统主界面
 Fig. 2  Main Windows of the system
   (a) 第35 帧       (b) 第6 帧
 图3  捕捉到的视频流中的两帧
 Fig. 3  Two frames of the avi captuered by the system
图3 是本系统的在游戏中所捕捉到的视频中的
几帧(用一个提取avi 中任意帧的程序显示) .
在Windows 下,可以直接用PrintScreen 键抓图,但是
它只能把抓到的图片放在剪贴板中,需要再运行别
的程序将图象保存. 这样每捕捉一帧图片都需要用
户的多次操作,不利于一些快速捕捉中对速度的要
求,而且捕捉到的只是静态图片,不能捕捉视频动
画,有很大的局限性,不能适应多媒体快速发展的需
要. 相比之下,本文所实现的屏幕视频捕捉系统有很
大的优越性,首先有简明的向导式抓取窗口,功能强
大而且方便的快捷键,大大提高了捕捉图像的效率,
在静态捕捉时可以在捕捉窗口预览捕捉效果,最关
键的是可以实现视频捕捉,可以将计算机屏幕上的
3D 游戏动画记录下来,以avi 格式的文件保存,这有
广泛的应用,可以辅助制作多媒体教学软件,演示虚
拟技术成果,游戏精彩过程再现等等.
参考文献:
[1 ]  (美) David Iseminger. Win32 开发人员参考库. 第1 卷:
Windows 基本服务[M] . 前导工作室译. 北京:机械工业
出版社,2001.
[2 ]  Jeffrey Richter.Windows 高级编程指南[M] . 王书洪,刘光
明,译. 北京:清华大学出版社,1999.
[3 ]  邱建雄. Hook 技术及其在软件研发中的应用[J ] . 国防
科技大学学报,2002 ,24 (1) :77 - 80.
[4 ]  武永康. DirectDraw 原理与API 参考[M] . 北京:清华大
学出版社,2000.
[5 ]  Charles Petzold. Programing Windows[M] . Redmond ,Wash. :
Microsoft Press ,1999.
Implementation of Hook Technology in Video Snap
QIU Lan ,LI Cui2hua
(Dept . of Comp. Sci . ,Xiamen Univ. ,Xiamen 361005 ,China)
Abstract : This paper introduces the basic concepts and methods of Hook technology , and describes some Hook types in
common use ,and then presents two techniques to snap screen image :DirectX and DC. Experimental results show that Di2
rectX method is much quicker ,while DC technique can be used more widely. After the snap of screen image ,the method
how to creat a video stream in Windows is put forward. Finally ,how to implement Hook technology in video snap is de2
scribed in detail .
Key words : Hook ;grabgraf ;video ;DirectX
第6期            邱 岚等:Hook技术在视频截取中的应用研究与实现·713 ·
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
描述
快速回复

您目前还是游客,请 登录注册
如果您提交过一次失败了,可以用”恢复数据”来恢复帖子内容