事件的消息队列机制
filter
通过发布事件通知来通知 Filter Graph 管理器有关事件。 该事件可能是预期事件(例如流的末尾),也可能表示错误,例如无法呈现流。 Filter Graph 管理器自行处理某些filter
事件,而其他filter
事件则留给应用程序处理。 如果 Filter Graph 管理器不处理filter
事件,则会将事件通知放入队列中。 filter
图还可以为应用程序将自己的事件通知排队。
应用程序从队列中检索事件,并根据事件类型对其进行响应。 因此,DirectShow 中的事件通知类似于 Microsoft Windows 消息队列方案。 应用程序还可以取消filter
关系图管理器对给定事件类型的默认行为。 然后,Filter Graph 管理器将这些事件直接放入队列中供应用程序处理。
- 用于与应用程序通信的 Filter Graph 管理器。
- 用于与应用程序和
filter
关系图管理器通信的filter
。 - 应用程序,以确定其在处理事件中的参与程度。
Filter Graph 管理器公开三个支持事件通知的接口。
- IMediaEventSink 包含用于发布事件的
filter
的方法。 - IMediaEvent 包含应用程序用于检索事件的方法。
- IMediaEventEx 继承自 并扩展 IMediaEvent 接口。
通过在 Filter Graph 管理器上调用 IMediaEventSink::Notify 方法筛选事件通知。 事件通知由一个定义事件类型的事件代码和两个提供其他信息的参数组成。 根据事件代码,参数可能包含指针、返回代码、引用时间或其他信息。 有关事件代码和参数的完整列表,请参阅 事件通知代码。
若要从队列中检索事件,应用程序在 Filter Graph Manager 上调用 IMediaEvent::GetEvent 方法。 此方法将阻塞,直到有要返回的事件,或直到指定的时间过去。 假设存在排队事件,该方法返回事件代码和两个事件参数。 调用 GetEvent 后,应用程序应始终调用 IMediaEvent::FreeEventParams 方法,以释放与事件参数关联的任何资源。 例如,参数可能是由filter
图分配的 BSTR 值。
下面的代码示例概述了如何从队列中检索事件。
long evCode;
LONG_PTR param1, param2;
HRESULT hr;
while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))
{
switch(evCode)
{
// Call application-defined functions for each
// type of event that you want to handle.
}
hr = pEvent->FreeEventParams(evCode, param1, param2);
}
若要替代 Filter Graph 管理器对事件的默认处理,请使用事件代码作为参数调用 IMediaEvent::CancelDefaultHandling 方法。 可以通过调用 IMediaEvent::RestoreDefaultHandling 方法来恢复默认处理。 如果filter
图未对指定的事件代码执行默认处理,则调用这些方法不起作用。
事件的两种响应机制
若要处理 DirectShow 事件,应用程序需要一种方法来查明事件何时在队列中等待。 Filter Graph 管理器提供了两种执行此操作的方法:
- 窗口通知: 每当有新事件时,Filter Graph 管理器会将用户定义的 Windows 消息发送到应用程序窗口。
- 事件信号: 如果队列中有 DirectShow 事件,Filter Graph 管理器会发出 Windows 事件信号,如果队列为空,则重置事件。
应用程序可以使用任一技术。 窗口通知通常更简单。
窗口通知
若要设置窗口通知,请调用 IMediaEventEx::SetNotifyWindow 方法并指定私人消息。 应用程序可以使用从WM_APP到0xBFFF范围内的消息号作为私人消息。 每当 Filter Graph 管理器在队列中放置新事件通知时,它会将此消息发布到指定的窗口。 应用程序从窗口的消息循环中响应消息。
下面的代码示例演示如何设置通知窗口。
#define WM_GRAPHNOTIFY WM_APP + 1 // Private message.
pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
该消息是普通的 Windows 消息,独立于 DirectShow 事件通知队列发布。 此方法的优点是大多数应用程序已实现消息循环。 因此,无需执行大量额外工作即可合并 DirectShow 事件处理。
下面的代码示例演示了如何响应通知消息的概述。 有关完整示例,请参阅 响应事件。
LRESULT CALLBACK WindowProc( HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
switch (msg)
{
case WM_GRAPHNOTIFY:
HandleEvent(); // Application-defined function.
break;
// Handle other Windows messages here too.
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
由于事件通知和消息循环都是异步的,因此在应用程序响应消息时,队列可能包含多个事件。 此外,如果事件变得无效,有时可以从队列中清除这些事件。 因此,在事件处理代码中,调用 IAMMediaEvent::GetEvent ,直到返回失败代码,指示队列为空。
在释放 IMediaEventEx 指针之前,请使用 NULL 指针调用 SetNotifyWindow 来取消事件通知。 在事件处理代码中,检查 IMediaEventEx 指针在调用 GetEvent 之前是否有效。 这些步骤可防止可能出现的错误,即应用程序在释放 IMediaEventEx 指针后接收事件通知。
事件信号
Filter Graph 管理器保留一个反映事件队列状态的手动重置事件。 如果队列包含挂起的事件通知,Filter Graph 管理器会发出手动重置事件的信号。 如果队列为空,则对 IMediaEvent::GetEvent 方法的调用将重置事件。 应用程序可以使用此事件来确定队列的状态。
备注
此处的术语可能会令人困惑。 手动重置事件是由 Windows CreateEvent 函数创建的事件类型, 它与 DirectShow 定义的事件无关。
调用 IMediaEvent::GetEventHandle 方法以获取手动重置事件的句柄。 等待通过调用 WaitForMultipleObjects 等函数发出事件信号。 发出事件信号后,调用 IMediaEvent::GetEvent 以获取 DirectShow 事件。
以下代码示例演示了此方法。 它获取事件句柄,然后以 100 毫秒的间隔等待事件发出信号。 如果事件收到信号,它将调用 GetEvent 并将事件代码和事件参数输出到控制台窗口。 循环在 发生EC_COMPLETE 事件时终止,指示播放已完成。
HANDLE hEvent;
long evCode, param1, param2;
BOOLEAN bDone = FALSE;
HRESULT hr = S_OK;
hr = pEvent->GetEventHandle((OAEVENT*)&hEvent);
if (FAILED(hr))
{
/* Insert failure-handling code here. */
}
while(!bDone)
{
if (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, 100))
{
while (S_OK == pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0))
{
printf("Event code: %#04x\n Params: %d, %d\n", evCode, param1, param2);
pEvent->FreeEventParams(evCode, param1, param2);
bDone = (EC_COMPLETE == evCode);
}
}
}
由于filter
图会在适当时自动设置或重置事件,因此应用程序不应这样做。 此外,释放filter
图时,filter
图将关闭事件句柄,因此不要在该点之后使用事件句柄。