音视频播放 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 播放音视频

播放流程图

播放代码

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

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 methodsSTDMETHODIMP QueryInterface(REFIID iid, void** ppv);STDMETHODIMP_(ULONG) AddRef();STDMETHODIMP_(ULONG) Release();// IMFPMediaPlayerCallback methodsvoid STDMETHODCALLTYPE OnMediaPlayerEvent(MFP_EVENT_HEADER * pEventHeader);// PlaybackHRESULT OpenURL(const WCHAR *sURL);HRESULT Play();HRESULT Pause();HRESULT Stop();HRESULT Shutdown();HRESULT GetState(MFP_MEDIAPLAYER_STATE *pState);// VideoHRESULT HasVideo(BOOL *pfHasVideo);HRESULT UpdateVideo();// SeekingHRESULT GetDuration(MFTIME *phnsDuration);HRESULT CanSeek(BOOL *pbCanSeek);HRESULT GetCurrentPosition(MFTIME *phnsPosition);HRESULT SetPosition(MFTIME hnsPosition);// EffectsHRESULT 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,              // Flagsthis,           // 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();elsehr = m_pPlayer->InsertEffect(pMFT, TRUE);RETURN_IF_FAILED(hr);return hr;
}

MF 播放音频的 Topology

以下是播放一首 MP3 生成的 Topology,包含三个模块:Source, Decoder 和 Renderer。
注意:不管你选用的是哪种 MF 的编程模型,最后的 Topology 其实是一样的。

MF 播放视频的 Topology

以下是播放一个 WMV 文件生成的 Topology,包含五个模块:Source, Audio Decoder, Audio Renderer, Video Decoder 和 Video Renderer 。

其他框架下的播放

音频播放其他实现方式:

  • Waveform API
  • FFmpeg
  • DirectShow

视频播放其他实现方式:

  • FFmpeg
  • DirectShow
  • Media Foundation I


EOF

音视频播放 via Media Foundation II相关推荐

  1. Qt基于QMediaPlayer音视频播放

    一. 前言 打开Qt的示例,可以找到Media Player Example,这是一个Qt官方提供的简易视频播放器示例,基于QMediaPlayer实现. 但是运行发现,视频怎么也播放不了,后来通过查 ...

  2. android音视频播放器开发百度云,Android 播放端 SDK

    1 概述 PLDroidPlayer 是一个适用于 Android 平台的音视频播放器 SDK,可高度定制化和二次开发,为 Android 开发者提供了简单.快捷的接口,帮助开发者在 Android ...

  3. AVFounction学习笔记之--音视频播放.md

    AVFounction学习笔记之–音视频播放 AVFounction是用于处理音视频的框架.它位于Core Audio.Core Video.Core Media.Core Animation框架之上 ...

  4. MediaPlayer音视频播放

    MediaPlayer介绍 MediaPlayer类可用于控制音频/视频文件或流的播放. MediaPlayer函数: 实例化方式 使用直接new的方式: MediaPlayer mp = new M ...

  5. Media Foundation——媒体类型(1)

    Media Foundaton对象的属性和特性 每一个对象上的数据,都可通过"属性(Attributes)"和"特性(Properties)"来设置.描述.属性 ...

  6. Qt - 音视频播放

    播放内存中的音乐 QFile read("./music/Nevada.mp3");if (!read.open(QIODevice::ReadOnly)){qDebug() &l ...

  7. Media Foundation与DirectShow的具体差别有哪些

    DirectShow是微软公司在ActiveMovie和Video for Windows的基础上推出的新一代基于COM(Component Object Model)的流媒体处理的开发包,与Dire ...

  8. 6、Qt Project之音视频播放

    音视频播放  这里简单的制作了一个音乐播放器,播放器的界面设计如下所示: Step1:这里是界面对应的HTML文件: <?xml version="1.0" encoding ...

  9. Media Foundation

    1简介 Media Foundation是微软在Windows Vista上推出的新一代多媒体应用库,目的是提供Windows平台一个统一的多媒体影音解决方案,开发者可以通过Media Foundat ...

最新文章

  1. python编程图片_python下载百度图片,python图片下载程序
  2. Greenplum,HAWQ interval parser带来的问题 - TPCH 测试注意啦
  3. 三种方式实现日志记录
  4. Microsoft Jet 数据库引擎找不到输入表或查询或者找不到文件
  5. 2020蓝桥杯省赛---java---B---7(分类计数)
  6. 你可能也会掉进这个简单的 String 的坑
  7. uva 111 History Grading(最长公共子序列)
  8. 关于曼哈顿距离和切比雪夫距离的转换和应用
  9. android判断是否登陆过_如何判断车辆是否受到过碰撞?_搜狐汽车
  10. com.haodf.android,有坑!Android新版QQ获取packageInfo引发异常崩溃
  11. 百度开源移动端深度学习框架mobile-deep-learning(MDL)
  12. 【2022最新Java面试宝典】—— Nginx面试题(23道含答案)
  13. mockito验证参数_Mockito验证
  14. 光猫需要已经开通了 telnet 功能
  15. shark恒破解视频的笔记
  16. linux 编辑文件出现E45readonly option is set (add! to .....)
  17. python学习14:字典和集合
  18. 计算机显存影响什么,老司机告诉你显存是怎样影响电脑速度的
  19. 学习笔记整理:Photoshop软件应用-基础-图像选择
  20. 共享锁和排它锁(ReentrantReadWriteLock)

热门文章

  1. C++ list (tcy)
  2. 你真的理解雪崩击穿与齐纳击穿的区别吗?
  3. Spring Security配置全局 AuthenticationManager
  4. 【2023更新】通过硬件触发信号实现OAK多相机之间的同步拍摄
  5. python编程:从入门到实践6-7,6-8,6-9,6-10,6-11,6-12
  6. Spring 官方简介【spring】
  7. Python locals() 的陷阱
  8. 聚名:华为申请注册模盒商标 企业商标注册申请费用是多少?
  9. 2020 CCPC Wannafly Winter Camp Day2 Div.12——A 托米的字符串【构造、数学】
  10. GDUT_排位赛题解报告_第2场_Fence Planning