DirectShow技术介绍(长篇)-8

    技术2022-06-29  52

     

    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 2graph中移除掉,替换成另一个filter,而此时graph还处于运行状态,那么必须具备以下几个条件:

         filter 3的输入pin(pin D)必须支持IPinConnection接口,这个接口可以重新连接pin而不需要停止它。

         filter 1的输出pin(pin A)必须能够在重连时阻塞媒体数据,数据不再在pin Apin D之间传递。也就是说,输出Pin必须支持IPinFlowControl接口。但是,如果filter 1是发起重连的那个filter,那么它有可能已经在其内部实现了阻塞;

         动态重连包括下列步骤:

         1. Pin A那里阻塞数据流

         2. 重新连接Pin APin 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,查询graphIGraphConfig接口并调用IGraphConfig::ReconnectIGraphConfig::ReconfigureReconnect方法使用比较简单:

         *停止中间filter(比如filter 2),并移除它

         *如果需要的话,加入新的中间filter

         *连接所有的pin

         pauserun所有新的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方法。(在例子中,要阻塞的那个输出pinpin A,那么直接与之相连的那个输入pinPin B)

         3. 等待事件触发。输入pinpin 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-BC-DF-G-H是一个filter chains。每个F-G-H中的子链(F-GG-H)也是一个filter chain。一个filter chain同样可以是由单个filter组成的,因此ABCDFGH同样也是filter chainfilter E由于有两个输入连接,所以任何含有E的一系列filter都不是filter chain

        IFilterChain接口提供下述方法来控制filter chain:

     

        IFilterChain::StartChain   开启一个链

        IFilterChain::StopChain    停止一个链

        IFilterChain::PauseChain   暂停一个链

        IFilterChain::RemoveChain   graph中移除一个链

     

         没有特殊的方法来添加一个链,要添加链,通过调用IFilterGraph::AddFilter方法来插入新的filter,然后调用IGraphBuilder::ConnectIGraphBuilder::Render或类似的方法来连接它们。

         graph运行时,一个filter chain可以在运行和停止状态间切换。当graph处理就绪状态时,它可以在就绪和停止状态间切换。这是两种仅有的filter chain状态切换可能。

        Filter链指南

         当你使用IFilterChain方法时,确认在graph中的filter是否能支持filter链操作是十分必要的,否则,可能会发生死锁或graph错误。filter连接到链上必须发生在链状态改变后。

         使用IFilterChain的最佳情况是与一系统为链而设计的filter一起使用。使用下面的指南来确保你的filter是链操作安全的。

        *在filter链状态变化前,所在在filter链分界线上调用的数据处理都必须已完成。这个规则应用于IMemInputPin::ReceiveIPin::NewSeqmentIPin::EndOfStream方法。链中的filter必须从由链外filter实现的这些方法调用中返回;而链外的filter也必须从这些由链内filter实现的这些方法调用中返回。

         举个例子,在上图中,filter B必须完成在filter A上的所有数据处理调用,而filter E也必须完成从filter D上的调用。如果pin暴露了IPinFlowControlIPinConnection接口,那么如在动态重连那一节中所讲的,你可以通过调用IPinFlowControl::BlockIGraphConfig::PushThroughData方法来推数据。filter也可能通过自己的方法来推数据。

        *上游filter必须与链的状态一起发生变化。比如,在上图中,假如链已停止,但filter A调用IMemInputPin::Receive方法,那么调用将失败,作为回应,filter A停止流。当应用程序重新开启链时,不会产生什么影响,因为filter A不再向使数据流动了。

        *下游filter必须同样与链的状态一起发生变化,否则,下游filter在等待取得sample时会发生死锁,因为sample不会再到来了。比如,多路复用(MUXfilter总是在它所有的input pin上需要数据,如果挂起其中的一个input pin,在其它input pin上的流处理也会被阻塞。这会导致graph死锁

        *每个与链内部filter相连的外部filterpin必须拥有自己的分配器(allocator),它不能被其它pin连接共享。当链的状态发生变化或从graph移除掉时,分配器便不可用了,此时如果还有其它的连接使用这个分配器的话,它们将不能再处理sample了。

        *除非与链相连的filter支持动态断开,否则不要移除链。典型的,已连接的filter会支持IPinConnectionIPinFlowControl接口,或者用它自己定义的接口代替。

     


    最新回复(0)