Directshow开发的基本技巧.docx
- 文档编号:11563443
- 上传时间:2023-03-19
- 格式:DOCX
- 页数:22
- 大小:25.74KB
Directshow开发的基本技巧.docx
《Directshow开发的基本技巧.docx》由会员分享,可在线阅读,更多相关《Directshow开发的基本技巧.docx(22页珍藏版)》请在冰豆网上搜索。
Directshow开发的基本技巧
Directshow开发的基本技巧
摘要:
本篇文档主要讲述了Directshow开发的一些基本概念和技巧
1视频播放(VideoRendering)
dshow的视频提交过滤器可以在窗口模式和无窗口模式下工作。
在窗口模式下,过滤器创建一个自己的窗口,在里面播放视频。
在无窗口模式下,过滤器直接将视频在应用程序提供的窗口上显示,过滤器本身不创建窗口。
窗口模式
在窗口模式下,视频提交过滤器创建一个窗口,然后将视频祯帖到窗口上,你可以将这个窗口帖到你的应用程序的窗口。
VideoRenderer只支持窗口模式,VMR-7andVMR-9缺省的是窗口模式,也支持无窗口模式。
为了在你的应用程序中显示视频,你可以将视频窗口设置成应用程序的子窗口。
你可以通过
IVideoWindow*pVidWin=NULL;
pGraph->QueryInterface(IID_IVideoWindow,(void**)&g_pVidWin);
pVidWin->put_Owner((OAHWND)hwnd);
pVidWin->put_WindowStyle(WS_CHILD|WS_CLIPSIBLINGS);
RECTgrc;
GetClientRect(hwnd,&grc);
pVidWin->SetWindowPosition(0,0,grc.right,grc.bottom);
结束时一定要清理现场
pControl->Stop();
pVidWin->put_Visible(OAFALSE);
pVidWin->put_Owner(NULL);
无窗口模式
当采用无窗口的模式时,就没有必要暴露IVideoWindow接口了。
为了能够使用VMR的缺省行为,在构建Graph图之前必须要调整VMR。
1创建一个过虑器图表管理器,
2创建一个VMR,加入到graph中,
3调用VMR的IVMRFilterConfig:
:
SetRenderingMode方法设置VMRMode_Windowless标志。
4调用IVMRWindowlessControl:
:
SetVideoClippingWindow给视频指定一个显示窗口。
然后调用IGraphBuilder:
:
RenderFile或者其他的方法来创建其他的Graph。
下面的代码显示了如何创建一个VMR,将其添加到Graph,如何设置无窗口模式
HRESULTInitWindowlessVMR(
HWNDhwndApp,//Windowtoholdthevideo.
IGraphBuilder*pGraph,//PointertotheFilterGraphManager.
IVMRWindowlessControl**ppWc,//ReceivesapointertotheVMR.)
{
if(!
pGraph||!
ppWc)returnE_POINTER;
IBaseFilter*pVmr=NULL;
IVMRWindowlessControl*pWc=NULL;
//CreatetheVMR.
HRESULThr=CoCreateInstance(CLSID_VideoMixingRenderer,NULL,
CLSCTX_INPROC,IID_IBaseFilter,(void**)&pVmr);
if(FAILED(hr))
{
returnhr;
}
//AddtheVMRtothefiltergraph.
hr=pGraph->AddFilter(pVmr,L"VideoMixingRenderer");
if(FAILED(hr))
{
pVmr->Release();
returnhr;
}
//Settherenderingmode.
IVMRFilterConfig*pConfig;
hr=pVmr->QueryInterface(IID_IVMRFilterConfig,(void**)&pConfig);
if(SUCCEEDED(hr))
{
hr=pConfig->SetRenderingMode(VMRMode_Windowless);
pConfig->Release();
}
if(SUCCEEDED(hr))
{
//Setthewindow.
hr=pVmr->QueryInterface(IID_IVMRWindowlessControl,(void**)&pWc);
if(SUCCEEDED(hr))
{
hr=pWc->SetVideoClippingWindow(hwndApp);
if(SUCCEEDED(hr))
{
*ppWc=pWc;//ReturnthisasanAddRef'dpointer.
}
else
{
//Anerroroccurred,soreleasetheinterface.
pWc->Release();
}
}
}
pVmr->Release();
returnhr;
}
你也可以调用下面的函数
IVMRWindowlessControl*pWc=NULL;
hr=InitWindowlessVMR(hwnd,pGraph,&g_pWc);
if(SUCCEEDED(hr))
{
//Buildthegraph.Forexample:
pGraph->RenderFile(wszMyFileName,0);
//ReleasetheVMRinterfacewhenyouaredone.
pWc->Release();
}
下面看看如何设置视频的位置
有两个矩形需要考虑,一个是源矩形,一个是目的矩形。
源矩形决定开始播放视频的位置,目的矩形决定在窗口显示视频的区域。
VMR将源矩形按照目的矩形的大小进行扩展。
IVMRWindowlessControl:
:
SetVideoPosition可以设置两个矩形的大小,源矩形必须小于等于本地视频大小。
你可以通过IVMRWindowlessControl:
:
GetNativeVideoSize获取本地的视频区域大小。
//Findthenativevideosize.
longlWidth,lHeight;
HRESULThr=g_pWc->GetNativeVideoSize(&lWidth,&lHeight,NULL,NULL);
if(SUCCEEDED(hr))
{
RECTrcSrc,rcDest;
//Setthesourcerectangle.
SetRect(&rcSrc,0,0,lWidth/2,lHeight/2);
//Getthewindowclientarea.
GetClientRect(hwnd,&rcDest);
//Setthedestinationrectangle.
SetRect(&rcDest,0,0,rcDest.right/2,rcDest.bottom/2);
//Setthevideoposition.
hr=g_pWc->SetVideoPosition(&rcSrc,&rcDest);
}
处理窗口消息
因为VMR没有自己的窗口,所以当视频需要重画或者改变的时候你要通知它。
1当你接到一个WM_PAINT消息,你就要调用IVMRWindowlessControl:
:
RepaintVideo来重画视频
2当你接到一个WM_DISPLAYCHANGE消息,你就要调用IVMRWindowlessControl:
:
DisplayModeChanged.
3当你接到一个WM_SIZE消息时,重新计算视频的位置,然后调用SetVideoPostion。
下面的代码演示了WM_PAINT消息的处理
voidOnPaint(HWNDhwnd)
{
PAINTSTRUCTps;
HDChdc;
RECTrcClient;
GetClientRect(hwnd,&rcClient);
hdc=BeginPaint(hwnd,&ps);
if(g_pWc!
=NULL)
{
//Findtheregionwheretheapplicationcanpaintbysubtracting
//thevideodestinationrectanglefromtheclientarea.
//(Assumethatg_rcDestwascalculatedpreviously.)
HRGNrgnClient=CreateRectRgnIndirect(&rcClient);
HRGNrgnVideo=CreateRectRgnIndirect(&g_rcDest);
CombineRgn(rgnClient,rgnClient,rgnVideo,RGN_DIFF);
//Paintonwindow.
HBRUSHhbr=GetSysColorBrush(COLOR_BTNFACE);
FillRgn(hdc,rgnClient,hbr);
//Cleanup.
DeleteObject(hbr);
DeleteObject(rgnClient);
DeleteObject(rgnVideo);
//RequesttheVMRtopaintthevideo.
HRESULThr=g_pWc->RepaintVideo(hwnd,hdc);
}
else//Thereisnovideo,sopaintthewholeclientarea.
{
FillRect(hdc,&rc2,(HBRUSH)(COLOR_BTNFACE+1));
}
EndPaint(hwnd,&ps);
}
尽管我们要自己处理onpaint消息,但是已经非常简单了。
2如何处理事件通知(EventNotification)
当一个Directshow的应用程序运行的时候,在filterGraph内部就会发生各种各样的事件,例如,一个filter也许发生数据流错误。
Filter通过给graphmangaer发送事件通知来和graph通信,这个事件通知包括一个事件码和两个事件参数。
事件码表示发生事件的类型,两个参数用来传递信息。
Filter发送的这些事件,其中的一部分可以被Manager直接处理,不通知应用程序,但有一部分事件,Manager将事件放入到一个队列中,等待应用程序处理。
这里我们主要讨论在应用程序中经常遇到的三种事件
EC_COMPLETE表明回放已经结束
EC_USERABORT表明用户中断了回放。
用户关闭视频播放窗口时,视频Render会发生这个事件
EC_ERRORABORT表明出现了一个错误。
应用程序可以通知filtergraphmanager,在某个指定的事件发生时,向指定的窗口发生一个指定的消息。
这样应用程序就可以在消息循环中对发生的事件产生反应。
首先定义消息,
#defineWM_GRAPHNOTIFYWM_APP+1
然后向filtergraphmanager请求IMediaEventEx接口,然后调用IMediaEventEx:
:
SetNotifyWindow方法来设置消息通知窗口
IMediaEventEx*g_pEvent=NULL;
g_pGraph->QueryInterface(IID_IMediaEventEx,(void**)&g_pEvent);
g_pEvent->SetNotifyWindow((OAHWND)g_hwnd,WM_GRAPHNOTIFY,0);
然后在WindowProc函数增加一个处理WM_GRAPHNOTIFY消息的函数
caseWM_GRAPHNOTIFY:
HandleGraphEvent();
break;
HandleGraphEvent()函数具体定义如下
voidHandleGraphEvent()
{
//Disregardifwedon'thaveanIMediaEventExpointer.
if(g_pEvent==NULL)
{
return;
}
//Getalltheevents
longevCode;
LONG_PTRparam1,param2;
HRESULThr;
while(SUCCEEDED(g_pEvent->GetEvent(&evCode,¶m1,¶m2,0)))
{
g_pEvent->FreeEventParams(evCode,param1,param2);
switch(evCode)
{
caseEC_COMPLETE:
//Fallthrough.
caseEC_USERABORT:
//Fallthrough.
caseEC_ERRORABORT:
CleanUp();
PostQuitMessage(0);
return;
}
}
}
在释放IMediaEventEx指针前,要取消事件通知消息,代码如下
//Disableeventnotificationbeforereleasingthegraph.
g_pEvent->SetNotifyWindow(NULL,0,0);
g_pEvent->Release();
g_pEvent=NULL;
3如何枚举系统的设备和过虑器
有时,应用程序需要查看系统中所有的filter。
例如,视频应用程序需要列出系统中可用的捕捉设备。
因为dshow基于com结构的,你在设计程序的时候是没法知道系统中正在使用的过滤器。
Directshow提供了两种方法来枚举系统中注册的过虑器。
1系统设备枚举器
系统设备枚举器提供了一个很好的方法根据种类来枚举系统中注册的过虑器。
也许枚一种不同的硬件都会有自己的过虑器,或许所有的硬件设备共用同一个filter。
这个对于采用WDM驱动程序的硬件很有用。
系统设备枚举器根据不同的种类创建了一个枚举器,例如,音频压缩,视频捕捉。
不同种类的枚举器对于每一种设备返回一个独立的名称(moniker)。
种类枚举器自动将相关的即插即用,演播设备包括进来。
按照下面的步骤使用设备枚举器
1创建枚举器组件,CLSID为CLSID_SystemDeviceEnum
2指定某一种类型设备,参数CLSID,通过ICreateDevEnum:
:
CreateClassEnumerator获取某一种类的枚举器,这个函数返回一个IEnumMoniker接口指针,如果该种类的空或者不存在,这个方法就返回S_FALSE。
因此,当你调用这个函数时一定要检查返回值是否为S_OK,而不要用SUCCEEDED宏。
3然后IEnumMoniker:
:
Next枚举每一个moniker。
这个方法返回一个IMoniker接口指针。
4要想知道设备的名称,可以通过下面的函数IMoniker:
:
BindToStorage
5然后利用IMoniker:
:
BindToObject生成绑定道设备上的filter。
调用IFilterGraph:
:
AddFilter将filter添加到Graph图中。
图1
//CreatetheSystemDeviceEnumerator.
HRESULThr;
ICreateDevEnum*pSysDevEnum=NULL;
hr=CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum,(void**)&pSysDevEnum);
if(FAILED(hr))
{
returnhr;
}
//Obtainaclassenumeratorforthevideocompressorcategory.
IEnumMoniker*pEnumCat=NULL;
hr=pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory,&pEnumCat,0);
if(hr==S_OK)
{
//Enumeratethemonikers.
IMoniker*pMoniker=NULL;
ULONGcFetched;
while(pEnumCat->Next(1,&pMoniker,&cFetched)==S_OK)
{
IPropertyBag*pPropBag;
hr=pMoniker->BindToStorage(0,0,IID_IPropertyBag,
(void**)&pPropBag);//知道设备的名称
if(SUCCEEDED(hr))
{
//Toretrievethefilter'sfriendlyname,dothefollowing:
VARIANTvarName;
VariantInit(&varName);
hr=pPropBag->Read(L"FriendlyName",&varName,0);
if(SUCCEEDED(hr))
{
//DisplaythenameinyourUIsomehow.
}
VariantClear(&varName);
//Tocreateaninstanceofthefilter,dothefollowing:
IBaseFilter*pFilter;
hr=pMoniker->BindToObject(NULL,NULL,IID_IBaseFilter,
(void**)&pFilter);//生成一个filter绑定到设备上。
//Nowaddthefiltertothegraph.
//RemembertoreleasepFilterlater.
pPropBag->Release();
}
pMoniker->Release();
}
pEnumCat->Release();
}
pSysDevEnum->Release();
在上面我们用IMoniker:
:
BindToObject生成绑定道设备上的filter,当然我们还可以用另外的一种方法来生成绑定到设备上的filter
利用IMoniker:
:
GetDisplayName得到moniker的名字。
然后你把moniker的名字做参数传递给IFilterGraph2:
:
AddSourceFilterForMoniker,就可以创建一个绑定到设备的filter了。
在上面我们是调用IMoniker:
:
BindToObject生成filter的,还是上面的简单些。
看看代码吧。
LPOLESTRstrName=NULL;
IBaseFilterpSrc=NULL;
hr=pMoniker->GetDisplayName(NULL,NULL,&strName);
if(SUCCEEDED(hr))
{
//QuerytheFilterGraphManagerforIFilterGraph2.
IFilterGraph2*pFG2=NULL;
hr=pGraph->QueryInterface(IID_IFilterGraph2,(void**)&pFG2);
if(SUCCEEDED(hr))
{
hr=pFG2->AddSourceFilterForMoniker(pMoniker,0,L"Source",&pSrc);
pFG2->Release();
}
CoTaskMemFree(strName);
}
//Ifsuccessful,remembertoreleasepSrc.
2FilterMapper
搜索系统中的filter的另一个方法就是采用FilerMapper。
Filtermapper是一个com对象,它按照一定的条件来搜索系统的filer,它比系统设备枚举器(SystemDeviceEnumerator)的效率要低一些。
所以当你要枚举某特定种类的filter时,你应该使用系统设备枚举器,但是当你搜索支持某种媒体类型的filter时,同时也找不到清晰的filter,你应该使用filtermapper。
FilterMapper暴露一个IFilerMapper2接口,要想搜索一个接口,你可以调用该接口的IFilterMapper2:
:
EnumMatchingFilters方法,这个方法需要传递一些参数来定义搜索条件,同时该方法返回一个适合条件的filter的枚举器,这个枚举器提供一个IEnumMoniker接口,并且对于每个适合的filter都提供一个单独的moniker。
下面的例子演示了,枚举所有的支持DV,并且至少有一个输出pin的filter,这个filter支持任何媒体类型。
IFilterMapper2*pMapper=NULL;
IEnumMoniker*pEnum=NULL;
hr=CoCreateInstance(CLSID_FilterMapper2,NULL,CLSCTX_INPROC,IID_IFilterMapper2,
(void**)&pMapper);
if(FAILED(hr))
{
//Errorhandlingomittedforclarity.
}
GUIDarrayInTypes[2];
arrayInTypes[0]=MEDIATYPE_Video;
arrayInTypes[1]=MEDIASUBTYPE_dvsd;
hr=pMapper->EnumMatchingFilter
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Directshow 开发 基本 技巧