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;
};
其所存贮的信息包括该Filter的GUID、其所属目录项的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指针指定源Filter,pTarget指针指定目的Filter,接连的过程是尝试把源Filter之上的OutPut Pin连接到目的Filter的Input 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文件所适用的链接图也可能不同,需要根据情况做适当改变。
- DirectShow应用:用程序连接Filters实现媒体播放
- directshow媒体播放
- directshow媒体播放
- directshow媒体播放-即学即会
- 编写DirectShow Filters—Filters如何连接
- 用DirectShow实现音视频文件的播放
- DirectShow应用:只用API调用实现网络播放
- [DirectShow] 004 - About DirectShow Filters
- DirectShow学习之三媒体播放过程分析
- DirectShow学习之三媒体播放过程分析
- DirectShow学习之三媒体播放过程分析
- DirectShow学习之三媒体播放过程分析
- DirectShow学习之三媒体播放过程分析
- DirectShow Video Capture Filters
- Morgan RTP DirectShow Filters
- [DirectShow] 038 - Enumerating Filters
- About DirectShow Filters
- 编写DirectShow Filters—编写transform filters
- 谈工作与职业
- ActionBar
- Hessian4.0.2笔记
- matlab 之 norm函数
- Mongodb FAQ fundamentals(基础篇)
- DirectShow应用:用程序连接Filters实现媒体播放
- linux忘记root密码怎么办
- 检测计算机是否联网
- Eclipse sysout 后使用 Alt+/快捷键 失效
- [Ext JS4] 数据包
- Unity3D编辑器的结构介绍之Project(工程)视图
- android.view.WindowManager$BadTokenException
- 百度质量部测试开发实习生面试总结(技术一面)
- 正则表达式