使用无窗口模式
[与此页面关联的功能 DirectShow 是旧版功能。 它已被 MediaPlayer、 IMFMediaEngine 和 Media Foundation 中的音频/视频捕获所取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能在 Media Foundation 中使用 MediaPlayer、 IMFMediaEngine 和 音频/视频捕获 ,而不是 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
- 创建 Filter Graph 管理器。
- 创建 VMR-7 并将其添加到筛选图。
- 使用 VMRMode_Windowless 标志在 VMR-7 上调用 IVMRFilterConfig::SetRenderingMode。
- 查询 VMR-7 以获取 IVMRWindowlessControl 接口。
- 在 VMR-7 上调用 IVMRWindowlessControl::SetVideoClippingWindowWindow 。 指定应显示视频的窗口的句柄。
VMR-9
- 创建 Filter Graph 管理器。
- 创建 VMR-9 并将其添加到筛选器图。
- 使用 VMR9Mode_Windowless 标志在 VMR-9 上调用 IVMRFilterConfig9::SetRenderingMode。
- 在 VMR-9 中查询 IVMRWindowlessControl9 接口。
- 在 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
- 调用 IVMRWindowlessControl::SetVideoPosition 方法以指定这两个矩形。
- 源矩形必须等于或小于本机视频大小;可以使用 IVMRWindowlessControl::GetNativeVideoSize 方法获取本机视频大小。
VMR-9
- 调用 IVMRWindowlessControl9::SetVideoPosition 方法以指定这两个矩形。
- 源矩形必须等于或小于本机视频大小;可以使用 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
- WM_PAINT。 调用 IVMRWindowlessControl::RepaintVideo。 此方法会导致 VMR-7 重新绘制最新的视频帧。
- WM_DISPLAYCHANGE:调用 IVMRWindowlessControl::D isplayModeChanged。 此方法通知 VMR-7 视频必须以新的分辨率或颜色深度显示。
- WM_SIZE 或 WM_WINDOWPOSCHANGED:根据需要重新计算视频的位置并调用 IVMRWindowlessControl::SetVideoPosition 以更新位置。
VMR-9
- WM_PAINT。 调用 IVMRWindowlessControl9::RepaintVideo。 此方法会导致 VMR-9 重新绘制最新的视频帧。
- WM_DISPLAYCHANGE:调用 IVMRWindowlessControl9::D isplayModeChanged。 此方法通知 VMR-9 视频必须以新的分辨率或颜色深度显示。
- WM_SIZE 或 WM_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 消息之间无需执行任何操作。 如本示例所示,无窗口模式允许你简单地将视频图像视为窗口上的自绘图区域。
相关主题