天天看点

用DirectShow实现视频采集-流程构建

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

1.系统环境及开发环境

l       系统支持directx(win 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系统下的视频采集,我们首先要构造出一个适当的过滤器图表,然后通过应用程序对过滤器图表的管理来完成视频采集的功能。 

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

这几个过滤器分别是:

l       video capture filter 采集设备filter

l       smart tee filter 将没有preview引脚filter的capture引脚分为两支数据流(可选)

l       video venderer  视频渲染及回放filter

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

用DirectShow实现视频采集-流程构建

                                                                    图1

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

用DirectShow实现视频采集-流程构建

                                                                   图2

用DirectShow实现视频采集-流程构建

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

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<void**>(&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);

                     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;                   

                            {           

                                   widechartomultibyte(cp_acp,0,name.bstrval,-1,friendlyname,256,null,null);                            

                                   moniker->bindtoobject(0,0,iid_ibasefilter,(void **)&pcap);

                                   return false;

                            if(propertybag)

                                  propertybag->release();

                                   propertybag=null;

                            moniker->release();

                     }

              enummoniker->release();

       enumhardware->release();

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

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

用DirectShow实现视频采集-流程构建

                  图3

bool initcapturegraphbuilder()

       hresult hr = cocreateinstance(clsid_capturegraphbuilder2, null,

        clsctx_inproc_server, iid_icapturegraphbuilder2, (void**)&pbuild); 

       if(failed(hr))

       hr = cocreateinstance(clsid_filtergraph, 0, clsctx_inproc_server,

            iid_igraphbuilder, (void**)&pgraph);

              pbuild->release();

       }

       pbuild->setfiltergraph(pgraph);   ///////////////////// 过滤器图表添加到管理器中 

       pgraph->queryinterface(iid_imediacontrol,(void **)&pcontrol);

       pgraph->queryinterface(iid_imediaevent,(void **)&pevent);

4)创建剩余的smart tee和video renderer filter并连接成完整的图表 

在创建完video capture filter后,我们需要将filter添加到过滤器图表中。

pgraph->addfilter(pcap,l"capture filter");

然后,我们创建剩余的filter并相连即可,值得注意的是:icapturegraphbuilder2为用户提供了一个renderstream函数,它可以自动构建smart tee和video 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引脚

              hresult hr = psmarttee->findpin(l"capture",&ppin);

ipin * getsmartteepreviewpin()  //取得smart tee preview引脚

              hresult hr = psmarttee->findpin(l"preview",&ppin);

ipin * getrendererpin()  //取得video renderer filter的输入pin

       if(pbuild)

              hresult hr = pbuild->findpin(prender,pindir_input,null,null,false,0,&ppin);

将各个引脚按序连接:

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系统还是一个巨大的宝藏,等待着我们去发掘和开采。