在 DirectShow 中支援 DXVA 2.0
本主題描述如何在 DirectShow 譯碼器篩選中支援 DirectX Video Acceleration (DXVA) 2.0。 具體來說,它會描述譯碼器和視訊轉譯器之間的通訊。 本主題不會描述如何實作 DXVA 譯碼。
先決條件
本主題假設您已熟悉撰寫 DirectShow 篩選。 如需詳細資訊,請參閱 DirectShow SDK 檔中 撰寫 DirectShow 篩選 主題。 本主題中的程式代碼範例假設譯碼器篩選衍生自 CTransformFilter 類別,並具有下列類別定義:
class CDecoder : public CTransformFilter
{
public:
static CUnknown* WINAPI CreateInstance(IUnknown *pUnk, HRESULT *pHr);
HRESULT CompleteConnect(PIN_DIRECTION direction, IPin *pPin);
HRESULT InitAllocator(IMemAllocator **ppAlloc);
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp);
// TODO: The implementations of these methods depend on the specific decoder.
HRESULT CheckInputType(const CMediaType *mtIn);
HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut);
HRESULT CTransformFilter::GetMediaType(int,CMediaType *);
private:
CDecoder(HRESULT *pHr);
~CDecoder();
CBasePin * GetPin(int n);
HRESULT ConfigureDXVA2(IPin *pPin);
HRESULT SetEVRForDXVA2(IPin *pPin);
HRESULT FindDecoderConfiguration(
/* [in] */ IDirectXVideoDecoderService *pDecoderService,
/* [in] */ const GUID& guidDecoder,
/* [out] */ DXVA2_ConfigPictureDecode *pSelectedConfig,
/* [out] */ BOOL *pbFoundDXVA2Configuration
);
private:
IDirectXVideoDecoderService *m_pDecoderService;
DXVA2_ConfigPictureDecode m_DecoderConfig;
GUID m_DecoderGuid;
HANDLE m_hDevice;
FOURCC m_fccOutputFormat;
};
在本主題的其餘部分,譯碼器 一詞是指譯碼器篩選器,它會接收壓縮的視訊和輸出未壓縮的視訊。 譯碼器裝置 一詞是指由圖形驅動程式實現的硬體視頻加速器。
以下是譯碼器篩選必須執行的基本步驟,以支援 DXVA 2.0:
- 協商媒體類型。
- 尋找 DXVA 譯碼器組態。
- 通知視頻渲染器,解碼器正在使用 DXVA 解碼。
- 提供一個分配 Direct3D 表面的自訂分配器。
本主題其餘部分將詳細說明這些步驟。
移轉注意事項
如果您要從 DXVA 1.0 移轉,您應該注意這兩個版本之間的一些重大差異:
- DXVA 2.0 不會使用 IAMVideoAccelerator 和 IAMVideoAcceleratorNotify 介面,因為譯碼器可以直接透過 IDirectXVideoDecoder 介面存取 DXVA 2.0 API。
- 在媒體類型交涉期間,譯碼器不會使用視訊加速 GUID 作為子類型。 相反地,子類型只是未壓縮的視訊格式(例如 NV12),如同軟體譯碼。
- 設定加速器的程式已變更。 在 DXVA 1.0 中,譯碼器會透過執行 Execute 並使用 DXVA_ConfigPictureDecode 結構來設定加速器。 在 DXVA 2.0 中,譯碼器會使用 IDirectXVideoDecoderService 介面,如下一節所述。
- 譯碼器會配置未壓縮的緩衝區。 視頻渲染器不再分配這些資源。
- 譯碼器不再呼叫 IAMVideoAccelerator::DisplayFrame 來顯示解碼后的影格,而是跟軟體解碼一樣,藉由呼叫 IMemInputPin::Receive,將影格傳送至渲染器。
- 譯碼器不再負責檢查數據緩衝區何時安全進行更新。 因此,DXVA 2.0 沒有任何相當於 IAMVideoAccelerator::QueryRenderStatus的方法。
- 子圖片混合是由影片轉譯器使用 DXVA2.0 視訊處理器 API 來完成。 提供子圖片的解碼器(例如 DVD 解碼器)應該在不同的輸出端子上傳送子圖片資料。
針對譯碼作業,DXVA 2.0 會使用與 DXVA 1.0 相同的數據結構。
增強的視訊轉譯器 (EVR) 篩選器支援 DXVA 2.0。 影片混合轉譯器篩選器 (VMR-7 和 VMR-9) 僅支援 DXVA 1.0。
尋找譯碼器組態
譯碼器交涉輸出媒體類型之後,它必須找到 DXVA 譯碼器裝置的相容組態。 您可以在輸出腳位的 CBaseOutputPin::CompleteConnect 方法內執行此步驟。 此步驟可確保在譯碼器認可使用 DXVA 之前,圖形驅動程式支持譯碼器所需的功能。
若要尋找譯碼器裝置的設定,請執行下列動作:
查詢渲染器的輸入針腳,獲取 IMFGetService 介面。
呼叫 IMFGetService::GetService,以取得 IDirect3DDeviceManager9 介面的指標。 服務 GUID 是 MR_VIDEO_ACCELERATION_SERVICE。
呼叫 IDirect3DDeviceManager9::OpenDeviceHandle,以取得渲染器 Direct3D 裝置的控制代碼。
呼叫 IDirect3DDeviceManager9::GetVideoService 並傳入裝置控制代碼。 這個方法會傳回一個指向 IDirectXVideoDecoderService 介面的指標。
呼叫 IDirectXVideoDecoderService::GetDecoderDeviceGuids。 這個方法會傳回譯碼器裝置 GUID 的陣列。
遍歷解碼器 GUID 的陣列,以找到解碼器篩選器所支援的那些。 例如,針對 MPEG-2 譯碼器,您會尋找 DXVA2_ModeMPEG2_MOCOMP、DXVA2_ModeMPEG2_IDCT或 DXVA2_ModeMPEG2_VLD。
當您找到候選譯碼器裝置 GUID 時,請將 GUID 傳遞至 IDirectXVideoDecoderService::GetDecoderRenderTargets 方法。 這個方法會傳回一個陣列,其中包含被指定為 D3DFORMAT 值的轉譯目標格式。
輪詢轉譯目標格式,尋找與您輸出格式相符的格式。 譯碼器裝置通常支援單一轉譯目標格式。 譯碼器濾波器應該使用此子類型連接到轉譯器。 在第一次呼叫 completeConnect 時,譯碼器可以判斷轉譯目標格式,然後將這個格式當做慣用的輸出類型傳回。
呼叫 IDirectXVideoDecoderService::GetDecoderConfigurations。 傳入相同的解碼器裝置 GUID,並附上描述建議格式的 DXVA2_VideoDesc 結構。 方法會傳回 DXVA2_ConfigPictureDecode 結構的陣列。 每個結構都會描述譯碼器裝置的一個可能設定。
假設先前的步驟成功,請儲存 Direct3D 裝置句柄、譯碼器裝置 GUID 和組態結構。 篩選會使用這項資訊來建立譯碼器裝置。
下列程式代碼示範如何尋找譯碼器組態。
HRESULT CDecoder::ConfigureDXVA2(IPin *pPin)
{
UINT cDecoderGuids = 0;
BOOL bFoundDXVA2Configuration = FALSE;
GUID guidDecoder = GUID_NULL;
DXVA2_ConfigPictureDecode config;
ZeroMemory(&config, sizeof(config));
// Variables that follow must be cleaned up at the end.
IMFGetService *pGetService = NULL;
IDirect3DDeviceManager9 *pDeviceManager = NULL;
IDirectXVideoDecoderService *pDecoderService = NULL;
GUID *pDecoderGuids = NULL; // size = cDecoderGuids
HANDLE hDevice = INVALID_HANDLE_VALUE;
// Query the pin for IMFGetService.
HRESULT hr = pPin->QueryInterface(IID_PPV_ARGS(&pGetService));
// Get the Direct3D device manager.
if (SUCCEEDED(hr))
{
hr = pGetService->GetService(
MR_VIDEO_ACCELERATION_SERVICE,
IID_PPV_ARGS(&pDeviceManager)
);
}
// Open a new device handle.
if (SUCCEEDED(hr))
{
hr = pDeviceManager->OpenDeviceHandle(&hDevice);
}
// Get the video decoder service.
if (SUCCEEDED(hr))
{
hr = pDeviceManager->GetVideoService(
hDevice, IID_PPV_ARGS(&pDecoderService));
}
// Get the decoder GUIDs.
if (SUCCEEDED(hr))
{
hr = pDecoderService->GetDecoderDeviceGuids(
&cDecoderGuids, &pDecoderGuids);
}
if (SUCCEEDED(hr))
{
// Look for the decoder GUIDs we want.
for (UINT iGuid = 0; iGuid < cDecoderGuids; iGuid++)
{
// Do we support this mode?
if (!IsSupportedDecoderMode(pDecoderGuids[iGuid]))
{
continue;
}
// Find a configuration that we support.
hr = FindDecoderConfiguration(pDecoderService, pDecoderGuids[iGuid],
&config, &bFoundDXVA2Configuration);
if (FAILED(hr))
{
break;
}
if (bFoundDXVA2Configuration)
{
// Found a good configuration. Save the GUID and exit the loop.
guidDecoder = pDecoderGuids[iGuid];
break;
}
}
}
if (!bFoundDXVA2Configuration)
{
hr = E_FAIL; // Unable to find a configuration.
}
if (SUCCEEDED(hr))
{
// Store the things we will need later.
SafeRelease(&m_pDecoderService);
m_pDecoderService = pDecoderService;
m_pDecoderService->AddRef();
m_DecoderConfig = config;
m_DecoderGuid = guidDecoder;
m_hDevice = hDevice;
}
if (FAILED(hr))
{
if (hDevice != INVALID_HANDLE_VALUE)
{
pDeviceManager->CloseDeviceHandle(hDevice);
}
}
SafeRelease(&pGetService);
SafeRelease(&pDeviceManager);
SafeRelease(&pDecoderService);
return hr;
}
HRESULT CDecoder::FindDecoderConfiguration(
/* [in] */ IDirectXVideoDecoderService *pDecoderService,
/* [in] */ const GUID& guidDecoder,
/* [out] */ DXVA2_ConfigPictureDecode *pSelectedConfig,
/* [out] */ BOOL *pbFoundDXVA2Configuration
)
{
HRESULT hr = S_OK;
UINT cFormats = 0;
UINT cConfigurations = 0;
D3DFORMAT *pFormats = NULL; // size = cFormats
DXVA2_ConfigPictureDecode *pConfig = NULL; // size = cConfigurations
// Find the valid render target formats for this decoder GUID.
hr = pDecoderService->GetDecoderRenderTargets(
guidDecoder,
&cFormats,
&pFormats
);
if (SUCCEEDED(hr))
{
// Look for a format that matches our output format.
for (UINT iFormat = 0; iFormat < cFormats; iFormat++)
{
if (pFormats[iFormat] != (D3DFORMAT)m_fccOutputFormat)
{
continue;
}
// Fill in the video description. Set the width, height, format,
// and frame rate.
DXVA2_VideoDesc videoDesc = {0};
FillInVideoDescription(&videoDesc); // Private helper function.
videoDesc.Format = pFormats[iFormat];
// Get the available configurations.
hr = pDecoderService->GetDecoderConfigurations(
guidDecoder,
&videoDesc,
NULL, // Reserved.
&cConfigurations,
&pConfig
);
if (FAILED(hr))
{
break;
}
// Find a supported configuration.
for (UINT iConfig = 0; iConfig < cConfigurations; iConfig++)
{
if (IsSupportedDecoderConfig(pConfig[iConfig]))
{
// This configuration is good.
*pbFoundDXVA2Configuration = TRUE;
*pSelectedConfig = pConfig[iConfig];
break;
}
}
CoTaskMemFree(pConfig);
break;
} // End of formats loop.
}
CoTaskMemFree(pFormats);
// Note: It is possible to return S_OK without finding a configuration.
return hr;
}
由於此範例是泛型的,因此某些邏輯已放在譯碼器需要實作的協助程式函式中。 下列程式代碼顯示這些函式的宣告:
// Returns TRUE if the decoder supports a given decoding mode.
BOOL IsSupportedDecoderMode(const GUID& mode);
// Returns TRUE if the decoder supports a given decoding configuration.
BOOL IsSupportedDecoderConfig(const DXVA2_ConfigPictureDecode& config);
// Fills in a DXVA2_VideoDesc structure based on the input format.
void FillInVideoDescription(DXVA2_VideoDesc *pDesc);
通知影片轉譯器
如果解碼器找到解碼器配置,下一個步驟是通知視訊渲染器,解碼器將使用硬體加速。 您可以在 CompleteConnect 方法內執行此步驟。 此步驟必須在選取配置器之前發生,因為它會影響配置器選取的方式。
- 查詢渲染器的輸入端子以獲取 IMFGetService 介面。
- 呼叫 IMFGetService::GetService,以取得 IDirectXVideoMemoryConfiguration 介面的指標。 服務 GUID 是 MR_VIDEO_ACCELERATION_SERVICE。
- 在迴圈中呼叫 IDirectXVideoMemoryConfiguration::GetAvailableSurfaceTypeByIndex,將 dwTypeIndex 變數從零遞增。 當方法傳回 pdwType 參數中的 DXVA2_SurfaceType_DecoderRenderTarget 值時停止。 此步驟可確保影片轉譯器支持硬體加速譯碼。 EVR 篩選器的這個步驟總是會成功。
- 如果上一個步驟成功,請使用值DXVA2_SurfaceType_DecoderRenderTarget呼叫 IDirectXVideoMemoryConfiguration::SetSurfaceType。 使用此值呼叫 SetSurfaceType,會將視訊轉譯器置於 DXVA 模式。 當視訊轉譯器處於這個模式時,譯碼器必須提供自己的配置器。
下列程式代碼示範如何通知影片轉譯器。
HRESULT CDecoder::SetEVRForDXVA2(IPin *pPin)
{
HRESULT hr = S_OK;
IMFGetService *pGetService = NULL;
IDirectXVideoMemoryConfiguration *pVideoConfig = NULL;
// Query the pin for IMFGetService.
hr = pPin->QueryInterface(__uuidof(IMFGetService), (void**)&pGetService);
// Get the IDirectXVideoMemoryConfiguration interface.
if (SUCCEEDED(hr))
{
hr = pGetService->GetService(
MR_VIDEO_ACCELERATION_SERVICE, IID_PPV_ARGS(&pVideoConfig));
}
// Notify the EVR.
if (SUCCEEDED(hr))
{
DXVA2_SurfaceType surfaceType;
for (DWORD iTypeIndex = 0; ; iTypeIndex++)
{
hr = pVideoConfig->GetAvailableSurfaceTypeByIndex(iTypeIndex, &surfaceType);
if (FAILED(hr))
{
break;
}
if (surfaceType == DXVA2_SurfaceType_DecoderRenderTarget)
{
hr = pVideoConfig->SetSurfaceType(DXVA2_SurfaceType_DecoderRenderTarget);
break;
}
}
}
SafeRelease(&pGetService);
SafeRelease(&pVideoConfig);
return hr;
}
如果譯碼器找到有效的組態並成功通知影片轉譯器,譯碼器可以使用 DXVA 進行譯碼。 譯碼器必須為其輸出的接腳實作自訂的記憶體分配器,如下節所述。
配置未壓縮的緩衝區
在 DXVA 2.0 中,譯碼器負責配置 Direct3D 表面做為未壓縮的視訊緩衝區。 因此,解碼器必須實作一個自定義配置器來建立畫面。 這個分配器所提供的媒體範例會保存 Direct3D 表面的指標。 EVR 會藉由在媒體範例上 呼叫 IMFGetService::GetService 來擷取指向表面的指標。 服務識別碼 MR_BUFFER_SERVICE。
若要提供客製化配置器,請執行下列步驟:
- 定義媒體範例的類別。 這個類別可以衍生自 CMediaSample 類別。 在此類別內,執行下列動作:
- 儲存指向 Direct3D 表面的指標。
- 實作 IMFGetService 介面。 在 GetService 方法中,如果服務 GUID 是 MR_BUFFER_SERVICE,請查詢所要求介面的 Direct3D 介面。 否則,GetService 可以傳回 MF_E_UNSUPPORTED_SERVICE。
- 覆寫 CMediaSample::GetPointer 方法以傳回E_NOTIMPL。
- 定義分配器的類別。 配置器可以衍生自 CBaseAllocator 類別。 在此類別內,執行下列動作。
- 覆寫 CBaseAllocator::Alloc 方法。 在此方法中,呼叫 IDirectXVideoAccelerationService::CreateSurface 來建立介面。 (IDirectXVideoDecoderService 介面會從 IDirectXVideoAccelerationService繼承此方法。
- 覆寫 CBaseAllocator::Free 方法來釋放表面。
- 在篩選器的輸出端中,覆寫 CBaseOutputPin::InitAllocator 方法。 在此方法中,建立自訂分配器的實例。
- 在您的篩選器中,實作 CTransformFilter::DecideBufferSize 方法。 pProperties 參數表示 EVR 所需的表面數目。 將譯碼器所需的表面數目加到該值,然後在配置器上呼叫 IMemAllocator::SetProperties。
下列程式代碼示範如何實作媒體範例類別:
class CDecoderSample : public CMediaSample, public IMFGetService
{
friend class CDecoderAllocator;
public:
CDecoderSample(CDecoderAllocator *pAlloc, HRESULT *phr)
: CMediaSample(NAME("DecoderSample"), (CBaseAllocator*)pAlloc, phr, NULL, 0),
m_pSurface(NULL),
m_dwSurfaceId(0)
{
}
// Note: CMediaSample does not derive from CUnknown, so we cannot use the
// DECLARE_IUNKNOWN macro that is used by most of the filter classes.
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
CheckPointer(ppv, E_POINTER);
if (riid == IID_IMFGetService)
{
*ppv = static_cast<IMFGetService*>(this);
AddRef();
return S_OK;
}
else
{
return CMediaSample::QueryInterface(riid, ppv);
}
}
STDMETHODIMP_(ULONG) AddRef()
{
return CMediaSample::AddRef();
}
STDMETHODIMP_(ULONG) Release()
{
// Return a temporary variable for thread safety.
ULONG cRef = CMediaSample::Release();
return cRef;
}
// IMFGetService::GetService
STDMETHODIMP GetService(REFGUID guidService, REFIID riid, LPVOID *ppv)
{
if (guidService != MR_BUFFER_SERVICE)
{
return MF_E_UNSUPPORTED_SERVICE;
}
else if (m_pSurface == NULL)
{
return E_NOINTERFACE;
}
else
{
return m_pSurface->QueryInterface(riid, ppv);
}
}
// Override GetPointer because this class does not manage a system memory buffer.
// The EVR uses the MR_BUFFER_SERVICE service to get the Direct3D surface.
STDMETHODIMP GetPointer(BYTE ** ppBuffer)
{
return E_NOTIMPL;
}
private:
// Sets the pointer to the Direct3D surface.
void SetSurface(DWORD surfaceId, IDirect3DSurface9 *pSurf)
{
SafeRelease(&m_pSurface);
m_pSurface = pSurf;
if (m_pSurface)
{
m_pSurface->AddRef();
}
m_dwSurfaceId = surfaceId;
}
IDirect3DSurface9 *m_pSurface;
DWORD m_dwSurfaceId;
};
下列程式代碼示範如何在配置器上實作 Alloc 方法。
HRESULT CDecoderAllocator::Alloc()
{
CAutoLock lock(this);
HRESULT hr = S_OK;
if (m_pDXVA2Service == NULL)
{
return E_UNEXPECTED;
}
hr = CBaseAllocator::Alloc();
// If the requirements have not changed, do not reallocate.
if (hr == S_FALSE)
{
return S_OK;
}
if (SUCCEEDED(hr))
{
// Free the old resources.
Free();
// Allocate a new array of pointers.
m_ppRTSurfaceArray = new (std::nothrow) IDirect3DSurface9*[m_lCount];
if (m_ppRTSurfaceArray == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
ZeroMemory(m_ppRTSurfaceArray, sizeof(IDirect3DSurface9*) * m_lCount);
}
}
// Allocate the surfaces.
if (SUCCEEDED(hr))
{
hr = m_pDXVA2Service->CreateSurface(
m_dwWidth,
m_dwHeight,
m_lCount - 1,
(D3DFORMAT)m_dwFormat,
D3DPOOL_DEFAULT,
0,
DXVA2_VideoDecoderRenderTarget,
m_ppRTSurfaceArray,
NULL
);
}
if (SUCCEEDED(hr))
{
for (m_lAllocated = 0; m_lAllocated < m_lCount; m_lAllocated++)
{
CDecoderSample *pSample = new (std::nothrow) CDecoderSample(this, &hr);
if (pSample == NULL)
{
hr = E_OUTOFMEMORY;
break;
}
if (FAILED(hr))
{
break;
}
// Assign the Direct3D surface pointer and the index.
pSample->SetSurface(m_lAllocated, m_ppRTSurfaceArray[m_lAllocated]);
// Add to the sample list.
m_lFree.Add(pSample);
}
}
if (SUCCEEDED(hr))
{
m_bChanged = FALSE;
}
return hr;
}
以下是 Free 方法的程式代碼:
void CDecoderAllocator::Free()
{
CMediaSample *pSample = NULL;
do
{
pSample = m_lFree.RemoveHead();
if (pSample)
{
delete pSample;
}
} while (pSample);
if (m_ppRTSurfaceArray)
{
for (long i = 0; i < m_lAllocated; i++)
{
SafeRelease(&m_ppRTSurfaceArray[i]);
}
delete [] m_ppRTSurfaceArray;
}
m_lAllocated = 0;
}
如需實作自定義配置器的詳細資訊,請參閱 DirectShow SDK 檔中 提供自定義設定器 主題。
解碼
若要建立譯碼器裝置,請呼叫 IDirectXVideoDecoderService::CreateVideoDecoder。 該方法會傳回指向解碼器裝置的 IDirectXVideoDecoder 介面 的指標。
在每個幀上,呼叫 IDirect3DDeviceManager9::TestDevice 以測試裝置控制代碼。 如果裝置已變更,此方法會傳回DXVA2_E_NEW_VIDEO_DEVICE。 如果發生這種情況,請執行下列動作:
- 通過呼叫 IDirect3DDeviceManager9::CloseDeviceHandle來關閉裝置控制代碼。
- 釋放 IDirectXVideoDecoderService 和 IDirectXVideoDecoder 指標。
- 開啟新的裝置控制碼。
- 交涉新的譯碼器組態,如尋找譯碼器組態 一節所述。
- 建立新的譯碼器裝置。
假設裝置句柄有效,譯碼程序的運作方式如下:
- 呼叫 IDirectXVideoDecoder::BeginFrame。
- 執行下列一或多次:
- 呼叫 IDirectXVideoDecoder::GetBuffer 以取得 DXVA 譯碼器緩衝區。
- 填入緩衝區。
- 呼叫 IDirectXVideoDecoder::ReleaseBuffer。
- 呼叫 IDirectXVideoDecoder::Execute,以在畫面上執行譯碼作業。
DXVA 2.0 使用與 DXVA 1.0 相同的數據結構進行譯碼作業。 針對原始的 DXVA 設定檔集(適用於 H.261、H.263 和 MPEG-2),這些數據結構會描述於 DXVA 1.0 規格。
在每對 BeginFrame/Execute 呼叫中,您可以多次呼叫 GetBuffer,但每種 DXVA 緩衝區類型只能呼叫一次。 如果您使用相同的緩衝區類型呼叫兩次,則會覆寫數據。
呼叫 Execute之後,呼叫 IMemInputPin::Receive,將畫面傳送至視訊轉譯器,如同軟體譯碼。 Receive 方法是異步的;傳回之後,譯碼器可以繼續譯碼下一個畫面。 顯示驅動程式會防止在緩衝區正在使用時,任何譯碼命令覆寫緩衝區。 譯碼器不應該重複使用表面來譯碼另一個畫面,直到轉譯器放開範例為止。 當轉譯器釋放範例時,配置器會將範例放回其可用範例集區。 若要取得下一個可用的範例,請呼叫 CBaseOutputPin::GetDeliveryBuffer,接著會呼叫 IMemAllocator::GetBuffer。 如需詳細資訊,請參閱 DirectShow 檔中 DirectShow 中的數據流概觀 主題。
相關主題