创建播放拓扑
本主题介绍如何为音频或视频播放创建拓扑。 对于基本播放,可以创建一个部分拓扑,其中源节点直接连接到输出节点。 无需为中间转换插入任何节点,例如解码器或颜色转换器。 媒体会话将使用拓扑加载程序解析拓扑,拓扑加载程序将插入所需的转换。
创建拓扑
下面是从媒体源创建部分播放拓扑的总体步骤:
- 创建媒体源。 在大多数情况下,将使用源解析程序创建媒体源。 有关详细信息,请参阅 源解析程序。
- 获取媒体源的演示文稿描述符。
- 创建空拓扑。
- 使用演示文稿描述符枚举流描述符。 对于每个流描述符:
- 获取流的主要媒体类型,例如音频或视频。
- 检查当前是否选择了流。 (可以选择或取消选择流,具体取决于媒体类型。)
- 如果选择流,请根据流的媒体类型为媒体接收器创建激活对象。
- 为流添加源节点,为媒体接收器添加输出节点。
- 将源节点连接到输出节点。
为了使此过程更易于执行,本主题中的示例代码分为多个函数。 顶级函数名为 CreatePlaybackTopology
。 它需要三个参数:
- 指向媒体源的 IMFMediaSource 接口的指针。
- 指向表示描述 符的 IMFPresentationDescriptor 接口的指针。 通过调用 IMFMediaSource::CreatePresentationDescriptor 获取此指针。 对于具有多个演示文稿的源,后续演示文稿的演示文稿描述符在 MENewPresentation 事件中传递。
- 应用程序窗口的句柄。 如果源具有视频流,则视频将显示在此窗口中。
函数返回指向 ppTopology 参数中部分播放拓扑的指针。
// Create a playback topology from a media source.
HRESULT CreatePlaybackTopology(
IMFMediaSource *pSource, // Media source.
IMFPresentationDescriptor *pPD, // Presentation descriptor.
HWND hVideoWnd, // Video window.
IMFTopology **ppTopology) // Receives a pointer to the topology.
{
IMFTopology *pTopology = NULL;
DWORD cSourceStreams = 0;
// Create a new topology.
HRESULT hr = MFCreateTopology(&pTopology);
if (FAILED(hr))
{
goto done;
}
// Get the number of streams in the media source.
hr = pPD->GetStreamDescriptorCount(&cSourceStreams);
if (FAILED(hr))
{
goto done;
}
// For each stream, create the topology nodes and add them to the topology.
for (DWORD i = 0; i < cSourceStreams; i++)
{
hr = AddBranchToPartialTopology(pTopology, pSource, pPD, i, hVideoWnd);
if (FAILED(hr))
{
goto done;
}
}
// Return the IMFTopology pointer to the caller.
*ppTopology = pTopology;
(*ppTopology)->AddRef();
done:
SafeRelease(&pTopology);
return hr;
}
此函数执行以下步骤:
- 调用 MFCreateTopology 来创建拓扑。 最初,拓扑不包含任何节点。
- 调用 IMFPresentationDescriptor::GetStreamDescriptorCount 以获取演示文稿中的流数。
- 对于每个流,将应用程序定义的
AddBranchToPartialTopology
函数调用拓扑中的分支。 下一节中将介绍此函数。
将流连接到媒体接收器
对于每个所选流,添加一个源节点和一个输出节点,然后连接这两个节点。 源节点表示流。 输出节点表示 增强的视频呈现器 (EVR) 或 流音频呈现器 (SAR) 。
下 AddBranchToPartialTopology
一个示例中所示的 函数采用以下参数:
- 指向拓扑的 IMFTopology 接口的指针。
- 指向媒体源的 IMFMediaSource 接口的指针。
- 指向表示描述 符的 IMFPresentationDescriptor 接口的指针。
- 流的从零开始的索引。
- 视频窗口的句柄。 此句柄仅用于视频流。
// Add a topology branch for one stream.
//
// For each stream, this function does the following:
//
// 1. Creates a source node associated with the stream.
// 2. Creates an output node for the renderer.
// 3. Connects the two nodes.
//
// The media session will add any decoders that are needed.
HRESULT AddBranchToPartialTopology(
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;
BOOL fSelected = FALSE;
HRESULT hr = pPD->GetStreamDescriptorByIndex(iStream, &fSelected, &pSD);
if (FAILED(hr))
{
goto done;
}
if (fSelected)
{
// Create the media sink activation object.
hr = CreateMediaSinkActivate(pSD, hVideoWnd, &pSinkActivate);
if (FAILED(hr))
{
goto done;
}
// Add a source node for this stream.
hr = AddSourceNode(pTopology, pSource, pPD, pSD, &pSourceNode);
if (FAILED(hr))
{
goto done;
}
// Create the output node for the renderer.
hr = AddOutputNode(pTopology, pSinkActivate, 0, &pOutputNode);
if (FAILED(hr))
{
goto done;
}
// Connect the source node to the output node.
hr = pSourceNode->ConnectOutput(0, pOutputNode, 0);
}
// else: If not selected, don't add the branch.
done:
SafeRelease(&pSD);
SafeRelease(&pSinkActivate);
SafeRelease(&pSourceNode);
SafeRelease(&pOutputNode);
return hr;
}
函数执行以下操作:
- 调用 IMFPresentationDescriptor::GetStreamDescriptorByIndex 并传入流索引。 此方法返回指向该流的流描述符的指针,以及指示是否选择流的布尔值。
- 如果未选择流,该函数将退出并返回S_OK,因为应用程序不需要为流创建拓扑分支,除非选择它。
- 如果选择流,函数将按如下所示完成拓扑分支:
- 通过调用应用程序定义的 CreateMediaSinkActivate 函数,为接收器创建激活对象。 下一节中将介绍此函数。
- 将源节点添加到拓扑。 本主题 “创建源节点”中显示了此步骤的代码。
- 将输出节点添加到拓扑。 本主题 “创建输出节点”中显示了此步骤的代码。
- 通过在源节点上调用 IMFTopologyNode::ConnectOutput 来连接这两个节点。 通过连接节点,应用程序指示上游节点应将数据传递到下游节点。 一个源节点有一个输出,一个输出节点有一个输入,因此两个流索引都是零。
更高级的应用程序可以选择或取消选择流,而不是使用源的默认配置。 一个源可以有多个流,并且其中任何一个流都可以在默认情况下被选中。 媒体源的演示文稿描述符具有一组默认的流选择。 在具有单个音频流和视频流的简单视频文件中,媒体源通常会默认选择这两个流。 但是,一个文件可能有多个不同语言的音频流,或者以不同的比特率编码的多个视频流。 在这种情况下,默认情况下将取消选择某些流。 应用程序可以通过在演示文稿描述符上调用 IMFPresentationDescriptor::SelectStream 和 IMFPresentationDescriptor::D eselectStream 来更改选择。
创建媒体接收器
下一个函数为 EVR 或 SAR 媒体接收器创建激活对象。
// Create an activation object for a renderer, based on the stream media type.
HRESULT CreateMediaSinkActivate(
IMFStreamDescriptor *pSourceSD, // Pointer to the stream descriptor.
HWND hVideoWindow, // Handle to the video clipping window.
IMFActivate **ppActivate
)
{
IMFMediaTypeHandler *pHandler = NULL;
IMFActivate *pActivate = NULL;
// Get the media type handler for the stream.
HRESULT hr = pSourceSD->GetMediaTypeHandler(&pHandler);
if (FAILED(hr))
{
goto done;
}
// Get the major media type.
GUID guidMajorType;
hr = pHandler->GetMajorType(&guidMajorType);
if (FAILED(hr))
{
goto done;
}
// Create an IMFActivate object for the renderer, based on the media type.
if (MFMediaType_Audio == guidMajorType)
{
// Create the audio renderer.
hr = MFCreateAudioRendererActivate(&pActivate);
}
else if (MFMediaType_Video == guidMajorType)
{
// Create the video renderer.
hr = MFCreateVideoRendererActivate(hVideoWindow, &pActivate);
}
else
{
// Unknown stream type.
hr = E_FAIL;
// Optionally, you could deselect this stream instead of failing.
}
if (FAILED(hr))
{
goto done;
}
// Return IMFActivate pointer to caller.
*ppActivate = pActivate;
(*ppActivate)->AddRef();
done:
SafeRelease(&pHandler);
SafeRelease(&pActivate);
return hr;
}
此函数执行以下步骤:
在流描述符上调用 IMFStreamDescriptor::GetMediaTypeHandler 。 此方法返回 IMFMediaTypeHandler 接口指针。
调用 IMFMediaTypeHandler::GetMajorType。 此方法返回流的主类型 GUID。
如果流类型是音频,则函数调用 MFCreateAudioRendererActivate 来创建音频呈现器激活对象。 如果流类型为视频,则函数调用 MFCreateVideoRendererActivate 来创建视频呈现器激活对象。 这两个函数都返回指向 IMFActivate 接口的指针。 此指针用于初始化接收器的输出节点,如前所示。
对于任何其他流类型,此示例返回错误代码。 或者,只需取消选择流即可。
后续步骤
若要一次播放一个媒体文件,请通过调用 IMFMediaSession::SetTopology 在媒体会话上将拓扑排队。 媒体会话将使用拓扑加载程序来解析拓扑。 有关完整示例,请参阅 如何使用 Media Foundation 播放媒体文件。
相关主题