如何创建播放列表

本主题介绍如何使用序列源播放一系列文件。

概述

若要按顺序播放媒体文件,应用程序必须按顺序添加拓扑以创建播放列表,并在媒体会话中将这些拓扑排队以供播放。

在媒体会话开始播放当前拓扑之前,序列器源通过初始化和加载下一个拓扑来确保无缝播放。 这使应用程序能够在需要时快速启动下一个拓扑。

媒体会话负责将数据馈送到接收器,并在序列源中播放拓扑。 此外,媒体会话还管理段的演示时间。

有关 Sequencer 源如何管理拓扑的详细信息,请参阅 关于 Sequencer 源

本演练包含以下步骤:

  1. 先决条件
  2. 初始化媒体基础
  3. 创建媒体基础对象
  4. 创建媒体源
  5. 创建部分拓扑
  6. 将拓扑添加到 Sequencer 源
  7. 在媒体会话上设置第一个拓扑
  8. 在媒体会话上排队下一个拓扑
  9. 释放 Sequencer 源

显示本主题的代码示例摘自主题 Sequencer Source Example Code,其中包含完整的示例代码。

先决条件

在开始本演练之前,请先熟悉以下媒体基础概念:

另请阅读 如何使用 Media Foundation 播放媒体文件,因为此处的示例代码 shwon 扩展了该主题中的代码。

初始化媒体基础

在使用任何 Media Foundation 接口或方法之前,请通过调用 MFStartup 函数初始化 Media Foundation。 有关详细信息,请参阅 初始化媒体基础

    hr = MFStartup(MF_VERSION);

创建媒体基础对象

接下来,创建以下 Media Foundation 对象:

  • 媒体会话。 此对象公开 IMFMediaSession 接口,该接口提供播放、暂停和停止当前拓扑的方法。
  • Sequencer 源。 此对象公开 IMFSequencerSource 接口,该接口提供按顺序添加、更新和删除拓扑的方法。
  1. 调用 MFCreateMediaSession 函数以创建媒体会话。
  2. 调用 IMFMediaEventQueue::BeginGetEvent 以从媒体会话请求第一个事件。
  3. 调用 MFCreateSequencerSource 函数来创建 sequencer 源。

以下代码创建媒体会话并请求第一个事件:

//  Create a new instance of the media session.
HRESULT CPlayer::CreateSession()
{
    // Close the old session, if any.
    HRESULT hr = CloseSession();
    if (FAILED(hr))
    {
        goto done;
    }

    assert(m_state == Closed);

    // Create the media session.
    hr = MFCreateMediaSession(NULL, &m_pSession);
    if (FAILED(hr))
    {
        goto done;
    }

    // Start pulling events from the media session
    hr = m_pSession->BeginGetEvent((IMFAsyncCallback*)this, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    m_state = Ready;

done:
    return hr;
}

创建媒体源

接下来,为第一个播放列表段创建媒体源。 使用 源冲突解决程序 根据 URL 创建媒体源。 为此,请调用 MFCreateSourceResolver 函数来创建源解析程序,然后调用 IMFSourceResolver::CreateObjectFromURL 方法来创建媒体源。

有关媒体源的信息,请参阅 媒体源

创建部分拓扑

sequencer 源中的每个段都有自己的部分拓扑。 接下来,为媒体源创建部分拓扑。 对于部分拓扑,拓扑源节点直接连接到输出节点,而无需指定任何中间转换。 媒体会话使用拓扑加载程序对象来解析拓扑。 解析拓扑后,将添加所需的解码器和其他转换节点。 sequencer 源还可以包含完整的拓扑。

若要创建拓扑对象,请使用 MFCreateTopology 函数,然后使用 IMFTopologyNode 接口创建流节点。

有关使用这些编程元素创建拓扑的完整说明,请参阅 创建播放拓扑

应用程序可以通过配置源节点来播放本机源的选定部分。 为此,请在 MF_TOPOLOGY_SOURCESTREAM_NODE 拓扑节点上设置 MF_TOPONODE_MEDIASTART 属性和MF_TOPONODE_MEDIASTOP 属性。 将媒体启动时间和媒体停止时间相对于本机源的开始时间指定为 UINT64 类型。

将拓扑添加到 Sequencer 源

接下来,将创建的部分拓扑添加到 sequencer 源。 每个序列元素(称为 )都分配有一个 MFSequencerElementId 标识符。 有关 Sequencer 源如何管理拓扑的详细信息,请参阅 关于 Sequencer 源

将所有拓扑添加到 sequencer 源后,应用程序必须标记序列中的最后一个段,以结束在管道中的播放。 如果没有此标志,排序程序源需要添加更多拓扑。

  1. 调用 IMFSequencerSource::AppendTopology 方法,将特定拓扑添加到 sequencer 源。

        hr = m_pSequencerSource->AppendTopology(
            pTopology, 
            SequencerTopologyFlags_Last, 
            &SegmentId
            );
    

    AppendTopology 将指定的拓扑添加到序列。 此方法返回 pdwId 参数中的段标识符。

    如果拓扑是排序程序源中的最后一个拓扑,请在 dwFlags 参数中传递SequencerTopologyFlags_Last。 此值在 MFSequencerTopologyFlags 枚举中定义。

  2. 调用 IMFSequencerSource::UpdateTopologyFlags 以更新与输入列表中的段标识符关联的拓扑的标志。 在这种情况下,调用指示指定的段是排序器中的最后一段。 (如果在 AppendTopology 调用中指定了最后一个拓扑,则此调用是可选的)

        BOOL bFirstSegment = (NumSegments() == 0);
    
        if (!bFirstSegment)
        {
            // Remove the "last segment" flag from the last segment.
            hr = m_pSequencerSource->UpdateTopologyFlags(LastSegment(), 0);
            if (FAILED(hr))
            {
                goto done;
            }
        }
    

应用程序可以通过调用 IMFSequencerSource::UpdateTopology 并在 pTopology 中传递新拓扑,将段的拓扑替换为另一个拓扑。 如果新拓扑中存在新的本机源,则会将源添加到源缓存中。 预滚动列表也会刷新。

在媒体会话上设置第一个拓扑

接下来,在媒体会话上将序列源中的第一个拓扑排队。 若要从 sequencer 源获取第一个拓扑,应用程序必须调用 IMFMediaSourceTopologyProvider::GetMediaSourceTopology 方法。 此方法返回部分拓扑,该拓扑由媒体会话解析。

有关部分拓扑的信息,请参阅 关于拓扑

  1. 检索序列源的第一个拓扑的本机媒体源。

  2. 通过调用 IMFMediaSource::CreatePresentationDescriptor 方法为媒体源创建演示文稿描述符。

  3. 通过调用 IMFMediaSourceTopologyProvider::GetMediaSourceTopology 方法检索演示文稿的关联拓扑。

  4. 通过调用 IMFMediaSession::SetTopology 在媒体会话上设置第一个拓扑。

    调用 SetTopology ,并将 dwSetTopologyFlags 参数设置为 NULL。 这会指示媒体会话在当前拓扑完成时启动指定的拓扑。 因为在这种情况下,指定的拓扑是第一个拓扑,没有当前演示文稿,因此媒体会话会立即启动新演示文稿。

    NULL 值还指示媒体会话必须解析拓扑,因为拓扑提供程序返回的拓扑始终是部分拓扑。

// Queues the next topology on the session.

HRESULT CPlaylist::QueueNextSegment(IMFPresentationDescriptor *pPD)
{
    IMFMediaSourceTopologyProvider *pTopoProvider = NULL;
    IMFTopology *pTopology = NULL;

    //Get the topology for the presentation descriptor
    HRESULT hr = m_pSequencerSource->QueryInterface(IID_PPV_ARGS(&pTopoProvider));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pTopoProvider->GetMediaSourceTopology(pPD, &pTopology);
    if (FAILED(hr))
    {
        goto done;
    }

    //Set the topology on the media session
    m_pSession->SetTopology(NULL, pTopology);

done:
    SafeRelease(&pTopoProvider);
    SafeRelease(&pTopology);
    return hr;
}

在媒体会话上排队下一个拓扑

接下来,应用程序需要处理 MENewPresentation 事件。

当媒体会话开始播放其后有另一段段的段时,Sequencer 源将引发 MENewPresentation 。 此事件通过为预卷列表中的下一段提供表示描述符,通知应用程序序列源中的下一个拓扑。 应用程序必须使用拓扑提供程序检索关联的拓扑,并在媒体会话中将其排队。 然后,sequencer 源将预生成此拓扑,以确保在演示文稿之间无缝转换。

当应用程序跨段查找时,当 sequencer 源刷新预滚动列表并设置正确的拓扑时,应用程序会收到多个 MENewPresentation 事件。 应用程序必须处理每个事件,并在媒体会话上对事件数据中返回的拓扑进行排队。 有关跳过段的信息,请参阅 使用 Sequencer 源

有关获取 sequencer 源通知的信息,请参阅 Sequencer 源事件

  1. MENewPresentation 事件处理程序中,从事件数据中检索下一段的表示描述符。

  2. 通过调用 IMFMediaSourceTopologyProvider::GetMediaSourceTopology 方法获取演示文稿的关联拓扑。

  3. 通过调用 IMFMediaSession::SetTopology 方法在媒体会话上设置拓扑。

    当当前演示文稿完成时,媒体会话将启动新演示文稿。

HRESULT CPlaylist::OnNewPresentation(IMFMediaEvent *pEvent)
{
    IMFPresentationDescriptor *pPD = NULL;

    HRESULT hr = GetEventObject(pEvent, &pPD);

    if (SUCCEEDED(hr))
    {
        // Queue the next segment on the media session
        hr = QueueNextSegment(pPD);
    }

    SafeRelease(&pPD);
    return hr;
}

释放 Sequencer 源

最后,关闭 Sequencer 源。 为此,请在排序器源上调用 IMFMediaSource::Shutdown 方法。 此调用将关闭排序器源中的所有基础本机媒体源。

释放 Sequencer 源后,应用程序应按该顺序调用 IMFMediaSession::CloseIMFMediaSession::Shutdown 来关闭和关闭媒体会话。

为了避免内存泄漏,应用程序必须在不再需要媒体基础接口时释放指向这些接口的指针。

后续步骤

本演练演示了如何使用 Sequencer 源创建基本播放列表。 创建播放列表后,可能需要添加高级功能,例如跳过段、更改播放状态以及在段内查找。 以下列表提供了指向相关主题的链接:

Sequencer 源