OpenGL/D3D:如何获取在 Windows 中全屏运行的游戏的屏幕截图?
假设我有一个全屏运行的 OpenGL 游戏(Left 4 Dead 2)。我想以编程方式获取它的屏幕截图,然后将其写入视频文件。
我尝试过 GDI、D3D 和 OpenGL 方法(例如 glReadPixels),但要么收到空白屏幕,要么在捕获流中闪烁。
有什么想法吗?
就其价值而言,与我想要实现的目标类似的典型例子是 Fraps。
Suppose I have an OpenGL game running full screen (Left 4 Dead 2). I'd like to programmatically get a screen grab of it and then write it to a video file.
I've tried GDI, D3D, and OpenGL methods (eg glReadPixels) and either receive a blank screen or flickering in the capture stream.
Any ideas?
For what it's worth, a canonical example of something similar to what I'm trying to achieve is Fraps.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
解决这个问题有几种方法。其中大多数都很棘手,这完全取决于您想要针对哪种图形 API,以及目标应用程序使用哪些功能。
大多数 DirectX、GDI+ 和 OpenGL 应用程序都是双缓冲或三缓冲的,因此它们都会
在某个时刻调用:每当应该绘制窗口时,它们还会在消息队列中生成 WM_PAINT 消息。这给了你两个选择。
您可以在目标进程中安装全局钩子或线程本地钩子并捕获 WM_PAINT 消息。这允许您在绘画发生之前从设备上下文复制内容。可以通过枚举系统上的所有进程并查找已知的窗口名称或已知的模块句柄来找到该进程。
您可以将代码注入目标进程的 SwapBuffers 本地副本。在 Linux 上,通过 LD_PRELOAD 环境变量或显式调用 ld-linux.so.2 可以轻松完成此操作,但在 Windows 上没有等效的方法。幸运的是,微软研究院有一个名为Detours的框架可以为您做到这一点。您可以在这里找到:链接。
demoscene 小组 Farbrausch 制作了一个名为 kkapture 的演示捕获工具,它利用了 Detours 库。然而,他们的工具针对的是不需要用户输入的应用程序,因此他们基本上通过连接所有可能的时间函数(例如 timeGetTime()、GetTickCount() 和 QueryPerformanceCounter())以固定帧速率运行演示。太棒了。 ryg(我想?)撰写的有关 kkapture 内部结构的演示文稿可以在此处找到< /a>.我想你对此感兴趣。
有关 Windows 挂钩的详细信息,请参阅此处 和此处。
编辑:
这个想法引起了我的兴趣,所以我使用 Detours 来连接 OpenGL 应用程序并弄乱图形。这是添加了绿色雾的 Quake 2:
关于 Detours 如何工作的更多信息,因为我已经使用过它现在第一手资料:
Detours 在两个层面上发挥作用。实际的挂钩仅在与目标进程相同的进程空间中起作用。因此,Detours 有一个将 DLL 注入进程并强制其 DLLMain 运行的函数,以及应该在该 DLL 中使用的函数。当 DLLMain 运行时,DLL 应调用 DetourAttach() 来指定要挂钩的函数,以及“detour”函数,这是您要覆盖的代码。
所以它基本上是这样工作的:
但也有一些注意事项。当 DllMain 运行时,稍后使用 LoadLibrary() 导入的库尚不可见。因此,您不必在 DLL 附件事件期间设置所有内容。解决方法是跟踪到目前为止被重写的所有函数,并尝试初始化这些函数中您已经可以调用的其他函数。这样,一旦 LoadLibrary 将新函数映射到进程的内存空间中,您就会发现它们。我不太确定这对于 wglGetProcAddress 来说效果如何。 (也许这里的其他人对此有想法?)
一些 LoadLibrary() 调用似乎失败。我用 Quake 2 进行了测试,DirectSound 和 waveOut API 由于某种原因未能初始化。我仍在调查此事。
There are a few approaches to this problem. Most of them are icky, and it totally depends on what kind of graphics API you want to target, and which functions the target application uses.
Most DirectX, GDI+ and OpenGL applications are double or tripple-buffered, so they all call:
at some point. They also generate WM_PAINT messages in their message queue whenever the window should be drawn. This gives you two options.
You can install a global hook or thread-local hook into the target process and capture WM_PAINT messages. This allows you to copy the contents from the device context just before the painting happens. The process can be found by enumerating all the processes on the system and look for a known window name, or a known module handle.
You can inject code into the target process's local copy of SwapBuffers. On Linux this would be easy to do via the LD_PRELOAD environmental variable, or by calling ld-linux.so.2 explicitly, but there is no equivalient on Windows. Luckily there is a framework from Microsoft Research which can do this for you called Detours. You can find this here: link.
The demoscene group Farbrausch made a demo-capturing tool named kkapture which makes use of the Detours library. Their tool targets applications that require no user input however, so they basically run the demos at a fixed framerate by hooking into all the possible time functions, like timeGetTime(), GetTickCount() and QueryPerformanceCounter(). It's totally rad. A presentation written by ryg (I think?) regarding kkapture's internals can be found here. I think that's of interest to you.
For more information about Windows hooks, see here and here.
EDIT:
This idea intrigued me, so I used Detours to hook into OpenGL applications and mess with the graphics. Here is Quake 2 with green fog added:
Some more information about how Detours works, since I've used it first hand now:
Detours works on two levels. The actual hooking only works in the same process space as the target process. So Detours has a function for injecting a DLL into a process and force its DLLMain to run too, as well as functions that are supposed to be used in that DLL. When DLLMain is run, the DLL should call DetourAttach() to specify the functions to hook, as well as the "detour" function, which is the code you want to override with.
So it basically works like this:
There are some caveats though. When DllMain is run, libraries that are imported later with LoadLibrary() aren't visible yet. So you can't necessarily set up everything during the DLL attachment event. A workaround is to keep track of all the functions that are overridden so far, and try to initialize the others inside these functions that you can already call. This way you will discover new functions as soon as LoadLibrary have mapped them into the memory space of the process. I'm not quite sure how well this would work for wglGetProcAddress though. (Perhaps someone else here has ideas regarding this?)
Some LoadLibrary() calls seem to fail. I tested with Quake 2, and DirectSound and the waveOut API failed to initalize for some reason. I'm still investigating this.
我发现了一个名为 taksi 的 sourceforge 项目:
http://taksi.sourceforge.net/
Taksi 没有不过,提供音频捕获。
I found a sourceforge'd project called taksi:
http://taksi.sourceforge.net/
Taksi does not provide audio capture, though.
我过去写过屏幕抓取器(DirectX7-9 时代)。我发现旧的 DirectDraw 工作得非常好,并且可以可靠地抓取硬件加速/视频屏幕内容,而其他方法(D3D、GDI、OpenGL)似乎会留下空白或混乱。速度也很快。
I've written screen grabbers in the past (DirectX7-9 era). I found good old DirectDraw worked remarkably well and would reliably grab bits of hardware-accelerated/video screen content which other methods (D3D, GDI, OpenGL) seemed to leave blank or scrambled. It was very fast too.