Tutorial de MFPlay: Reproducción de vídeo
[La característica asociada con esta página, MFPlay, es una característica heredada. Se ha reemplazado por MediaPlayer y IMFMediaEngine. Esas características se han optimizado para Windows 10 y Windows 11. Microsoft recomienda que el nuevo código use MediaPlayer y IMFMediaEngine en vez de DirectShow, siempre que sea posible. Microsoft sugiere que el código existente que usa las API heredadas se reescriba para usar las nuevas API si es posible].
Este tutorial presenta una aplicación completa que reproduce vídeo mediante MFPlay. Se basa en el SDK de ejemplo de SimplePlay.
Este tutorial contiene las siguientes secciones:
- Requisitos
- Archivos de encabezado y biblioteca
- Variables globales
- Declarar la clase Callback (devolución de llamada)
- Declarar la función SafeRelease
- Abrir un archivo multimedia
- Controladores de mensajes de ventana
- Implementar el método Callback (devolución de llamada)
- Implementar WinMain
- Temas relacionados
Para ver una explicación más detallada de la API de MFPlay, consulte Introducción a MFPlay.
Requisitos
MFPlay requiere Windows 7.
Archivos de encabezado y biblioteca
Incluya los siguientes archivos de encabezado en el proyecto:
#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>
Vincúlelos a las siguientes bibliotecas de código:
- mfplay.lib
- shlwapi.lib
Variables globales
Declare las siguientes variables globales:
IMFPMediaPlayer *g_pPlayer = NULL; // The MFPlay player object.
MediaPlayerCallback *g_pPlayerCB = NULL; // Application callback object.
BOOL g_bHasVideo = FALSE;
Estas variables se usarán de la siguiente manera:
-
g_hwnd
-
Identificador de la ventana de la aplicación.
-
g_bVideo
-
Valor booleano que controla si se está reproduciendo vídeo.
-
g_pPlayer
-
Un puntero a la interfaz IMFPMediaPlayer. Esta interfaz se usa para controlar la reproducción.
-
g_pCallback
-
Un puntero a la interfaz IMFPMediaPlayerCallback. La aplicación implementa esta interfaz de devolución de llamada para recibir notificaciones del objeto del reproductor.
Declarar la clase Callback (devolución de llamada)
Para recibir notificaciones de eventos del objeto del reproductor, la aplicación debe implementar la interfaz IMFPMediaPlayerCallback. El código siguiente declara una la clase que implementa la interfaz. La única variable miembro es m_cRef, que almacena el número de referencias.
Los métodos IUnknown se implementan insertándolos. La implementación del método IMFPMediaPlayerCallback::OnMediaPlayerEvent se ve más adelante; consulte Implementar el método Callback (devolución de llamada).
// 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);
};
Declarar la función SafeRelease
En este tutorial, la función SafeRelease se usa para liberar punteros de interfaz:
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
Abrir un archivo multimedia
La función PlayMediaFile
abre un archivo multimedia, tal como se indica a continuación:
- Si g_pPlayer es NULL, la función llama a MFPCreateMediaPlayer para crear una nueva instancia del objeto del reproductor multimedia. Los parámetros de entrada de MFPCreateMediaPlayer incluyen un puntero a la interfaz de devolución de llamada y un identificador a la ventana de vídeo.
- Para abrir el archivo multimedia, la función llama a IMFPMediaPlayer::CreateMediaItemFromURL, pasando la dirección URL del archivo. Este método se completa de forma asíncrona. Cuando se completa, se llama al método IMFPMediaPlayerCallback::OnMediaPlayerEvent de la aplicación.
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 función OnFileOpen
abre el cuadro de diálogo común del archivo, que permite al usuario seleccionar un archivo para reproducirlo. La interfaz IFileOpenDialog se usa para que se abra el cuadro de diálogo común del archivo. Esta interfaz forma parte de las API de Windows Shell; se introdujo en Windows Vista para sustituir la antigua función GetOpenFileName. Después de que el usuario seleccione un archivo, OnFileOpen
llama a PlayMediaFile
para iniciar la reproducción.
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);
}
Controladores de mensajes de ventana
Luego, declare los controladores de mensajes para los siguientes mensajes de ventana:
- WM_PAINT
- WM_SIZE
- WM_CLOSE
En el mensaje WM_PAINT, debe controlar si el vídeo se está reproduciendo en un momento dado. Si es así, llame al método IMFPMediaPlayer::UpdateVideo. Este método hace que el objeto del reproductor vuelva a dibujar el fotograma de vídeo más reciente.
Si no hay ningún vídeo, la aplicación se encargará de pintar la ventana. En este tutorial, la aplicación llama a la función FillRect de GDI para rellenar todo el área del 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);
}
En el mensaje WM_SIZE, llame a IMFPMediaPlayer::UpdateVideo. Este método hace que el objeto del reproductor reajuste el vídeo para que se ajuste al tamaño actual de la ventana. Tenga en cuenta que UpdateVideo se usa tanto en WM_PAINT como en 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();
}
}
}
En el mensaje WM_CLOSE, libre los punteros IMFPMediaPlayer y IMFPMediaPlayerCallback.
void OnClose(HWND /*hwnd*/)
{
SafeRelease(&g_pPlayer);
SafeRelease(&g_pPlayerCB);
PostQuitMessage(0);
}
Implementar el método Callback (devolución de llamada)
La interfaz IMFPMediaPlayerCallback define un método único, OnMediaPlayerEvent. Este método avisa a la aplicación cada vez que se produce un evento durante la reproducción. El método toma un parámetro, un puntero a una estructura MFP_EVENT_HEADER. El miembro eEventType de la estructura especifica el evento que se produjo.
La estructura MFP_EVENT_HEADER puede ir seguida de datos adicionales. Para cada tipo de evento, se define una macro que convierte el puntero MFP_EVENT_HEADER en una estructura específica del evento. (Consulte MFP_EVENT_TYPE).
En este tutorial, hay dos eventos que son significativos:
Evento | Descripción |
---|---|
MFP_EVENT_TYPE_MEDIAITEM_CREATED | Se envía cuando se completa CreateMediaItemFromURL. |
MFP_EVENT_TYPE_MEDIAITEM_SET | Se envía cuando se completa SetMediaItem. |
En el código siguiente se muestra cómo convertir el puntero MFP_EVENT_HEADER en la estructura específica del 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;
}
}
El evento MFP_EVENT_TYPE_MEDIAITEM_CREATED avisa a la aplicación de que se ha completado el método IMFPMediaPlayer::CreateMediaItemFromURL. La estructura del evento contiene un puntero a la interfaz IMFPMediaItem, que representa el elemento multimedia creado a partir de la dirección URL. Para poner en cola el elemento para la reproducción, pase el puntero al método 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);
}
}
}
El evento MFP_EVENT_TYPE_MEDIAITEM_SET avisa a la aplicación de que se ha completado SetMediaItem. Llame a IMFPMediaPlayer::Play para iniciar la reproducción:
void OnMediaItemSet(MFP_MEDIAITEM_SET_EVENT * /*pEvent*/)
{
HRESULT hr = g_pPlayer->Play();
if (FAILED(hr))
{
ShowErrorMessage(L"IMFPMediaPlayer::Play failed.", hr);
}
}
Implementar WinMain
En lo que queda del tutorial, no hay llamadas a las API de Media Foundation. En el código siguiente se muestra el procedimiento de ventanas:
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 función InitializeWindow
registra la clase de ventana de la aplicación y crea la ventana.
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;
}
Por último, implemente el punto de entrada de la aplicación:
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;
}
Temas relacionados