本主題描述如何在 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 中,譯碼器會使用 DXVA_ConfigPictureDecode 結構呼叫 Execute,以設定加速器。 在 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 中的數據流概觀 主題。
相關主題