音视频播放 via Media Foundation II

音视频播放 via Media Foundation II

  • Media Foundation 简介
  • Media Foundation 播放音视频
    • 播放流程图
    • 播放代码
      • MFPlayer 类
        • MFPlayer::CreateInstance 静态函数
        • MFPlayer::Initialize 函数
        • CAudioSessionVolume 类
          • CAudioSessionVolume::Initialize 函数
        • MFPlayer::OpenURL 函数
        • IMFPMediaPlayerCallback::OnMediaPlayerEvent 回调函数
        • MFPlayer::OnMediaItemCreated 函数
        • MFPlayer::OnMediaItemSet 函数
        • MFPlayer::SetPosition 函数
        • MFPlayer::SetEffect 函数
    • MF 播放音频的 Topology
    • MF 播放视频的 Topology
  • 其他框架下的播放

Media Foundation 简介

Media Foundation (简称 MF)是微软在 Windows Vista上 推出的新一代多媒体应用库,目的是提供 Windows 平台一个统一的多媒体影音解决方案,开发者可以通过 MF 播放视频或声音文件、进行多媒体文件格式转码,或者将一连串图片编码为视频等等。

MF 是 DirectShow 为主的旧式多媒体应用程序接口的替代者与继承者,在微软的计划下将逐步汰换 DirectShow 技术。MF 要求 Windows Vista 或更高版本,不支持较早期的 Windows 版本,特别是 Windows XP。

MF 长于高质量的音频和视频播放,高清内容(如 HDTV,高清电视)和数字版权管理(DRM)访问控制。MF 在不同的 Windows 版本上能力不同,如 Windows 7 上就添加了 h.264 编码支持。Windows 8 上则提供数种更高质量的设置。

MF 提供了两种编程模型,第一种是以 Media Session 为主的 Media pipeline 模型,但是该模型太过复杂,且曝露过多底层细节,故微软于 Windows 7 上推出第二种编程模型,内含 SourceReader、Transcode API 、SinkWriter 及 MFPlay 等高度封装模块,大大简化了 MF 的使用难度。

# 本文使用了第二种(简单的)编程模型。

Media Foundation 播放音视频

播放流程图

音视频播放 via Media Foundation II_第1张图片

播放代码

以下是整个播放过程的概要代码,略去错误处理和一些函数的具体实现:

hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
hr = MFPCreateMediaPlayer(NULL, FALSE, 0, this, hwndVideo, &m_pPlayer);

// Create the object that manages to audio session.
hr = CAudioSessionVolume::CreateInstance(m_hwndEvent, WM_AUDIO_EVENT, &m_pVolume);
hr = m_pPlayer->CreateMediaItemFromURL(sURL, FALSE, 0, NULL);
hr = m_pPlayer->SetMediaItem(pEvent->pMediaItem);
hr = m_pPlayer->InsertEffect(pMFT, TRUE);
hr = m_pVolume->SetVolume(fLevel);
hr = m_pPlayer->Play();
// Playing ... 

hr = m_pPlayer->SetPosition(MFP_POSITIONTYPE_100NS, &var); // Seek
hr = m_pVolume->SetMute(TRUE); // Mute
hr = m_pVolume->SetMute(FALSE); // Unmute
// Playing ... 

hr = m_pPlayer->Stop();
hr = m_pPlayer->Shutdown();
CoUninitialize();

MFPlayer 类

Wrap 了 IMFPMediaPlayer,封装无止境 0_0

class MFPlayer : public IMFPMediaPlayerCallback
{
    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    
    // IMFPMediaPlayerCallback methods
    void STDMETHODCALLTYPE OnMediaPlayerEvent(MFP_EVENT_HEADER * pEventHeader);
    
    // Playback
    HRESULT OpenURL(const WCHAR *sURL);
    HRESULT Play();
    HRESULT Pause();
    HRESULT Stop();
    HRESULT Shutdown();
    HRESULT GetState(MFP_MEDIAPLAYER_STATE *pState);
    
    // Video
    HRESULT HasVideo(BOOL *pfHasVideo);
    HRESULT UpdateVideo();
    
    // Seeking
    HRESULT GetDuration(MFTIME *phnsDuration);
    HRESULT CanSeek(BOOL *pbCanSeek);
    HRESULT GetCurrentPosition(MFTIME *phnsPosition);
    HRESULT SetPosition(MFTIME hnsPosition);
    
    // Effects
    HRESULT SetEffect(IMFTransform *pMFT);
}
MFPlayer::CreateInstance 静态函数

类厂模式。

HRESULT MFPlayer::CreateInstance(HWND hwndEvent, HWND hwndVideo, MFPlayer **ppPlayer)
{
    HRESULT hr = S_OK;

    MFPlayer *pPlayer = new (std::nothrow) MFPlayer(hwndEvent);
    RETURN_IF_BADNEW(pPlayer);

    hr = pPlayer->Initialize(hwndVideo);
    GOTO_IF_FAILED(hr);

    *ppPlayer = pPlayer;
    (*ppPlayer)->AddRef();

RESOURCE_FREE:
    SAFE_RELEASE(pPlayer);
    return hr;
}
MFPlayer::Initialize 函数

这里除了创建了 IMFPMediaPlayer,还创建了 CAudioSessionVolume 用来设置及读取音量。

HRESULT MFPlayer::Initialize(HWND hwndVideo)
{
    HRESULT hr = MFPCreateMediaPlayer( NULL,
        FALSE,          // Start playback automatically?
        0,              // Flags
        this,           // Callback pointer   
        hwndVideo,      // Video window
        &m_pPlayer );   // type of IMFPMediaPlayer 
    RETURN_IF_FAILED(hr);
    
    HRESULT hrAudio = CAudioSessionVolume::CreateInstance(m_hwndEvent, WM_AUDIO_EVENT, &m_pVolume);
    if (SUCCEEDED(hrAudio)) // Ask for audio session events.
        hrAudio = m_pVolume->EnableNotifications(TRUE);
        
    return S_OK;
}
CAudioSessionVolume 类

通过继承自 IAudioSessionEvents 可以看出,设置音量是一个异步的操作。我个人觉得 MF 和 DShow 的一个很大的差别就是大量使用了异步的机制。
该类主要还封装了 ISimpleAudioVolume 接口来控制音量。

class CAudioSessionVolume : public IAudioSessionEvents
{
public:
    // Static method to create an instance of the object.
    static HRESULT CreateInstance( 
        HWND hwndNotification, 
        UINT uNotificationMessage, 
        CAudioSessionVolume **ppAudioSessionVolume 
        );
  
    // IUnknown methods.
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IAudioSessionEvents methods.
 
    //  Callback when the session volume level or muting state changes.
    STDMETHODIMP OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext);
    STDMETHODIMP OnDisplayNameChanged(LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/);
    STDMETHODIMP OnIconPathChanged(LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/);  
    STDMETHODIMP OnChannelVolumeChanged(DWORD /*ChannelCount*/, float /*NewChannelVolumeArray*/[],
        DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/);        
    STDMETHODIMP OnGroupingParamChanged(LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/);        
    STDMETHODIMP OnStateChanged(AudioSessionState /*NewState*/);        
    STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason /*DisconnectReason*/);

    // Other methods

    //  Enables or disables notifications from the audio session. For example, if the user mutes 
    //  the audio through the system volume-control program (Sndvol), the application will be notified.
    HRESULT EnableNotifications(BOOL bEnable );    
    HRESULT GetVolume(float *pflVolume);   
    //  flVolume: Ranges from 0 (silent) to 1 (full volume)
    HRESULT SetVolume(float flVolume);
    HRESULT GetMute(BOOL *pbMute);
    HRESULT SetMute(BOOL bMute);

protected:
    CAudioSessionVolume(HWND hwndNotification, UINT uNotificationMessage);
    ~CAudioSessionVolume();
    HRESULT Initialize();   

protected:
    LONG m_cRef;                        // Reference count.
    UINT m_uNotificationMessage;        // Window message to send when an audio event occurs.
    HWND m_hwndNotification;            // Window to receives messages.
    BOOL m_bNotificationsEnabled;       // Are audio notifications enabled?

    IAudioSessionControl *m_pAudioSession;
    ISimpleAudioVolume   *m_pSimpleAudioVolume;
};
CAudioSessionVolume::Initialize 函数

挖地三尺,终于挖到了 ISimpleAudioVolume 对象。
注意:IMMDeviceEnumerator 的 GetDefaultAudioEndpoint 函数第二个参数可选 eConsole、eMultimedia 和 eCommunications 之一,对应的可能是不同的 speaker,具体可参考 MSDN。

HRESULT Initialize()
{
    HRESULT hr = S_OK;
    CComPtr<IMMDeviceEnumerator> pDeviceEnum = NULL;
    CComPtr<IMMDevice> pDevice = NULL;
    CComPtr<IAudioSessionManager> pAudioSessMgr = NULL;

    // Get the enumerator for the audio endpoint devices.
    hr = CoCreateInstance( __uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDeviceEnum));
    RETURN_IF_FAILED(hr);

    // Get the default audio endpoint that the SAR will use.
    // The SAR uses 'eConsole' by default.
    hr = pDeviceEnum->GetDefaultAudioEndpoint( eRender, eConsole, &pDevice );
    RETURN_IF_FAILED(hr);

    // Get the session manager for this device.
    hr = pDevice->Activate(__uuidof(IAudioSessionManager), CLSCTX_INPROC_SERVER, NULL, (void**)&pAudioSessMgr);
    RETURN_IF_FAILED(hr);

    // Get the audio session. 
    hr = pAudioSessMgr->GetAudioSessionControl( 
        &GUID_NULL,     // Get the default audio session. 
        FALSE,          // The session is not cross-process.
        &m_pAudioSession 
        );
    RETURN_IF_FAILED(hr);

    hr = pAudioSessMgr->GetSimpleAudioVolume( &GUID_NULL, 0, &m_pSimpleAudioVolume );
    RETURN_IF_FAILED(hr);

    return hr;
}
MFPlayer::OpenURL 函数

打开一个媒体文件进行播放,这也是异步的操作,将在 IMFPMediaPlayerCallback 的 OnMediaPlayerEvent 回调函数中收到通知。

HRESULT MFPlayer::OpenURL(const WCHAR *sURL)
{
    // Create a new media item for this URL.
    HRESULT hr = m_pPlayer->CreateMediaItemFromURL(sURL, FALSE, 0, NULL);
    RETURN_IF_FAILED(hr);
    
    // The CreateMediaItemFromURL method completes asynchronously. When it does,
    // MFPlay sends an MFP_EVENT_TYPE_MEDIAITEM_CREATED event.
    return hr;
}
IMFPMediaPlayerCallback::OnMediaPlayerEvent 回调函数

上面的函数调用将得到 MFP_EVENT_TYPE_MEDIAITEM_CREATED 事件通知。

void MFPlayer::OnMediaPlayerEvent(MFP_EVENT_HEADER * pEventHeader)
{
    if (FAILED(pEventHeader->hrEvent)) {
        NotifyError(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;
        
    case MFP_EVENT_TYPE_PLAY:
    case MFP_EVENT_TYPE_PAUSE:
    case MFP_EVENT_TYPE_STOP:
    case MFP_EVENT_TYPE_PLAYBACK_ENDED:
        break;
    }

    NotifyState(pEventHeader->eState);
}
MFPlayer::OnMediaItemCreated 函数

这里的 SetMediaItem 调用也是异步的,将在 OnMediaPlayerEvent 回调函数中收到通知。

void MFPlayer::OnMediaItemCreated(MFP_MEDIAITEM_CREATED_EVENT *pEvent)
{
    HRESULT hr = S_OK; 
    CComPtr<IUnknown> pMFT = NULL;
    m_bHasVideo = TRUE;

    if ((m_pPlayer != NULL) && (pEvent->pMediaItem != NULL)) {
        BOOL bHasVideo = FALSE;
        BOOL bIsSelected = FALSE;
        hr = pEvent->pMediaItem->HasVideo(&bHasVideo, &bIsSelected);
        GOTO_IF_FAILED(hr);

        m_bHasVideo = bHasVideo && bIsSelected;

        hr = m_pPlayer->SetMediaItem(pEvent->pMediaItem);
        GOTO_IF_FAILED(hr);
    }

RESOURCE_FREE:
    if (FAILED(hr))
        NotifyError(hr);
}
MFPlayer::OnMediaItemSet 函数

绕啊绕,终于可以播放了。。

// Called when the SetMediaItem method completes.
void MFPlayer::OnMediaItemSet(MFP_MEDIAITEM_SET_EVENT *pEvent)
{
    HRESULT hr = S_OK;

    hr = pEvent->header.hrEvent;
    GOTO_IF_FAILED(hr);

    if (pEvent->pMediaItem) {
        hr = pEvent->pMediaItem->GetCharacteristics(&m_caps);
        GOTO_IF_FAILED(hr);
    }

    hr = m_pPlayer->Play();
    GOTO_IF_FAILED(hr);

RESOURCE_FREE:
    if (FAILED(hr))
        NotifyError(hr);
}
MFPlayer::SetPosition 函数

也即 seek。

HRESULT MFPlayer::SetPosition(MFTIME hnsPosition)
{
    HRESULT hr = E_FAIL;
    PROPVARIANT var;
    PropVariantInit(&var);
    var.vt = VT_I8;
    var.hVal.QuadPart = hnsPosition;
    
    hr = m_pPlayer->SetPosition(MFP_POSITIONTYPE_100NS, &var);
    GOTO_IF_FAILED(hr);
    
RESOURCE_FREE:
    PropVariantClear(&var);
    return hr;
}
MFPlayer::SetEffect 函数

可以插入或移除一个 MF Transform(类似于 DShow 的 Filter)。

HRESULT MFPlayer::SetEffect(IMFTransform *pMFT)
{
    HRESULT hr = S_OK;
    
    if (pMFT == NULL)
        hr = m_pPlayer->RemoveAllEffects();
    else
        hr = m_pPlayer->InsertEffect(pMFT, TRUE);
    RETURN_IF_FAILED(hr);
    
    return hr;
}

MF 播放音频的 Topology

以下是播放一首 MP3 生成的 Topology,包含三个模块:Source, Decoder 和 Renderer。
注意:不管你选用的是哪种 MF 的编程模型,最后的 Topology 其实是一样的。
音视频播放 via Media Foundation II_第2张图片

MF 播放视频的 Topology

以下是播放一个 WMV 文件生成的 Topology,包含五个模块:Source, Audio Decoder, Audio Renderer, Video Decoder 和 Video Renderer 。
音视频播放 via Media Foundation II_第3张图片

其他框架下的播放

音频播放其他实现方式:

  • Waveform API
  • FFmpeg
  • DirectShow

视频播放其他实现方式:

  • FFmpeg
  • DirectShow
  • Media Foundation I

Blueware
EOF

你可能感兴趣的:(Multimedia,多媒体开发,音频播放,Media,Foundation,视频播放,IMFPMediaPlayer)