Filter Graph的事件交互机制

来源:互联网 发布:linux怎么启动nginx 编辑:程序博客网 时间:2024/05/01 09:04

Filter Graph的事件交互机制

作者:陆其明 刊载于《中文信息——程序春秋》2003.9期




关键词: DirectShow Event Notification

编写基于DirectShow的应用程序,需要有一种机制,能够让应用程序与Filter Graph之间实现交互控制。DirectShow设计了这样一套事件通知(Event Notification)机制:当Filter状态转换、运行时遇到错误、或要求重画视频窗口时,发出一个相应的事件,由Filter Graph Manager处理或者转发给应用程序;应用程序可以接收事件,并根据事件类型再做出相应的处理。常见的事件有EC_COMPLETE,表示Filter Graph中所有的数据都已经回放完毕(此时Filter Graph不会自动转入Stopped状态,需要我们在应用程序中接收到这个事件后调用Filter Graph Manager的IMediaControl::Stop方法),有EC_ERRORABORT,表示运行时出错,有EC_DEVICE_LOST,表示热插拔设备(典型的如USB设备、1394接口设备等)脱离系统等等。(其他事件参见DirectX文档。)下面,我们来具体看一下这个事件处理机制。

Filter Graph Manager上有三个接口(IMediaEventSink、IMediaEvent和IMediaEventEx)跟事件通知有关。其中IMediaEventSink用在Filter内部,它的接口方法Notify用以向Filter Graph Manager发送事件通知;IMediaEventEx是IMediaEvent的扩展,在应用程序中一般使用这个接口处理Filter Graph Manager发出的事件。事件处理的大致过程如下:Filter Graph中的Filter发出一个事件(运行出错了或者满足了一定的条件),接收者为Filter Graph Manager;Filter Graph Manager对一些特殊的事件有默认的处理方法,在接收到事件后,要么按默认方法直接处理这个事件,要么放到一个事件队列中(我们当然也可以通过IMediaEvent::CancelDefaultHandling调用来取消指定事件的默认处理,使得Filter Graph Manager接收到指定的事件后直接将其放到事件队列中),等待上层的应用程序处理;应用程序在获知Filter Graph Manager有事件发出后,就可以使用IMediaEventEx接口从事件队列中读取事件,然后根据事件类型作出相应的处理。这里说到的事件队列有点类似于Windows的消息队列,Filter Graph Manager与应用程序之间的事件交互过程也跟Windows的消息循环差不多。

接着,我们来重点看一下应用程序对事件通知的处理方法。首先要弄明白的是,应用程序怎么知道Filter Graph Manager的事件队列中有事件在等待处理?有两种方法,一种是Filter Graph Manager通过发送指定的窗口消息通知应用程序,另一种是通过事件同步对象(注意,这里的事件是指CreateEvent创建的事件,而不是Filter发送的类似于窗口消息的事件)。两种方法相比,第一种比较常用,因为Windows程序处理窗口消息要容易得多。步骤如下:
1. 自定义一个消息,然后调用IMediaEventEx::SetNotifyWindow将其设置给Filter Graph Manager:
#define WM_GRAPHNOTIFY (WM_APP + 1) // Private message.
pEvent->SetNotifyWindow((OAHWND)m_hwnd, WM_GRAPHNOTIFY, 0);
2. 在指定的消息接收窗口类中定义消息映射:
BEGIN_MESSAGE_MAP(CNotifyWnd, …)
//{{AFX_MSG_MAP(CNotifyWnd)
//}}AFX_MSG_MAP
ON_MESSAGE(WM_ GRAPHNOTIFY, OnGraphNotify)
END_MESSAGE_MAP()
3. 在消息响应函数中获取Filter Graph的事件通知,并作出相应处理:
void CNotifyWnd:: OnGraphNotify(WPARAM inWParam, LPARAM inLParam)
{
if (mGraphEvent)
{
LONG eventCode = 0, eventParam1 = 0, eventParam2 = 0;
while (SUCCEEDED(mGraphEvent->GetEvent(&eventCode, &eventParam1, &eventParam2, 0)))
{
mGraphEvent->FreeEventParams(eventCode, eventParam1, eventParam2);
switch (eventCode)
{
case EC_COMPLETE:
// Do something…
break;
default:
break;
}
}
}
}
其中,mGraphEvent是从Filter Graph Manager得到的IMediaEventEx接口。由于Filter Graph Manager使用事件队列,与窗口消息的处理一样,都是异步的。所以当接收到通知消息后,使用了While语句循环调用IMediaEvent::GetEvent,直到事件队列中的所有事件处理完毕。还需要特别注意的是,当得到事件成功后,必须调用IMediaEvent::FreeEventParams释放Filter Graph Manager为该事件分配的资源。
4. 在释放IMediaEventEx接口之前,务必再次调用IMediaEventEx::SetNotifyWindow,将参数设置为NULL:mGraphEvent->SetNotifyWindow(NULL, 0, 0);

第二种方法其实也很简单。因为Filter Graph Manager在内部创建了一个事件同步对象,它在事件队列中有尚未处理的事件时状态标记为有效,当应用程序不断调用IMediaEvent::GetEvent,取空事件队列中所有事件之后状态复位。我们可以通过IMediaEvent::GetEventHandle得到这个事件同步对象的Handle。一般的处理方法为:
HANDLE hEvent;
long evCode, param1, param2;
BOOLEAN bDone = FALSE;
HRESULT hr = S_OK;
hr = pEvent->GetEventHandle((OAEVENT*)&hEvent);
while(!bDone)
{
if (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, 100))
{
while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))
{
printf("Event code: %#04x/n Params: %d, %d/n", evCode, param1, param2);
hr = pEvent->FreeEventParams(evCode, param1, param2);
bDone = (EC_COMPLETE == evCode);
}
}
}
其中,pEvent是从Filter Graph Manager得到的IMediaEventEx接口。需要说明的是,这种方法适用于控制台程序,或者主程序包含多线程的应用程序,以及其他一些不适合使用窗口消息的场合。两种处理事件通知的方法,没有哪一种好,哪一种不好;选择哪一种方法,完全取决于你的应用环境。

原创粉丝点击