步骤 5:处理媒体会话事件

本主题是教程 如何使用 Media Foundation 播放媒体文件的步骤 5。 完整的代码显示在主题 媒体会话播放示例中。

有关本主题的背景信息,请阅读 媒体事件生成器。 本主题包含以下各节:

获取会话事件

为了从媒体会话获取事件,CPlayer 对象调用 IMFMediaEventGenerator::BeginGetEvent 方法,如 步骤 4:创建媒体会话所示。 此方法是异步的,这意味着它会立即返回到调用方。 发生下一个会话事件时,媒体会话将调用 CPlayer 对象的 IMFAsyncCallback::Invoke 方法。

请务必记住, Invoke 是从工作线程调用的,而不是从应用程序线程调用的。 因此, Invoke 的实现必须是多线程安全的。 一种方法是使用关键部分保护成员数据。 但是, CPlayer 类显示了一种替代方法:

  1. Invoke 方法中,CPlayer 对象将 WM_APP_PLAYER_EVENT 消息发布到应用程序。 message 参数是 IMFMediaEvent 指针。
  2. 应用程序接收 WM_APP_PLAYER_EVENT 消息。
  3. 应用程序调用 CPlayer::HandleEvent 方法,传入 IMFMediaEvent 指针。
  4. 方法 HandleEvent 响应 事件。

以下代码显示了 Invoke 方法:

//  Callback for the asynchronous BeginGetEvent method.

HRESULT CPlayer::Invoke(IMFAsyncResult *pResult)
{
    MediaEventType meType = MEUnknown;  // Event type

    IMFMediaEvent *pEvent = NULL;

    // Get the event from the event queue.
    HRESULT hr = m_pSession->EndGetEvent(pResult, &pEvent);
    if (FAILED(hr))
    {
        goto done;
    }

    // Get the event type. 
    hr = pEvent->GetType(&meType);
    if (FAILED(hr))
    {
        goto done;
    }

    if (meType == MESessionClosed)
    {
        // The session was closed. 
        // The application is waiting on the m_hCloseEvent event handle. 
        SetEvent(m_hCloseEvent);
    }
    else
    {
        // For all other events, get the next event in the queue.
        hr = m_pSession->BeginGetEvent(this, NULL);
        if (FAILED(hr))
        {
            goto done;
        }
    }

    // Check the application state. 
        
    // If a call to IMFMediaSession::Close is pending, it means the 
    // application is waiting on the m_hCloseEvent event and
    // the application's message loop is blocked. 

    // Otherwise, post a private window message to the application. 

    if (m_state != Closing)
    {
        // Leave a reference count on the event.
        pEvent->AddRef();

        PostMessage(m_hwndEvent, WM_APP_PLAYER_EVENT, 
            (WPARAM)pEvent, (LPARAM)meType);
    }

done:
    SafeRelease(&pEvent);
    return S_OK;
}

Invoke 方法执行以下步骤:

  1. 调用 IMFMediaEventGenerator::EndGetEvent 以获取事件。 此方法返回指向 IMFMediaEvent 接口的指针。
  2. 调用 IMFMediaEvent::GetType 以获取事件代码。
  3. 如果事件代码为 MESessionClosed,则调用 SetEvent 以设置 m_hCloseEvent 事件。 步骤 7:关闭媒体会话以及代码注释中解释了此步骤的原因。
  4. 对于所有其他事件代码,请调用 IMFMediaEventGenerator::BeginGetEvent 以请求下一个事件。
  5. WM_APP_PLAYER_EVENT 消息发布到窗口。

以下代码显示了 HandleEvent 方法,该方法在应用程序收到 WM_APP_PLAYER_EVENT 消息时调用:

HRESULT CPlayer::HandleEvent(UINT_PTR pEventPtr)
{
    HRESULT hrStatus = S_OK;            
    MediaEventType meType = MEUnknown;  

    IMFMediaEvent *pEvent = (IMFMediaEvent*)pEventPtr;

    if (pEvent == NULL)
    {
        return E_POINTER;
    }

    // Get the event type.
    HRESULT hr = pEvent->GetType(&meType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Get the event status. If the operation that triggered the event 
    // did not succeed, the status is a failure code.
    hr = pEvent->GetStatus(&hrStatus);

    // Check if the async operation succeeded.
    if (SUCCEEDED(hr) && FAILED(hrStatus)) 
    {
        hr = hrStatus;
    }
    if (FAILED(hr))
    {
        goto done;
    }

    switch(meType)
    {
    case MESessionTopologyStatus:
        hr = OnTopologyStatus(pEvent);
        break;

    case MEEndOfPresentation:
        hr = OnPresentationEnded(pEvent);
        break;

    case MENewPresentation:
        hr = OnNewPresentation(pEvent);
        break;

    default:
        hr = OnSessionEvent(pEvent, meType);
        break;
    }

done:
    SafeRelease(&pEvent);
    return hr;
}

此方法调用 IMFMediaEvent::GetType 以获取事件类型,并调用 IMFMediaEvent::GetStatus 以获取与事件关联的失败代码的成功。 下一个操作取决于事件代码。

MESessionTopologyStatus

MESessionTopologyStatus 事件发出拓扑状态更改的信号。 事件对象的 MF_EVENT_TOPOLOGY_STATUS 属性包含状态。 对于此示例,唯一感兴趣的值是 MF_TOPOSTATUS_READY,这表示已准备好开始播放。

HRESULT CPlayer::OnTopologyStatus(IMFMediaEvent *pEvent)
{
    UINT32 status; 

    HRESULT hr = pEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status);
    if (SUCCEEDED(hr) && (status == MF_TOPOSTATUS_READY))
    {
        SafeRelease(&m_pVideoDisplay);

        // Get the IMFVideoDisplayControl interface from EVR. This call is
        // expected to fail if the media file does not have a video stream.

        (void)MFGetService(m_pSession, MR_VIDEO_RENDER_SERVICE, 
            IID_PPV_ARGS(&m_pVideoDisplay));

        hr = StartPlayback();
    }
    return hr;
}

方法 CPlayer::StartPlayback 显示在 步骤 6:控制播放中。

此示例还调用 MFGetService,以从增强的视频呈现器 (EVR) 获取 IMFVideoDisplayControl 接口。 需要此接口来处理重新绘制和调整视频窗口的大小,如 步骤 6:控制播放所示。

MEEndOfPresentation

MEEndOfPresentation 事件指示播放已到达文件末尾。 媒体会话自动切换回停止状态。

//  Handler for MEEndOfPresentation event.
HRESULT CPlayer::OnPresentationEnded(IMFMediaEvent *pEvent)
{
    // The session puts itself into the stopped state automatically.
    m_state = Stopped;
    return S_OK;
}

MENewPresentation

MENewPresentation 事件表示新演示文稿的开始。 事件数据是用于新演示文稿 的 IMFPresentationDescriptor 指针。

在许多情况下,根本不会收到此事件。 如果这样做,请使用 IMFPresentationDescriptor 指针创建新的播放拓扑,如 步骤 3:打开媒体文件所示。 然后在媒体会话中对新拓扑进行排队。

//  Handler for MENewPresentation event.
//
//  This event is sent if the media source has a new presentation, which 
//  requires a new topology. 

HRESULT CPlayer::OnNewPresentation(IMFMediaEvent *pEvent)
{
    IMFPresentationDescriptor *pPD = NULL;
    IMFTopology *pTopology = NULL;

    // Get the presentation descriptor from the event.
    HRESULT hr = GetEventObject(pEvent, &pPD);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create a partial topology.
    hr = CreatePlaybackTopology(m_pSource, pPD,  m_hwndVideo,&pTopology);
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the topology on the media session.
    hr = m_pSession->SetTopology(0, pTopology);
    if (FAILED(hr))
    {
        goto done;
    }

    m_state = OpenPending;

done:
    SafeRelease(&pTopology);
    SafeRelease(&pPD);
    return S_OK;
}

下一 步:步骤 6:控制播放

音频/视频播放

如何使用 Media Foundation 播放媒体文件