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;
}
}
}
}
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!


