在 window 上以 60 赫兹显示重新绘制图像
Display repaint an image on window at 60 hz
如果您打开 Skype 并单击 "share screen",它会向您显示将要流式传输的内容的视频预览。
到目前为止我有这个代码:
获取屏幕:
HBITMAP screenshot()
{
// get the device context of the screen
HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);
// and a device context to put it in
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int width = GetDeviceCaps(hScreenDC, HORZRES);
int height = GetDeviceCaps(hScreenDC, VERTRES);
// maybe worth checking these are positive values
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
// get a new bitmap
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);
return hBitmap;
在表单上呈现:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
case WM_CREATE:
//hBitmap = (HBITMAP)LoadImage(NULL, LPCSTR("c:/users/they/documents/file.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
case WM_PAINT:
hBitmap = screenshot();
PAINTSTRUCT ps;
HDC hdc;
BITMAP bitmap;
HDC hdcMem;
HGDIOBJ oldBitmap;
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc);
oldBitmap = SelectObject(hdcMem, hBitmap);
GetObject(hBitmap, sizeof(bitmap), &bitmap);
BitBlt(hdc, 200, 50, bitmap.bmWidth,bitmap.bmHeight,
hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
if(millis % 70) RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
问题是计时 "millis % 70" 我读过有关计时器队列和标准计时器的内容,但听说它们在高速时不可靠,
重绘也是在没有库的情况下逐帧渲染 "video" 的最佳方式吗?
这里有几个不同的问题:
GDI 可能不会快到每秒 60 或 70 帧。评论中还建议了其他技术(如@Richard Critten),旨在与硬件密切合作来执行这些类型的操作。不可否认,那些 API 可能更难学习和使用。
您是对的,这些类型的计时器不会可靠地触发您的 per-frame 代码。人们使用 hack 来提高这些计时器的分辨率,但这样做有很多缺点,特别是如果你不是很小心的话。
话虽这么说,但还是可以编写一个程序来使用 GDI 以较低的帧速率复制视频帧。我在 "Video for Windows" 天之前就做过这个。我设法以接近 30 fps 的速度从网络摄像头视频预览中获取 640x480 帧 windows,偶尔会掉帧。
为此,您可以使用 "game loop" 而不是依赖计时器事件。游戏循环是一个紧密的循环,它会监视时钟直到处理下一帧。这会消耗大量 CPU 并耗尽笔记本电脑的电池,但这实际上是大多数 Windows 视频游戏在您玩游戏时的方式。 (好游戏会在游戏暂停时停止"spinning"。)
一个典型的event-based Windows程序有一个这样的消息循环:
while (GetMessage(&msg, NULL, 0, 0) > 0) {
DispatchMessage(&msg);
}
(实际代码可能稍微复杂一些,但这些是我们关心的内容。)
GetMessage
调用会一直等待,直到有消息供您的程序响应。
游戏循环不会等待。它检查是否有消息准备好而无需等待。它使用 PeekMessage 来执行此操作。它做的另一件事是跟踪时间。如果无事可做,它就会立即循环。在 semi-pseudocode 中,它看起来像这样:
SomeType next_frame_time = now;
for (;;) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) break;
DispatchMessage(msg);
}
if (current time >= next_frame_time) {
HandleNextFrame();
next_frame_time += frame interval;
}
}
请注意,除非 WM_QUIT 消息到达,否则循环将永远运行。它会尽可能快地运行。不是在响应 WM_TIMER 和 WM_PAINT 时做你的工作,而是在调用 HandleNextFrame 时做它们。
剩下的技巧是使用高分辨率时钟。您可以为此使用 Windows API QueryPeformanceCounter。请注意,您必须在运行时使用 QueryPerformanceFrequency 确定 QueryPerformanceCounter 使用的单位。
如果您打开 Skype 并单击 "share screen",它会向您显示将要流式传输的内容的视频预览。
到目前为止我有这个代码:
获取屏幕:
HBITMAP screenshot()
{
// get the device context of the screen
HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);
// and a device context to put it in
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int width = GetDeviceCaps(hScreenDC, HORZRES);
int height = GetDeviceCaps(hScreenDC, VERTRES);
// maybe worth checking these are positive values
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
// get a new bitmap
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);
return hBitmap;
在表单上呈现:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
case WM_CREATE:
//hBitmap = (HBITMAP)LoadImage(NULL, LPCSTR("c:/users/they/documents/file.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
case WM_PAINT:
hBitmap = screenshot();
PAINTSTRUCT ps;
HDC hdc;
BITMAP bitmap;
HDC hdcMem;
HGDIOBJ oldBitmap;
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc);
oldBitmap = SelectObject(hdcMem, hBitmap);
GetObject(hBitmap, sizeof(bitmap), &bitmap);
BitBlt(hdc, 200, 50, bitmap.bmWidth,bitmap.bmHeight,
hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
if(millis % 70) RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}
问题是计时 "millis % 70" 我读过有关计时器队列和标准计时器的内容,但听说它们在高速时不可靠, 重绘也是在没有库的情况下逐帧渲染 "video" 的最佳方式吗?
这里有几个不同的问题:
GDI 可能不会快到每秒 60 或 70 帧。评论中还建议了其他技术(如@Richard Critten),旨在与硬件密切合作来执行这些类型的操作。不可否认,那些 API 可能更难学习和使用。
您是对的,这些类型的计时器不会可靠地触发您的 per-frame 代码。人们使用 hack 来提高这些计时器的分辨率,但这样做有很多缺点,特别是如果你不是很小心的话。
话虽这么说,但还是可以编写一个程序来使用 GDI 以较低的帧速率复制视频帧。我在 "Video for Windows" 天之前就做过这个。我设法以接近 30 fps 的速度从网络摄像头视频预览中获取 640x480 帧 windows,偶尔会掉帧。
为此,您可以使用 "game loop" 而不是依赖计时器事件。游戏循环是一个紧密的循环,它会监视时钟直到处理下一帧。这会消耗大量 CPU 并耗尽笔记本电脑的电池,但这实际上是大多数 Windows 视频游戏在您玩游戏时的方式。 (好游戏会在游戏暂停时停止"spinning"。)
一个典型的event-based Windows程序有一个这样的消息循环:
while (GetMessage(&msg, NULL, 0, 0) > 0) {
DispatchMessage(&msg);
}
(实际代码可能稍微复杂一些,但这些是我们关心的内容。)
GetMessage
调用会一直等待,直到有消息供您的程序响应。
游戏循环不会等待。它检查是否有消息准备好而无需等待。它使用 PeekMessage 来执行此操作。它做的另一件事是跟踪时间。如果无事可做,它就会立即循环。在 semi-pseudocode 中,它看起来像这样:
SomeType next_frame_time = now;
for (;;) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) break;
DispatchMessage(msg);
}
if (current time >= next_frame_time) {
HandleNextFrame();
next_frame_time += frame interval;
}
}
请注意,除非 WM_QUIT 消息到达,否则循环将永远运行。它会尽可能快地运行。不是在响应 WM_TIMER 和 WM_PAINT 时做你的工作,而是在调用 HandleNextFrame 时做它们。
剩下的技巧是使用高分辨率时钟。您可以为此使用 Windows API QueryPeformanceCounter。请注意,您必须在运行时使用 QueryPerformanceFrequency 确定 QueryPerformanceCounter 使用的单位。