使用无窗口模式

[与此页面关联的功能 DirectShow 是旧版功能。 它已被 MediaPlayerIMFMediaEngineMedia Foundation 中的音频/视频捕获所取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能在 Media Foundation 中使用 MediaPlayerIMFMediaEngine音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]

视频混合呈现器筛选器 7 (VMR-7) 和视频混合呈现器筛选器 9 (VMR-9) 都支持无窗口模式,这比 IVideoWindow 接口有了重大改进。 本主题介绍无窗口模式和窗口模式之间的差异,以及如何使用无窗口模式。

为了保持与现有应用程序向后兼容,VMR 默认为窗口模式。 在窗口模式中,呈现器创建自己的窗口来显示视频。 通常,应用程序将视频窗口设置为应用程序窗口的子窗口。 但是,存在单独的视频窗口会导致一些问题:

  • 最重要的是,如果在线程之间发送窗口消息,则可能存在死锁。
  • Filter Graph 管理器必须将某些窗口消息(例如WM_PAINT)转发到视频呈现器。 应用程序必须使用筛选图管理器实现的 IVideoWindow (而不是视频呈现器的) ,以便 Filter Graph 管理器保持正确的内部状态。
  • 若要从视频窗口接收鼠标或键盘事件,应用程序必须设置 消息排出,导致视频窗口将这些消息转发到应用程序。
  • 若要防止出现剪辑问题,视频窗口必须具有正确的窗口样式。

无窗口模式通过使用 DirectDraw 剪辑视频矩形,使 VMR 直接在应用程序窗口的工作区上绘制,从而避免了这些问题。 无窗口模式可显著降低死锁的可能性。 此外,应用程序不必设置所有者窗口或窗口样式。 事实上,当 VMR 处于无窗口模式时,它甚至不会公开不再需要的 IVideoWindow 接口。

若要使用无窗口模式,必须显式配置 VMR。 但是,你会发现它比窗口模式更灵活,更易于使用。

VMR-7 筛选器和 VMR-9 筛选器会公开不同的接口,但每个接口的步骤等效。

为无窗口模式配置 VMR

若要替代 VMR 的默认行为,请在生成筛选器图之前配置 VMR:

VMR-7

  1. 创建 Filter Graph 管理器。
  2. 创建 VMR-7 并将其添加到筛选图。
  3. 使用 VMRMode_Windowless 标志在 VMR-7 上调用 IVMRFilterConfig::SetRenderingMode
  4. 查询 VMR-7 以获取 IVMRWindowlessControl 接口。
  5. VMR-7 上调用 IVMRWindowlessControl::SetVideoClippingWindowWindow 。 指定应显示视频的窗口的句柄。

VMR-9

  1. 创建 Filter Graph 管理器。
  2. 创建 VMR-9 并将其添加到筛选器图。
  3. 使用 VMR9Mode_Windowless 标志在 VMR-9 上调用 IVMRFilterConfig9::SetRenderingMode
  4. 在 VMR-9 中查询 IVMRWindowlessControl9 接口。
  5. VMR-9 上调用 IVMRWindowlessControl9::SetVideoClippingWindowWindow 。 指定应显示视频的窗口的句柄。

现在,通过调用 IGraphBuilder::RenderFile 或其他图形生成方法生成筛选器图的其余部分。 筛选器图形管理器会自动使用添加到图形中的 VMR 实例。 (有关发生这种情况的原因的详细信息,请参阅 Intelligent Connect.)

以下代码显示了一个帮助程序函数,该函数创建 VMR-7,将其添加到图形中,并设置无窗口模式。

HRESULT InitWindowlessVMR( 
    HWND hwndApp,                  // Window to hold the video. 
    IGraphBuilder* pGraph,         // Pointer to the Filter Graph Manager. 
    IVMRWindowlessControl** ppWc   // Receives a pointer to the VMR.
    ) 
{ 
    if (!pGraph || !ppWc) 
    {
        return E_POINTER;
    }
    IBaseFilter* pVmr = NULL; 
    IVMRWindowlessControl* pWc = NULL; 
    // Create the VMR. 
    HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL, 
        CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr); 
    if (FAILED(hr))
    {
        return hr;
    }
    
    // Add the VMR to the filter graph.
    hr = pGraph->AddFilter(pVmr, L"Video Mixing Renderer"); 
    if (FAILED(hr)) 
    {
        pVmr->Release();
        return hr;
    }
    // Set the rendering mode.  
    IVMRFilterConfig* pConfig; 
    hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig); 
    if (SUCCEEDED(hr)) 
    { 
        hr = pConfig->SetRenderingMode(VMRMode_Windowless); 
        pConfig->Release(); 
    }
    if (SUCCEEDED(hr))
    {
        // Set the window. 
        hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWc);
        if( SUCCEEDED(hr)) 
        { 
            hr = pWc->SetVideoClippingWindow(hwndApp); 
            if (SUCCEEDED(hr))
            {
                *ppWc = pWc; // Return this as an AddRef'd pointer. 
            }
            else
            {
                // An error occurred, so release the interface.
                pWc->Release();
            }
        } 
    } 
    pVmr->Release(); 
    return hr; 
} 

此函数假定仅显示一个视频流,并且不会在视频上混合使用静态位图。 有关详细信息,请参阅 VMR 无窗口模式。 可按如下所示调用此函数:

IVMRWindowlessControl *pWc = NULL;
hr = InitWindowlessVMR(hwnd, pGraph, &g_pWc);
if (SUCCEEDED(hr))
{
    // Build the graph. For example:
    pGraph->RenderFile(wszMyFileName, 0);
    // Release the VMR interface when you are done.
    pWc->Release();
}

定位视频

配置 VMR 后,下一步是设置视频的位置。 需要考虑两个矩形: 矩形和 目标 矩形。 源矩形定义要显示的视频部分。 目标矩形指定窗口工作区中将包含视频的区域。 VMR 将视频图像裁剪到源矩形,并拉伸裁剪的图像以适应目标矩形。

VMR-7

  1. 调用 IVMRWindowlessControl::SetVideoPosition 方法以指定这两个矩形。
  2. 源矩形必须等于或小于本机视频大小;可以使用 IVMRWindowlessControl::GetNativeVideoSize 方法获取本机视频大小。

VMR-9

  1. 调用 IVMRWindowlessControl9::SetVideoPosition 方法以指定这两个矩形。
  2. 源矩形必须等于或小于本机视频大小;可以使用 IVMRWindowlessControl9::GetNativeVideoSize 方法获取本机视频大小。

例如,以下代码为 VMR-7 设置源矩形和目标矩形。 它将源矩形设置为等于整个视频图像,将目标矩形设置为等于整个窗口工作区:

// Find the native video size.
long lWidth, lHeight; 
HRESULT hr = g_pWc->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL); 
if (SUCCEEDED(hr))
{
    RECT rcSrc, rcDest; 
    // Set the source rectangle.
    SetRect(&rcSrc, 0, 0, lWidth, lHeight); 
    
    // Get the window client area.
    GetClientRect(hwnd, &rcDest); 
    // Set the destination rectangle.
    SetRect(&rcDest, 0, 0, rcDest.right, rcDest.bottom); 
    
    // Set the video position.
    hr = g_pWc->SetVideoPosition(&rcSrc, &rcDest); 
}

如果希望视频占用工作区的一小部分,请修改 rcDest 参数。 如果要裁剪视频图像,请修改 rcSrc 参数。

处理窗口消息

由于 VMR 没有自己的窗口,因此如果需要重新绘制视频或调整视频大小,必须通知它。 通过调用列出的 VMR 方法响应以下窗口消息。

VMR-7

  1. WM_PAINT。 调用 IVMRWindowlessControl::RepaintVideo。 此方法会导致 VMR-7 重新绘制最新的视频帧。
  2. WM_DISPLAYCHANGE:调用 IVMRWindowlessControl::D isplayModeChanged。 此方法通知 VMR-7 视频必须以新的分辨率或颜色深度显示。
  3. WM_SIZEWM_WINDOWPOSCHANGED:根据需要重新计算视频的位置并调用 IVMRWindowlessControl::SetVideoPosition 以更新位置。

VMR-9

  1. WM_PAINT。 调用 IVMRWindowlessControl9::RepaintVideo。 此方法会导致 VMR-9 重新绘制最新的视频帧。
  2. WM_DISPLAYCHANGE:调用 IVMRWindowlessControl9::D isplayModeChanged。 此方法通知 VMR-9 视频必须以新的分辨率或颜色深度显示。
  3. WM_SIZEWM_WINDOWPOSCHANGED:重新计算视频的位置,并根据需要调用 IVMRWindowlessControl9::SetVideoPosition 以更新位置。

注意

WM_WINDOWPOSCHANGED消息的默认处理程序发送WM_SIZE消息。 但是,如果应用程序截获WM_WINDOWPOSCHANGED并且未将其传递给 DefWindowProc,则除了WM_SIZE处理程序外,还应在WM_WINDOWPOSCHANGED处理程序中调用 SetVideoPosition

 

以下示例演示 WM_PAINT 消息处理程序。 它绘制由客户端矩形减去视频矩形定义的区域。 请勿在视频矩形上绘制,因为 VMR 将在该矩形上绘制,从而导致闪烁。 出于同样的原因,请勿在窗口类中设置背景画笔。

void OnPaint(HWND hwnd) 
{ 
    PAINTSTRUCT ps; 
    HDC         hdc; 
    RECT        rcClient; 
    GetClientRect(hwnd, &rcClient); 
    hdc = BeginPaint(hwnd, &ps); 
    if (g_pWc != NULL) 
    { 
        // Find the region where the application can paint by subtracting 
        // the video destination rectangle from the client area.
        // (Assume that g_rcDest was calculated previously.)
        HRGN rgnClient = CreateRectRgnIndirect(&rcClient); 
        HRGN rgnVideo  = CreateRectRgnIndirect(&g_rcDest);  
        CombineRgn(rgnClient, rgnClient, rgnVideo, RGN_DIFF);  
        
        // Paint on window.
        HBRUSH hbr = GetSysColorBrush(COLOR_BTNFACE); 
        FillRgn(hdc, rgnClient, hbr); 

        // Clean up.
        DeleteObject(hbr); 
        DeleteObject(rgnClient); 
        DeleteObject(rgnVideo); 

        // Request the VMR to paint the video.
        HRESULT hr = g_pWc->RepaintVideo(hwnd, hdc);  
    } 
    else  // There is no video, so paint the whole client area. 
    { 
        FillRect(hdc, &rc2, (HBRUSH)(COLOR_BTNFACE + 1)); 
    } 
    EndPaint(hwnd, &ps); 
} 

尽管必须响应 WM_PAINT 消息,但在 更新视频WM_PAINT 消息之间无需执行任何操作。 如本示例所示,无窗口模式允许你简单地将视频图像视为窗口上的自绘图区域。

使用视频混合呈现器

视频渲染