Condividi tramite


Esercitazione su MFPlay: Riproduzione video

[La funzionalità associata a questa pagina, MFPlay, è una funzionalità legacy. È stato sostituito da MediaPlayer e IMFMediaEngine. Queste funzionalità sono state ottimizzate per Windows 10 e Windows 11. Microsoft consiglia vivamente che il nuovo codice usi MediaPlayer e IMFMediaEngine invece di DirectShow, quando possibile. Microsoft suggerisce che il codice esistente che usa le API legacy venga riscritto per usare le nuove API, se possibile.

Questa esercitazione presenta un'applicazione completa che riproduce video con MFPlay. Si basa sull'esempio SimplePlay SDK.

Questa esercitazione contiene le sezioni seguenti:

Per una descrizione più dettagliata dell'API MFPlay, vedere Introduzione a MFPlay.

Requisiti

MFPlay richiede Windows 7.

File di intestazione e libreria

Includere i file di intestazione seguenti nel progetto:

#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>

Collegamento alle librerie di codice seguenti:

  • mfplay.lib
  • shlwapi.lib

Variabili globali

Dichiarare le variabili globali seguenti:

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

BOOL                    g_bHasVideo = FALSE;

Queste variabili verranno usate come segue:

g_hwnd

Handle per la finestra dell'applicazione.

g_bVideo

Valore booleano che tiene traccia della riproduzione del video.

g_pPlayer

Puntatore all'interfaccia IMFPMediaPlayer . Questa interfaccia viene usata per controllare la riproduzione.

g_pCallback

Puntatore all'interfaccia IMFPMediaPlayerCallback . L'applicazione implementa questa interfaccia di callback per ottenere notifiche dall'oggetto lettore.

Dichiarare la classe callback

Per ottenere notifiche degli eventi dall'oggetto lettore, l'applicazione deve implementare l'interfaccia IMFPMediaPlayerCallback . Il codice seguente dichiara una classe che implementa l'interfaccia . L'unica variabile membro è m_cRef, che archivia il conteggio dei riferimenti.

I metodi IUnknown vengono implementati inline. L'implementazione del metodo IMFPMediaPlayerCallback::OnMediaPlayerEvent viene visualizzata in un secondo momento. Vedere Implementare il metodo di callback.

// 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);
};

Dichiarare la funzione SafeRelease

In questa esercitazione viene usata la funzione SafeRelease per rilasciare i puntatori di interfaccia:

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

Aprire un file multimediale

La PlayMediaFile funzione apre un file multimediale, come indicato di seguito:

  1. Se g_pPlayer è NULL, la funzione chiama MFPCreateMediaPlayer per creare una nuova istanza dell'oggetto lettore multimediale. I parametri di input per MFPCreateMediaPlayer includono un puntatore all'interfaccia di callback e un handle per la finestra video.
  2. Per aprire il file multimediale, la funzione chiama IMFPMediaPlayer::CreateMediaItemFromURL, passando l'URL del file. Questo metodo viene completato in modo asincrono. Al termine, viene chiamato il metodo IMFPMediaPlayerCallback::OnMediaPlayerEvent dell'applicazione.
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;
}

La OnFileOpen funzione visualizza la finestra di dialogo file comune, che consente all'utente di selezionare un file per la riproduzione. L'interfaccia IFileOpenDialog viene usata per visualizzare la finestra di dialogo dei file comuni. Questa interfaccia fa parte delle API della shell di Windows; è stato introdotto in Windows Vista come sostituzione della funzione GetOpenFileName precedente. Dopo che l'utente seleziona un file, OnFileOpen chiama PlayMediaFile per avviare la riproduzione.

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);
}

Gestori messaggi finestra

Successivamente, dichiarare i gestori di messaggi per i messaggi della finestra seguenti:

  • WM_PAINT
  • WM_SIZE
  • WM_CLOSE

Per il messaggio WM_PAINT , è necessario tenere traccia del fatto che il video sia attualmente in riproduzione. In tal caso, chiamare il metodo IMFPMediaPlayer::UpdateVideo . Questo metodo fa sì che l'oggetto lettore ridisegni il fotogramma video più recente.

Se non è presente alcun video, l'applicazione è responsabile della pittura della finestra. Per questa esercitazione, l'applicazione chiama semplicemente la funzione FillRect GDI per riempire l'intera area client.

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);
}

Per il messaggio di WM_SIZE , chiama IMFPMediaPlayer::UpdateVideo. Questo metodo fa sì che l'oggetto lettore riaggiusta il video in modo che corrisponda alle dimensioni correnti della finestra. Si noti che UpdateVideo viene usato sia per WM_PAINT che per WM_SIZE.

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

Per il messaggio WM_CLOSE, rilasciare i puntatori IMFPMediaPlayer e IMFPMediaPlayerCallback.

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

Implementare il metodo callback

L'interfaccia IMFPMediaPlayerCallback definisce un singolo metodo OnMediaPlayerEvent. Questo metodo invia una notifica all'applicazione ogni volta che si verifica un evento durante la riproduzione. Il metodo accetta un parametro, un puntatore a una struttura MFP_EVENT_HEADER. Il membro eEventType della struttura specifica l'evento che si è verificato.

La struttura MFP_EVENT_HEADER può essere seguita da dati aggiuntivi. Per ogni tipo di evento, viene definita una macro che esegue il cast del puntatore MFP_EVENT_HEADER a una struttura specifica dell'evento. (Vedere MFP_EVENT_TYPE.)

Per questa esercitazione, due eventi sono significativi:

Evento Descrizione
MFP_EVENT_TYPE_MEDIAITEM_CREATED Inviato al completamento di CreateMediaItemFromURL.
MFP_EVENT_TYPE_MEDIAITEM_SET Inviato al completamento di SetMediaItem.

 

Il codice seguente illustra come eseguire il cast del puntatore MFP_EVENT_HEADER alla struttura specifica dell'evento.

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;
    }
}

L'evento MFP_EVENT_TYPE_MEDIAITEM_CREATED notifica all'applicazione che il metodo IMFPMediaPlayer::CreateMediaItemFromURL è stato completato. La struttura di eventi contiene un puntatore all'interfaccia IMFPMediaItem , che rappresenta l'elemento multimediale creato dall'URL. Per accodare l'elemento per la riproduzione, passare questo puntatore al metodo 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);
        }
   }
}

L'evento MFP_EVENT_TYPE_MEDIAITEM_SET notifica all'applicazione che SetMediaItem è stato completato. Chiama IMFPMediaPlayer::P lay per avviare la riproduzione:

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

Implementare WinMain

Nella parte restante di questa esercitazione non sono presenti chiamate alle API di Media Foundation. Il codice seguente illustra la procedura della finestra:

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);
    }
}

La InitializeWindow funzione registra la classe window dell'applicazione e crea la finestra.

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;
}

Infine, implementare il punto di ingresso dell'applicazione:

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;
}

Uso di MFPlay per la riproduzione audio/video