创建播放拓扑

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

创建拓扑

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

  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 播放媒体文件

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

媒体会话

拓扑