如何创建播放列表

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

概述

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

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

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

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

本演练包含以下步骤:

  1. 先决条件
  2. 初始化媒体基础架构
  3. 创建媒体基础对象
  4. 创建媒体源
  5. 创建部分拓扑
  6. 将拓扑添加到序列发生器源
  7. 在媒体会话上设置第一个拓扑
  8. 在媒体会话中排定下一个拓扑结构
  9. 发布 Sequencer 源代码

本主题所示的代码示例是主题 Sequencer 源代码的摘录,其中包含完整的示例代码。

先决条件

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

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

初始化媒体基础

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

    hr = MFStartup(MF_VERSION);

创建媒体基础对象

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

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

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

//  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 方法来创建媒体源。

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

创建部分拓扑

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

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

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

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

将拓扑添加到 Sequencer 源

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

将所有拓扑添加到序列器源后,应用程序必须标记序列中的最后一段以结束管道中的播放。 没有这个标志,序列器源会期望增加更多的拓扑。

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

        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 中传递新拓扑,将段的拓扑替换为另一个拓扑。 如果新拓扑中存在新的本机源,则源将添加到源缓存中。 预注册列表也会刷新。

在媒体会话中设置第一个拓扑结构

接下来,在媒体会话上的序列源中对第一个拓扑进行排队。 若要从序列器源获取第一个拓扑,应用程序必须调用 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 源会刷新预注册列表并设置正确的拓扑,应用程序因此会收到多个 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;
}

发布序列器源代码

最后,关闭序列器源。 为此,请对序列器源调用 IMFMediaSource::Shutdown 方法。 此调用关闭序列器源中的所有原生媒体源。

发布序列器源后,应用程序应按顺序调用 IMFMediaSession::CloseIMFMediaSession::Shutdown 来关闭并关闭媒体会话。

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

后续步骤

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

Sequencer 源