3.7. Graph动态重建(Dynamic Graph Building)
如果你需要修改一个已经存在的filter graph,你可以停止,修改后再重新启动它。这通常是一种最佳的解决方法。但是,在某此情况下,你可能需要在一个graph处于运行状态时来修改它,比如:
*应用程序在进行视频回放时需要插入一个(视频滤镜filter)Video effect filter;
*source filter在播放的过程中改变了媒体格式,此时可能需要接入新的解码filter;
*应用程序在graph中加入一个新的视频流。
上面的这些都是graph动态重建的例子。所有在graph继续处于运行状态而做的graph修改都被叫做graph动态重建。动态重建可以由应用程序发起,也可以由一个在graph中的filter发起。动态重建有三种可能:
*媒体格式动态变化:一个filter可以在运行的中途改变媒体格式,而不需要重新被替换为另一个;
*动态重连:在graph中添加或删除filter
*Filter Chain操作:添加,删除,控制filter chain,(Filter Chain是相互连接着的一条Filter链路,并且链路中的每个Filter至多有一个Input pin,至多有一个Output pin)
3.7.1. 动态重连
在绝大多数的directshow filter中,当graph处于运行状态时pin是不能被重新连接的,应用程序必须在重连前停止graph。但是,某些filter却支持动态重连,这既可以由应用程序来执行,也可以由graph中的一个filter来执行。
如下图:
假设我们要将filter 2从graph中移除掉,替换成另一个filter,而此时graph还处于运行状态,那么必须具备以下几个条件:
*filter 3的输入pin(pin D)必须支持IPinConnection接口,这个接口可以重新连接pin而不需要停止它。
*filter 1的输出pin(pin A)必须能够在重连时阻塞媒体数据,数据不再在pin A和pin D之间传递。也就是说,输出Pin必须支持IPinFlowControl接口。但是,如果filter 1是发起重连的那个filter,那么它有可能已经在其内部实现了阻塞;
动态重连包括下列步骤:
1. 从Pin A那里阻塞数据流
2. 重新连接Pin A和Pin D,或者在中间加入新的filter
3. 取消Pin A上的阻塞
步骤1. 阻塞数据流
通过调用Pin A上的IPinFlowControl::Block方法来阻塞数据流。这个方法既可以被同步调用,也可以被异步调用。要异步调用这个方法,需要创建一个win32事件对象,并将事件句柄传给Block,方法会立即返回,然后使用WaitForSingleObject或其它函数来等待事件的触发。当阻塞工作完成时,pin会触发这个事件。如:
// Create an event HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (hEvent != NULL) { // Block the data flow. hr = pFlowControl->Block(AM_PIN_FLOW_CONTROL_BLOCK, hEvent); if (SUCCEEDED(hr)) { // Wait for the pin to finish. DWORD dwRes = WaitForSingleObject(hEvent, dwMilliseconds); } } |
如果是同步调用Block,那么只需将传入的hEvent参数设为NULL,此时这个方法会一直阻塞到阻塞工作完成为止。如果pin还没有准备好deliver一个新的sample,那么就会一直阻塞。而如果filter处于就绪状态,这可能会花费任意长的时间,因此,不要在应用程序的主线程中使用同步调用,以免发生死锁,开一个工作线程来使用同步调用,或者干脆就使用异步调用。
步骤2. 重连pin
要重新连接pin,查询graph的IGraphConfig接口并调用IGraphConfig::Reconnect或IGraphConfig::Reconfigure。Reconnect方法使用比较简单:
*停止中间filter(比如filter 2),并移除它
*如果需要的话,加入新的中间filter
*连接所有的pin
*pause或run所有新的filter,使它的状态与graph相同
Reconnect方法有参数可以用来指定pin连接的媒体类型和中间filter。如:
pGraph->AddFilter(pNewFilter, L"New Filter for the Graph"); pConfig->Reconnect( pPinA, // Reconnect this output pin... pPinD, // ... to this input pin. pMediaType, // Use this media type. pNewFilter, // Connect them through this filter. NULL, 0); |
如果Reconnect还不够用来应付我们的要求,那么你可以使用Reconfigure方法,它调用一个由应用程序定义的回调函数来重连这些pin。要调用这个方法,需要在你的应用程序中实现IGraphConfigCallback接口。
在调用Reconfigure之前,如前面所述地那样阻塞输出pin的数据流。然后如下所示,将处于待处理状态的数据push下去:
1. 在重连链路中处于下游的最远的那个输入pin(例子中为Pin D)上调用IPinConnection::NotifyEndOfStream方法,方法的参数是一个Win32事件句柄;
2. 在与要阻塞数据的那个输出pin直接相连的那个输入pin上调用IPin::EndOfStream方法。(在例子中,要阻塞的那个输出pin是pin A,那么直接与之相连的那个输入pin为Pin B);
3. 等待事件触发。输入pin(pin D)在它接收到end-of-stream事件通告时触发事件。这表示再没有数据需要传输,此时就可以安全地进行重连了。
注意:IGraphConfig::Reconnect方法会自动处理上述步骤,你仅在调用Reconfigure方法时才需要自己来处理。
当数据完成push后,调用Reconfigure,传入IGraphConfigCallback回调函数的指针。Filter Graph Manager会调用IGraphConfigCallback::Reconfigure方法。
步骤3. 取消数据流的阻塞
当你完成重连后,通过调用IPinFlowControl::Block,第一个参数为0来取消阻塞。
注意:如果动态重连是由一个filter来执行的,那么你需要知道一点线程方面的问题。如果filter graph manager尝试去停止filter,它可能会死锁,因为graph等待filter停止,而与此同时,filter有可能在等待数据在graph中完成push。要防止这个可能存在的死锁问题,如前所述可以用事件机制来处理。
3.7.2. filter链(filter chains)
一个filter chain是一系列具备下述条件的相互连接的filter:
*每一个在链中的filter最多只有一个已连接的输入pin和一个已连接的输出pin;
*Filter链路中的数据流不依赖于链路外的其他Filter
举个例子,在下图中,filter A-B,C-D和F-G-H是一个filter chains。每个F-G-H中的子链(F-G和G-H)也是一个filter chain。一个filter chain同样可以是由单个filter组成的,因此A、B、C、D、F、G和H同样也是filter chain。filter E由于有两个输入连接,所以任何含有E的一系列filter都不是filter chain。
IFilterChain接口提供下述方法来控制filter chain:
IFilterChain::StartChain 开启一个链
IFilterChain::StopChain 停止一个链
IFilterChain::PauseChain 暂停一个链
IFilterChain::RemoveChain 从graph中移除一个链
没有特殊的方法来添加一个链,要添加链,通过调用IFilterGraph::AddFilter方法来插入新的filter,然后调用IGraphBuilder::Connect,IGraphBuilder::Render或类似的方法来连接它们。
当graph运行时,一个filter chain可以在运行和停止状态间切换。当graph处理就绪状态时,它可以在就绪和停止状态间切换。这是两种仅有的filter chain状态切换可能。
Filter链指南
当你使用IFilterChain方法时,确认在graph中的filter是否能支持filter链操作是十分必要的,否则,可能会发生死锁或graph错误。filter连接到链上必须发生在链状态改变后。
使用IFilterChain的最佳情况是与一系统为链而设计的filter一起使用。使用下面的指南来确保你的filter是链操作安全的。参考下图:
*在filter链状态变化前,所在在filter链分界线上调用的数据处理都必须已完成。这个规则应用于IMemInputPin::Receive、IPin::NewSeqment和IPin::EndOfStream方法。链中的filter必须从由链外filter实现的这些方法调用中返回;而链外的filter也必须从这些由链内filter实现的这些方法调用中返回。
举个例子,在上图中,filter B必须完成在filter A上的所有数据处理调用,而filter E也必须完成从filter D上的调用。如果pin暴露了IPinFlowControl和IPinConnection接口,那么如在动态重连那一节中所讲的,你可以通过调用IPinFlowControl::Block和IGraphConfig::PushThroughData方法来推数据。filter也可能通过自己的方法来推数据。
*上游filter必须与链的状态一起发生变化。比如,在上图中,假如链已停止,但filter A调用IMemInputPin::Receive方法,那么调用将失败,作为回应,filter A停止流。当应用程序重新开启链时,不会产生什么影响,因为filter A不再向使数据流动了。
*下游filter必须同样与链的状态一起发生变化,否则,下游filter在等待取得sample时会发生死锁,因为sample不会再到来了。比如,多路复用(MUX)filter总是在它所有的input pin上需要数据,如果挂起其中的一个input pin,在其它input pin上的流处理也会被阻塞。这会导致graph死锁
*每个与链内部filter相连的外部filter的pin必须拥有自己的分配器(allocator),它不能被其它pin连接共享。当链的状态发生变化或从graph移除掉时,分配器便不可用了,此时如果还有其它的连接使用这个分配器的话,它们将不能再处理sample了。
*除非与链相连的filter支持动态断开,否则不要移除链。典型的,已连接的filter会支持IPinConnection或IPinFlowControl接口,或者用它自己定义的接口代替。
2005年3月28日 13:34