使用無視窗模式
[與此頁面 相關的功能 DirectShow是舊版功能。 它已被 MediaPlayer、 IMFMediaEngine和 Media Foundation 中的音訊/視訊擷取取代。 這些功能已針對Windows 10和Windows 11進行優化。 Microsoft 強烈建議新程式碼盡可能使用 MediaPlayer、 IMFMediaEngine 和 音訊/視訊擷取 ,而不是 DirectShow。 Microsoft 建議盡可能重寫使用舊版 API 的現有程式碼,以使用新的 API。]
視訊混合轉譯器篩選器 7 (VMR-7) 和視訊混合轉譯器篩選器 9 (VMR-9) 支援無視窗模式,這代表IVideoWindow介面的重大改善。 本主題描述無視窗模式與視窗模式之間的差異,以及如何使用無視窗模式。
為了保持與現有應用程式的回溯相容,VMR 預設為視窗模式。 在視窗模式中,轉譯器會建立自己的視窗以顯示視訊。 應用程式通常會將視訊視窗設定為應用程式視窗的子系。 不過,個別視訊視窗的存在會造成一些問題:
- 最重要的是,如果視窗訊息線上程之間傳送,可能會發生死結。
- 篩選圖形管理員必須將某些視窗訊息轉送至影片轉譯器,例如WM_PAINT。 應用程式必須使用 Filter Graph Manager 的 IVideoWindow (實作,而不是影片轉譯器的) ,讓 Filter Graph 管理員維持正確的內部狀態。
- 若要從視訊視窗接收滑鼠或鍵盤事件,應用程式必須設定 清空訊息,導致視訊視窗將這些訊息轉送至應用程式。
- 若要避免裁剪問題,視訊視窗必須有正確的視窗樣式。
無視窗模式可避免這些問題,方法是使用 DirectDraw 直接在應用程式視窗的工作區上繪製 VMR 來裁剪視訊矩形。 無視窗模式可大幅減少死結的機會。 此外,應用程式不需要設定擁有者視窗或視窗樣式。 事實上,當 VMR 處於無視窗模式時,甚至不會公開不再需要的 IVideoWindow 介面。
若要使用無視窗模式,您必須明確設定 VMR。 不過,您會發現比視窗模式更有彈性且更容易使用。
VMR-7 篩選器和 VMR-9 篩選器會公開不同的介面,但步驟相當於每個介面。
設定無視窗模式的 VMR
若要覆寫 VMR 的預設行為,請先設定 VMR,再建置篩選圖形:
VMR-7
- 建立 Filter Graph Manager。
- 建立 VMR-7,並將其新增至篩選圖形。
- 使用VMRMode_Windowless旗標在 VMR-7 上呼叫IVMRFilterConfig::SetRenderingMode。
- 查詢 VMR-7 以取得 IVMRWindowlessControl 介面。
- 在 VMR-7 上呼叫 IVMRWindowlessControl::SetVideoClippingWindow 。 指定應顯示視訊之視窗的控制碼。
VMR-9
- 建立 Filter Graph Manager。
- 建立 VMR-9,並將其新增至篩選圖形。
- 使用VMR9Mode_Windowless旗標在 VMR-9 上呼叫IVMRFilterConfig9::SetRenderingMode。
- 查詢 VMR-9 以取得 IVMRWindowlessControl9 介面。
- 在 VMR-9 上呼叫 IVMRWindowlessControl9::SetVideoClippingWindow 。 指定應顯示視訊之視窗的控制碼。
現在呼叫 IGraphBuilder::RenderFile 或其他圖形建置方法,以建置其餘的篩選圖形。 Filter Graph 管理員會自動使用您新增至圖形的 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 訊息之間不需要執行任何動作,即可更新影片。 如本範例所示,無視窗模式可讓您將視訊影像視為視窗上的自我繪製區域。
相關主題