4.3 Filter States
Filter
有三种状态,停止,暂停,运行。暂停状态是为了在
Graph
中
Cue Data,
使得运行命令可以立即响应。
Filter Graph Manager
控制着所有状态的转换。当应用程序调用
IMediaControl
的
Run, Pause, Stop
方法时,
Filter Graph Manager
就调用所有
Filter
的相应
IMediaFilter
方法。停止,运行状态的切换总是要经过暂停,因此,当应用程序对停止的
Graph
调用
RUN
命令时,
Filter
图表管理器在
Run
之前首先要暂停。对于大多数的
Filter
来说,
Running
和
Paused
状态是一样的。考虑如下的
Filter Graph:
Source -> Transform -> Renderer
假定
Source Filter
不是实时源。当
Source Filter
暂停,它创建一个线程生成新的数据并尽可能快的写入到
Media Samples.
线程通过调用
Transform Filter
的输入
PIN
上的
IMemInputPin
的
Receive
方法把数据向下推。
Transform Filter
接收到在
Source Filter
的线程中的
Sample.
它可能用一个工作线程把
Sample
递送给
Renderer
,但是通常是在同一个线程完成。此时
Renderer
暂停,它等待接收
Sample.
在接收到一个
Sample
后,它就阻塞线程并不定的保存数据。如果是视频
Renderer,
把
Sample
作为张贴图像显示,如果必要就重画图像。
现在,数据流完全准备好进行提交。如果
Graph
保持暂停,在第一帧
Sample
后
Sample
就堆积在
Graph,
直到每个
Filter
都阻塞在
Receive
或
GetBuffer.
但是不会有数据丢失。一旦
Source
线程非阻塞,它简单从阻塞的点恢复。
Source Filter
和
Transform Filter
忽略从暂停到运行的变换,它们简单的尽可能快的继续处理数据。当
Render
开始运行,它就开始提交
Sample.
首先提交的就是当它阻塞时保存的
Sample.
然后,每次都接收到新
Sample.
计算
Sample
的提交时间。(细节可参考
Time and Clocks in DirectShow
),
Render
会保存每个
Sample
直到提交时间,到点后再提交
Sample.
当它等待提交时间时,线程也阻塞在
Receive
方法,或者在工作线程用队列接收
Sample.
从
Renderer
向上的所有
Filter
都不会陷于时间安排。
实时源(比如捕捉设备)与普通结构有些不同。在实时源中,不合适提前准备任何数据。应用程序可能暂停
Graph
,在运行前等待比较长的时间。
Graph
不应该提交过时
Sample.
因此,暂停时实时源不会产生数据,只有运行时才有。为了把事件通知给
Filter Graph Manager, Source Filter
的
IMediaFilter
方法
GetState
返回
VFW_S_CANT_CUE
。此返回值表示
Filter
已经转换到暂停状态,即使
Renderer
没有接收到任何数据。
当一个
Filter
停止时,它拒绝发送给它的任何
Samples
,
Source Filter
关闭他们的
Stream
线程,其他
Filter
也关闭他们创建的工作线程,
Pin
再
Decommit
他们的内存分配器。
4.3.1 State Transitions
Filter Graph Manager
按照逆流的方向来切换
Filter
的状态,从
Renderer Filter
到
Source Filter
,这种方式可以防止死锁和
Sample
的丢失。最关键的状态切换是暂停和停止之间。
·从停止到暂停,当
Filter
暂停时就准备好了从连接
Filter
接收
Sample
。
Source Filter
是最后一个暂停的。它创建
Streaming
线程发送
Sample
,因为下游的
Filter
的状态都已经切换到暂停了,所以,所有的
Filter
都可以接收
Sample
。只有当
Graph
所有的
Renderer
都接收到
Sample
,
Filter Graph Manager
才算完成了状态的切换
(
除了前面讲的实时源例外
)
。
·从暂停到停止。当
Filter
停止时它要释放它拥有的所有的
Sample
,
以使得上一级
Filter
中的
IMemAllocator::GetBuffer
调用脱离阻塞。如果
Filter
在
Receive
函数中等待资源,它也会停止等待,并从
Receive
返回,以使调用
Filter
解锁。因此,当
Filter Graph Manager
停止连接的上一级
Filter
时,
Filter
就不会在
GetBuffer
和
Receive
函数中阻塞,也就能响应停止命令。上级
Filter
在得到停止命令前可以会递送一些额外
Sample
。但是下级
Filter
可能拒绝他们,因为他们已经停止。
4.4 Pull Model
在
IMemInputPin
接口中,上级
Filter
决定发送什么样数据,然后将数据推给下
级
Filter
。但对某些
Filter
,拉模式更适合。现在,下
级
Filter
向上
级
Filter
请求数据。数据依然是从上
级到下级,从输出
Pin
到输入
Pin
,但是由下
级
Filter
发动数据流动。这种类型连接采用的是
IAsyncReader
接口。
拉模式的典型应用是文件回放。例如在
AVI
文件的回放
Graph
中,
Async File Source Filter
就执行通用文件读写操作,然后将数据以字节流的方式(不带格式信息)发送给下
级
Filter
。
AVI Splitter
读取
AVI
头并把数据流分析为视频、音频流。
AVI Splitter
能比
Async File Source Filter
更好的决定它需要什么类型的数据,因此它用
IMemInputPin
代替
IAsyncReader
接口。
为了从输出
PIN
请求数据,输入
PIN
调用如下一种方法:
·
IAsyncReader::Request
·
IAsyncReader::SyncRead
·
IAsyncReader::SyncReadAligned
第一个方法是异步的,支持多个重叠读写。其他是同步的。
理论上,任何
Filter
可以支持
IAsyncReader
,但是实际上它被设计来连接
Source Filter
和
Parser Filter
。
Parser
的功能与推模式中的
Source Filter
非常相同。当它暂停时,创建一个数据流线程从
IAsyncReader
连接拉数据,并推向下一级。输出
PIN
使用
IMemInputPin
,
Graph
的余下部分使用标准的推模式。
5. Event Notification in DirectShow
5.1 Overview of Event Notification
当
某个事件发生时,比如数据流结束,产生一个错误,提交流失败等,
Filter
就给
Filter Graph Manager
发送一个事件通知。
Filter Graph Manager
自己处理部分事件,另一部分交给应用程序处理。如果
Filter Graph Manager
没有处理事件,它就把事件通知放入到一个队列中。图表管理器也可以将自己的事件通知放进队列中。
应用程序以事件队列获取事件并根据事件类型进行处理。
DirectShow
中的事件通知和
Windows
的消息机制差不多。应用程序也可以取消
Filter Graph Manager
特定事件的默认行为。
Filter Graph Manager
就直接把事件放入队列,留给应用程序来处理。
5.2 Retrieving Events
Filter Graph Manager
暴露了三个接口处理事件通知:
·
IMediaEventSink Filter
用这个接口来
Post
事件。
·
IMediaEvent
应用程序利用这个接口来从队列中查询消息。
·
IMediaEventEx
是
IMediaEvent
的扩展。
Filter
通过
IMediaEventSink::Notify
方法向
Filter Graph Manager
提交事件。事件通知包括一个事件
Code
,这个
Code
不仅仅代表了事件的类型,还包含两个
DWORD
类型的参数用来传递一些其他的信息。根据不同的事件
Code
,附加信息可能是指针、返回码、参考时间或其他信息。获取事件
Code
和参数的全部列表可参考
Event Notification Codes.
应用程序通过调用
IMediaEvent::GetEvent
从事件队列中获取事件。如果有事件发生,该函数就返回一个事件码和两个参数,如果没有事件,则一直阻塞直到有事件发生和超过某个时间。调用
GetEvent
函数后,应用程序必须调用
IMediaEvent::FreeEventParams
来释放事件码所关联的资源。例如,参数可能是
Filter Graph
分配的
BSTR
内存。下面的代码演示了如何从事件队列中提取事件:
long evCode, 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 Manager
对事件的缺省处理,可以用某个事件码做参数调用
IMediaEvent
的
CancelDefaultHandling
方法。这样就可以屏蔽
Filter Graph Manager
对某个事件码的处理了。如果要恢复图表管理器对该事件码的缺省处理,可以调用
RestoreDefaultHandling
方法。如果
Filter Graph
对某个事件没有缺省的处理,调用这两个函数是不起作用的。
5.3 Learning When an Event Occurs
为了处理
DirectShow
事件,应用程序需要一种机制来知道什么时候队列中有等待的事件。
Filter Graph Manager
提供了两种方法
:
·窗口通知:
Filter Graph Manager
发送开发者自己定义的窗口消息
·事件信号:如果队列中有
DirectShow
事件,就用事件信号通知应用程序,如果队列为空就重新设置事件信号。
应用程序可以使用这两种技术。窗口通知通常更简单。
5.3.1 Window Notification
建立窗口通知可调用
IMediaEventEx::SetNotifyWindow
方法,并指定一个私有消息。应用程序可使用从
WM_APP
到
0XBFFF
之间的消息值作为私有消息。只要
Filter Graph Manager
把一个新事件放入到队列时,就向目标窗口发送消息。应用程序从消息队列响应消息。
下面的代码演示了如何利用消息通知:
#define WM_GRAPHNOTIFY WM_APP+1 // Private message.
pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
消息是一个普通的窗口消息,是从
DirectShow
的事件通知队列单独提交的。这种方法的好处多数程序已经实现了消息循环。因此,我们可以合并
DirectShow
的事件通知而不需要过多额外工作。下面的代码说明了如何响应消息通知的框架。完整的例子可参考
Responding to Events.
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));
}
由于事件通知和窗口的消息循环都是异步的,因此,当你的应用程序处理消息的时候,队列中或许有
N
个事件等待处理。同样,如果事件失效,它也可能从事件队列清除掉。因此,在你调用
GetEvent
的时候,一定要循环调用,直到返回一个错误码,这表明队列是空的。
当你释放
IMediaEventEx
指针时,你可以调用
SetNotifyWindow
来取消事件通知,记住此时要给这个函数传递一个
NULL
指针。在你的事件处理程序中,在调用
GetEvent
之前一定要检查
IMediaEventEx
指针是否为空,这样就可以避免错误。因为有时在释放
IMediaEventEx
指针后可能会得到通知。
5.3.2 Event Signaling
在
Filter Graph Manager
里有一个手动设置的
Event
内核对象,用来反映事件队列的状态。如果队列中有等待处理的事件,
Event
就处于通知状态,如果队列是空的,
IMediaEvent::GetEvent
函数调用就会重置该
Event
对象。应用程序可利用此事件来判断队列的状态。
应用程序可以调用
IMediaEvent::GetEventHandle
获得
Event
内核对象的句柄,然后就可以调用
WaitForMultipleObjects
来等待事件的发生,如果
Event
被通知了,就可以调用
GetEvent
来获得
DirectShow
的事件。下面的代码演示了事件信号。返回事件句柄,再等待
1000
毫秒。如果事件变为信号状态,调用
GetEvent
返回事件
Code
和参数。获取到
EC_COMPLETE
事件
Code
时表示回放结束中止循环:
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 (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))
{
printf("Event code: %#04x/n Params: %d, %d/n", evCode, param1, param2);
pEvent->FreeEventParams(evCode, param1, param2);
switch (evCode)
{
case EC_COMPLETE: // Fall through.
case EC_USERABORT: // Fall through.
case EC_ERRORABORT:
CleanUp();
PostQuitMessage(0);
return;
}
}
}
}