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 的使用难度。
# 本文使用了第二种(简单的)编程模型。
以下是整个播放过程的概要代码,略去错误处理和一些函数的具体实现:
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();
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);
}
类厂模式。
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;
}
这里除了创建了 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;
}
通过继承自 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;
};
挖地三尺,终于挖到了 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;
}
打开一个媒体文件进行播放,这也是异步的操作,将在 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;
}
上面的函数调用将得到 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);
}
这里的 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);
}
绕啊绕,终于可以播放了。。
// 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);
}
也即 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;
}
可以插入或移除一个 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;
}
以下是播放一首 MP3 生成的 Topology,包含三个模块:Source, Decoder 和 Renderer。
注意:不管你选用的是哪种 MF 的编程模型,最后的 Topology 其实是一样的。
以下是播放一个 WMV 文件生成的 Topology,包含五个模块:Source, Audio Decoder, Audio Renderer, Video Decoder 和 Video Renderer 。
音频播放其他实现方式:
视频播放其他实现方式:
– EOF –