Archive for the 'DirectShow' Category

Enumerating things in DirectShow

2010-4-11 17:52 | by 2ndboy

  在任何领域做段日子以后,或多或少都会积累起一些自己熟悉和习惯的工具函数/类,今天把最近做 DirectShow 程序的时候写的两个小函数晒一下:)

  在 DirectShow 里做东西肯定有需要枚举一些 object 的场合,比如枚举出某 filter 的所有 pin,或者某 pin 支持的所有 media type。由于 DS 基于 COM 来构建,所以在枚举代码里时不时要照顾到 release 的逻辑,而且这些枚举代码也比较有共性,所以就写了两个小函数来封装一把,下面这个是枚举某 filter 特定方向上所有 pin 的:

  1. bool
  2.  EnumPinsOfFilter(
  3.      IBaseFilter    *pFilter,
  4.      PIN_DIRECTION   dir,
  5.      IEnumPins     **ppEnum,
  6.      IPin          **ppPin )
  7.  {
  8.      bool bResult = false;
  9.  
  10.      while( true )
  11.      {
  12.          if( NULL == pFilter || NULL == ppEnum || NULL == ppPin )
  13.              break;
  14.  
  15.          SafeRelease( *ppPin );
  16.  
  17.          HRESULT hResult = S_OK;
  18.          if( NULL == *ppEnum )
  19.          {
  20.              hResult = pFilter->EnumPins( ppEnum );
  21.              if( FAILED( hResult ) )
  22.                  break;
  23.              (*ppEnum)->Reset();
  24.          }
  25.  
  26.          ULONG uFetched = 0;
  27.          hResult = (*ppEnum)->Next( 1, ppPin, &uFetched );
  28.          if( FAILED( hResult ) )
  29.              break;
  30.  
  31.          if( 0 == uFetched )  // no more pins
  32.              break;
  33.  
  34.          PIN_DIRECTION pindir = PINDIR_INPUT;
  35.          hResult = (*ppPin)->QueryDirection( &pindir );
  36.          if( FAILED( hResult ) || pindir != dir )
  37.          {
  38.              SafeRelease( *ppPin );
  39.              continue;
  40.          }
  41.  
  42.          bResult = true;
  43.          break;
  44.      }
  45.  
  46.      return( bResult );
  47.  }

调用者需要实现准备一个枚举器指针和一个 pin 指针,不断调用这个函数,直到得到一个 false 的返回值。每成功的调用一次需要 release 一下 pin 指针,最后一次调用结束后 release 枚举器指针:

  1. IEnumPins *pEnum = NULL;
  2.  IPin      *pPin  = NULL;
  3.  while( EnumPinsOfFilter( pFilter, PINDIR_OUTPUT, &pEnum, &pPin ) )
  4.  {
  5.      // use pPin
  6.      SafeRelease( pPin );
  7.  }
  8.  SafeRelease( pEnum );

下面这个函数是用来枚举某 pin 支持的所有 media type 的:

  1. bool
  2.  EnumMediaTypesOfPin(
  3.      IPin             *pPin,
  4.      IEnumMediaTypes **ppEnum,
  5.      AM_MEDIA_TYPE   **ppType )
  6.  {
  7.      bool bResult = false;
  8.  
  9.      do
  10.      {
  11.          if( NULL == pPin || NULL == ppEnum || NULL == ppType )
  12.              break;
  13.  
  14.          SafeDeleteMediaType( *ppType );
  15.  
  16.          HRESULT hResult = S_OK;
  17.          if( NULL == *ppEnum )
  18.          {
  19.              hResult = pPin->EnumMediaTypes( ppEnum );
  20.              if( FAILED( hResult ) )
  21.                  break;
  22.              (*ppEnum)->Reset();
  23.          }
  24.  
  25.          ULONG uFetched = 0;
  26.          hResult = (*ppEnum)->Next( 1, ppType, &uFetched );
  27.          if( FAILED( hResult ) )
  28.              break;
  29.  
  30.          if( 0 == uFetched )  // no more types
  31.              break;
  32.  
  33.          bResult = true;
  34.      }
  35.      while( false );
  36.  
  37.      return( bResult );
  38.  }

用法类似,不多说了;)

-=-= =-=-

iPhone OS 4.0 发布以后,果然支持多任务,不过不是真正的多任务就对了。当然,这也不一定完全是坏处,还是要兼顾电力和内存的限制,达到硬件限制和使用体验的最佳平衡。不管怎么说,到夏天的时候就可以升级了。

Build DirectShow Source Filter with Media Foundation

2010-3-29 20:27 | by 2ndboy

  Microsoft 在 Windows 7 里增加了对 H.264 和 AAC 这两种日渐流行的编码格式的 native support。对于 H.264,在 DirectShow 里你可以找到一个名字叫“Microsoft DTV-DVD Video Decoder”的 filter。相应的,在 Media Foundation 里有一个叫“Microsoft H264 Video Decoder”的 MFT;对于 AAC,在 DirectShow 里你可以找到一个名字叫“Microsoft DTV-DVD Audio Decoder”的 filter。在 Media Foundation 里与之对应的 MFT 叫做“Microsoft AAC Audio Decoder”。

  虽然 Windows 7 提供了对上述格式的支持,但是基于 DirectShow 构建的应用程序如果想 playback MP4 视频文件的话却不能直接从这个改进里获得好处,原因是 Microsoft 并没有随 H.264/AAC decoder 一起发布 MP4 的 source filter。所以基于 DirectShow 架构的程序想要回放 MP4 文件的话,有两个解决方案:第一种方法是自己实现一个 MP4 文件的 parser,输出合适的 media type 来配接系统自带的 H.264/AAC decoder,当然也可以用现成的。第二种方法是用 Media Foundation 来实现一个特殊的 DirectShow source filter,这个 source filter 调用 MF 来解码 MP4 数据,拿到原始数据以后直接推送给 A/V renderer。

  第一种方法需要熟悉 MP4 容器的结构,我没有动手实现过,所以这里不详述。现成的解决方案有 Haali Media SplitterGabest MP4 Splitter

  Haali Media Splitter 其实不是一个单纯的 MP4 parser,它还支持其它几种容器格式,具体可以看它官网上的介绍。Gabest MP4 Splitter 作为 guliverkli(MPC) 的一部分发行,没发现它有独立的官方网站。据两者都用过的用户说 Gabest MP4 Splitter 相对来说更稳定一些。我只尝试了 Gabest MP4 Splitter,注册过它的 DLL/ax 以后系统里多了两个新的 filter(MP4 source 和 MP4 splitter),这时如果用 GraphEdit render 一个 MP4 文件的话,会发现 MP4 source 连接了 MP4 splitter,MP4 splitter 分别连接了 Microsoft DTV-DVD Audio Decoder 和 Microsoft DTV-DVD Video Decoder,最后连接至 Audio Renderer 和 Video Renderer。

  第二种解决方案,如果只看过 Media Session 的文档的话,会感觉这是个不可能的任务,幸好 Microsoft 在 Windows 7 里对 Media Foundation 做了改进,新增的一些新组件和接口使得这个任务变得简单起来。在 Windows 7 里,我们可以用 Source Reader 来实现一个可以直接输出 uncompressed data 的 source filter。

  Source Reader 可以在某些场合下替代 Media Session 来处理多媒体音视频数据,好处是接口非常简单,还可以选择是读取 compressed data 还是 uncompressed data。你可以简单的把 Source Reader 看作是没有接 renderer 的 Media Session。具体的实现就不多说了,感兴趣的可以自行参考 Source Reader 的使用文档例子。下面侃侃 Source Reader 和 DXVA。

  Source Reader 的使用文档里其实是简单的提过一下如何配合 DXVA 做硬件加速的,但是我的实做结果是——这要看你把 Source Reader 用在什么地方了。如果在创建 Source Reader 实例的模块里你同时也做 render,那么这个硬件加速和可能实现的(这里之所以用“可能”这两个字是因为我没有去做这个尝试,因为这里提到的方案属于下面要说的情况);如果 render 是交给其它模块做的(像我这里提到的把 Source Reader 包装成一个 DS 的 source filter,rendering 是在 renderer 里做的),那 Source Reader 跟 renderer 之间的数据交换和 D3D 设施共享就不容易完成。

  把 Source Reader 的硬件加速打开可以简化成如下代码:

  1. IDirect3D9 *m_pD3D9 = Direct3DCreate9( D3D_SDK_VERSION );
  2.  
  3.  IDirect3DDevice9 *m_pD3D9Device = NULL;
  4.  HRESULT hResult = m_pD3D9->CreateDevice( ..., &m_pD3D9Device );
  5.  
  6.  UINT uResetToken = 0;
  7.  IDirect3DDeviceManager9 *m_pD3D9DeviceManager = NULL;
  8.  hResult = DXVA2CreateDirect3DDeviceManager9( &uResetToken, &m_pD3D9DeviceManager );
  9.  
  10.  hResult = m_pD3D9DeviceManager->ResetDevice( m_pD3D9Device, uResetToken );
  11.  
  12.  IMFAttributes *pAttributes = NULL;
  13.  hResult = MFCreateAttributes( &pAttributes, 1 );
  14.  hResult = pAttributes->SetUnknown( MF_SOURCE_READER_D3D_MANAGER, m_pD3D9DeviceManager );
  15.  hResult = pAttributes->SetUINT32( MF_SOURCE_READER_DISABLE_DXVA, FALSE );

  Source Reader 开启 DXVA 硬件加速要用到 Direct3D device manager,D3D device manager 其实是用来在 decoder 和 renderer 之间共享 D3D device 的。如果作为 Source Reader 使用者的我们没有直接负责 render 的话,已经用 GPU 解压到显存的 uncompressed data 要被复制出来,这样做的性能损耗据说比较大。所以在这种情况下,没什么好的方法来开启 DXVA,而且开了也没用。

-=-= 我是很普通的分割线 =-=-

WinMEnc 之后,又发现了一个可以压 iPod Touch 视频的好工具,而且同样开源——Handbrake。WinMEnc 什么都好,就是在处理 WMV 和某些 AVI 的时候,压完的 M4V 视频有问题,而且 iPT 不认,Handbrake 就没这个问题,推荐 iPT 机主使用,双剑合璧,基本上无敌;-)

FOURCC

2010-1-31 22:34 | by 2ndboy

  在 DirectShow 里面,各种 audio/video 的编码格式都可以用一个 GUID 来表示,比如 RGB24 的 GUID 是 MEDIASUBTYPE_RGB24(e436eb7d-524f-11ce-9f53-0020af0ba770)。但是在多媒体播放软件里(比如暴风影音),我们经常看到的是类似 AVC1、AAC2 这样的表示方法,这种用 4 个字符来表示的多媒体数据编码格式叫做 FourCC(Four-Character Codes)。

  FourCC 是一个 32 bits 的数值,基本上你可以把这个 32 bits 的值看作是 FourCC 里 4 个字符的 ASCII 码的组合,比如 YUY2 的 FourCC 是 0x32595559(Y=0x59, U=0x55, 2=0x32)。但并不是所有 FourCC 都符合这个规律,比如我上面举的 RGB24 就不是这样地:)

  FourCC 跟它对应的 GUID 之间是可以相互转换的,其实在 FourCC 后面加上 0000-0010-8000-00AA00389B71 就得到了它所对应的 GUID。比如 FourCC YUY2 所对应的 GUID MEDIASUBTYPE_YUY2 的值就是 {32595559-0000-0010-8000-00AA00389B71}。DirectShow 里面提供了一个工具类就是用来干这件事的:GUID g = (GUID)FOURCCMap( MAKEFOURCC(‘Y’,’U’,’Y’,’2′) );。

  FourCC 的分配是由 Microsoft 管理的,在这里可以找到一个已经注册的 FourCC 的列表,当然,不太全。

通过 IWMPGraphCreation 接口来定制 WMP 的 Filter Graph

2010-1-10 16:46 | by 2ndboy

  Windows Media Player 内部也是利用 DirectShow 来播放音视频内容的,这一点可以通过注册了 DirectShow Filter Graph SpyGraphEdit 来证实。那么有没有可能把 WMP 控件内嵌到我们自己的程序里,然后控制它的 Filter Graph 生成过程呢?答案就是 IWMPGraphCreation 接口。去年(其实是上个月;))研究了一下这个接口,最后终因版本要求过高,不适合用在我们的项目里而放弃使用,这里简单的做点笔记。

  根据 MSDN,这个接口只有在 WMP 10 及以上版本才支持。实现这个接口,跟 WMP 交互然后达到控制 WMP 的 Filter Graph 生成的步骤如下:

1) 首先要实现一个从 IOleClientSite, IWMPGraphCreation 和 IServiceProvider 这 3 个接口派生的类
2) 从 WMP 控件实例中取得 IOleObject 接口指针,然后调用 IOleObject::SetClientSite() 把我们新实现的类作为 WMP 的 site
3) WMP 会从它的 site 那里通过 QueryInterface() 得到 IServiceProvider 接口指针
4) 得到 IServiceProvider 接口以后 WMP 会调用 IServiceProvider::QueryService() 来尝试得到 IWMPGraphCreation 的接口指针
5) 在从我们这里得到 IWMPGraphCreation 接口指针以后,WMP 会调用我们的 IWMPGraphCreation::GetGraphCreationFlags(),这时我们需要给 flag 设置一个 WMPGC_FLAGS_USE_CUSTOM_GRAPH,表示我们想要定制 WMP 的 Filter Graph
6) WMP 控件在播放音视频文件之前会调用我们的 IWMPGraphCreation::GraphCreationPreRender() 实现,同时把它内部的 Filter Graph 接口指针传给我们,拿到 Filter Graph 指针以后我们就可以做我们想做的事情了

  最大的问题出在 step 5,WMPGC_FLAGS_USE_CUSTOM_GRAPH 这个 flag 在 WMP 12 及以上版本才支持,所以这个版本需求实在是有点太高了!另外一个意外是我的实测观察到,在 WMP 12 上面,step 5 及其后的动作,WMP 会做两遍,而不是我们直觉中的一遍,原因不明(Google 到一些资料,也有人遇到了这样的问题)。

DirectShow filter 体系初探

2009-12-6 13:11 | by 2ndboy

  再次工作上项目需要,要研究下 DirectShow 的 filter 体系,以及通过将自己实现的 filter 插入到播放 audio/video 时的 filter graph,最后达到获取进入 render filter 前的已经解码的 A/V 数据的目的。

  上周五利用下午时间已经找到了实现的方向,记录如下。其实网上公开的资料多如牛毛,现在的感受,不管是做哪方面的 research,只要不是高精尖到什么隐形飞行器涂料技术,你基本都可以在 Internet 上找到很多线索,有时候甚至是标准答案(所以说熟练运用搜索引擎很重要,这是快速学习的基础所在)。

  DirectShow 基于 COM 来构建,设计思想非常值得借鉴。在 DS 中发挥作用的组件都被称作 filter,每个 filter 有各自不同的作用,当它们被正确的连接在一起时就可以完成 audio/video 的回放。

  filter 这个名字起的不错,当多媒体数据流经一个个“过滤器”,经过 A/V 分离、decode 和 render 之后被最终呈现的用户面前。总体上说,有这么 3 类 filter:
1) source filter,负责读取源数据
2) transform filter,负责做各种数据变换
3) render filter,负责渲染结果

  每个 filter 都暴露出一到多个 pin,他们各自之间通过 pin 进行连接。source filter 只有 output pin,而 render filter 只有 input pin,transform filter 则既有 input 又有 output pin。当这些 filter 正确的通过各自的 pin 连接在一起时,就形成一个 filter graph。

  就拿用 DS 来播放一个 MP3 来说,filter graph 里会出现如下几个 filer:
一个 source filter,后面接一个 MPEG-I Stream Splitter(当然这只是一个 audio,所以不会分离出 video 数据来),然后是一个 MPEG Layer-3 Decoder,最末端是一个 Default DirectSound Device 的 render filter。
filter graph

  所以看来我们如果想在这中间插一杠子,把已经解码过的 A/V 数据拿出来的话,就需要开发一个 transform filter。经过一番 Google,发现我们要做的就是实现一个 Sample Grabber Filter,它本质上还是一个 transform filter,DS 架构里有这么一号的本意是为了在开发 transform filter 的过程中方便测试 transform filter 工作是否正常的,但 sample grabber filter 也同样非常适合抓取流经 filter graph 的数据!其实系统中是存在一个现成的 Sample Grabber Filter 的,我们要做的工作就是把这个 filter 加到 filter graph 里来,然后去实现一个 Sample Grabber Filter Callback 就可以拿到数据了。