MFPlay 자습서: 비디오 재생

[MFPlay는 요구 사항 섹션에 지정된 운영 체제에서 사용할 수 있습니다. 이후 버전에서는 변경되거나 제공되지 않을 수 있습니다. ]

이 자습서에서는 MFPlay를 사용하여 비디오를 재생하는 전체 애플리케이션을 제공합니다. SimplePlay SDK 샘플을 기반으로 합니다.

이 자습서에는 다음 섹션이 포함되어 있습니다.

MFPlay API에 대한 자세한 내용은 MFPlay를 사용하여 시작 참조하세요.

요구 사항

MFPlay에는 Windows 7이 필요합니다.

헤더 및 라이브러리 파일

프로젝트에 다음 헤더 파일을 포함합니다.

#define WINVER _WIN32_WINNT_WIN7

#include <new>
#include <windows.h>
#include <windowsx.h>
#include <mfplay.h>
#include <mferror.h>
#include <shobjidl.h>   // defines IFileOpenDialog
#include <strsafe.h>
#include <Shlwapi.h>

다음 코드 라이브러리에 연결합니다.

  • mfplay.lib
  • shlwapi.lib

전역 변수

다음 전역 변수를 선언합니다.

IMFPMediaPlayer         *g_pPlayer = NULL;      // The MFPlay player object.
MediaPlayerCallback     *g_pPlayerCB = NULL;    // Application callback object.

BOOL                    g_bHasVideo = FALSE;

이러한 변수는 다음과 같이 사용됩니다.

g_hwnd

애플리케이션 창에 대한 핸들입니다.

g_bVideo

비디오가 재생되고 있는지 여부를 추적하는 부울 값입니다.

g_pPlayer

IMFPMediaPlayer 인터페이스에 대한 포인터입니다. 이 인터페이스는 재생을 제어하는 데 사용됩니다.

g_pCallback

IMFPMediaPlayerCallback 인터페이스에 대한 포인터입니다. 애플리케이션은 이 콜백 인터페이스를 구현하여 플레이어 개체에서 알림을 받습니다.

콜백 클래스 선언

플레이어 개체에서 이벤트 알림을 받으려면 애플리케이션이 IMFPMediaPlayerCallback 인터페이스를 구현해야 합니다. 다음 코드는 인터페이스를 구현하는 클래스를 선언합니다. 유일한 멤버 변수는 참조 수를 저장하는 m_cRef.

IUnknown 메서드는 인라인으로 구현됩니다. IMFPMediaPlayerCallback::OnMediaPlayerEvent 메서드의 구현은 나중에 표시됩니다. 콜백 메서드 구현을 참조하세요.

// Implements the callback interface for MFPlay events.

class MediaPlayerCallback : public IMFPMediaPlayerCallback
{
    long m_cRef; // Reference count

public:

    MediaPlayerCallback() : m_cRef(1)
    {
    }

    IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
    {
        static const QITAB qit[] =
        {
            QITABENT(MediaPlayerCallback, IMFPMediaPlayerCallback),
            { 0 },
        };
        return QISearch(this, qit, riid, ppv);
    }

    IFACEMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_cRef);
    }

    IFACEMETHODIMP_(ULONG) Release()
    {
        ULONG count = InterlockedDecrement(&m_cRef);
        if (count == 0)
        {
            delete this;
            return 0;
        }
        return count;
    }

    // IMFPMediaPlayerCallback methods
    IFACEMETHODIMP_(void) OnMediaPlayerEvent(MFP_EVENT_HEADER *pEventHeader);
};

SafeRelease 함수 선언

이 자습서 전체에서 SafeRelease 함수는 인터페이스 포인터를 해제하는 데 사용됩니다.

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}

미디어 파일 열기

함수는 PlayMediaFile 다음과 같이 미디어 파일을 엽니다.

  1. g_pPlayerNULL인 경우 함수는 MFPCreateMediaPlayer를 호출하여 미디어 플레이어 개체의 새 instance 만듭니다. MFPCreateMediaPlayer에 대한 입력 매개 변수에는 콜백 인터페이스에 대한 포인터와 비디오 창에 대한 핸들이 포함됩니다.
  2. 미디어 파일을 열기 위해 함수는 IMFPMediaPlayer::CreateMediaItemFromURL을 호출하여 파일의 URL을 전달합니다. 이 메서드는 비동기적으로 완료됩니다. 완료되면 애플리케이션의 IMFPMediaPlayerCallback::OnMediaPlayerEvent 메서드가 호출됩니다.
HRESULT PlayMediaFile(HWND hwnd, PCWSTR pszURL)
{
    HRESULT hr = S_OK;

    // Create the MFPlayer object.
    if (g_pPlayer == NULL)
    {
        g_pPlayerCB = new (std::nothrow) MediaPlayerCallback();

        if (g_pPlayerCB == NULL)
        {
            return E_OUTOFMEMORY;
        }

        hr = MFPCreateMediaPlayer(
            NULL,
            FALSE,          // Start playback automatically?
            0,              // Flags
            g_pPlayerCB,    // Callback pointer
            hwnd,           // Video window
            &g_pPlayer
            );
    }

    // Create a new media item for this URL.

    if (SUCCEEDED(hr))
    {
        hr = g_pPlayer->CreateMediaItemFromURL(pszURL, FALSE, 0, NULL);
    }

    // The CreateMediaItemFromURL method completes asynchronously.
    // The application will receive an MFP_EVENT_TYPE_MEDIAITEM_CREATED
    // event. See MediaPlayerCallback::OnMediaPlayerEvent().

    return hr;
}

함수는 OnFileOpen 사용자가 재생을 위해 파일을 선택할 수 있도록 하는 공통 파일 대화 상자를 표시합니다. IFileOpenDialog 인터페이스는 공통 파일 대화 상자를 표시하는 데 사용됩니다. 이 인터페이스는 Windows 셸 API의 일부입니다. 이전 GetOpenFileName 함수의 대체 기능으로 Windows Vista에서 도입되었습니다. 사용자가 파일을 OnFileOpen 선택한 후 를 호출 PlayMediaFile 하여 재생을 시작합니다.

void OnFileOpen(HWND hwnd)
{
    IFileOpenDialog *pFileOpen = NULL;
    IShellItem *pItem = NULL;
    PWSTR pwszFilePath = NULL;

    // Create the FileOpenDialog object.
    HRESULT hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL,
        CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFileOpen));
    if (SUCCEEDED(hr))
    {
        hr = pFileOpen->SetTitle(L"Select a File to Play");
    }

    // Show the file-open dialog.
    if (SUCCEEDED(hr))
    {
        hr = pFileOpen->Show(hwnd);
    }

    if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
    {
        // User canceled.
        SafeRelease(&pFileOpen);
        return;
    }

    // Get the file name from the dialog.
    if (SUCCEEDED(hr))
    {
        hr = pFileOpen->GetResult(&pItem);
    }

    if (SUCCEEDED(hr))
    {
       hr = pItem->GetDisplayName(SIGDN_URL, &pwszFilePath);
    }

    // Open the media file.
    if (SUCCEEDED(hr))
    {
        hr = PlayMediaFile(hwnd, pwszFilePath);
    }

    if (FAILED(hr))
    {
        ShowErrorMessage(L"Could not open file.", hr);
    }

    CoTaskMemFree(pwszFilePath);

    SafeRelease(&pItem);
    SafeRelease(&pFileOpen);
}

창 메시지 처리기

다음으로, 다음 창 메시지에 대한 메시지 처리기를 선언합니다.

  • WM_PAINT
  • WM_SIZE
  • WM_CLOSE

WM_PAINT 메시지의 경우 비디오가 현재 재생 중인지 여부를 추적해야 합니다. 그렇다면 IMFPMediaPlayer::UpdateVideo 메서드를 호출합니다. 이 메서드를 사용하면 플레이어 개체가 가장 최근의 비디오 프레임을 다시 그리게 됩니다.

비디오가 없으면 애플리케이션은 창 그리기를 담당합니다. 이 자습서의 경우 애플리케이션은 GDI FillRect 함수를 호출하여 전체 클라이언트 영역을 채웁니다.

void OnPaint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if (g_pPlayer && g_bHasVideo)
    {
        // Playback has started and there is video.

        // Do not draw the window background, because the video
        // frame fills the entire client area.

        g_pPlayer->UpdateVideo();
    }
    else
    {
        // There is no video stream, or playback has not started.
        // Paint the entire client area.

        FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
    }

    EndPaint(hwnd, &ps);
}

WM_SIZE 메시지의 경우 IMFPMediaPlayer::UpdateVideo를 호출합니다. 이 메서드를 사용하면 플레이어 개체가 비디오를 창의 현재 크기와 일치하도록 다시 조정합니다. UpdateVideoWM_PAINTWM_SIZE 모두에 사용됩니다.

void OnSize(HWND /*hwnd*/, UINT state, int /*cx*/, int /*cy*/)
{
    if (state == SIZE_RESTORED)
    {
        if (g_pPlayer)
        {
            // Resize the video.
            g_pPlayer->UpdateVideo();
        }
    }
}

WM_CLOSE 메시지의 경우 IMFPMediaPlayerIMFPMediaPlayerCallback 포인터를 해제합니다.

void OnClose(HWND /*hwnd*/)
{
    SafeRelease(&g_pPlayer);
    SafeRelease(&g_pPlayerCB);
    PostQuitMessage(0);
}

콜백 메서드 구현

IMFPMediaPlayerCallback 인터페이스는 단일 메서드 OnMediaPlayerEvent를 정의합니다. 이 메서드는 재생 중에 이벤트가 발생할 때마다 애플리케이션에 알 줍니다. 메서드는 하나의 매개 변수인 MFP_EVENT_HEADER 구조체에 대한 포인터를 사용합니다. 구조체의 eEventType 멤버는 발생한 이벤트를 지정합니다.

MFP_EVENT_HEADER 구조 뒤에 추가 데이터가 있을 수 있습니다. 각 이벤트 유형에 대해 MFP_EVENT_HEADER 포인터를 이벤트별 구조로 캐스팅하는 매크로가 정의됩니다. ( MFP_EVENT_TYPE 참조하세요.)

이 자습서에서는 두 가지 이벤트가 중요합니다.

이벤트 설명
MFP_EVENT_TYPE_MEDIAITEM_CREATED CreateMediaItemFromURL이 완료되면 전송됩니다.
MFP_EVENT_TYPE_MEDIAITEM_SET SetMediaItem이 완료되면 전송됩니다.

 

다음 코드에서는 MFP_EVENT_HEADER 포인터를 이벤트별 구조로 캐스팅하는 방법을 보여 있습니다.

void MediaPlayerCallback::OnMediaPlayerEvent(MFP_EVENT_HEADER * pEventHeader)
{
    if (FAILED(pEventHeader->hrEvent))
    {
        ShowErrorMessage(L"Playback error", pEventHeader->hrEvent);
        return;
    }

    switch (pEventHeader->eEventType)
    {
    case MFP_EVENT_TYPE_MEDIAITEM_CREATED:
        OnMediaItemCreated(MFP_GET_MEDIAITEM_CREATED_EVENT(pEventHeader));
        break;

    case MFP_EVENT_TYPE_MEDIAITEM_SET:
        OnMediaItemSet(MFP_GET_MEDIAITEM_SET_EVENT(pEventHeader));
        break;
    }
}

MFP_EVENT_TYPE_MEDIAITEM_CREATED 이벤트는 IMFPMediaPlayer::CreateMediaItemFromURL 메서드가 완료되었음을 애플리케이션에 알릴 수 있습니다. 이벤트 구조에는 URL에서 만든 미디어 항목을 나타내는 IMFPMediaItem 인터페이스에 대한 포인터가 포함됩니다. 재생을 위해 항목을 큐에 추가하려면 IMFPMediaPlayer::SetMediaItem 메서드에 이 포인터를 전달합니다.

void OnMediaItemCreated(MFP_MEDIAITEM_CREATED_EVENT *pEvent)
{
    // The media item was created successfully.

    if (g_pPlayer)
    {
        BOOL    bHasVideo = FALSE;
        BOOL    bIsSelected = FALSE;

        // Check if the media item contains video.
        HRESULT hr = pEvent->pMediaItem->HasVideo(&bHasVideo, &bIsSelected);
        if (SUCCEEDED(hr))
        {
            g_bHasVideo = bHasVideo && bIsSelected;

            // Set the media item on the player. This method completes
            // asynchronously.
            hr = g_pPlayer->SetMediaItem(pEvent->pMediaItem);
        }

        if (FAILED(hr))
        {
            ShowErrorMessage(L"Error playing this file.", hr);
        }
   }
}

MFP_EVENT_TYPE_MEDIAITEM_SET 이벤트는 SetMediaItem이 완료되었음을 애플리케이션에 알 수 있습니다. IMFPMediaPlayer::P lay를 호출하여 재생을 시작합니다.

void OnMediaItemSet(MFP_MEDIAITEM_SET_EVENT * /*pEvent*/)
{
    HRESULT hr = g_pPlayer->Play();
    if (FAILED(hr))
    {
        ShowErrorMessage(L"IMFPMediaPlayer::Play failed.", hr);
    }
}

WinMain 구현

이 자습서의 나머지 부분에서는 Media Foundation API에 대한 호출이 없습니다. 다음 코드는 창 프로시저를 보여줍니다.

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        HANDLE_MSG(hwnd, WM_CLOSE,   OnClose);
        HANDLE_MSG(hwnd, WM_PAINT,   OnPaint);
        HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
        HANDLE_MSG(hwnd, WM_SIZE,    OnSize);

    case WM_ERASEBKGND:
        return 1;

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

함수는 InitializeWindow 애플리케이션의 창 클래스를 등록하고 창을 만듭니다.

BOOL InitializeWindow(HWND *pHwnd)
{
    const wchar_t CLASS_NAME[]  = L"MFPlay Window Class";
    const wchar_t WINDOW_NAME[] = L"MFPlay Sample Application";

    WNDCLASS wc = {};

    wc.lpfnWndProc   = WindowProc;
    wc.hInstance     = GetModuleHandle(NULL);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = CLASS_NAME;
    wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU1);

    RegisterClass(&wc);

    HWND hwnd = CreateWindow(
        CLASS_NAME, WINDOW_NAME, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, GetModuleHandle(NULL), NULL);

    if (!hwnd)
    {
        return FALSE;
    }

    ShowWindow(hwnd, SW_SHOWDEFAULT);
    UpdateWindow(hwnd);

    *pHwnd = hwnd;

    return TRUE;
}

마지막으로 애플리케이션 진입점을 구현합니다.

int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

    HRESULT hr = CoInitializeEx(NULL,
        COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

    if (FAILED(hr))
    {
        return 0;
    }

    HWND hwnd = NULL;
    if (InitializeWindow(&hwnd))
    {
        // Message loop
        MSG msg = {};
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        DestroyWindow(hwnd);
    }
    CoUninitialize();

    return 0;
}

오디오/비디오 재생에 MFPlay 사용