创建播放拓扑

本主题介绍如何为音频或视频播放创建拓扑。 对于基本播放,可以创建一个部分拓扑,在该拓扑中,源节点直接连接到输出节点。 无需为中间转换(例如解码器或颜色转换器)插入任何节点。 媒体会话将使用拓扑加载程序解析拓扑,拓扑加载程序将插入所需的转换。

创建拓扑

下面是从媒体源创建部分播放拓扑的总体步骤:

  1. 创建媒体源。 在大多数情况下,将使用源解析程序创建媒体源。 有关详细信息,请参阅 源解析程序
  2. 获取媒体源的演示文稿描述符。
  3. 创建空拓扑。
  4. 使用演示文稿描述符枚举流描述符。 对于每个流描述符:
    1. 获取流的主要媒体类型,例如音频或视频。
    2. 检查流当前是否处于选中状态。 ((可选)可以根据媒体类型选择或取消选择流。)
    3. 如果选择流,请根据流的媒体类型为媒体接收器创建激活对象。
    4. 为流添加源节点和媒体接收器的输出节点。
    5. 连接源节点到输出节点。

为了简化此过程,本主题中的示例代码组织成多个函数。 顶级函数命名 CreatePlaybackTopology。 它采用三个参数:

该函数返回指向 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;
}

此函数执行以下步骤:

  1. 调用 MFCreateTopology 以创建拓扑。 最初,拓扑不包含任何节点。
  2. 调用 IMFPresentationDescriptor::GetStreamDescriptorCount 以获取演示文稿中的流数。
  3. 对于每个流,将应用程序定义的 AddBranchToPartialTopology 函数调用到拓扑中的分支。 下一部分显示了此函数。

将流连接到媒体接收器

对于每个所选流,请添加源节点和输出节点,然后连接这两个节点。 源节点表示流。 输出节点表示 增强的视频呈现器 (EVR) 或 流式处理音频呈现器 (SAR) 。

AddBranchToPartialTopology 一个示例中所示的函数采用以下参数:

//  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;
}

该函数执行以下操作:

  1. 调用 IMFPresentationDescriptor::GetStreamDescriptorByIndex 并传入流索引。 此方法返回指向该流的流描述符的指针,以及指示是否选择流的布尔值。
  2. 如果未选择流,函数将退出并返回S_OK,因为应用程序不需要为流创建拓扑分支,除非已选择它。
  3. 如果选择了流,函数将完成拓扑分支,如下所示:
    1. 通过调用应用程序定义的 CreateMediaSinkActivate 函数为接收器创建激活对象。 下一部分显示了此函数。
    2. 将源节点添加到拓扑。 此步骤的代码显示在主题 “创建源节点”中。
    3. 将输出节点添加到拓扑。 此步骤的代码显示在“ 创建输出节点”主题中。
    4. 通过在源节点上调用 IMFTopologyNode::ConnectOutput 来连接这两个节点。 通过连接节点,应用程序指示上游节点应将数据传递到下游节点。 源节点有一个输出,一个输出节点有一个输入,因此两个流索引均为零。

更高级的应用程序可以选择或取消选择流,而不是使用源的默认配置。 源可以有多个流,并且其中任何一个流都可能默认处于选中状态。 媒体源的演示文稿描述符具有一组默认的流选择。 在具有单个音频流和视频流的简单视频文件中,媒体源通常默认选择这两个流。 但是,文件可能有多个不同语言的音频流,或者多个以不同比特率编码的视频流。 在这种情况下,默认情况下,某些流将取消选中。 应用程序可以通过在演示文稿描述符上调用 IMFPresentationDescriptor::SelectStreamIMFPresentationDescriptor::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;
}

此函数执行以下步骤:

  1. 在流描述符上调用 IMFStreamDescriptor::GetMediaTypeHandler 。 此方法返回 IMFMediaTypeHandler 接口指针。

  2. 调用 IMFMediaTypeHandler::GetMajorType。 此方法返回流的主要类型 GUID。

  3. 如果流类型为音频,函数将调用 MFCreateAudioRendererActivate 来创建音频呈现器激活对象。 如果流类型为视频,函数将调用 MFCreateVideoRendererActivate 以创建视频呈现器激活对象。 这两个函数都返回指向 IMFActivate 接口的指针。 此指针用于初始化接收器的输出节点,如前所述。

对于任何其他流类型,此示例返回错误代码。 或者,只需取消选择流。

后续步骤

若要一次播放一个媒体文件,请通过调用 IMFMediaSession::SetTopology 在媒体会话上排队拓扑。 媒体会话将使用拓扑加载程序解析拓扑。 有关完整示例,请参阅 如何使用 Media Foundation 播放媒体文件

如何播放未受保护的媒体文件

媒体会话

拓扑