在GraphEdit中模拟构建Graph
智慧的鱼(aoosang)
摘要:本篇文档主要讲述如何使用GraphEdit来模拟构建graph图,测试你的filter。
1 GraphEdit概述
GraphEdit是一个很有用的工具,可以用来构建graph图。通过GraphEdit,你可以在开发代码之前进行一下体验,你也可以装载一个你的应用程序创建的Graph文件。如果你想开发一个自己的filter,GraphEdit 给你提供了一个快速测试的方法:将你的filter添加到graph中,然后运行graph。如果你是一个Directshow的初学者,那么通过GraphEdit你可以熟悉Filter和Dshow的特性。
下面图表演示了GraphEdit如何构建了一个简单的graph。
<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"></shapetype><stroke joinstyle="miter"></stroke><formulas></formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f><lock v:ext="edit" aspectratio="t"></lock><shape id="_x0000_i1025" style="WIDTH: 391.5pt; HEIGHT: 80.25pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:title=""></imagedata>
图1
每一个矩形代表一个Filter,每一个filter的边上的小矩形代表了pin,输入pin在filter的左边,输出pin在Filter的右边,箭头代表两个pin的连接方向。
通过GraphEdit,你可以做到下面的事情:
1 可视化的创建一个Graph,可以动态的拖拉来调整filter。
2 可以模拟如何构建一个graph。
3 运行,停止,暂停,see一个graph。
4可以看看你的机器上都注册了那些filter,以及这些filter的信息
5 查看filter的属性页
6 查看pin连接时采用的媒体类型。
2使用GraphEdit
如果你安装了DirectX的SDK,GraphEdit就会出现在你的开始菜单中找到GraphEdit,启动它。如下图
<shape id="_x0000_i1026" style="WIDTH: 345pt; HEIGHT: 213.75pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image003.png" o:title=""></imagedata>
图2
构建一个文件回放的Graph
GraphEdit可以自动的构建一个文件回放Graph。这个特性其实类似于在应用程序中调用
IGraphBuilder::RenderFile方法。从文件菜单中,选择Render Media File,然后出现一个文件选择对话框,选择一个多媒体文件后单击打开,GraphEdit会自动地建立一个Filter Graph来播放你选择的文件。
你也可以播放一个网络上媒体文件,从文件菜单中,选择Render URL,也会出现一个选择URL的对话框。其他同上。
构建一个普通的Graph图
使用你机器上注册的filter,GraphEdit可以构建一个普通的Filter graph,从Graph菜单中,选择Insert Filters,会出现一个对话框,如下图:
<shape id="_x0000_i1027" style="WIDTH: 291pt; HEIGHT: 198.75pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image005.png" o:title=""></imagedata>
图3
在这个对话框中列出了所有在你机器上注册的Filter的信息。选择filter的名字,然后单击Insert Filters按钮,或者双击filter的名字,filter就会自动添加到graph中,添加完filter以后,你就拖动鼠标,将一个Filter的输出pin和另一个Fiter的输入pin连接起来。如果pin接受这个连接,GraphEdite就会用一个带箭头的
下面的图是一个捕捉桌面的graph图
<shape id="_x0000_i1031" style="WIDTH: 414.75pt; HEIGHT: 56.25pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image007.png" o:title=""></imagedata>
图4
Run the Graph
当你在GraphEdit中构建好一个Filter graph的时候,你可以让你的graph运行一下看是否和你期望的一样。Graph菜单中包含了Play,Pause,和Stop命令,这些命令会触发IMediaControl
接口的Run, Pause, and Stop,GraphEdit的工具栏也有代表这三个命令的按钮,见下图,单击第一个按钮就开始运行你的Graph图了
<shape id="_x0000_i1028" style="WIDTH: 54.75pt; HEIGHT: 23.25pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image009.png" o:title=""></imagedata>
图 5
注:GraphEdit的Stop命令首先会暂停Graph,然后Seek到时间的零点(我们坚定graph是可Seek的)。对于文件的回放,这个命令会将视频窗口的图像设置为第一桢,然后GraphEdit才调用IMediaControl::Stop.
查看属性View Property Pages
一些Filter提供了属性页可以让用户设置Filter的属性。鼠标右键单击filter,在弹出的菜单上选择Properties,就会弹出Filter的属性页设置对话框,用户可以从这里设置属性。
<shape id="_x0000_i1032" style="WIDTH: 414.75pt; HEIGHT: 59.25pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image011.png" o:title=""></imagedata>
图6
<shape id="_x0000_i1033" style="WIDTH: 289.5pt; HEIGHT: 374.25pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image013.png" o:title=""></imagedata>
图7
3 Loading a Graph From an External Process
GraphEdit可以加载其他进程创建的filter Graph,利用这个特性,只使用少量的代码,你可以清楚地看到你的应用程序创建的所有的filter Graph。
这个特性只有win2000,XP才支持。
应用程序首先必须在Running Object Table (ROT).中注册一个filter graph的实例。ROT是一个全局的对象表,用来查看所有正在运行的对象。对象都是通过moniker注册到rot上。Graph Edit通过搜索ROT中和指定名字moniker就ok。
!FilterGraph X pid Y
这里,x是Filter Graph Manager的地址,y是进程ID,也是16进制。
当你的应用程序创建filter graph的时候,调用下面的代码:
HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister)
{
IMoniker * pMoniker;
IRunningObjectTable *pROT;
if (FAILED(GetRunningObjectTable(0, &pROT))) {
return E_FAIL;
}
WCHAR wsz[256];
wsprintfW(wsz, L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph, GetCurrentProcessId());
HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker);
if (SUCCEEDED(hr)) {
hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph,
pMoniker, pdwRegister);
pMoniker->Release();
}
pROT->Release();
return hr;
}
这个函数创建了一个moniker作为filter graph在ROT中的入口,第一个参数是指向filter Graph的指针,第二个参数返回filter graph在ROT中的入口。当应用程序销毁filter graph的时候,一定要调用下面的函数来删除这个ROT入口
void RemoveFromRot(DWORD pdwRegister)
{
IRunningObjectTable *pROT;
if (SUCCEEDED(GetRunningObjectTable(0, &pROT))) {
pROT->Revoke(pdwRegister);
pROT->Release();
}
}
下面的代码演示了如何调用上面的两个函数,
IGraphBuilder *pGraph;
DWORD dwRegister;
// Create the filter graph manager.
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
#ifdef _DEBUG
hr = AddToRot(pGraph, &dwRegister);
#endif
// Rest of the application (not shown).
#ifdef _DEBUG
RemoveFromRot(dwRegister);
#endif
pGraph->Release();
同时运行你的应用程序和GraphEdit,你就可以在GraphEdit中查看你应用程序中的filter graph了。在GraphEdit中,如下
<shape id="_x0000_i1029" style="WIDTH: 220.5pt; HEIGHT: 153pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image015.png" o:title="" cropbottom="30702f"></imagedata>
图8
然后就出现了下面的对话框
<shape id="_x0000_i1030" style="WIDTH: 214.5pt; HEIGHT: 132.75pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image017.png" o:title=""></imagedata>
图9
4 Saving a Filter Graph to a GraphEdit File
下面的代码演示了如何保存一个GraphEdit(.gif)文件,这个可以用来调试你的应用程序。
HRESULT SaveGraphFile(IGraphBuilder *pGraph, WCHAR *wszPath)
{
const WCHAR wszStreamName[] = L"ActiveMovieGraph";
HRESULT hr;
IStorage *pStorage = NULL;
hr = StgCreateDocfile(
wszPath,
STGM_CREATE | STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
0, &pStorage);
if(FAILED(hr))
{
return hr;
}
IStream *pStream;
hr = pStorage->CreateStream(
wszStreamName,
STGM_WRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
0, 0, &pStream);
if (FAILED(hr))
{
pStorage->Release();
return hr;
}
IPersistStream *pPersist = NULL;
pGraph->QueryInterface(IID_IPersistStream, (void**)&pPersist);
hr = pPersist->Save(pStream, TRUE);
pStream->Release();
pPersist->Release();
if (SUCCEEDED(hr))
{
hr = pStorage->Commit(STGC_DEFAULT);
}
pStorage->Release();
return hr;
}
例如,下面的代码创建了文件回放的graph并保存为MyGraph.grf:
void __cdecl main(void)
{
HRESULT hr;
IGraphBuilder *pGraph;
CoInitialize(NULL);
// Create the Filter Graph Manager and render a file.
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, reinterpret_cast<void**>(&pGraph));
hr = pGraph->RenderFile(L"C://Video.avi", NULL);
if (SUCCEEDED(hr))
{
hr = SaveGraphFile(pGraph, L"C://MyGraph.grf");
}
pGraph->Release();
CoUninitialize();
}
5 Loading a GraphEdit File Programmatically
在应用程序中可以通过IPersistStream接口来加载一个GraphEdit (.grf) file,实例代码如下
HRESULT LoadGraphFile(IGraphBuilder *pGraph, const WCHAR* wszName)
{
IStorage *pStorage = 0;
if (S_OK != StgIsStorageFile(wszName))
{
return E_FAIL;
}
HRESULT hr = StgOpenStorage(wszName, 0,
STGM_TRANSACTED | STGM_READ | STGM_SHARE_DENY_WRITE,
0, 0, &pStorage);
if (FAILED(hr))
{
return hr;
}
IPersistStream *pPersistStream = 0;
hr = pGraph->QueryInterface(IID_IPersistStream,
reinterpret_cast<void**>(&pPersistStream));
if (SUCCEEDED(hr))
{
IStream *pStream = 0;
hr = pStorage->OpenStream(L"ActiveMovieGraph", 0,
STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream);
if(SUCCEEDED(hr))
{
hr = pPersistStream->Load(pStream);
pStream->Release();
}
pPersistStream->Release();
}
pStorage->Release();
return hr;
}
必须要注意的是,GraphEdit文件只是用来测试或者调试用的,并不是为了让终端客户用的
Directshow开发的基本技巧
摘要:本篇文档主要讲述了Directshow开发的一些基本概念和技巧
1视频播放(Video Rendering)
dshow的视频提交过滤器可以在窗口模式和无窗口模式下工作。在窗口模式下,过滤器创建一个自己的窗口,在里面播放视频。在无窗口模式下,过滤器直接将视频在应用程序提供的窗口上显示,过滤器本身不创建窗口。
窗口模式
在窗口模式下,视频提交过滤器创建一个窗口,然后将视频祯帖到窗口上,你可以将这个窗口帖到你的应用程序的窗口。
Video Renderer只支持窗口模式,VMR-7 and VMR-9缺省的是窗口模式,也支持无窗口模式。
为了在你的应用程序中显示视频,你可以将视频窗口设置成应用程序的子窗口。你可以通过
IVideoWindow *pVidWin = NULL;
pGraph->QueryInterface(IID_IVideoWindow, (void **)&g_pVidWin);
pVidWin->put_Owner((OAHWND)hwnd);
pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
RECT grc;
GetClientRect(hwnd, &grc);
pVidWin->SetWindowPosition(0, 0, grc.right, grc.bottom);
结束时一定要清理现场
无窗口模式
当采用无窗口的模式时,就没有必要暴露IVideoWindow接口了。
为了能够使用VMR的缺省行为,在构建Graph图之前必须要调整VMR。
1 创建一个过虑器图表管理器,
2创建一个VMR,加入到graph中,
3 调用VMR的IVMRFilterConfig::SetRenderingMode方法设置VMRMode_Windowless标志。
4调用IVMRWindowlessControl::SetVideoClippingWindow 给视频指定一个显示窗口。
然后调用IGraphBuilder::RenderFile或者其他的方法来创建其他的Graph。
下面的代码显示了如何创建一个VMR,将其添加到Graph,如何设置无窗口模式
HRESULT InitWindowlessVMR(
HWND hwndApp, // Window to hold the video.
IGraphBuilder* pGraph, // Pointer to the Filter Graph Manager.
IVMRWindowlessControl** ppWc, // Receives a pointer to the VMR. )
{
if (!pGraph || !ppWc) return E_POINTER;
IBaseFilter* pVmr = NULL;
IVMRWindowlessControl* pWc = NULL;
// Create the VMR.
HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL,
CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr);
if (FAILED(hr))
{
return hr;
}
// Add the VMR to the filter graph.
hr = pGraph->AddFilter(pVmr, L"Video Mixing Renderer");
if (FAILED(hr))
{
pVmr->Release();
return hr;
}
// Set the rendering mode.
IVMRFilterConfig* pConfig;
hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig);
if (SUCCEEDED(hr))
{
hr = pConfig->SetRenderingMode(VMRMode_Windowless);
pConfig->Release();
}
if (SUCCEEDED(hr))
{
// Set the window.
hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWc);
if( SUCCEEDED(hr))
{
hr = pWc->SetVideoClippingWindow(hwndApp);
if (SUCCEEDED(hr))
{
*ppWc = pWc; // Return this as an AddRef'd pointer.
}
else
{
// An error occurred, so release the interface.
pWc->Release();
}
}
}
pVmr->Release();
return hr;
}
你也可以调用下面的函数
IVMRWindowlessControl *pWc = NULL;
hr = InitWindowlessVMR(hwnd, pGraph, &g_pWc);
if (SUCCEEDED(hr))
{
// Build the graph. For example:
pGraph->RenderFile(wszMyFileName, 0);
// Release the VMR interface when you are done.
pWc->Release();
}
下面看看如何设置视频的位置
有两个矩形需要考虑,一个是源矩形,一个是目的矩形。源矩形决定开始播放视频的位置,目的矩形决定在窗口显示视频的区域。VMR将源矩形按照目的矩形的大小进行扩展。
IVMRWindowlessControl::SetVideoPosition可以设置两个矩形的大小,源矩形必须小于等于本地视频大小。你可以通过IVMRWindowlessControl::GetNativeVideoSize获取本地的视频区域大小。
// Find the native video size.
long lWidth, lHeight;
HRESULT hr = g_pWc->GetNativeVideoSize(&lWidth, &lHeight, NULL, NULL);
if (SUCCEEDED(hr))
{
RECT rcSrc, rcDest;
// Set the source rectangle.
SetRect(&rcSrc, 0, 0, lWidth/2, lHeight/2);
// Get the window client area.
GetClientRect(hwnd, &rcDest);
// Set the destination rectangle.
SetRect(&rcDest, 0, 0, rcDest.right/2, rcDest.bottom/2);
// Set the video position.
hr = g_pWc->SetVideoPosition(&rcSrc, &rcDest);
}
处理窗口消息
因为VMR没有自己的窗口,所以当视频需要重画或者改变的时候你要通知它。
1 当你接到一个WM_PAINT消息,你就要调用IVMRWindowlessControl::RepaintVideo来重画视频
2 当你接到一个WM_DISPLAYCHANGE消息,你就要调用IVMRWindowlessControl::DisplayModeChanged.
3 当你接到一个WM_SIZE消息时,重新计算视频的位置,然后调用SetVideoPostion。
下面的代码演示了WM_PAINT消息的处理
void OnPaint(HWND hwnd)
{
PAINTSTRUCT ps;
HDC hdc;
RECT rcClient;
GetClientRect(hwnd, &rcClient);
hdc = BeginPaint(hwnd, &ps);
if (g_pWc != NULL)
{
// Find the region where the application can paint by subtracting
// the video destination rectangle from the client area.
// (Assume that g_rcDest was calculated previously.)
HRGN rgnClient = CreateRectRgnIndirect(&rcClient);
HRGN rgnVideo = CreateRectRgnIndirect(&g_rcDest);
CombineRgn(rgnClient, rgnClient, rgnVideo, RGN_DIFF);
// Paint on window.
HBRUSH hbr = GetSysColorBrush(COLOR_BTNFACE);
FillRgn(hdc, rgnClient, hbr);
// Clean up.
DeleteObject(hbr);
DeleteObject(rgnClient);
DeleteObject(rgnVideo);
// Request the VMR to paint the video.
HRESULT hr = g_pWc->RepaintVideo(hwnd, hdc);
}
else // There is no video, so paint the whole client area.
{
FillRect(hdc, &rc2, (HBRUSH)(COLOR_BTNFACE + 1));
}
EndPaint(hwnd, &ps);
}
尽管我们要自己处理onpaint消息,但是已经非常简单了。
2 如何处理事件通知(Event Notification)
当一个Directshow的应用程序运行的时候,在 filter Graph内部就会发生各种各样的事件,例如,一个filter也许发生数据流错误。Filter通过给graph mangaer发送事件通知来和graph通信,这个事件通知包括一个事件码和两个事件参数。事件码表示发生事件的类型,两个参数用来传递信息。
Filter发送的这些事件,其中的一部分可以被Manager直接处理,不通知应用程序,但有一部分事件,Manager将事件放入到一个队列中,等待应用程序处理。这里我们主要讨论在应用程序中经常遇到的三种事件
EC_COMPLETE表明回放已经结束
EC_USERABORT表明用户中断了回放。用户关闭视频播放窗口时,视频Render会发生这个事件
EC_ERRORABORT表明出现了一个错误。
应用程序可以通知filter graph manager,在某个指定的事件发生时,向指定的窗口发生一个指定的消息。这样应用程序就可以在消息循环中对发生的事件产生反应。
首先定义消息,
#define WM_GRAPHNOTIFY WM_APP + 1
然后向filter graph manager请求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消息的函数
case WM_GRAPHNOTIFY:
HandleGraphEvent();
break;
HandleGraphEvent()函数具体定义如下
void HandleGraphEvent()
{
// Disregard if we don't have an IMediaEventEx pointer.
if (g_pEvent == NULL)
{
return;
}
// Get all the events
long evCode;
LONG_PTR param1, param2;
HRESULT hr;
while (SUCCEEDED(g_pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0)))
{
g_pEvent->FreeEventParams(evCode, param1, param2);
switch (evCode)
{
case EC_COMPLETE: // Fall through.
case EC_USERABORT: // Fall through.
case EC_ERRORABORT:
CleanUp();
PostQuitMessage(0);
return;
}
}
}
在释放IMediaEventEx指针前,要取消事件通知消息,代码如下
// Disable event notification before releasing the graph.
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图中。
<shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"></shapetype><stroke joinstyle="miter"></stroke><formulas></formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f><lock v:ext="edit" aspectratio="t"></lock><shape id="_x0000_i1025" style="WIDTH: 261.75pt; HEIGHT: 150pt" type="#_x0000_t75"></shape><imagedata src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.png" o:title=""></imagedata>
图1
// Create the System Device Enumerator.
HRESULT hr;
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void **)&pSysDevEnum);
if (FAILED(hr))
{
return hr;
}
// Obtain a class enumerator for the video compressor category.
IEnumMoniker *pEnumCat = NULL;
hr=pSysDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory, &pEnumCat, 0);
if (hr == S_OK)
{
// Enumerate the monikers.
IMoniker *pMoniker = NULL;
ULONG cFetched;
while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
(void **)&pPropBag);//知道设备的名称
if (SUCCEEDED(hr))
{
// To retrieve the filter's friendly name, do the following:
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
// Display the name in your UI somehow.
}
VariantClear(&varName);
// To create an instance of the filter, do the following:
IBaseFilter *pFilter;
hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter,
(void**)&pFilter); //生成一个filter绑定到设备上。
// Now add the filter to the graph.
//Remember to release pFilter later.
pPropBag->Release();
}
pMoniker->Release();
}
pEnumCat->Release();
}
pSysDevEnum->Release();
在上面我们用IMoniker::BindToObject生成绑定道设备上的filter,当然我们还可以用另外的一种方法来生成绑定到设备上的filter
利用IMoniker::GetDisplayName得到moniker的名字。然后你把moniker的名字做参数传递给IFilterGraph2::AddSourceFilterForMoniker,就可以创建一个绑定到设备的filter了。在上面我们是调用IMoniker::BindToObject生成filter的,还是上面的简单些。看看代码吧。
LPOLESTR strName = NULL;
IBaseFilter pSrc = NULL;
hr = pMoniker->GetDisplayName(NULL, NULL, &strName);
if (SUCCEEDED(hr))
{
// Query the Filter Graph Manager for IFilterGraph2.
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);
}
// If successful, remember to release pSrc.
2 Filter Mapper
搜索系统中的filter的另一个方法就是采用Filer Mapper。Filter mapper是一个com对象,它按照一定的条件来搜索系统的filer,它比系统设备枚举器(System Device Enumerator)的效率要低一些。所以当你要枚举某特定种类的filter时,你应该使用系统设备枚举器,但是当你搜索支持某种媒体类型的filter时,同时也找不到清晰的filter,你应该使用filter mapper。
Filter Mapper 暴露一个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))
{
// Error handling omitted for clarity.
}
GUID arrayInTypes[2];
arrayInTypes[0] = MEDIATYPE_Video;
arrayInTypes[1] = MEDIASUBTYPE_dvsd;
hr = pMapper->EnumMatchingFilters(
&pEnum,
0, // Reserved.
TRUE, // Use exact match?
MERIT_DO_NOT_USE+1, // Minimum merit.
TRUE, // At least one input pin?
1, // Number of major type/subtype pairs for input.
arrayInTypes, // Array of major type/subtype pairs for input.
NULL, // Input medium.
NULL, // Input pin category.
FALSE, // Must be a renderer?
TRUE, // At least one output pin?
0, // Number of major type/subtype pairs for output.
NULL, // Array of major type/subtype pairs for output.
NULL, // Output medium.
NULL); // Output pin category.
// Enumerate the monikers.
IMoniker *pMoniker;
ULONG cFetched;
//下面就是枚举filter了,就是系统枚举设备filter
while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
{
IPropertyBag *pPropBag = NULL;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
(void **)&pPropBag);
if (SUCCEEDED(hr))
{
// To retrieve the friendly name of the filter, do the following:
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
// Display the name in your UI somehow.
}
VariantClear(&varName);
// To create an instance of the filter, do the following:
IBaseFilter *pFilter;
hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);
// Now add the filter to the graph. Remember to release pFilter later.
// Clean up.
pPropBag->Release();
}
pMoniker->Release();
}
// Clean up.
pMapper->Release();
pEnum->Release();
4如何枚举Graph图中的对象(filter,pin)
有些时候,应用程序需要枚举graph中的filter或者是枚举filter所支持的pin。因此directshow提供了枚举graph filter中的com组件方法。
1 枚举filter
Filter图表管理器支持IFilterGraph::EnumFilters方法,来枚举graph图中的所有的filter。他返回一个IEnumFilters接口,利用这个接口就可以遍历graph中的所有的filter。
下面的代码演示了,如何遍历graph中的filter,并且显示filter的名字。
HRESULT EnumFilters (IFilterGraph *pGraph)
{
IEnumFilters *pEnum = NULL;
IBaseFilter *pFilter;
ULONG cFetched;
HRESULT hr = pGraph->EnumFilters(&pEnum);
if (FAILED(hr)) return hr;
while(pEnum->Next(1, &pFilter, &cFetched) == S_OK)
{
FILTER_INFO FilterInfo;
hr = pFilter->QueryFilterInfo(&FilterInfo);
if (FAILED(hr))
{
MessageBox(NULL, TEXT("Could not get the filter info"),
TEXT("Error"), MB_OK | MB_ICONERROR);
continue; // Maybe the next one will work.
}
#ifdef UNICODE
MessageBox(NULL, FilterInfo.achName, TEXT("Filter Name"), MB_OK);
#else
char szName[MAX_FILTER_NAME];
int cch = WideCharToMultiByte(CP_ACP, 0, FilterInfo.achName,
MAX_FILTER_NAME, szName, MAX_FILTER_NAME, 0, 0);
if (chh > 0)
MessageBox(NULL, szName, TEXT("Filter Name"), MB_OK);
#endif
// The FILTER_INFO structure holds a pointer to the Filter Graph
// Manager, with a reference count that must be released.
if (FilterInfo.pGraph != NULL)
{
FilterInfo.pGraph->Release();
}
pFilter->Release();
}
pEnum->Release();
return S_OK;
}
2 枚举pin
Filter支持IBaseFilter::EnumPins方法,这个方法可以可以枚举filter所有的pin。它返回一个IEnumPins接口,IEnumPins::Next可以遍历pin的接口。
下面的代码演示了如何如何查找一个输出和输入pin。利用PIN_DIRECTION参数来制定pin的类型(输入还是输出)。
HRESULT GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin)
{
IEnumPins *pEnum = NULL;
IPin *pPin = NULL;
HRESULT hr;
if (ppPin == NULL)
{
return E_POINTER;
}
hr = pFilter->EnumPins(&pEnum);
if (FAILED(hr))
{
return hr;
}
while(pEnum->Next(1, &pPin, 0) == S_OK)
{
PIN_DIRECTION PinDirThis;
hr = pPin->QueryDirection(&PinDirThis);
if (FAILED(hr))
{
pPin->Release();
pEnum->Release();
return hr;
}
if (PinDir == PinDirThis) //如果类型符合
{
// Found a match. Return the IPin pointer to the caller.
**ppPin = pPin;
pEnum->Release();
return S_OK;
}
// Release the pin for the next time through the loop.
pPin->Release();
}
// No more pins. We did not find a match.
pEnum->Release();
return E_FAIL;
}
利用这个方法可以很容易的就查找一个pin,然后调用IPin::ConnectedTo方法确定这个pin是否被连接,可以查找一个空闲的pin。
3 查找媒体类型
每个pin都支持一个IPin::EnumMediaTypes方法,可以来枚举pin支持的媒体类型。它返回一个IEnumMediaTypes接口,这个接口的方法IEnumMediaTypes::Next返回一个指向AM_MEDIA_TYPE类型的指针。可以参考上面的代码来遍历pin所支持的媒体类型。
5 Seeking Filter graph
主要讲述了如何在一个媒体数据流中定位,任意指定开始播放的位置。
1 检查是否支持seek
Directshow通过IMediaSeeking接口支持seeking。Filter graph管理器支持这个接口,但是实际seeking的功能是有graph中的filter来实现的。
有一些数据是不能seek的,例如,你不可能seek从照相机中采集的活动的视频流。如果一个数据流可以被seek,但是,seek的类型还分以下几种类型,可以给你的数据流选择一种
1 定位到数据流中的一个绝对位置
2 返回数据流的持续时间
3返回数据流中的当前播放位置
4回放。
IMediaSeeking接口定义了一套标志AM_SEEKING_SEEKING_CAPABILITIES,用来描述可能支持的seek功能。
可以通过IMediaSeeking::GetCapabilities查看数据流支持的seek能力都有哪些。应用程序可以采取 &测试每一项。例如,下面的代码检查了graph是否可以seek 一个任意的位置
DWORD dwCap = 0;
HRESULT hr = pSeek->GetCapabilities(&dwCap);
if (AM_SEEKING_CanSeekAbsolute & dwCap)
{
// Graph can seek to absolute positions.
}
2Setting and Retrieving the Position
Filter graph包含两个位置,当前位置和停止位置,定义如下:
1当前位置,当一个graph正处于运行的时候,当前位置就是当前的回放位置,相对于开始的位置而言。如果graph处于停止或者暂停状态的时候,当前位置就是数据流下次开始播放的位置点。
2 停止位置,停止位置就是数据流将要停止的位置,当一个graph到达一个停止位置时,将没有数据流,filter graph管理器将会发送一个EC_COMPLETE事件。
可以通过IMediaSeeking::GetPositions方法可以获取这些位置值。返回值都是相对于原始的开始位置。
通过IMediaSeeking::SetPositions方法可以seek一个新的位置,见下面:
#define ONE_SECOND 10000000
REFERENCE_TIME rtNow = 2 * ONE_SECOND,
rtStop = 5 * ONE_SECOND;
hr = pSeek->SetPositions(
&rtNow, AM_SEEKING_AbsolutePositioning,
&rtStop, AM_SEEKING_AbsolutePositioning
);
注:1秒是10,000,000参考时间单位。为了方便,这个例子将这个值定义为ONE_SECOND,如果你使用的dshow的基类,常量CUITS的值和这个值相等。
RtNow参数指定新的当前位置,第二个参数用来标示如何来定位rtNow参数。在这个例子中,AM_SEEKING_AbsolutePositioning 标志表示rtNow指定的位置是一个绝对的位置。RtStop参数指定了停止时间,最后一个参数也指定了绝对位置。
如果想指定一个相对的位置,可以指定一个AM_SEEKING_RelativePositioning参数,
为了设置这个位置不能改变,可以指定一个AM_SEEKING_NoPositioning参数。此时,参考时间应该设置为NULL。下面的例子将位置向前seek 10秒,然后停止位置不变。
hr = pSeek->SetPositions(
&rtNow, AM_SEEKING_RelativePositioning,
NULL, AM_SEEKING_NoPositioning
);
3Setting the Playback Rate
将新的速率设置为原来速率的两倍。比率大于1说明回放的速度比原来的大,如果介于0和1之间,就比正常的速度慢。
如果我们不考虑回放速率,当前位置和停止位置相对于开始位置都是不变的。举个例子,如果我们有一个可以播放20秒的文件,将当前时间设置为10秒就会将播放位置设置到中间,如果播放的速率提高要原来的2倍,如果停止时间是20秒,你将播放位置设置到原来的10秒处,结果现在只能播放5秒了,因为速度提高了两倍。
4Time Formats For Seek Commands
IMediaSeeking接口中的许多函数的参数都要求指定一个位置值,比如当前位置,或者停止位置,缺省的情况下这些参数是以of 100 nanoseconds为时间单位的,称为参考时间,任何支持seek的filter必须支持按参考时间来进行定位。一些filter也支持采取其他时间单位进行定位。例如,根据指定的桢的数量,或在数据流偏移的字节数进行定位。
这种用来定位的时间单位称为时间格式,采用一个GUID来标示。Directshow定义了一系列的时间格式,详细地可以参考SDK。第三方也可以定义自己的时间格式。
为了确定graph中的当前的filter是否支持特定的时间格式,可以调用
IMediaSeeking::IsFormatSupported方法,如果filter支持该时间格式,该函数返回ok否则返回false或者一个错误码。如果filter支持某种指定的时间格式,可以调用IMediaSeeking::SetTimeFormat方法切换到其他的时间格式。如果SetTimeFormat方法成功,下面的seek命令就要使用新的时间格式。
下面的代码检查graph是否支持用桢的数量进行定位,如果支持,定位到第20桢。
hr = pSeek->IsFormatSupported(&TIME_FORMAT_FRAME);
if (hr == S_OK)
{
hr = pSeek->SetTimeFormat(&TIME_FORMAT_FRAME);
if (SUCCEEDED(hr))
{
// Seek to frame number 20.
LONGLONG rtNow = 20;
hr = pSeek->SetPositions(
&rtNow, AM_SEEKING_AbsolutePositioning,
0, AM_SEEKING_NoPositioning);
}
}
6 如何设置Graph时钟(Setting Graph Clock)
当你构建了一个graph后,graph管理器会自动地给你的graph选择一个参考时钟的。Graph中的所有filter都同步于时钟。特别的,Renderer filter还要根据参考时钟的时间来决定每一个sample的Presentation 时间。
通常的情况下,应用程序是没有必要重新设置graph管理器选择好的参考时钟的。但是,如果你想修改参考时钟,你可以通过graph管理器提供的IMediaFilter::SetSyncSource方法来重新设置参考时钟。这个方法的参数是一个时钟的IReferenceClock接口指针。可以在graph停止的时候调用这个函数,下面的例子演示了如何指定一个时钟
IGraphBuilder *pGraph = 0;
IReferenceClock *pClock = 0;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
// Build the graph.
pGraph->RenderFile(L"C://Example.avi", 0);
// Create your clock.
hr = CreateMyPrivateClock(&pClock);
if (SUCCEEDED(hr))
{
// Set the graph clock.
IMediaFilter *pMediaFilter = 0;
pGraph->QueryInterface(IID_IMediaFilter, (void**)&pMediaFilter);
pMediaFilter->SetSyncSource(pClock);
pClock->Release();
pMediaFilter->Release();
}
这段代码假定CreateMyPrivateClock 是应用程序定义的一个函数,用来创建一个时钟,然后返回一个IReferenceClock接口。
你也可以在graph没有设置时钟的情况下运行graph。当SetSyncSource 函数的参数为NULL的时候就给graph设置了一个空的参考时钟。如果graph没有时钟,graph将运行的快许多。因为renderer 不用再按照sample的presentation 时间了,只要sample到达了renderer filter,就可以立即被提交。所以,当你想处理数据尽可能快,而不是还要考虑预览的实际时间,你就可以给graph设置一个空的时间。
<script language="javascript" type="text/javascript"> <!-- var Page_Validators = new Array(document.all["PostComment.ascx_RequiredFieldValidator2"], document.all["PostComment.ascx_RequiredFieldValidator3"]); // --> </script><script language="javascript" type="text/javascript"> <!-- var Page_ValidationActive = false; if (typeof(clientInformation) != "undefined" && clientInformation.appName.indexOf("Explorer") != -1) { if ((typeof(Page_ValidationVer) != "undefined") && (Page_ValidationVer == "125")) ValidatorOnLoad(); } function ValidatorOnSubmit() { if (Page_ValidationActive) { return ValidatorCommonOnSubmit(); } return true; } // --> </script>