DirectShow使用大全

一.使用directshow时应该添加lib文件Strmiids.lib、Quartz.lib 、Strmbase.lib 具体添加那个见下面的说明

Header Files
All DirectShow applications use the Dshow.h header file. Some DirectShow interfaces require additional header files. These requirements are noted in the interface reference.

Library Files
The following table shows the library files DirectShow uses.

Library file Description 
Quartz.lib Exports the AMGetErrorText function. If you do not call this function, this library is not required. 
Strmbase.lib If you do not use the DirectShow base classes, this library is not required. For information about the difference between Strmbase.lib and Qtzbase.lib, see Building DirectShow Filters  
Strmiids.lib Exports class identifiers (CLSIDs) and interface identifiers (IIDs). All DirectShow applications require this library.

In your build environment, the SDK Include and Lib directories should be the first directories in the search path. This ensures that you are using the most recent versions of these files.

DirectShow初探

可能到现在为止,还没有哪个玩过游戏的人没有接触过Microsoft的DirectX的。因为现今大多数的游戏都是用DirectX开发出来的。

相比之下,DirectShow只是DirectX的一个子集。DirectX中还包括DirectSound、Direct3D等集合。DirectShow主要用来处理一些与音视频有关的多媒体任务,比如音视频采集、回放等。过年回来后,在一个项目中要求在WIN32下采集视频信号,以前做过GDI的,不过效率低下,而且效果也很烂,所以决定加速,怎样加速,答案就在DirectShow。

1、DirectShow的结构

我们知道,Windows操作系统只用了CPU中的两个特权级(0和3)。0是内核模式,它可以直接访问硬件;3是用户模式,它不能直接访问硬件。DirectShow的基本工作单元是Filter(过滤器),过滤器的就像一个筛子,它一般有输入引脚(PIN)和输出引脚,数据从输入引脚流入而从输出端口流出。DirectShow中的过滤器一般分为下列3类:

       1) Source Filter (用于提供原始的多媒体数据,比如一个视频文件)

       2)Transform Filter(用于处理从Source Filter 传过来的多媒体数据,比如MPEG-4解码器)

       3)Rendering Filter(用于显示、回放和存储多媒体数据给用户,比如文件写入器)

要完成特定的多媒体功能,必须用相应的Filter组成特定的Filter Graph。多媒体数据在Graph中流动,到达Rendering Filter时回放给用户。

用户应用程序怎么样控制Filter Graph呢?他必须创建一个相应的Filter Graph Manager,应用程序向Manager发送相应的命令(Command),然后从Manager那接收相应的Event,最后做出相应的响应。

每个Filter都有操作硬件的能力,这就是为什么DirectShow能如此高效的使用多媒体了。

DirectShow的结构原理图如下:

2、使用DirectShow编写一个简单的应用程序

      使用DirectShow编写应用程序的一般步骤如下:

      1)根据系统完成的功能构建相应的Filter Graph。

      2)构建Graph中的每个Filter,并将它们连接起来。

      3)创建Filter Graph Manager,运用Application控制Manager,从而控制整个的流程。

       4)DirectShow是基于COM(组件对象模型)的,所以在编写Filter前必须初始化COM库。

      下面是一个完整的播放AVI文件的小程序,其中的注解说明了编程的步骤。

#include
#include     //DirectShow必须包含的头文件,LIB库为Strmiids.lib 和 Quartz.lib

int main()
{
 IGraphBuilder *pGraph = NULL;      //  用于创建Filter Graph的接口
 IMediaControl *pControl = NULL;    //  用于向Filter Graph Manager发送Command
 IMediaEvent   *pEvent = NULL;        //   用于接收Filter Graph Manager发出的Event

 // Initialize the COM library.
 HRESULT hr = CoInitialize(NULL);   //初始化COM Library

 if( FAILED(hr) )
 {
  fprintf(stderr,"could not init the COM library!");
  return 0;
 }

//创建Filter Graph Manager

hr = CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder,(void **)&pGraph);     

 if( FAILED(hr) )
 {
  fprintf(stderr,"could not create Filter Graph Manager!");
  return 0;
 }

 //查询并获得接口指针
 hr = pGraph->QueryInterface(IID_IMediaControl,(void **)&pControl);
 hr = pGraph->QueryInterface(IID_IMediaEvent,(void **)&pEvent);

 // RenderFile库函数是少数几个能够自动创建Graph的成页,所以这里省去了创建Graph这一步
 hr = pGraph->RenderFile(L"e://football.avi",NULL);

 if( SUCCEEDED(hr) )
 {
  hr = pControl->Run();

  if( SUCCEEDED(hr) )
  {
   // 等待视频结束
   long evCode;
   pEvent->WaitForCompletion(INFINITE, &evCode);

   // 无限等待

  }
  
 }

 pControl->Release();
 pEvent->Release();
 pGraph->Release();

//释放接口

 CoUninitialize();   //御载COM 库


 return 0;
}

三、

用DirectShow实现视频采集

DirectShow作为DirectX的一个子集,它为用户提供了强大、方便的多媒体开接口,并且它拥有直接操作硬件的能力,这使得它的效率远胜于用GDI等图形方式编写的多媒体程序。前面一篇文章已经对DirectShow作了粗略的介绍,阐述了它的原理及一些编程方法。这里结合实践中运用DirectShow实现视频采集(WIN32)来加深对DirectShow的理解和操作能力。

1.系统环境及开发环境

  

l       系统支持DirectXWin 2K以上系统)

l       VC++  6.0安装有DirectX  SDK(最好与系统支持的DirectX版本相同)

l       视频采集设备(如USB摄像头,本文以USB PC Camera 310P为例)

2.基本思想

DirectShow的基本原理是多媒体数据在过滤器图表(Filter Graph)中流动,通过过滤器图表中各过滤器(Filter)实现在功能,最终实现多媒体数据在渲染过滤器(Vendering Filters)中的显示和回放。

前面我们已经知道,一般过滤器可分为三类:源过滤器(Source Filters)、转换过滤器(Transform Filters)、渲染过滤器(Vendering Filters)。它们分别完成数据提供、数据格式转换(压缩编码等)和数据渲染和回放功能。所以,为了实现在WIN32系统下的视频采集,我们首先要构造出一个适当的过滤器图表,然后通过应用程序对过滤器图表的管理来完成视频采集的功能。 

这里我们一般需要23个过滤器。为什么这个数字会不准确呢?那是因为一方面系统采集设备的驱动模型是不确定的(一般有WDMVFW两种);另一方面同一采集设备它们的Filter会由于驱动程序的差异造成Filter中引脚(Pin)的不一致;还有就是不同总线的采集设备(PCIUSBAGP)它们的Filter也是不一致的。比如:同为USB摄像头,有些Filter有两个输出引脚(CapturePreview);而有些Filter则只有一个输出引脚(Capture)。这里Preview引脚用来将做视频预览,Capture引脚用来将输入数据以供编码、保存等用处。

这几个过滤器分别是:

l       Video Capture Filter 采集设备Filter

l       Smart Tee Filter 将没有Preview引脚FilterCapture引脚分为两支数据流(可选)

l       Video Venderer  视频渲染及回放Filter

通过上面3个过滤器,我们可以构造出一个完整的视频采集过滤器图表(如图1 

 

                                                                    1

我们也可以对上面的过滤器图表稍做修改,将它变为一个既可以预览视频,又可以将视频保存为媒体文件的图表(如图2)。

 

                                                                   2

图表构造出来后,接下来就午剩下具体的实现了,我们只需依次构造每个Filter,然后将各信FilterPin按序相连即可完成图表的构造。最后,我们通过应用程序向图表发送命令(通过图表管理器完成)来控制整个视频采集的流程。

3.具体实现

首先我们需要创建几个接口全局变量。

IGraphBuilder *pGraph;   //过滤器图表管理器

ICaptureGraphBuilder2 *pBuild;  //视频采集过滤器图表

IBaseFilter *pCap;   //Video Capture Filter

IBaseFilter *pSmartTee;   //Smart Tee Filter

IBaseFilter *pRender;  //Video Renderer Filter

IMediaControl *pControl;  //用户命令接口,用来控制过滤器图表

IMediaEvent *pEvent;     //过滤器图表事件接口

1)  采集设备枚举

在构造Video Capture Filter前,我们必须列举出系统的所有采集设备,然后才能根据列举的设备名称创建Video Capture Filter。列举设备的函数实现如下

bool ListCaptureDevices()

{

       ICreateDevEnum *pDevEnum = NULL;  //设备枚举器Interface

       IEnumMoniker *pEnum = NULL;       //名称枚举Interface

       // Create the System Device Enumerator.

       HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,

    CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,

    reinterpret_cast(&pDevEnum));  //创建设备枚举COM对象

      

       if (SUCCEEDED(hr))

       {

              // Create an enumerator for the video capture category.

              hr = pDevEnum->CreateClassEnumerator(

                       CLSID_VideoInputDeviceCategory,

                       &pEnum, 0);       //创建视频采集设备枚举COM对象

       }

       ////////////////////////////////////////////////////////////

       IMoniker *pMoniker = NULL;

       if(pEnum == NULL)

       {

              return false;  //如果没有设备,返回

       } 

       while (pEnum->Next(1, &pMoniker, NULL) == S_OK)  //依次枚举,直至为空

       {

             

              IPropertyBag *pPropBag;

              hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,

                  (void**)(&pPropBag));

              if (FAILED(hr))

              {

                  pMoniker->Release();

                  continue;  // Skip this one, maybe the next one will work.

              }

               // Find the description or friendly name.

              VARIANT varName;

              VariantInit(&varName);

              hr = pPropBag->Read(L"Description", &varName, 0);

              if (FAILED(hr))

              {

                     hr = pPropBag->Read(L"FriendlyName", &varName, 0);  //设备友好名称

              }

              if (SUCCEEDED(hr))

              {

               // Add it to the application's list box.

                     char displayName[1024];

                     WideCharToMultiByte(CP_ACP,0,varName.bstrVal,-1,displayName,1024,"",NULL);

                     m_nList.AddString(displayName);  //字符转换,枚举名称均为UNICODE 

                     VariantClear(&varName);

              }

              pPropBag->Release();

              pMoniker->Release();

       }

       return true;

}

2)创建Video Capture Filter

根据枚举出来的设备友好名称(FriendlyName)创建Video Capture Filter

bool CTest_capDlg::CreateHardwareFilter(const char * friendlyName)

{   //friendlyName与所有的设备名称依次对比,如果相同,则创建Filter

       ICreateDevEnum * enumHardware = NULL;

  HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_ALL

,IID_ICreateDevEnum,(void **)&enumHardware); 

       if( FAILED(hr) )

       {

              return false;

       }

       IEnumMoniker * enumMoniker = NULL; 

       hr = enumHardware->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&enumMoniker,0); 

       if(enumMoniker)

       {

              enumMoniker->Reset();

              ULONG fetched = 0;

              IMoniker * moniker = NULL;

              char friendlyName[256];

              while(!pCap && SUCCEEDED(enumMoniker->Next(1,&moniker,&fetched)) && fetched)

              {

                     if(moniker)

                     {

                            IPropertyBag * propertyBag = NULL;

                            VARIANT name;

                            friendlyName[0]=0;

                            hr=moniker->BindToStorage(0,0,IID_IPropertyBag,(void **)&propertyBag);                           

                            if(SUCCEEDED(hr))

                            {

                                   name.vt=VT_BSTR;

                                   hr = propertyBag->Read(L"FriendlyName",&name,NULL);

                            }

                            else

                                   return false;                   

                            if(SUCCEEDED(hr))

                            {           

                                   WideCharToMultiByte(CP_ACP,0,name.bstrVal,-1,friendlyName,256,NULL,NULL);                            

                                   moniker->BindToObject(0,0,IID_IBaseFilter,(void **)&pCap);

                            }

                            else

                                   return false;

                            if(propertyBag)

                            {

                                  propertyBag->Release();

                                   propertyBag=NULL;

                            }

                            moniker->Release();

                     }

              }

              enumMoniker->Release();

       }

       enumHardware->Release();

       return true;

}

3)创建视频采集过滤器图表

DirectX较高版本中一般都为开发者提供了一个ICaptureGraphBuilder2接口,开发者可以通过它方便地创建视频采集过滤器图表,然后再将它添加到IGraphBuilder图表管理器中(如图3)。                                                             

                         

                  3

bool InitCaptureGraphBuilder()

{

       HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,

        CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuild); 

       if(FAILED(hr))

              return false;

       hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,

            IID_IGraphBuilder, (void**)&pGraph);

       if(FAILED(hr))

       {

              pBuild->Release();

              return false;

       }

       pBuild->SetFiltergraph(pGraph);   ///////////////////// 过滤器图表添加到管理器中 

       pGraph->QueryInterface(IID_IMediaControl,(void **)&pControl);

       pGraph->QueryInterface(IID_IMediaEvent,(void **)&pEvent);

       return true;

}

4)创建剩余的Smart TeeVideo Renderer Filter并连接成完整的图表 

在创建完Video Capture Filter后,我们需要将Filter添加到过滤器图表中。

pGraph->AddFilter(pCap,L"Capture Filter");

然后,我们创建剩余的Filter并相连即可,值得注意的是:ICaptureGraphBuilder2为用户提供了一个RenderStream函数,它可以自动构建Smart TeeVideo Renderer Filter并将它们连接成一个完整的图表,从而完成视频采集的功能。

pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,

    pCap, NULL, NULL);

为了说明整个过程,这里我们按部就搬,依次创建各个Filter

Smart Tee

CoCreateInstance(CLSID_SmartTee,NULL,CLSCTX_INPROC_SERVER,IID_IBaseFilter,(void **)&pSmartTee);

Video Renderer Filter

CoCreateInstance(CLSID_VideoRenderer,NULL,CLSCTX_INPROC_SERVER,IID_IBaseFilter,(void **)&pRender); 

创建好各个Filter后,我们依次取得它们的引脚(Pin),将它们按序相连即可。

IPin * GetSmartTeeInputPin()  //取得Smart Tee 输入引脚

{

       if(pSmartTee)

       {

              IPin * pPin;

              HRESULT hr = pSmartTee->FindPin(L"Input",&pPin);

              if(SUCCEEDED(hr))

              {

                     pPin->Release();

                     return pPin;

              }

       }

       return NULL;

}

IPin * GetSmartTeeCapturePin()  //取得Smart Tee Capture引脚

{

       if(pSmartTee)

       {

              IPin * pPin;

              HRESULT hr = pSmartTee->FindPin(L"Capture",&pPin);

              if(SUCCEEDED(hr))

              {

                     pPin->Release();

                     return pPin;

              }

       }

       return NULL;

}

IPin * GetSmartTeePreviewPin()  //取得Smart Tee Preview引脚

{

       if(pSmartTee)

       {

              IPin * pPin;

              HRESULT hr = pSmartTee->FindPin(L"Preview",&pPin);

              if(SUCCEEDED(hr))

              {

                     pPin->Release();

                     return pPin;

              }

       }

       return NULL;

}

IPin * GetRendererPin()  //取得Video Renderer Filter的输入Pin

{

       if(pBuild)

       {

              IPin * pPin;

              HRESULT hr = pBuild->FindPin(pRender,PINDIR_INPUT,NULL,NULL,FALSE,0,&pPin);

              if(SUCCEEDED(hr))

              {

                     pPin->Release();

                     return pPin;

              }

       }

       return NULL;

将各个引脚按序连接:

IPin * pOut = FindVideoPin(&PIN_CATEGORY_CAPTURE);

IPin * pIn = GetSmartTeeInputPin();

pGraph->Connect(pOut,pIn);  //Video Capture Filter’ Capture Pin à Smart Tee’Input Pin

IPin * mOut = GetSmartTeePreviewPin(); 

IPin * mIn = GetRendererPin();                 

      

             

pGraph->Connect(mOut,mIn); //Smart Tee’s Preview Pin à Video Renderer Filter’s Input Pin

这样,一个完整的视频采集图表管理器就构造完成了。

5)开始视频采集

通过用户命令接口,我们可以方便的完成开始,暂停,停止视频采集。

pControl->Run();

pControl->Stop(); 

4.小结

通过上述视频采集过程的实现,不难发现DirectShow是一个流程清晰,开发容易的多媒体开发工具。我们在使用DirectX为我们提供的Filter构建多媒体功能的同时,也可以自己着手创建具备特定功能的Filter。总之,Direct系统还是一个巨大的宝藏,等待着我们去发掘和开采。

 四

提取DirectShow中视频采集的数据.

DirectShow中,数据流(Data Flow)都是依次流过各个Filter的.它对数据的管理也有自己的方法,而且并没有向用户提供一个统一的接口,供用户操作数据流.这里以提取视频采集在的每帧为位图数据为例,说说如何在Directshow中提取数据.

这里我们用到了DirectShow提供给我们的接口ISampleGrabber,并定义了一个供它回调的CSampleGrabberCB对象(继承ISampleGrabberCB接口).

我们知道,DirectShow中的数据存储是通过Sample完成的,所以提取数据也需要通过SampleGrabber.

步骤如下:

1.建立CSampleGrabberCB对象.

   class CSampleGrabberCB : public ISampleGrabberCB 
   {

       STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize )

       {

                 //Callback method that receives a pointer to the sample buffer.

       }

      STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample )
      {
               //Callback method that receives a pointer to the media sample.
      }

   }

2.定义ISampleGrabber接口并初始化

   CComPtr< ISampleGrabber > m_pGrabber;

   HRESULT hr;

   hr = m_pGrabber.CoCreateInstance( CLSID_SampleGrabber );

   if(FAILED(hr))

          //error action;

3.定义Grabber Filter,设置它的媒体类型,并将它加入Graph中

   CComQIPtr< IBaseFilter, &IID_IBaseFilter > pGrabBase( m_pGrabber );

  CMediaType VideoType;
  VideoType.SetType(&MEDIATYPE_Video);
  VideoType.SetSubtype(&MEDIASUBTYPE_RGB24);
  hr = m_pGrabber->SetMediaType(&VideoType);

  hr = pGraph->AddFilter(pGrabBase,L"Grabber");

4.设置回调(CallBack),使Grabber能够通过BufferCB自动完成采集数据.

   // don't buffer the samples as they pass through
      //
      hr = m_pGrabber->SetBufferSamples( FALSE );

      // only grab one at a time, stop stream after
      // grabbing one sample
      //
      hr = m_pGrabber->SetOneShot( FALSE );

      // set the callback, so we can grab the one sample
      //
      hr = m_pGrabber->SetCallback( &mCB, 1 );  //mCB为CSampleGrabber对象

这样,在DirectShow数据流动过程中,mCB.bufferCB会自动执行,提取Graph中的数据

怎样改变DirectShow中采集的视频大小.

在利用Directshow采集视频的过程中,一般初始化时我们要给Capture Graph视频参数..比如它的长和宽,亮度什么的...当然,如果你什么都不给,DirectShow会为你分配一个系统默认的值..这里介绍如何在初始化时改变采集的视频的长和宽..

1.定义IAMStreamConfig Interface

IAMStreamConfig *pConfig;

2.初始化IAMStreamConfig Interface

hr = pBuild->FindInterface(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Interleaved,pCap,IID_IAMStreamConfig,(void **)&pConfig);

// 这pBuild是一个ICaptureGraphBuilder2 Interface.

  if( hr != NOERROR )
  {
   hr = pBuild->FindInterface(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,pCap,IID_IAMStreamConfig,(void **)&pConfig);
   if(hr == NOERROR)
    AfxMessageBox("load config filter succeed!");
   else
   {
    AfxMessageBox("load config filter failed!");
    return;
   }
  }

3.设置参数(取原来的参数,修改想要改变的部分)

AM_MEDIA_TYPE * mmt;

pConfig->GetFormat(&mmt);    //取得默认参数

VIDEOINFOHEADER * vvih = (VIDEOINFOHEADER*) mmt->pbFormat;

AM_MEDIA_TYPE * pmt = mmt;

VIDEOINFOHEADER * pvih = (VIDEOINFOHEADER*) pmt->pbFormat;

pvih->bmiHeader.biHeight=288;  //修改采集视频的高为288
pvih->bmiHeader.biWidth=352;   //修改采集视频的宽为352

pmt->pbFormat = (unsigned char *) pvih;

pConfig->SetFormat(pmt);   //重新设置参数

这样,DirectShow采集的视频的Data Flow被设置为宽为352,高为288.





1,时间戳

1.           // Set the graph clock. 

2.           IMediaFilter *pMediaFilter = 0; 

3.           hr=m_pBuilder->QueryInterface(IID_IMediaFilter, (void**)&pMediaFilter); 

4.           pMediaFilter->SetSyncSource(NULL); 

通过如上设置,可以使文件尽快解码完成。

1.           IMediaSample * pSample; 

2.           REFERENCE_TIME startTime,stopTime; 

3.           hr=pSample->GetTime(&startTime,&stopTime); 

获取该帧数据的相对时间戳,以100ns为单位。即使文件不以实际速度播放,获取的时间戳也是按实际帧率递增的。

2,在dshowutil.cpp中提供了一些常用的函数,如GetOutPin,GetInPin,FindAudioRenderer,FindVideoRenderer等。

在dshow中可调用pBuilder->connect(pout,pin),连接两个filter,如果这两个pin之间不能直接连接,dshow会自动插入必要的filter。但是智能连接不能在同一个输出pin上连接两次,如一个文件同时含有音视频,此时通过splitter filter需要连接两个filter分别处理音视频,智能连接不能对source filter的同一个输出pin调用两次connect。

在dshow中通过pBuilder->AddFilter(…),加入filter。在调用renderfile智能连接时,会对加入的filter优先连接。另外在enumfilter进行枚举时,只要加入进来的filter都能找到,不必一定要进行connect。

3,获取音视频的基本信息,如宽度、高度,音频采样率、通道等信息。

通过IMediaSample.GetMediaType或者IPin. ConnectionMediaType得到一个AM_MEDIA_TYPE结构体。从该结构体中元素pbFormat可以得到VIDEOINFOHEADER或者WAVEFORMATEX信息。从中可以访问音视频的一些基本信息。

4,可以通过m_pSeeking->SetPositions设置文件播放的开始时间和结束时间,如果结束时间大于文件长度,播放到文件尾结束。如果开始时间大于文件长度,播放不会进行。调用上述函数后,从media sample中获取到的时间(hr=pSample->GetTime(&startTime,&stopTime);)是相对于文件开始播放位置的,而不是文件头。 

5,自定义guid用下面的方式

DEFINE_GUID(MEDIATYPE_HikVideo,0x0d117d31, 0xef56,0x4fbe, 0x89, 0x10, 0x28, 0x53, 0x81, 0x60, 0xe0, 0x4e);

另外需要在之前包含#include "initguid.h"。该头文件不能在多个文件中包含。

6,尽量用CComPtr这种方式代替IPin* ,可以减少内存泄露,不需要应用程序调用release释放内存。

(lipku)

你可能感兴趣的:(windows)