다음을 통해 공유


5단계: 미디어 세션 이벤트 처리

이 항목은 Media Foundation을 사용하여 미디어 파일을 재생하는 방법 자습서의 5단계입니다. 전체 코드는 미디어 세션 재생 예제 항목에 나와 있습니다.

이 항목에 대한 배경 정보는 미디어 이벤트 생성기를 참조하세요. 이 항목에는 다음과 같은 섹션이 포함되어 있습니다.

세션 이벤트 가져오기

미디어 세션에서 이벤트를 가져오기 위해 CPlayer 개체는 4단계: 미디어 세션 만들기에 표시된 대로 IMFMediaEventGenerator::BeginGetEvent 메서드를 호출합니다. 이 메서드는 비동기적입니다. 즉, 호출자에게 즉시 반환됩니다. 다음 세션 이벤트가 발생하면 미디어 세션은 CPlayer 개체의 IMFAsyncCallback::Invoke 메서드를 호출합니다.

Invoke는 애플리케이션 스레드가 아닌 작업자 스레드에서 호출됩니다. 따라서 Invoke 구현은 다중 스레드로부터 안전해야 합니다. 한 가지 방법은 중요한 섹션을 사용하여 멤버 데이터를 보호하는 것입니다. 그러나 클래스는 CPlayer 대체 방법을 보여 줍니다.

  1. Invoke 메서드에서 CPlayer 개체는 애플리케이션에 WM_APP_PLAYER_EVENT 메시지를 게시합니다. 메시지 매개 변수는 IMFMediaEvent 포인터입니다.
  2. 애플리케이션은 WM_APP_PLAYER_EVENT 메시지를 받습니다.
  3. 애플리케이션은 메서드를 호출하여 CPlayer::HandleEventIMFMediaEvent 포인터를 전달합니다.
  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 메시지를 게시합니다.

다음 코드는 애플리케이션이 WM_APP_PLAYER_EVENT 메시지를 받을 때 호출되는 HandleEvent 메서드를 보여 줍니다.

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::StartPlayback6단계: 컨트롤 재생에 표시됩니다.

이 예제에서는 MFGetService를 호출하여 EVR(Enhanced Video Renderer)에서 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 포인터입니다.

대부분의 경우 이 이벤트는 전혀 수신되지 않습니다. 이 경우 3단계: 미디어 파일 열기에 표시된 것처럼 IMFPresentationDescriptor 포인터를 사용하여 새 재생 토폴로지를 만듭니다. 그런 다음 미디어 세션에서 새 토폴로지를 큐에 대기합니다.

//  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을 사용하여 미디어 파일을 재생하는 방법