显示器在60 Hz的窗口上重画图像

时间:2019-07-11 14:51:26

标签: c++ winapi win32gui

如果您打开skype并单击“共享屏幕”,则它将向您显示将要流式传输的视频的预览。

到目前为止,我有以下代码:

获取屏幕:

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);
}

问题是,我已经阅读了有关计时器队列和标准计时器的时间“毫秒%70”,但听说它们在快速运行时不可靠, 还可以像不使用库那样逐帧渲染“视频”的最佳方法一样进行重新绘制吗?

1 个答案:

答案 0 :(得分:0)

这里有几个不同的问题:

  1. GDI可能不够快,无法每秒完成60或70帧。注释中还建议了其他技术(如@Richard Critten),这些技术旨在与硬件紧密协作来进行此类操作。不可否认,这些API可能更难学习和使用。

  2. 您说对了,这些计时器不会可靠地触发每帧代码。人们可以使用一些技巧来提高这些计时器的分辨率,但是这样做有很多弊端,尤其是如果您对此不太谨慎的话。

话虽如此,有可能将一个程序捆绑在一起,以较低的帧速率使用GDI复制视频帧。我已经在“ Windows视频”这一天做完了。我设法以接近30 fps的速度从网络摄像头视频预览窗口中捕捉640x480帧,偶尔掉帧。

为此,您可以使用“游戏循环”而不是依赖计时器事件。游戏循环是一个紧密的循环,它会监视时钟直到需要处理下一帧为止。这将消耗大量的CPU并消耗笔记本电脑的电池,但这实际上是大多数Windows视频游戏在玩时的样子。 (暂停游戏后,好的游戏将停止“旋转”。)

典型的基于事件的Windows程序具有如下消息循环:

while (GetMessage(&msg, NULL, 0, 0) > 0) {
  DispatchMessage(&msg);
}

(实际代码可能稍微复杂一些,但这是我们关心的胆量。)

GetMessage调用将等待,直到有消息可供您的程序响应为止。

游戏循环不会等待。它检查是否有消息准备就绪而无需等待。它使用PeekMessage来做到这一点。它要做的另一件事是跟踪时间。如果无事可做,它将立即循环。在半伪代码中,它看起来像这样:

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使用的单位。