Поделиться через


Шаг 5. Обработка событий сеанса мультимедиа

В этом разделе описывается шаг 5 руководства Воспроизведение файлов мультимедиа с помощью Media Foundation. Полный код показан в разделе пример воспроизведения сеансов мультимедиа.

Дополнительные сведения об этом разделе см. в генераторах событий мультимедиа. В этом разделе содержатся следующие разделы:

Получение событий сеанса

Чтобы получить события из сеанса мультимедиа, объект CPlayer вызывает метод IMFMediaEventGenerator::BeginGetEvent, как показано на шаге 4. Создание сеанса мультимедиа. Этот метод асинхронный, то есть он сразу возвращается вызывающему. При наступлении следующего события сеанса медиа-сессия вызывает метод IMFAsyncCallback::Invoke объекта CPlayer.

Важно помнить, что вызоввызывается из рабочего потока, а не из потока приложения. Поэтому реализация Invoke должна быть потокобезопасной. Одним из подходов будет защита данных-членов с помощью критического раздела. Однако класс CPlayer показывает альтернативный подход:

  1. В методе Invoke объект CPlayer отправляет в приложение сообщение WM_APP_PLAYER_EVENT. Параметр сообщения — это указатель 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, чтобы получить интерфейс IMFVideoDisplayControl из расширенного отрисовщика видео (EVR). Этот интерфейс необходим для обработки перерисовки и изменения размера окна видео, также показанного в шаге 6: Управление воспроизведением.

Конец презентации

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