在Windows平台上实现一个文件播放器有什么好的开发库和方案呢?方案有很多,比如基于FFmpeg,VLC的插件,mplayer,Directshow。用FFmpeg来实现文件格式解析、分离视频音频流、解码是很方便的,但是要实现一个播放器,还要实现视音频的显示和回放、视音频同步的处理,要做很多额外的开发工作,比较麻烦。而用VLC的插件不方便调试,扩充功能要改VLC的源代码,不灵活。而用Directshow来做播放器在Windows平台上是比较成熟和简单的方案,所以特别给大家介绍一下。

微软的Directshow是一个实现多媒体播放,视音频处理等功能的多媒体库,并且提供了Filter的机制使得多媒体任务的各个处理单元(读取数据、分离、解码、编码、回放)达到模块化,提高了复用性。开发者可以用别人提供的Filter,也可以实现自己的Filter来处理特定的任务。每个Filter至少有一个Pin,Pin分输入Pin和输出Pin,按照Filter的类型输入Pin和输出Pin的数目不等。每个Pin都有自己接受的媒体格式,比如视频渲染器Filter的输入Pin只接受RGB或YUV的格式。两个Filter的Pin连接前需要对Pin进行媒体类型协商(确定两方的Pin的媒体类型是否一致),如果协商成功,两个Pin就可以连接起来。一连串的Filter通过这种机制连接形成一个Graph,从而完成复杂的多媒体任务,比如播放文件,录制麦克风的音频,采集摄像头图像。下面演示了一个播放AVI文件的Graph的Filter连接图:

在上面的Graph图中,包括几种Filter:Source Filter,Splitter,Video Decoder,Audio Decoder,Video Renderer,Audio Renderer。而这些Filter大部分系统已经提供了,还有少数功能需要第三方的Filter来完成。因为多媒体文件的格式众多,所以播放文件的Graph需要加入支持不同格式的Filter,其中最主要的Filter是:分离器,解码器。解码器Filter目前有很多了,免费的而又出名的解码器Filter有FFDshow。FFDshow底层用到了FFmpeg,基于Directshow框架来实现,解码效率很高,但是比较庞大和臃肿,现在已基本不维护了。并且FFDshow只提供了编码器和解码器,还没有实现分离器。以前微软平台上的Directshow开发员会了支持尽量多的媒体格式,想尽办法从各处收集到各种分离器Filter,结果程序打包之后体积很大,并且兼容各种格式要插入不同的的Filter使程序变得复杂。很多第三方Filter不开源,加上测试不够,稳定性不好,容易产生很多问题。幸好,现在有个老外开发了一套Directshow插件,叫LAV Filters。不错,是一套,包括分离器,视频解码器和音频解码器。只要安装这套插件,播放大多数的多媒体格式基本上没问题了,省了开发员很多功夫。还有,这个LAV Filters套件还是开源的,作者对工程的更新速度很快,已经修复了很多Bug,现在变得很稳定了,连大名鼎鼎的开源播放器MPC也使用了LAV Filters,将它列为优先选用的插件。

说了那么多,大家一定很想使用LAV Filters来做开发吧?不用急,我下面就介绍如何在Directshow Graph中使用它。
首先,我们要知道这几个Filter的CLSID,下面是这几个Filter的CLSID定义:
  DEFINE_GUID(CLSID_LAVSplitter,

0x171252A0, 0x8820, 0x4AFE, 0x9D, 0xF8, 0x5C, 0x92, 0xB2, 0xD6, 0x6B, 0x04);

DEFINE_GUID(CLSID_LAVVideoDecoder, 
                0xEE30215D, 0x164F, 0x4A92, 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F);

DEFINE_GUID(CLSID_LAVAudioDecoder,

0xE8E73B6B, 0x4CB3, 0x44A4, 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91);

另外还有一个LAV Source Filter,CLSID是:

DEFINE_GUID(CLSID_LAVSource,

0xB98D13E7, 0x55DB, 0x4385, 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38);

这个LAV Source Filter的功能跟LAVSplitter差不多,都是分离器,不同的是它没有输入Pin,它集成了了Async Source Filter + LAVSplitter的功能。
下面我们就准备在Directshow的Graph中加入这些Filter。首先,我们需要实现一个类来封装播放文件的所有接口,下面是类的声明:

  1. class CDXGraph
  2. {
  3. private:
  4. IGraphBuilder * mGraph;
  5. IMediaControl * mMediaControl;
  6. IMediaEventEx * mEvent;
  7. IBasicVideo * mBasicVideo;
  8. IBasicAudio * mBasicAudio;
  9. IVideoWindow * mVideoWindow;
  10. IMediaSeeking * mSeeking;
  11. IBaseFilter * m_pSourceFilter;
  12. IBaseFilter * m_pDumpFilter;
  13.  
  14. DWORD mObjectTableEntry;
  15. UINT m_GraphMsg;
  16.  
  17. public:
  18. bool m_bEnableSound;
  19. int m_nJPEGQuality;
  20. SIZE m_PictureSize;
  21. UINT m_nFramerate;
  22.  
  23. public:
  24. CDXGraph();
  25. virtual ~CDXGraph();
  26.  
  27. public:
  28. virtual bool Create(void);
  29. virtual void Release(void);
  30. virtual bool Attach(IGraphBuilder * inGraphBuilder);
  31.  
  32. IGraphBuilder * GetGraph(void); // Not outstanding reference count
  33. IMediaEventEx * GetEventHandle(void);
  34.  
  35. UINT GetGraphState();
  36.  
  37. bool SetClipSourceRect(RECT rcSource);
  38. bool SetDisplayWindow(HWND inWindow, LPRECT rcTarget);
  39. bool SetNotifyWindow(HWND inWindow, long lMsg);
  40. bool ResizeVideoWindow(long inLeft, long inTop, long inWidth, long inHeight);
  41.  
  42. void HandleEvent(LONG * eventCode);
  43.  
  44. bool Run(void); // Control filter graph
  45. bool Stop(void);
  46. bool Pause(void);
  47.  
  48. bool IsRunning(void); // Filter graph status
  49. bool IsStopped(void);
  50. bool IsPaused(void);
  51.  
  52. //bool SetFullScreen(BOOL inEnabled);
  53. //bool GetFullScreen(void);
  54.  
  55. // IMediaSeeking
  56. bool GetCurrentPosition(LONGLONG * outPosition); //获得当前播放时间(单位为100纳秒, 等于10^(-4) ms )
  57. bool GetStopPosition(LONGLONG * outPosition);
  58. bool SetCurrentPosition(LONGLONG inPosition); //设置当前播放时间(单位为100纳秒, 等于10^(-4) ms )
  59. bool SetStartStopPosition(LONGLONG inStart, LONGLONG inStop);
  60. bool GetDuration(LONGLONG * outDuration); //获得文件时间长度(单位为100纳秒, 等于10^(-4) ms )
  61. bool SetPlaybackRate(double inRate); //设置播放速度
  62. bool GetPlaybackRate(double * outRate);
  63.  
  64. // Attention: range from -10000 to 0, and 0 is FULL_VOLUME.
  65. bool SetAudioVolume(long inVolume);//调节音量
  66. long GetAudioVolume(void);
  67. // Attention: range from -10000(left) to 10000(right), and 0 is both.
  68. bool SetAudioBalance(long inBalance);
  69. long GetAudioBalance(void);
  70.  
  71. bool RenderFile(const TCHAR * inFile, DWORD & dwError);
  72. bool SnapshotBitmap(const TCHAR * outFile);
  73. bool GetTotalFrames(LONGLONG * outNum);
  74.  
  75. private:
  76. void AddToObjectTable(void) ;
  77. void RemoveFromObjectTable(void);
  78.  
  79. bool QueryInterfaces(void);
  80.  
  81. HRESULT ConnectFilters(IPin * inOutputPin, IPin * inInputPin, const AM_MEDIA_TYPE * inMediaType = 0);
  82. void DisconnectFilters(IPin * inOutputPin);
  83.  
  84. HRESULT RenderFilter(IBaseFilter * pFilter);
  85. HRESULT GetVideoProps(IPin * pVideoOutputPin);
  86. };

  

 接着列出最核心的一个函数CDXGraph::RenderFile,它负责创建FilterGraph,将各个Filter添加到Graph和连接起来。下面是函数的实现:

  1. bool CDXGraph::RenderFile(const TCHAR * inFile, DWORD & dwError)
  2. {
  3. if (mGraph == NULL)
  4. {
  5. dwError = ERROR_INVALID_POINTER;
  6. return false;
  7. }
  8.  
  9. dwError = 0;
  10.  
  11. HRESULT hr;
  12.  
  13. bool bVideoPinConnected = false;
  14. bool bAudioPinConnected = false;
  15. bool bPrivateStreamPinConnected = false;
  16.  
  17. #ifndef UNICODE
  18. WCHAR wszFilePath[MAX_PATH] = {0};
  19. MultiByteToWideChar(CP_ACP, 0, inFile, -1, wszFilePath, MAX_PATH);
  20. #else
  21. TCHAR wszFilePath[MAX_PATH] = {0};
  22. lstrcpy(wszFilePath, inFile);
  23. #endif
  24.  
  25. CComPtr<IFileSourceFilter> pFileSource;
  26. CComPtr<IBaseFilter> pSplitter;
  27. CComPtr<IBaseFilter> pVideoDecoder;
  28. CComPtr<IBaseFilter> pVideoRenderer;
  29.  
  30. #if 0
  31. hr = AddFilterByCLSID(mGraph, CLSID_MpegSourceFilter, L"Mpeg Splitter ", &pSplitter );
  32. if(FAILED(hr))
  33. {
  34. OutputDebugString("Add Mpeg Splitter Filter Failed \n");
  35. return FALSE;
  36. }
  37. #else
  38. hr = AddFilterByCLSID(mGraph, CLSID_LAVSource, L"LAV Source Splitter ", &pSplitter );
  39. if(FAILED(hr))
  40. {
  41. OutputDebugString("Add LAV Splitter Filter Failed \n");
  42. return FALSE;
  43. }
  44. #endif
  45. hr = pSplitter->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSource);
  46. if(FAILED(hr))
  47. {
  48. dwError = ERROR_GET_INTERFACE_FAIL;
  49. return false;
  50. }
  51.  
  52. hr = pFileSource->Load(wszFilePath, NULL);
  53. if(FAILED(hr))
  54. {
  55. dwError = ERROR_LOADFILE_FAIL;
  56. return false;
  57. }
  58.  
  59. hr = AddFilterByCLSID(mGraph, CLSID_LAVVideoDecoder, L"LAV Video Decoder", &pVideoDecoder);
  60. if(FAILED(hr))
  61. {
  62. OutputDebugString("Add LAV Video Filter Failed \n");
  63. //return FALSE;
  64. }
  65.  
  66. hr = AddFilterByCLSID(mGraph, CLSID_VideoMixingRenderer9, L"VMR9 Renderer ", &pVideoRenderer);
  67. if(FAILED(hr))
  68. {
  69. OutputDebugString("Add VMR9 Renderer Filter Failed \n");
  70. return FALSE;
  71. }
  72.  
  73. hr = RenderFilter(pSplitter);
  74. if(SUCCEEDED(hr))
  75. bVideoPinConnected = true;
  76. else
  77. bVideoPinConnected = false;
  78.  
  79. if(!bAudioPinConnected && !bVideoPinConnected)
  80. {
  81. OutputDebugString("RenderFilter Failed!\n");
  82.  
  83. OutputDebugString("第二次尝试用RenderFile自动连接\n");
  84.  
  85. if (FAILED(mGraph->RenderFile(wszFilePath, NULL)))
  86. {
  87. OutputDebugString("RenderFile Failed!\n\n");
  88.  
  89. dwError = ERROR_AUTO_RENDERFILE_FAIL;
  90. return false;
  91. }
  92.  
  93. IBaseFilter * pVideoRenderer = NULL;
  94. hr = FindVideoRenderer(mGraph, &pVideoRenderer);
  95. if(SUCCEEDED(hr))
  96. {
  97. CComPtr<IPin> pVideoPin = GetInPin(pVideoRenderer, 0);
  98. GetVideoProps(pVideoPin);
  99. }
  100. }
  101.  
  102. if(!m_bEnableSound)
  103. {
  104. SetAudioVolume(-10000);
  105. }
  106.  
  107. OutputDebugString("RenderFile Succeeded!\n");
  108.  
  109. return true;
  110. }

CDXGraph::RenderFile函数显示加入了这几个Filter:LAV Source,LAV Video Decoder,VMR9。其中,VMR9是视频渲染器,负责渲染图像和根据时间戳控制视频帧何时显示;LAVSource负责读取文件和从文件容器里分离出视频流和音频流,当我们调用AddFilterByCLSID函数(实际上调用了COM接口CoCreateInstance函数)创建这个Filter实例时,它是没有加载文件的,我们必须查询它的IFileSourceFilter接口指针,通过这个接口指针设置文件路径,把文件加载进去,如果文件加载成功,LAV Source会根据文件容器里媒体流的数目生成对应的OutputPin。接着,加入LAV Video Decoder,然后调用RenderFilter函数把LAV Source的每个OutputPin自动与下游的Filter进行连接,因为连接Filter的时候Graph Manager会优先选用已经添加到Graph中的Filter,那么LAV Source的Video Output Pin就会尝试与Video Decoder进行连接,而Audio Output Pin也一样,但是由于我们没有显示加入任何的Audio Decoder,Graph Manager会从系统安装的Filter中找到一个合适的解码器Filter插入进去,然后自动连接两个Filter的OutputPin与InputPin。如果所有Filter连接成功,那么播放文件Graph的Filter链路图就像下面这样子:

上面所讲的内容是Directshow开发很基本的操作,相信熟悉Directshow的读者看了觉得很熟悉和简单。

Mitov组件商使用的就是Directshow和Windows Api模式。

Windows 视频Directshow开发介绍的更多相关文章

  1. Microsoft Tech Summit 2018 课程简述:利用 Windows 新特性开发出更好的手绘视频应用

    概述 Microsoft Tech Summit 2018 微软技术暨生态大会将于10月24日至27日在上海世博中心举行,这也会是国内举办的最后一届 Tech Summit,2019 年开始会以 Mi ...

  2. Directshow开发播放器相关介绍

    原文地址:http://www.cnblogs.com/qiufa/archive/2006/12/19/596949.html DirectShow技术是DirectX推出的建立在DirectDra ...

  3. 转:Directshow开发的一些例子

    DirectShow Filter 开发典型例子分析 --字幕叠加 (FilterTitleOverlay)1 本文分析一下<DirectShow开发指南>中的一个典型的Transform ...

  4. windows phone 8 开发系列(三)程序清单说明与配置

    一 清单文件内容介绍 当我们先建了一个项目之后,我们可以看到vs自动会为我们创建了很多文件,正常人都会先一个个去翻看下每个文件都是干啥的,都主要写了些啥,在这些文件中,在Properies目录下面,我 ...

  5. XNA 4.0 环境搭建和 Hello World,Windows Phone 游戏开发

    XNA 4.0 环境搭建和 Hello World,Windows Phone 游戏开发 使用 Scene 类在 XNA 中创建不同的场景(八) 摘要: 平方已经开发了一些 Windows Phone ...

  6. Windows Phone 7 开发环境的搭建

    本节开始进行Windows Phone 开发环境的搭建,包括所需要的操作系统及硬件的介绍,开发工具的下载与安装,以及开发工具的介绍等.由于Jake Lin老师的视频中讲解的是早期的Windows Ph ...

  7. FFMPEG SDK 开发介绍(原创)

    来源:http://blog.sina.com.cn/s/blog_62a8419a01016exv.html 本文是作者在使用ffmpeg sdk开发过程中的实际经验,现在与大家分享,欢迎学习交流. ...

  8. 《Windows IoT 应用开发指南》

    物物互联的时代已经到来,智能家居.智慧校园.智慧交通.可穿戴.无人机.全息投影,各种各样的新名词.黑科技层出不穷.当我们为五年前能够通过手机控制家电而欣喜若狂的时候,可曾憧憬过当前使用增强现实设备完成 ...

  9. Windows 10 IoT Serials 1 - 针对Minnow Board MAX的Windows 10 IoT开发环境搭建

    目前,微软针对Windows IoT计划支持的硬件包括树莓派2,Minnow Board MAX 和Galileo (Gen 1和Gen 2).其中,Galileo (Gen 1和Gen 2)运行的是 ...

随机推荐

  1. Android 仿美团网,探索使用ViewPager+GridView实现左右滑动查看更多分类的功能

    看下效果图,自己考虑下自己会如何实现,然后再继续看看作者的实现~ 不记得什么时候,我留意到到美团网首页有使用ViewPager+GridView实现左右滑动查看更多分类的一个功能,感觉它很有趣,于是想 ...

  2. Linux学习之CentOS(三)----将Cent0S 7的网卡名称eno16777736改为eth0

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...

  3. javascript event visualize

    很多时候拿到一个spa,特别是基于jquery的比较复杂的spa时,如果你好奇他是如何工作的,往往没有头绪. 由于spa基本上都是基于事件触发的,一个可行的办法是通过查看事件处理代码能够对spa有一个 ...

  4. Django之modelform简介

    在django中内置了form类和model类,当页面中的form值和model字段值完全一样时,此时可以通过model生成一个完全一样的form,Django中的modelForm就因此而生. 目标 ...

  5. Oracle EBS 获取用户挂的职责 请求 请求的类别(RTF还是什么的)

    select fu.user_ID, fu.user_name, fu.start_date, fu.END_DATE, fu.description, fe.last_name, fr.RESPON ...

  6. Oracle EBS 更新客户地点

    --更新客户地点 declare x_return_status ); x_msg_count NUMBER; x_msg_data ); x_profile_id NUMBER; l_locatio ...

  7. 解决python2和python3的pip冲突

    最近突然出现了一种情况当电脑上同时安装python2和python3的时候会导致我的pip冲突 . 最终经过我的发现是因为其环境没有配置好 还有就是没有找到精准的包导致的 1.下载python2.7, ...

  8. [翻译] USING GIT IN XCODE [2] 在XCODE中使用GIT[2]

    USING GIT IN XCODE http://www.cimgf.com/2013/12/10/using-git-in-xcode/ USING AN EXISTING REMOTE PROJ ...

  9. 限定pan手势只能在圆内移动view

    限定pan手势只能在圆内移动view 效果: 虽然看起来很简单,但实现原理还是稍微有点复杂-_-!! 核心的地方,就是需要计算pan手势的点与指定点的距离,不能超过这个距离,超过了就让动画还原,很容易 ...

  10. Python静态方法实现单实例模式

    单实例模式 当程序中需要同一个实例就可以解决问题的场景,可以使用单实例模式