📜 ⬆️ ⬇️

Screenshot of a video player without black holes

Against the background of the current discourse on whether “my phone / calculator / watch copes with HD video playback”, an interesting historical fact receded into the background: just over 10 years ago, the problem was not even the video decoding speed (warm MPEG lamp was decoded by separate hardware decoders, the rest were interrupted by the capabilities of the existing CPU), and the speed of its output to the screen. Multiscreen environment kills at the root the idea of ​​transferring a block of memory with a decoded image into video memory, because windows can overlap each other in the most bizarre way.

The problem was solved by hardware. The application that should display the video on the screen now displayed only the background of a certain color (as a rule, very dark purple), and the video card was involved in “imprinting” the video. When composing the image, it replaced the background pixels with the corresponding pixels of the decoded frame. The application now had at its disposal a memory area into which it was possible to very quickly copy the frames from the video. The technology was called hardware overlay and very quickly incorporated it into all consumer and professional video cards.

Probably the only problem brought by this technology was the impossibility to make a screenshot of the video player, because it will not contain a frame from the video, but the same dark purple background.
image

Bypassing the hardware acceleration (both globally and at the level of the player application) was a workaround, pre-launching another copy of the player (this copy will take overlay to use, and the second copy of the player will have to do with the classic software output to the screen) and, if it is It is about players based on DirectShow technology, switching the filter renderer to a filter that does not use hardware overlay.
')
Almost all programs for capturing screenshots, as a rule, used the first two methods, or did not use them at all. The rest decided that it was useless to change the settings of users and went progressively, namely, by obtaining an image of a video frame directly from the same memory area somewhere in the depths of the video card.

The method proposed below solves this problem for players that output video through DirectShow.

Outline the range of tasks. First, our screen capture program is on its own, a video player that knows where to copy frames - on its own. Hence the need to have access to the address space of another process. Secondly, the data is likely to be in a color model other than RGB. So you have to convert them into an acceptable form. Thirdly, it is necessary to “imprint” the image into the screenshot yourself, taking into account all the charms of a multi-window environment.

The first task is solved in several ways, I chose the simplest one: through the hook installation. The type of hook is not so important, the main thing is to get into the address space of the player. For example:

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 that gets control when the hook is triggered is a standard “doing nothing”.

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 .


Let me remind you that in order to embed another process into the address space, this procedure must be in the DLL, not in the executable file. All further code is also located in the same library.

Now let's get to the image. All renderer filters have not so many ways to display the image on the screen: GDI, DirectDraw and Direct3D. The first and third options do not create problems when getting screenshots, but the second one is the subject of our interest.

The same memory area into which copying is performed is allocated by the IDirectDrawSurface object (also IDirectDrawSurface as the “DirectDraw-surface”), which was created with the DDSCAPS_OVERLAY flag. The transfer of the image to the screen, simply speaking, is the method IDirectDrawSurface::Flip . So, by intercepting the call to this method, you can access the data with the image of the video frame. Much has been said about call interception, for example . At one time, I used method number one of this article as the simplest. The only obstacle is that getting the address of the COM object method by calling GetProcAddress will fail. This is not so scary, because you can create a DirectDraw-surface, and find out the position of the Flip method relative to the base address of the executable file into which the embedding was performed. You can do this 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 .


To determine the position and size of the overlay surface, we need the IDirectDrawSurface::UpdateOverlay , which has method number 33.
After that, you can intercept the Flip and UpdateOverlay methods by specifying the addresses of the original and modified methods in the installation procedure of the interceptor. The prototype methods look like this:

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 .


Note that unlike the list of parameters of the Flip and UpdateOverlay in prototypes, the first in the list of arguments is a pointer to the COM object containing this method. This is due to the fact that the methods of COM objects have the type of call thiscall .

Similar actions should be taken for the IDirectDrawSurface7::Flip and IDirectDrawSurface7::UpdateOverlay , since some renderers use interfaces of the seventh version of DirectDraw.

After these preparations, the auxiliary surface and the DirectDraw object can be removed and transferred to the implementation of the interceptor functions. To begin, consider the UpdateOverlay function, since it can be used to extract a lot of useful 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 defines the same background color that will be replaced by pixels from the video. As mentioned earlier, it is usually dark purple color (0x100010), however, a number of players determine this value to their own taste and, surprisingly, color. This value can be in different places, so you should carefully follow the flags that come to us as input. At the same time, we determine the size and position of the rectangle into which the output will be made. Both the background value and the coordinates of the rectangle are stored in global variables so that you can access them later from our application.

Now you can "steal" the image itself.
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 checking the surface for “overlay”, you can start fishing the image; the GetPic() function deals with this, which in the shortened version looks like this:

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 .


The Lock method locks the surface for writing, and at the same time returns a pointer to the image. From there it should be picked up and unlocked as quickly as possible so as not to embarrass the video player with your presence. The IDirectDrawSurface::Lock method returns a surface description in a DDSURFACEDESC structure, while IDirectDrawSurface::Lock returns a description of a surface in DDSURFACEDESC2 . Hence some leapfrog with copying data from one structure to another.
The data is located according to the desc.lpSurface pointer, and the size of this data is calculated depending on what color model this data is stored in.

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 is the image height, lPitch is the shift in bytes to the beginning of the next line, dwYUVBitCount is the number of bits per image pixel.

The image can now be copied and saved. Recall, however, that we are in a different address space, and pointers do not act across process boundaries. Therefore, the image must be transferred to the main application by any method of interprocess communication ( IPC ). The mechanism of memory mapped files , in my opinion, will be the most appropriate here.

In the final part of the article, we will discuss the transformations of color models and the composition of a screenshot from two images: a screenshot with a “hole” and a frame from a video.

Source: https://habr.com/ru/post/111339/


All Articles