Partager via


Tutoriel MFPlay : lecture vidéo

[La fonctionnalité associée à cette page, MFPlay, est une fonctionnalité héritée. Elle a été remplacée par MediaPlayer et IMFMediaEngine. Ces fonctionnalités ont été optimisées pour Windows 10 et Windows 11. Lorsque cela est possible, Microsoft recommande vivement l'utilisation par le nouveau code de MediaPlayer et IMFMediaEngine au lieu de DirectShow. Microsoft recommande, si possible, la réécriture du code existant qui utilise les API héritées pour être à jour avec les nouvelles API.]

Ce tutoriel présente une application complète qui lit la vidéo à l’aide de MFPlay. Il est basé sur l’exemple du kit de développement logiciel (SDK) SimplePlay.

Ce tutoriel contient les sections suivantes :

Pour une présentation plus détaillée de l’API MFPlay, reportez-vous à la rubrique « Prise en main de MFPlay ».

Spécifications

MFPlay nécessite Windows 7.

Fichiers bibliothèque et d’en-tête

Ajoutez les fichiers d'en-tête suivants à votre projet :

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

Liez aux bibliothèques de codes suivantes :

  • mfplay.lib
  • shlwapi.lib

Variables globales

Déclarez les variables globales suivantes :

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

BOOL                    g_bHasVideo = FALSE;

Ces variables seront utilisées comme suit :

g_hwnd

Un handle vers la fenêtre d’application.

g_bVideo

Valeur booléenne qui vérifie si la vidéo est en cours de lecture.

g_pPlayer

Un pointeur vers l'interface IMFPMediaPlayer. Cette interface est utilisée pour contrôler la lecture.

g_pCallback

Un pointeur vers l'interface IMFPMediaPlayerCallback. L’application implémente cette interface de rappel pour obtenir des notifications à partir de l’objet lecteur.

Déclarer la classe de rappel

Pour obtenir des notifications d’événements à partir de l’objet lecteur, l’application doit implémenter l’interface IMFPMediaPlayerCallback. Le code suivant déclare une classe qui implémente l'interface. La seule variable membre est m_cRef, qui stocke le nombre de références.

Les méthodes IUnknown sont implémentées inline. L’implémentation de la méthode IMFPMediaPlayerCallback::OnMediaPlayerEvent est présentée ultérieurement. Reportez-vous à la rubrique « Implémenter la méthode de rappel ».

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

Déclarer la fonction SafeRelease

Tout au long de ce tutoriel, la fonction SafeRelease est utilisée pour libérer des pointeurs d’interface :

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

Ouvrir un fichier multimédia

La PlayMediaFile fonction ouvre un fichier multimédia, comme suit :

  1. Si la valeur de g_pPlayer est NULL, la fonction appelle MFPCreateMediaPlayer pour créer une nouvelle instance de l'objet lecteur multimédia. Les paramètres d’entrée de MFPCreateMediaPlayer incluent un pointeur vers l’interface de rappel et un handle vers la fenêtre vidéo.
  2. Pour ouvrir le fichier multimédia, la fonction appelle IMFPMediaPlayer::CreateMediaItemFromURL, en passant par l’URL du fichier. Cette méthode se termine de façon asynchrone. Une fois l’opération terminée, la méthode IMFPMediaPlayerCallback::OnMediaPlayerEvent est appelée.
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 fonction affiche la boîte de dialogue de fichier commune, qui permet à l’utilisateur de sélectionner un fichier à lire. L’interface IFileOpenDialog est utilisée pour afficher la boîte de dialogue de fichier commune. Cette interface fait partie des API Windows Shell ; elle a été intégrée à Windows Vista à la place de l’ancienne fonction GetOpenFileName. Une fois que l’utilisateur a sélectionné un fichier, OnFileOpen appelle PlayMediaFile pour démarrer la lecture.

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

Gestionnaires de messages de fenêtre

Ensuite, déclarez les gestionnaires de messages pour les messages de fenêtre suivants :

  • WM_PAINT
  • WM_SIZE
  • WM_CLOSE

Pour le message WM_PAINT, vous devez vérifier si la vidéo est en cours de lecture. Si c’est le cas, appelez la méthode IMFPMediaPlayer::UpdateVideo. Cette méthode amène l’objet lecteur à redessiner l'image vidéo la plus récente.

S’il n’y a pas de vidéo, l’application est responsable de la peinture de la fenêtre. Pour ce tutoriel, l’application appelle simplement la fonction GDI FillRect pour remplir toute la zone cliente.

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

Pour le message WM_SIZE, appelez IMFPMediaPlayer::UpdateVideo. Cette méthode amène l’objet lecteur à réajuster la vidéo pour qu’elle corresponde à la taille de fenêtre actuelle. Notez que UpdateVideo est utilisé pour WM_PAINT et 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();
        }
    }
}

Pour le message WM_CLOSE, libérez les pointeurs IMFPMediaPlayer et IMFPMediaPlayerCallback.

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

Implémenter la méthode de rappel.

L'interface IMFPMediaPlayerCallback définit une méthode unique, OnMediaPlayerEvent. Cette méthode envoie une notification à l’application chaque fois qu’un événement se produit pendant la lecture. La méthode prend un paramètre, un pointeur vers une structure MFP_EVENT_HEADER. Le membre eEventType de la structure spécifie l’événement qui s’est produit.

La structure MFP_EVENT_HEADER peut être suivie de données supplémentaires. Pour chaque type d’événement, une macro est définie qui convertit le pointeur MFP_EVENT_HEADER vers une structure spécifique à l’événement. (Voir MFP_EVENT_TYPE.)

Pour ce tutoriel, deux événements sont importants :

Événement Description
MFP_EVENT_TYPE_MEDIAITEM_CREATED Envoyé lorsque CreateMediaItemFromURL est terminé.
MFP_EVENT_TYPE_MEDIAITEM_SET Envoyé lorsque SetMediaItem est terminé.

 

Le code suivant montre comment convertir le pointeur MFP_EVENT_HEADER vers la structure spécifique à l’événement.

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'évènement MFP_EVENT_TYPE_MEDIAITEM_CREATED informe l'application que la méthode IMFPMediaPlayer::CreateMediaItemFromURL a terminé. La structure d’événements contient un pointeur vers l’interface IMFPMediaItem, qui représente l’élément multimédia créé à partir de l’URL. Pour mettre l'élément en file d'attente pour la lecture, passez ce pointeur à la méthode 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'évènement MFP_EVENT_TYPE_MEDIAITEM_SET informe l'application que SetMediaItem a terminé. Appelez IMFPMediaPlayer::Play pour démarrer la lecture:

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

Implémenter WinMain

Dans le reste de ce tutoriel, il n’existe aucun appel aux API Media Foundation. Le code suivant affiche la procédure de fenêtre.

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 fonction inscrit la classe de fenêtre de l’application et crée la fenêtre.

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

Enfin, implémentez le point d’entrée de l’application :

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

Utilisation de MFPlay pour la lecture audio/vidéo