MFPlay 入门

[可在“要求”部分中指定的操作系统中使用 MFPlay。 在后续版本中,它可能会被修改,也可能无法使用。 ]

MFPlay 是一个 API,通过它可使用 C++ 创建媒体播放应用程序。

本主题包含以下各节:

要求

MFPlay 需要 Windows 7。

关于 MFPlay

MFPlay 具有简单的编程模型,同时提供大多数播放应用程序所需的核心功能集。 应用程序会创建用于控制播放的 player 对象。 若要播放媒体文件,player 对象会创建一个媒体项,该媒体项可用于获取媒体文件内容的相关信息。 应用程序通过 player 对象的 IMFPMediaPlayer 接口上的方法控制播放。 (可选)应用程序可通过回调接口获取事件通知。下图说明了此过程。

conceptual diagram: application and player point to each other, both point to media item, which points to media file

播放媒体文件

若要播放媒体文件,请调用 MFPCreateMediaPlayer 函数。

// Global variables
IMFPMediaPlayer *g_pPlayer = NULL;

const WCHAR *sURL = L"C:\\Users\\Public\\Videos\\example.wmv";

HRESULT PlayVideo(HWND hwnd, const WCHAR* sURL)
{
    // Create the player object and play a video file.

    return MFPCreateMediaPlayer(
        sURL,
        TRUE,   // Start playback automatically?
        0,      // Flags.
        NULL,   // Callback pointer.
        hwnd,
        &g_pPlayer
        );
}

MFPCreateMediaPlayer 函数会创建 MFPlay player 对象的新实例。 该函数采用以下参数:

  • 第一个参数是要打开的文件的 URL。 这可以是本地文件,也可以是媒体服务器上的文件。
  • 第二个参数指定是否自动启动播放。 通过将此参数设置为 TRUE,文件将在 MFPlay 加载它后立即播放。
  • 第三个参数设置各种选项。 对于默认选项,传递零 (0)。
  • 第四个参数是指向可选回调接口的指针。 此参数可以为 NULL,如下所示。 有关回调,请参阅从播放器接收事件部分。
  • 第五个参数是应用程序窗口的句柄。 如果媒体文件包含视频流,视频将显示在此窗口的客户端区域内。 对于仅音频播放,此参数可以为 NULL。

最后一个参数接收指向 player 对象的 IMFPMediaPlayer 接口的指针。

在应用程序关闭之前,请务必释放 IMFPMediaPlayer 指针。 可以在 WM_CLOSE 消息处理程序中执行此操作。

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

注意

此示例使用 SafeRelease 函数释放接口指针。

 

对于简单的视频播放,这就是你需要的所有代码。 本教程的其余部分介绍如何添加更多功能,从如何控制播放开始。

控制播放

上一部分中显示的代码将播放媒体文件,直到文件结束。 可通过在 IMFPMediaPlayer 接口调用以下方法来停止和启动播放:

按空格键时,以下代码将暂停或恢复播放。

//-------------------------------------------------------------------
// OnKeyDown
//
// Handles the WM_KEYDOWN message.
//-------------------------------------------------------------------

void OnKeyDown(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
    HRESULT hr = S_OK;

    switch (vk)
    {
    case VK_SPACE:

        // Toggle between playback and paused/stopped.
        if (g_pPlayer)
        {
            MFP_MEDIAPLAYER_STATE state = MFP_MEDIAPLAYER_STATE_EMPTY;
            
            g_pPlayer->GetState(&state);

            if (state == MFP_MEDIAPLAYER_STATE_PAUSED ||  
                state == MFP_MEDIAPLAYER_STATE_STOPPED)
            {
                g_pPlayer->Play();
            }
            else if (state == MFP_MEDIAPLAYER_STATE_PLAYING)
            {
                g_pPlayer->Pause();
            }
        }
        break;
    }
}

此示例会调用 IMFPMediaPlayer::GetState 方法以获取当前播放状态(“已暂停”、“已停止”或“正在播放”),并相应地进行暂停或恢复。

从播放器接收事件

MFPlay 使用回调接口将事件发送到应用程序。 进行此回调有两个原因:

  • 播放发生在单独的线程上。 在播放期间,可能会发生某些事件,例如到达文件的末尾。 MFPlay 使用回调将事件通知给应用程序。
  • 许多 IMFPMediaPlayer 方法都是异步的,这意味着它们在操作完成之前返回。 使用异步方法可以从 UI 线程启动操作,该操作可能需要很长时间才能完成,而不会导致 UI 被阻止。 操作完成后,MFPlay 会发出回调信号。

若要接收回调通知,请实现 IMFPMediaPlayerCallback 接口。 此接口会继承 IUnknown 并定义单个方法 OnMediaPlayerEvent 若要设置回调,请在 MFPCreateMediaPlayer 函数的 pCallback 参数中传递指向 IMFPMediaPlayerCallback 实现的指针。

下面是本教程中的第一个示例,已修改为包含回调。

// Global variables.
IMFPMediaPlayer *g_pPlayer = NULL;
IMFPMediaPlayerCallback *g_pCallback = NULL;

// Call an application-defined function to create the callback object.
hr = CreateMyCallback(&g_pCallback);

// Create the player object and play a video file.

const WCHAR *sURL = L"C:\\Users\\Public\\Videos\\example.wmv";

if (SUCCEEDED(hr))
{
    hr = MFPCreateMediaPlayer(
        sURL,
        TRUE,        // Start playback automatically?
        0,           // Flags.
        g_pCallback, // Callback pointer.
        hwnd,
        &g_pPlayer
        );
}

OnMediaPlayerEvent 方法具有单个参数,它是指向 MFP_EVENT_HEADER 结构的指针。 通过此结构的 eEventType 成员,可知道发生了哪个事件。 例如,播放启动时,MFPlay 会发送 MFP_EVENT_TYPE_PLAY 事件。

每个事件类型都有一个相应的数据结构,其中包含该事件的信息。 其中每个结构都以 MFP_EVENT_HEADER 结构开头。 在回调中,将 MFP_EVENT_HEADER 指针强制转换为特定于事件的数据结构。 例如,如果事件类型为 MFP_PLAY_EVENT,则数据结构为 MFP_PLAY_EVENT 以下代码显示如何在回调中处理此事件。

void STDMETHODCALLTYPE MediaPlayerCallback::OnMediaPlayerEvent(
    MFP_EVENT_HEADER *pEventHeader
    )
{
    switch (pEventHeader->eEventType)
    {
    case MFP_EVENT_TYPE_PLAY:
        OnPlay(MFP_GET_PLAY_EVENT(pEventHeader));
        break;

    // Other event types (not shown).

    }
}

// Function to handle the event.
void OnPlay(MFP_PLAY_EVENT *pEvent)
{
    if (FAILED(pEvent->header.hrEvent))
    {  
        // Error occurred during playback.
    }  
}

此示例使用 MFP_GET_PLAY_EVENT 事件强制转换指向 MFP_PLAY_EVENT 结构的 pEventHeade 指针。 触发事件的操作的 HRESULT 存储在结构的 hrEvent 字段中。

有关所有事件类型和相应数据结构的列表,请参阅 MFP_EVENT_TYPE

有关线程的注意事项:默认情况下,MFPlay 从调用 MFPCreateMediaPlayer 的同一线程调用回调。 此线程必须具有消息循环。 或者,可以在 MFPCreateMediaPlayer 的 creationOptions 参数中传递 MFP_OPTION_FREE_THREADED_CALLBACK 标志。 此标志会导致 MFPlay 从单独的线程调用回调。 任一选项对应用程序都有影响。 如果是默认选项,则回调无法执行任何在消息循环上等待的任何操作,例如等待 UI 操作,因为这会阻止窗口程序。 如果是自由线程选项,则需要使用关键部分来保护在回调中访问的任何数据。 在大多数情况下,默认选项最简单。

获取有关媒体文件的信息

在 MFPlay 中打开媒体文件时,播放器将创建一个名为“媒体项”的对象,该对象表示媒体文件。 此对象公开 IMFPMediaItem 接口,它可用于获取媒体文件的相关信息。 许多 MFPlay 事件结构都包含指向当前媒体项的指针。 还可通过在播放器上调用 IMFPMediaPlayer::GetMediaItem 来获取当前媒体项。

两种特别有用的方法是 IMFPMediaItem::HasVideoIMFPMediaItem::HasAudio 这些方法会查询媒体源是否包含视频或音频。

例如,以下代码会测试当前媒体文件是否包含视频流。

IMFPMediaItem *pItem;
BOOL bHasVideo = FALSE;
BOOL bIsSelected = FALSE;

hr = g_pPlayer->GetMediaItem(TRUE, &pItem);
if (SUCCEEDED(hr))
{
    hr = pItem->HasVideo(&bHasVideo, &bIsSelected);
    pItem->Release();
}

如果源文件包含选择要播放的视频流,则 bHasVideo 和 bIsSelected 都设置为 TRUE

管理视频播放

MFPlay 播放视频文件时,它会在 MFPCreateMediaPlayer 函数中指定的窗口中绘制视频。 这发生在 Microsoft 媒体基础播放管道拥有的单独线程上。 在大多数情况下,应用程序不需要管理此过程。 但是,在下面两种情况下,应用程序必须通知 MFPlay 更新视频。

  • 如果播放已暂停或停止,则每当应用程序的视频窗口收到 WM_PAINT 消息时,都必须通知 MFPlay。 这样,MFPlay 就能重新绘制窗口。
  • 如果调整窗口大小,必须通知 MFPlay,以便它可缩放视频以匹配新的窗口大小。

IMFPMediaPlayer::UpdateVideo 方法可处理这两种情况。 在视频窗口的 WM_PAINT 和 WM_SIZE 消息处理程序中调用此方法。

重要

在调用 UpdateVideo 之前,调用 GDI BeginPaint 函数。

 

IMFPMediaPlayer *g_pPlayer;   // MFPlay player object

void OnSize(HWND hwnd, UINT state, int cx, int cy)
{
    HDC hdc;
    PAINTSTRUCT ps;

    if (g_pPlayer && (state == SIZE_RESTORED))
    {
        hdc = BeginPaint(hwnd, &ps);
        g_pPlayer->UpdateVideo();
         EndPaint(hwnd, &ps);
    }
}

void OnPaint(HWND hwnd)
{
    HDC hdc;
    PAINTSTRUCT ps;

    hdc = BeginPaint(hwnd, &ps);
    if (g_pPlayer)
    {
        g_pPlayer->UpdateVideo();
    }
      EndPaint(hwnd, &ps);
}

除非另行指定,否则 MFPlay 会根据需要加边框来按正确的纵横比显示视频。 如果不想保留纵横比,请使用 MFVideoARMode_None 标志调用 IMFPMediaPlayer::SetAspectRatioMode 这将导致 MFPlay 拉伸视频来填充整个矩形,无需加边框。 通常,应使用默认设置,让 MFPlay 对视频加边框。 默认的边框颜色为黑色,但可通过调用 IMFPMediaPlayer::SetBorderColor 来更改此颜色。

MFPlay 的限制

当前版本的 MFPlay 具有以下限制:

  • MFPlay 不支持受 DRM 保护的内容。
  • 默认情况下,MFPlay 不支持网络流式处理协议。 有关详细信息,请参阅 IMFPMediaPlayer::CreateMediaItemFromURL
  • MFPlay 不支持服务器端播放列表 (SSPL) 或播放多个段的其他类型的源。 从技术上讲,MFPlay 在第一次呈现后停止播放,并忽略媒体源中的任何 MENewPresentation 事件。
  • MFPlay 不支持媒体源之间的无缝转换。
  • MFPlay 不支持混合多个视频流。
  • MFPlay 仅支持媒体基础中本机支持的媒体格式。 (这包括系统上可能安装的第三方媒体基础组件。)

对音频/视频播放使用 MFPlay