DirectShow应用:用程序连接Filters实现媒体播放

来源:互联网 发布:土生华人知乎 编辑:程序博客网 时间:2024/05/14 02:36

                 DirectShow应用:用程序连接Filters实现媒体播放

 

Windows平台下的音视频应用,多以DirectShow为开发工具,通过Graphedit选择合适的Filters并连接成实际的链路,就可以实现简单播放。但在程序中如何实现呢,笔者带着这个问题进行了探索,现将初步成果呈现出来,以期收抛砖引玉之效,共同提高。

一.       枚举Filters

Windows系统中的Filters都经过了注册,并按照Filter目录进行组织,故为方便应用程序设计,宜将系统中所有的Filter依照其目录结构枚举出来,统一存放和管理,为此,设计了一个类CFilterManager来进行统一的管理,其定义如下:

#pragma once

#include "strmif.h"

#include "CurrentDevice.h"

#include "Category.h"

#include "ReadyFilter.h"

#pragma comment(lib,"strmiids.lib")

#pragma comment(lib,"quartz.lib")

class CFilterManager

{

public:

         ~CFilterManager(void);

         HRESULT init(void);

         IBaseFilter *GetFilterByFriendlyName(CString friendlyName );

         IBaseFilter *GetFilterByClsid( CLSIDclsid );

         IBaseFilter *GetFilterByDisplayName(CString displayName );

         staticCFilterManager *GetInstance(void );

         voidPlayFiles( void );

public:

         HRESULT EnumFilters(IEnumMoniker*pEnumCat, CLSID category);

         CList<CCategory *, CCategory *>mCategoryList;

         CList<CReadyFilter *, CReadyFilter*> mReadyFilterList;

private:

         HRESULT ConnectFilters( IGraphBuilder*pGraph, IBaseFilter *pSource, IBaseFilter *pTarget );

         CFilterManager(void);

         voidCreateCategory( void );

         voidCreateFilters( void );

         CComPtr<ICreateDevEnum>pCreateDevEnum;

         staticCFilterManager *pManager;

};

成员变量pCreateDevEnum用于存放系统枚举指针,列表mCategoryList存放所有Filter目录项,列表mReadyFilterList用于存放所有枚举到的Filter

系统中只应有一个Filter管理者实例,故此类设计成单件Singlton模式,通过静态函数GetInstance获取其唯一实例,其实现代码如下:

CFilterManager *CFilterManager::GetInstance( void )

{

         if(NULL == pManager )

         {

                   pManager = new CFilterManager();

                   if(NULL != pManager )

                   {

                            HRESULT hr =pManager->init();

                            if( FAILED(hr) )

                            {

                                     pManager =NULL;

                                     return NULL;

                            }

                            pManager->CreateCategory();

                            pManager->CreateFilters();

                   }

         }

         returnpManager;

}

如上所示,在第一次创建实例成功后,即调用init函数进行COM组件库的初始化,如初始化失败则撤销新建的实例,返回NULL以阻止后续程序操作。初始化成功后,再调用函数CreateCategory枚举系统中所有的Filter目录项,并存于列表mCategoryList中,调用CreateFilters枚举所有的Filters存于列表mReadFilterList中,这样所有的Filter都在掌握之中,方便查取。由于这几个函数只需调用一次,故放在这里与创建唯一实例的代码连成一体。

HRESULTCFilterManager::init(void)

{

         // Initializethe COM library

         HRESULT hr = CoInitializeEx( NULL,COINIT_APARTMENTTHREADED );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("COM initializing failed!") );

                   returnhr;

         }

    // Create thesystem device enumerator.

    hr =CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,

                          IID_ICreateDevEnum, (void**)&pCreateDevEnum);

         returnhr;

}

以上代码首先调用CoInitializeEx初始化COM库,然后取得系统设备枚举器指针pCreateDevEnum供后面的代码使用。

void CFilterManager::CreateCategory(void )

{

         ASSERT( NULL != pCreateDevEnum );

 

    HRESULT hr;

    CComPtr<IEnumMoniker> pEmCat;

         CComPtr<IPropertyBag> pPropBag;

         // Use themeta-category that contains a list of all categories.

         hr =pCreateDevEnum->CreateClassEnumerator( CLSID_ActiveMovieCategories,&pEmCat, NULL );

         // Enumerateover every category and get the category CLSID and description.

         if(FAILED(hr) )

         {

                   return;

         }

    CComPtr<IMoniker> pMoniker;

         ULONG wished = 1, fetched = 1;

    while( S_OK== pEmCat->Next( wished, &pMoniker, &fetched ) )

    {

                   VARIANT varCatName;

                   VariantInit(&varCatName);

                   VARIANT varCatClsid;

                   VariantInit(&varCatClsid);

                   CLSID clsidCat;

 

        // Associatemoniker

        hr = pMoniker->BindToStorage(0, 0,IID_IPropertyBag, (void **)&pPropBag);

                   //Read CLSID string from property bag

                   if(SUCCEEDED(hr))

                   {

                            hr =pPropBag->Read(L"CLSID",&varCatClsid, 0);

                   }

 

                   //Convert to a CLSID

                   if(SUCCEEDED(hr))

                   {

                            hr =CLSIDFromString(varCatClsid.bstrVal, &clsidCat);

                   }

 

                   //Read the category name. If that fails, use the CLSID string.

                   if(SUCCEEDED(hr))

                   {

                            HRESULT hrTmp =pPropBag->Read(L"FriendlyName",&varCatName, 0);

                            CString desc;

                            if(SUCCEEDED(hrTmp))

                            {

                                     desc =CString( varCatName.bstrVal );

                            }

                            else

                            {

                                     desc =CString( varCatClsid.bstrVal );

                            }

                            CCategory *pCat = new CCategory( desc, clsidCat );

                            POSITION pos =mCategoryList.GetHeadPosition();

                            POSITION currentpos= pos;

                            while( NULL != pos )

                            {

                                     currentpos= pos;

                                     CCategory*p = mCategoryList.GetNext( pos );

                                     if( desc.Compare(p->GetDesc()) < 0 )

                                     {

                                               break;

                                     }

                            }

                            mCategoryList.InsertBefore(currentpos, pCat );

                   }

                   pMoniker = NULL;

                   pPropBag = NULL;

                   VariantClear(&varCatName);

                   VariantClear(&varCatClsid);

 

    } // for loop

         pMoniker = NULL;

}

上述代码中,通过pCreateDevEnum->CreateClassEnumerator(CLSID_ActiveMovieCategories, &pEmCat, NULL )调用,取得系统Filter目录项枚举的指针存于指针变量pEmCat中,下面的while循环则枚举出每一个Filter目录项,取得该目录项之CLSID及描述desc,生成类CCategory之实例并存于列表mCategoryList中,代码段

                            POSITION pos =mCategoryList.GetHeadPosition();

                            POSITION currentpos= pos;

                            while( NULL != pos )

                            {

                                     currentpos= pos;

                                     CCategory*p = mCategoryList.GetNext( pos );

                                     if( desc.Compare(p->GetDesc()) < 0 )

                                     {

                                               break;

                                     }

                            }

用于对目录进行排序,找出依目录项描述desc排序的插入位置。

 

枚举所有Filter的代码如下:

void CFilterManager::CreateFilters(void )

{

    // to checkcategory

         if(mCategoryList.IsEmpty() )

         {

                   return;

         }

         // to get theenvironment pointers

         HRESULT hr;

    intnClasses=0;

         IPropertyBag *pPropBag = NULL;

         // to browsethe categories

         POSITION pos =mCategoryList.GetHeadPosition();

         IEnumMoniker *pEnumMoniker = NULL;

         while(NULL != pos )

         {

                   CCategory * pCat =mCategoryList.GetNext( pos );

                   CLSID psid;

                   pCat->GetCLSID( &psid);

 

                   hr =pCreateDevEnum->CreateClassEnumerator( psid, &pEnumMoniker, 0 );

                   if(SUCCEEDED(hr) && pEnumMoniker )

                   {

                            EnumFilters(pEnumMoniker, psid );

                   }

         }

}

Filter目录项列表中取出每一个目录项之CLSID,调用接口ICreateDevEnum之方法CreateClassEnumerator取得Filter枚举器指针pEnumMoniker,调用EnumFilters方法取得所有Filter相关信息存于列表mReadyFilterList中,其代码如下:

HRESULTCFilterManager::EnumFilters( IEnumMoniker *pEnumCat, CLSID category )

{

         ASSERT( NULL != pEnumCat );

    HRESULT hr=S_OK;

    IMoniker *pMoniker = NULL;

    // Enumerate allitems associated with the moniker

    while (S_OK == pEnumCat->Next(1, &pMoniker, NULL) )

    {

        IPropertyBag *pPropBag = NULL;

                   CLSID clsidFilter;

                   VARIANT varName;

        VARIANT varFilterClsid;

                   VariantInit(&varName);

                   VariantInit(&varFilterClsid);

                   chardisplayName[1024];

                   WCHAR * wzDisplayName = NULL;

                   hr=pMoniker->GetDisplayName(NULL,NULL,&wzDisplayName);

                   if(SUCCEEDED(hr))

                   {

                            WideCharToMultiByte(CP_ACP,0,wzDisplayName,-1,displayName,1024,"",NULL);

                CoTaskMemFree(wzDisplayName);

                   }

                   IBaseFilter *pBaseFilter =NULL;

                   hr =pMoniker->BindToObject( NULL, NULL, IID_IBaseFilter, (void **)&pBaseFilter );

                   if(NULL == pBaseFilter )

                   {

                            continue;

                   }

 

        // Associatemoniker with a file

        hr = pMoniker->BindToStorage( 0, 0,IID_IPropertyBag,(void **)&pPropBag );

                   //Read filter name from property bag

                   if(SUCCEEDED(hr))

                   {

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

                   }

 

        // Readfilter's CLSID from property bag.

                   if(SUCCEEDED(hr))

                   {

                            // Read CLSID string from property bag

                            hr =pPropBag->Read(L"CLSID",&varFilterClsid, 0);

 

            if(SUCCEEDED(hr))

            {

                hr = CLSIDFromString(varFilterClsid.bstrVal,&clsidFilter);

            }

            elseif (hr == E_PROP_ID_UNSUPPORTED)

            {

                clsidFilter = GUID_NULL; // No CLSID is listed.

                hr = S_OK;

            }

        }

 

                   if(SUCCEEDED(hr))

                   {

                            CStringtmpName(varName.bstrVal);

                            CReadyFilter*pFilter = new CReadyFilter( clsidFilter,category,

                                                                                     tmpName,CString(displayName), pBaseFilter );

                            // to find the inserting position

                            POSITION pos =mReadyFilterList.GetHeadPosition();

                            POSITION currentpos = pos;

                            while( NULL != pos )

                            {

                                     currentpos= pos;

                                     CReadyFilter*p = mReadyFilterList.GetNext( pos );

                                     if( tmpName.Compare(p->GetFriendlyName()) < 0 )

                                     {

                                               break;

                                     }

                            }

                            mReadyFilterList.InsertBefore(currentpos, pFilter );

                   }

                   VariantClear(&varName);

                   VariantClear(&varFilterClsid);

        // Cleanupinterfaces

        SAFE_RELEASE(pPropBag);

        SAFE_RELEASE(pMoniker);

    }

    return hr;

}

其中,类CReadyFilter用于保存取到的Filter相关信息,其定义如下:

#pragma once

#include <DShow.h>

class CReadyFilter

{

public:

         CReadyFilter(void);

         CReadyFilter( CLSID clsid, CLSIDcategory, CString friendlyName,

                                       CString displayName, IBaseFilter *pFilter );

         ~CReadyFilter(void);

         voidSetCLSID( CLSID clsid );

         CLSID GetCLSID( void );

         voidSetCategory( CLSID category );

         CLSID GetCategory( void );

         voidSetFriendlyName( CString friendlyName );

         CString GetFriendlyName( void );

         voidSetDisplayName( CString displayName );

         CString GetDisplayName( void );

         IBaseFilter *GetFilter( void );

private:

         CLSID clsid;

         CLSID category;

         CString friendlyName;

         CString displayName;

         IBaseFilter *pFilter;

};

其所存贮的信息包括该FilterGUID、其所属目录项的GUID、两种名称以及其接口指针pFilter,通过该接口指针即可直接使用该Filter

上述代码完成了创建Filter目录项列表及其Filter列表的工作,这样就可方便地通过Filter之友好名称friendlyName获取Filter,代码如下:

IBaseFilter*CFilterManager::GetFilterByFriendlyName( CString friendlyName )

{

         POSITION pos =mReadyFilterList.GetHeadPosition();

         CReadyFilter *pReady = NULL;

         while(NULL != pos )

         {

                   pReady =mReadyFilterList.GetNext( pos );

                   if(0 == friendlyName.Compare( pReady->GetFriendlyName() ) )

                   {

                            break;

                   }

         }

         if(NULL == pReady )

         {

                   returnNULL;

         }

         returnpReady->GetFilter();

}

一个简单的列表查询代码,就不多言了。

二.       连接Filters

为将查询得到的Filter组织起来,需要通过IGraphBuilder接口取得Filter管理器的指针,并连接两个Filter,函数ConnectFilters担此重任,其原型如下:

         HRESULT ConnectFilters( IGraphBuilder*pGraph, IBaseFilter *pSource, IBaseFilter *pTarget );

参数pGraph指定Filter管理器,pSource指针指定源FilterpTarget指针指定目的Filter,接连的过程是尝试把源Filter之上的OutPut Pin连接到目的FilterInput Pin上,成功则返回S_OK,实现代码如下:

HRESULTCFilterManager::ConnectFilters( IGraphBuilder *pGraph, IBaseFilter *pSource,IBaseFilter *pTarget )

{

         ASSERT( NULL != pSource && NULL!= pTarget );

 

         // to get thepin list from the target

         CList< IPin *, IPin * >targetList;

         IEnumPins *pEnum = NULL;

         HRESULT hr = pTarget->EnumPins(&pEnum );

         if(FAILED(hr) )

         {

                   returnhr;

         }

         ULONG wished = 1, fetched;

         IPin * pPin = NULL;

         while(S_OK == pEnum->Next( wished, &pPin, &fetched ) )

         {

                   PIN_DIRECTION direction;

                   pPin->QueryDirection(&direction );

                   if(direction == PINDIR_INPUT )

                   {

                            targetList.AddTail(pPin );

                   }

         }

         if(targetList.IsEmpty() )

         {

                   returnE_FAIL;

         }

 

         // toenumerate the source

         hr = pSource->EnumPins( &pEnum);

         if(FAILED(hr) )

         {

                   targetList.RemoveAll();

                   returnhr;

         }

 

         HRESULT ret = VFW_E_TYPE_NOT_ACCEPTED;

         while(S_OK == pEnum->Next( wished, &pPin, &fetched ) )

         {

                   PIN_DIRECTION direction;

                   pPin->QueryDirection(&direction );

                   if(direction == PINDIR_INPUT )

                   {

                            continue;

                   }

 

                   //to make connections

                   AM_MEDIA_TYPE *pType = NULL;

                   IEnumMediaTypes * pEnumTypes= NULL;

                   hr = pPin->EnumMediaTypes(&pEnumTypes );

                   if(FAILED(hr) )

                   {

                            continue;

                   }

                   //to list all the media types

                   constULONG wished = 1;

                   ULONG fetched = 1;

                   hr = pEnumTypes->Next( 1,&pType, &fetched );

                   if(FAILED(hr) )

                   {

                            continue;

                   }

 

                   //seek for supporting for istreambuilder

                   IStreamBuilder *pStream =NULL;

                   hr = pPin->QueryInterface(IID_IStreamBuilder, (void **)&pStream );

                   if(SUCCEEDED(hr) && NULL != pStream )

                   {

                            AfxMessageBox( _T("Support the stream builder !") );

                   }

                  

                   //try to make a connection

                   POSITION pos =targetList.GetHeadPosition();

                   while(NULL != pos )

                   {

                            IPin *tempPin =targetList.GetNext( pos );

                            if( NULL == tempPin )

                            {

                                     continue;

                            }

                            // attemtp to connect the two pins

                            HRESULT tmphr =pGraph->ConnectDirect( pPin, tempPin, NULL );

                            if( SUCCEEDED(tmphr) )

                            {

                                               // the connection was successful, skip to the next one

                                               ret= S_OK;

                                               break;

                            }

                   }

         }

         targetList.RemoveAll();

         returnret;

}

首先将目标Filter中的所有输入Pin作为待连接的Pin存于列表中,下面枚举源Filter的所有输出Pin,尝试进行配对连接,并尝试尽可能地接连多对Pin,只要有一对Pin连接成功,返回值即为S_OK

三.       播放示例

下面以播放一个avi文件作为示例,用graphedit做的Filter链接图如下:

voidCFilterManager::PlayFiles( void )

{

         // to creategraph manager

         CComPtr<IGraphBuilder> pGraph;

         HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,

                                                                              IID_IGraphBuilder, (void**)&pGraph);

         if(FAILED(hr) )

         {

                   return;

         }

 

         // to get thesource file filter

         IBaseFilter *pSourceFile =GetFilterByFriendlyName( _T("File Source(Async.)") );

         if(NULL == pSourceFile )

         {

                   AfxMessageBox( _T("The friendly name may be with errors!"));

                   return;

         }

 

         // to add thesource filter into the graph manager

         hr = pGraph->AddSourceFilter( L"D:\\test.avi", L"Source Filter", &pSourceFile );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Join Source Filter Error !") );

                   return;

         }

 

         // to get theAVI Splitter filter

         IBaseFilter *pSplitter =GetFilterByFriendlyName( _T("AVISplitter") );

         if(NULL == pSplitter )

         {

                   AfxMessageBox( _T("The friendly name of avi splitter may be witherrors!") );

                   return;

         }

         hr = pGraph->AddFilter( pSplitter, L"AVI Splitter" );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Join AVI Splitter Filter Error !") );

                   return;

         }

 

         // to connectthe two filters

         hr = ConnectFilters( pGraph,pSourceFile, pSplitter );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Connect soure and splitter error !") );

                   return;

         }

 

         // to getMpeg4s Decoder DMO

         IBaseFilter *pMpeg4s =GetFilterByFriendlyName( _T("Mpeg4s DecoderDMO") );

         if( NULL== pMpeg4s )

         {

                   AfxMessageBox( _T("Get Mpeg4s error!") );

                   return;

         }

         hr = pGraph->AddFilter( pMpeg4s, L"Mpeg4s Decoder DMO" );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Join Mpeg4s Decoder DMO Error!") );

                   return;

         }

 

         // to connectsplitter with mpeg4s

         hr = ConnectFilters( pGraph, pSplitter,pMpeg4s );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("connect splitter and mpeg4s error !"));

                   return;

         }

 

         // to getVideo Renderer

         IBaseFilter *pVideoRenderer =GetFilterByFriendlyName( _T("Video Renderer"));

         if(NULL == pVideoRenderer )

         {

                   AfxMessageBox( _T("Get Video Renderer error!") );

                   return;

         }

         hr = pGraph->AddFilter(pVideoRenderer, L"Video Renderer");

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Add Video Renderer Error!") );

                   return;

         }

         // to connectmpeg4s and Video Renderer

         hr = ConnectFilters( pGraph, pMpeg4s,pVideoRenderer );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Connect mpeg4s and Video Renderer error !"));

                   return;

         }

 

         // to get Mp3Decoder

         IBaseFilter *pMp3Decoder = GetFilterByFriendlyName(_T("MP3 Decoder DMO") );

         if(NULL == pVideoRenderer )

         {

                   AfxMessageBox( _T("Get Mp3 Decoder error!") );

                   return;

         }

         hr = pGraph->AddFilter( pMp3Decoder,L"Mp3 Decoder" );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Join Mp3 Decoder Error!") );

                   return;

         }

         // to connectmpeg4s and Video Renderer

         hr = ConnectFilters( pGraph, pSplitter,pMp3Decoder );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Connect mpeg4s and Video Renderer error !"));

                   return;

         }

 

         // to getSound Renderer

         IBaseFilter *pSoundRenderer =GetFilterByFriendlyName( _T("DefaultDirectSound Device") );

         if(NULL == pVideoRenderer )

         {

                   AfxMessageBox( _T("Get Sound Renderer!") );

                   return;

         }

         hr = pGraph->AddFilter(pSoundRenderer, L"Sound Renderer");

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Join Sound Renderer Error!") );

                   return;

         }

         // to connectmpeg4s and Video Renderer

         hr = ConnectFilters( pGraph,pMp3Decoder, pSoundRenderer );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("Connect Mp3 decoder and Sound Renderer error!") );

                   return;

         }

 

         // to chainshas been created already, and then to play it

         IMediaControl *pControl = NULL;

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

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("to get the media control error !") );

                   return;

         }

         IMediaEventEx *pEvent = NULL;

         hr = pGraph->QueryInterface(IID_IMediaEventEx, (void **)&pEvent );

         if(FAILED(hr) )

         {

                   AfxMessageBox( _T("to get media event error !") );

                   return;

         }

 

         // to playnow

         hr = pControl->Run();

         if( SUCCEEDED(hr))

         {

                   longevCode;

                   pEvent->WaitForCompletion(INFINITE, &evCode );

         }

 

         hr = pControl->Stop();

         pControl->Release();

         pEvent->Release();

         pControl = NULL;

         pEvent = NULL;

         pGraph = NULL;

 

         AfxMessageBox( _T("I am here !") );

}

首先取得IGraphBuilder接口指针,调用GetFilterByFriendlyName通过友好名称依秩取得链接中的Filter并加入graphManager中,调用ConnectFilters建立各Filter之间输出Pin和输入Pin之间的连接关系,全部工作完成后取得IMediaControl和IMediaEventEx接口指针用于播放控制,并通过IMediaControl接口方法Run进行播放。函数WaitForCompletion( INFINITE, &evCode )中的参数INFINITE不宜在实际的应用程序中使用,这里只是为了简捷而用之。

不同的avi文件所适用的链接图也可能不同,需要根据情况做适当改变。

原创粉丝点击