将解码器添加到拓扑
本主题介绍如何将音频或视频解码器添加到拓扑。
对于大多数播放应用程序,可以从发送到媒体会话的部分拓扑中省略解码器。 媒体会话使用拓扑加载程序来完成拓扑,拓扑加载程序插入所需的任何解码器。 但是,如果要选择特定的解码器,可以手动将解码器添加到拓扑。
下面是将解码器添加到拓扑的总体步骤。
- 查找解码器的 CLSID。
- 在拓扑中添加解码器的节点。
- 对于视频解码器,请启用 DirectX 视频加速。 音频解码器不需要此步骤。
查找解码器 CLSID
如果要使用特定的解码器,你可能已经知道解码器的 CLSID。 如果是这样,可以跳过此步骤。 否则,请使用 MFTEnum 函数在注册表中查找 CLSID。 此函数采用多个搜索条件作为输入。 若要查找解码器,只需 (主类型和子类型) 指定输入格式。 可以从流描述符获取这些内容,如以下代码所示。
// Returns the MFT decoder based on the major type GUID.
HRESULT GetDecoderCategory(const GUID& majorType, GUID *pCategory)
{
if (majorType == MFMediaType_Video)
{
*pCategory = MFT_CATEGORY_VIDEO_DECODER;
}
else if (majorType == MFMediaType_Audio)
{
*pCategory = MFT_CATEGORY_AUDIO_DECODER;
}
else
{
return MF_E_INVALIDMEDIATYPE;
}
return S_OK;
}
// Finds a decoder for a stream.
//
// If the stream is not compressed, pCLSID receives the value GUID_NULL.
HRESULT FindDecoderForStream(
IMFStreamDescriptor *pSD, // Stream descriptor for the stream.
CLSID *pCLSID // Receives the CLSID of the decoder.
)
{
BOOL bIsCompressed = FALSE;
GUID guidMajorType = GUID_NULL;
GUID guidSubtype = GUID_NULL;
GUID guidDecoderCategory = GUID_NULL;
CLSID *pDecoderCLSIDs = NULL; // Pointer to an array of CLISDs.
UINT32 cDecoderCLSIDs = NULL; // Size of the array.
IMFMediaTypeHandler *pHandler = NULL;
IMFMediaType *pMediaType = NULL;
// Find the media type for the stream.
HRESULT hr = pSD->GetMediaTypeHandler(&pHandler);
if (SUCCEEDED(hr))
{
hr = pHandler->GetCurrentMediaType(&pMediaType);
}
// Get the major type and subtype.
if (SUCCEEDED(hr))
{
hr = pMediaType->GetMajorType(&guidMajorType);
}
if (SUCCEEDED(hr))
{
hr = pMediaType->GetGUID(MF_MT_SUBTYPE, &guidSubtype);
}
// Check whether the stream is compressed.
if (SUCCEEDED(hr))
{
hr = pMediaType->IsCompressedFormat(&bIsCompressed);
}
#if (WINVER < _WIN32_WINNT_WIN7)
// Starting in Windows 7, you can connect an uncompressed video source
// directly to the EVR. In earlier versions of Media Foundation, this
// is not supported.
if (SUCCEEDED(hr))
{
if (!bIsCompressed && (guidMajorType == MFMediaType_Video))
{
hr = MF_E_INVALIDMEDIATYPE;
}
}
#endif
// If the stream is compressed, find a decoder.
if (SUCCEEDED(hr))
{
if (bIsCompressed)
{
// Select the decoder category from the major type (audio/video).
hr = GetDecoderCategory(guidMajorType, &guidDecoderCategory);
// Look for a decoder.
if (SUCCEEDED(hr))
{
MFT_REGISTER_TYPE_INFO tinfo;
tinfo.guidMajorType = guidMajorType;
tinfo.guidSubtype = guidSubtype;
hr = MFTEnum(
guidDecoderCategory,
0, // Reserved
&tinfo, // Input type to match. (Encoded type.)
NULL, // Output type to match. (Don't care.)
NULL, // Attributes to match. (None.)
&pDecoderCLSIDs, // Receives a pointer to an array of CLSIDs.
&cDecoderCLSIDs // Receives the size of the array.
);
}
// MFTEnum can return zero matches.
if (SUCCEEDED(hr) && (cDecoderCLSIDs == 0))
{
hr = MF_E_TOPO_CODEC_NOT_FOUND;
}
// Return the first CLSID in the list to the caller.
if (SUCCEEDED(hr))
{
*pCLSID = pDecoderCLSIDs[0];
}
}
else
{
// Uncompressed. A decoder is not required.
*pCLSID = GUID_NULL;
}
}
SafeRelease(&pHandler);
SafeRelease(&pMediaType);
CoTaskMemFree(pDecoderCLSIDs);
return hr;
}
有关流描述符的详细信息,请参阅 演示文稿描述符。
MFTEnum 函数返回指向 CLSID 数组的指针。 返回的数组的顺序是任意的。 在此示例中,该函数使用数组中的第一个 CLSID。 可以通过调用 MFTGetInfo 获取有关解码器的详细信息,包括解码器的友好名称。 另请注意, MFTEnum 可以成功,但返回空数组,因此检查在最后一个参数中返回的数组大小非常重要。
将解码器节点添加到拓扑
获取解码器的 CLSID 后,通过调用 MFCreateTopology 创建新的转换节点。 通过在节点上设置 MF_TOPONODE_TRANSFORM_OBJECTID 属性来指定 CLSID。 有关如何创建转换节点的示例,请参阅 “创建转换节点”。 然后,通过调用 IMFTopologyNode::ConnectOutput 将源节点连接到解码器节点,并将解码器节点连接到输出节点。
以下示例演示如何创建节点并连接它们。 该示例与主题AddBranchToPartialTopology
“创建播放拓扑”中所示的示例函数非常相似。 唯一的区别是此示例为解码器添加额外的节点。
HRESULT AddBranchToPartialTopologyWithDecoder(
IMFTopology *pTopology, // Topology.
IMFMediaSource *pSource, // Media source.
IMFPresentationDescriptor *pPD, // Presentation descriptor.
DWORD iStream, // Stream index.
HWND hVideoWnd // Window for video playback.
)
{
IMFStreamDescriptor *pSD = NULL;
IMFActivate *pSinkActivate = NULL;
IMFTopologyNode *pSourceNode = NULL;
IMFTopologyNode *pOutputNode = NULL;
IMFTopologyNode *pDecoderNode = NULL;
BOOL fSelected = FALSE;
CLSID clsidDecoder = GUID_NULL;
// Get the stream descriptor.
HRESULT hr = pPD->GetStreamDescriptorByIndex(iStream, &fSelected, &pSD);
if (FAILED(hr))
{
return hr;
}
if (fSelected)
{
// Add a source node for this stream.
hr = AddSourceNode(pTopology, pSource, pPD, pSD, &pSourceNode);
// Create the media sink activation object.
if (SUCCEEDED(hr))
{
hr = CreateMediaSinkActivate(pSD, hVideoWnd, &pSinkActivate);
}
// Create the output node for the renderer.
if (SUCCEEDED(hr))
{
hr = AddOutputNode(pTopology, pSinkActivate, 0, &pOutputNode);
}
// Find a decoder.
if (SUCCEEDED(hr))
{
hr = FindDecoderForStream(pSD, &clsidDecoder);
}
if (SUCCEEDED(hr))
{
if (clsidDecoder == GUID_NULL)
{
// No decoder is required.
// Connect the source node to the output node.
hr = pSourceNode->ConnectOutput(0, pOutputNode, 0);
}
else
{
// Add a decoder node.
hr = AddTransformNode(pTopology, clsidDecoder, &pDecoderNode);
// Connect the source node to the decoder node.
if (SUCCEEDED(hr))
{
hr = pSourceNode->ConnectOutput(0, pDecoderNode, 0);
}
// Connect the decoder node to the output node.
if (SUCCEEDED(hr))
{
hr = pDecoderNode->ConnectOutput(0, pOutputNode, 0);
}
}
}
// Mark this branch as not requiring a decoder.
if (SUCCEEDED(hr))
{
hr = pOutputNode->SetUINT32(
MF_TOPONODE_CONNECT_METHOD,
MF_CONNECT_ALLOW_CONVERTER
);
}
if (SUCCEEDED(hr))
{
hr = pDecoderNode->SetUINT32(
MF_TOPONODE_CONNECT_METHOD,
MF_CONNECT_ALLOW_CONVERTER
);
}
}
// else: If not selected, don't add the branch.
SafeRelease(&pSD);
SafeRelease(&pSinkActivate);
SafeRelease(&pSourceNode);
SafeRelease(&pOutputNode);
SafeRelease(&pDecoderNode);
return hr;
}
启用视频加速
将音频或视频解码器添加到拓扑中的下一步仅适用于视频解码器。 若要获得视频播放的最佳性能,如果视频解码器支持 DirectX 视频加速,则应启用 DirectX 视频加速 (DXVA) 。 通常,此步骤由拓扑加载程序执行,但如果手动将解码器添加到拓扑中,则必须自行执行此步骤。
作为此步骤的先决条件,拓扑中的所有输出节点都必须绑定到媒体接收器。 有关详细信息,请参阅 将输出节点绑定到媒体接收器。
首先,在托管 Direct3D 设备管理器的拓扑中找到对象。 为此,请从每个节点获取对象指针,并查询 IDirect3DDeviceManager9 服务的对象。 通常,增强的视频呈现器 (EVR) 充当此角色。 以下代码显示查找设备管理器的函数:
// Finds the node in the topology that provides the Direct3D device manager.
HRESULT FindDeviceManager(
IMFTopology *pTopology, // Topology to search.
IUnknown **ppDeviceManager, // Receives a pointer to the device manager.
IMFTopologyNode **ppNode // Receives a pointer to the node.
)
{
HRESULT hr = S_OK;
WORD cNodes = 0;
BOOL bFound = FALSE;
IMFTopologyNode *pNode = NULL;
IUnknown *pNodeObject = NULL;
IDirect3DDeviceManager9 *pD3DManager = NULL;
// Search all of the nodes in the topology.
hr = pTopology->GetNodeCount(&cNodes);
if (FAILED(hr))
{
return hr;
}
for (WORD i = 0; i < cNodes; i++)
{
// For each of the following calls, failure just means we
// did not find the node we're looking for, so keep looking.
hr = pTopology->GetNode(i, &pNode);
// Get the node's object pointer.
if (SUCCEEDED(hr))
{
hr = pNode->GetObject(&pNodeObject);
}
// Query the node object for the device manager service.
if (SUCCEEDED(hr))
{
hr = MFGetService(
pNodeObject,
MR_VIDEO_ACCELERATION_SERVICE,
IID_PPV_ARGS(&pD3DManager)
);
}
if (SUCCEEDED(hr))
{
// Found the right node. Return the pointers to the caller.
*ppDeviceManager = pD3DManager;
(*ppDeviceManager)->AddRef();
*ppNode = pNode;
(*ppNode)->AddRef();
bFound = TRUE;
break;
}
SafeRelease(&pNodeObject);
SafeRelease(&pD3DManager);
SafeRelease(&pNode);
} // End of for loop.
SafeRelease(&pNodeObject);
SafeRelease(&pD3DManager);
SafeRelease(&pNode);
return bFound ? S_OK : E_FAIL;
}
接下来,查找直接从包含 Direct3D 设备管理器的节点上游的转换节点。 从此转换节点获取 IMFTransform 指针。 IMFTransform 指针表示媒体基础转换 (MFT) 。 根据节点的创建方式,可能需要通过调用 CoCreateInstance 来创建 MFT,或者从激活对象激活 MFT。 以下代码处理所有各种情况:
// Returns the MFT for a transform node.
HRESULT GetTransformFromNode(
IMFTopologyNode *pNode,
IMFTransform **ppMFT
)
{
MF_TOPOLOGY_TYPE type = MF_TOPOLOGY_MAX;
IUnknown *pNodeObject = NULL;
IMFTransform *pMFT = NULL;
IMFActivate *pActivate = NULL;
IMFAttributes *pAttributes = NULL;
// Is this a transform node?
HRESULT hr = pNode->GetNodeType(&type);
if (FAILED(hr))
{
return hr;
}
if (type != MF_TOPOLOGY_TRANSFORM_NODE)
{
// Wrong node type.
return E_FAIL;
}
// Check whether the node has an object pointer.
hr = pNode->GetObject(&pNodeObject);
if (SUCCEEDED(hr))
{
// The object pointer should be one of the following:
// 1. Pointer to an MFT.
// 2. Pointer to an activation object.
// Is it an MFT? Query for IMFTransform.
hr = pNodeObject->QueryInterface(IID_IMFTransform, (void**)&pMFT);
if (FAILED(hr))
{
// It is not an MFT, so it should be an activation object.
hr = pNodeObject->QueryInterface(IID_PPV_ARGS(&pActivate));
// Use the activation object to create the MFT.
if (SUCCEEDED(hr))
{
hr = pActivate->ActivateObject(IID_PPV_ARGS(&pMFT));
}
// Replace the node's object pointer with the MFT.
if (SUCCEEDED(hr))
{
hr = pNode->SetObject(pMFT);
}
// If the activation object has the MF_ACTIVATE_MFT_LOCKED
// attribute, transfer this value to the
// MF_TOPONODE_MFT_LOCKED attribute on the node.
// However, don't fail if this attribute is not found.
if (SUCCEEDED(hr))
{
BOOL bLocked = MFGetAttributeUINT32(
pActivate, MF_ACTIVATE_MFT_LOCKED, FALSE);
hr = pNode->SetUINT32(MF_TOPONODE_LOCKED, bLocked);
}
}
}
else
{
GUID clsidMFT;
// The node does not have an object pointer. Look for a CLSID.
hr = pNode->GetGUID(MF_TOPONODE_TRANSFORM_OBJECTID, &clsidMFT);
// Create the MFT.
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(
clsidMFT, NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pMFT)
);
}
// If the MFT supports attributes, copy the node attributes to the
// MFT attribute store.
if (SUCCEEDED(hr))
{
if (SUCCEEDED(pMFT->GetAttributes(&pAttributes)))
{
// Copy from pNode to pAttributes.
hr = pNode->CopyAllItems(pAttributes);
}
}
// Set the object on the node.
if (SUCCEEDED(hr))
{
hr = pNode->SetObject(pMFT);
}
}
// Return the IMFTransform pointer to the caller.
if (SUCCEEDED(hr))
{
*ppMFT = pMFT;
(*ppMFT)->AddRef();
}
SafeRelease(&pNodeObject);
SafeRelease(&pMFT);
SafeRelease(&pActivate);
SafeRelease(&pAttributes);
return hr;
}
如果 MFT 具有值为 TRUE的 MF_SA_D3D_AWARE 属性,则表示 MFT 支持 DirectX 视频加速。 以下函数测试此属性:
// Returns TRUE is an MFT supports DirectX Video Acceleration.
BOOL IsTransformD3DAware(IMFTransform *pMFT)
{
BOOL bD3DAware = FALSE;
IMFAttributes *pAttributes = NULL;
HRESULT hr = pMFT->GetAttributes(&pAttributes);
if (SUCCEEDED(hr))
{
bD3DAware = MFGetAttributeUINT32(pAttributes, MF_SA_D3D_AWARE, FALSE);
pAttributes->Release();
}
return bD3DAware;
}
若要在此 MFT 上启用视频加速,请使用MFT_MESSAGE_SET_D3D_MANAGER消息调用 IMFTransform::P rocessMessage 。 还将转换节点上 的 MF_TOPONODE_D3DAWARE 属性设置为 TRUE 。 此属性通知管道视频加速已启用。 以下代码执行以下步骤:
// Enables or disables DirectX Video Acceleration in a topology.
HRESULT EnableVideoAcceleration(IMFTopology *pTopology, BOOL bEnable)
{
IMFTopologyNode *pD3DManagerNode = NULL;
IMFTopologyNode *pUpstreamNode = NULL;
IUnknown *pD3DManager = NULL;
IMFTransform *pMFT = NULL;
// Look for the node that supports the Direct3D Manager.
HRESULT hr = FindDeviceManager(pTopology, &pD3DManager, &pD3DManagerNode);
if (FAILED(hr))
{
// There is no Direct3D device manager in the topology.
// This is not a failure case.
return S_OK;
}
DWORD dwOutputIndex = 0;
// Get the node upstream from the device manager node.
hr = pD3DManagerNode->GetInput(0, &pUpstreamNode, &dwOutputIndex);
// Get the MFT from the upstream node.
if (SUCCEEDED(hr))
{
hr = GetTransformFromNode(pUpstreamNode, &pMFT);
}
// If the MFT is Direct3D-aware, notify the MFT of the device
// manager and mark the topology node as Direct3D-aware.
if (SUCCEEDED(hr))
{
if (IsTransformD3DAware(pMFT))
{
ULONG_PTR ptr = bEnable ? (ULONG_PTR)pD3DManager : NULL;
hr = pMFT->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, ptr);
// Mark the node.
if (SUCCEEDED(hr))
{
hr = pUpstreamNode->SetUINT32(MF_TOPONODE_D3DAWARE, bEnable);
}
}
}
SafeRelease(&pD3DManagerNode);
SafeRelease(&pUpstreamNode);
SafeRelease(&pD3DManager);
SafeRelease(&pMFT);
return hr;
}
有关 Media Foundation 中的 DXVA 的详细信息,请参阅 DirectX 视频加速 2.0。
相关主题