在 DirectShow 中支援 DXVA 2.0
本主題描述如何在 DirectShow 解碼器篩選中支援 DirectX 影片加速 (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 中,解碼器會使用DXVA_ConfigPictureDecode結構呼叫Execute,以設定存取器。 在 DXVA 2.0 中,解碼器會使用 IDirectXVideoDecoderService 介面,如下一節所述。
- 解碼器會配置未壓縮的緩衝區。 視訊轉譯器不再配置它們。
- 解碼器不會呼叫 IAMVideoAccelerator::D isplayFrame 來顯示解碼的框架,而是透過呼叫 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::D ecideBufferSize 方法。 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 相同的資料結構。 針對 H.261、H.263 和 MPEG-2) 的原始 DXVA 設定檔集 (,這些資料結構會在 DXVA 1.0 規格中說明。
在每對BeginFrame/Execute呼叫內,您可以多次呼叫GetBuffer,但每種類型的 DXVA 緩衝區只能呼叫一次。 如果您使用相同的緩衝區類型呼叫兩次,將會覆寫資料。
呼叫 Execute之後,呼叫 IMemInputPin::Receive 將畫面傳遞至視訊轉譯器,如同軟體解碼一樣。 Receive方法是非同步;傳回之後,解碼器可以繼續解碼下一個畫面。 顯示驅動程式可防止在緩衝區正在使用時覆寫緩衝區的任何解碼命令。 解碼器不應該重複使用表面來解碼另一個畫面,直到轉譯器放開樣本為止。 當轉譯器釋放樣本時,配置器會將範例放回其可用樣本集區。 若要取得下一個可用的範例,請呼叫 CBaseOutputPin::GetDeliveryBuffer,接著呼叫 IMemAllocator::GetBuffer。 如需詳細資訊,請參閱 DirectShow 檔中 DirectShow 中的資料流程概觀 主題。
相關主題