《DirectShow开发指南》学习笔记_6

动态重建技术

由于下列任何一个原因,我们都需要对已有的Filter Graph进行修改。

  1. 应用程序在播放一段视频的过程中想要插入一个视频效果Filter;
  2. Source Filter在运行过程中改变了数据流的媒体类型,需要接入新的解码Filter;
  3. 应用程序想要在Filter Graph中加入另外一条视频流。

       通常的做法是,现将Filter Graph停止,进行修改之后,再重新启动。那么,有没有一种方法能够在保持Filter Graph运行状态的同时实现Filter Graph的重建呢?答案是肯定的。

Filter Graph的重建包括如下几种情形:

  • 仅仅改变Filter之间连接媒体类型;
  • 增加或删除Filter,重新进行相关Filter之间的连接;
  • 对一条Filter链路(Filter Chain)进行操作。

媒体类型的动态改变

当两个Pin连接完成后,就会有一个双方商定的媒体类型用来描述以后在这两个连接Pin之间传递的数据格式。一般情况下,这个媒体类型在Filter Graph运行的整个过程中是不会改变的。但在一些特殊的场合下,“需要”这种媒体类型的动态改变。DirectShow是如何支持的呢?下面分不同情况来讨论。

从上往下要求媒体类型改变,但传送数据使用的内存不需增大

    媒体类型的改变由Filter A的输出Pin发起。首先,Filter A调用Filter B的输入Pin上的IPin::QueryAccept或IPinConnection::DynamicQueryAccept,看Filter B是否支持新的媒体类型,如果支持,则Filter A调用IMemAllocator::GetBuffer得到一个空的Sample,通过IMediaSample::SetMediaType将新的媒体类型与Sample相连。然后,Filter A就可以将Sample传送给Filter B。当Filter B接收到新的Sample时,可以通过调用IMediaSample::GetMediaType得到新的媒体类型。

      一般来说,Filter B应该具有对媒体类型改变的应变能力,这是在写自己的Filter时应该考虑到的问题。Filter A一般调用QueryAccept来判断Filter B的输入Pin是否支持新的媒体类型。可以从CBasePin::QueryAccept的实现中看到,它仅仅是调用Pin上的CheckMediaType函数。所以QueryAccept返回S_OK并不能保证媒体类型能够在Graph运行状态下改变成功。另一种可靠的方法是,如果Filter B的输入Pin支持IPinConnection接口,那么调用这个接口的DynamicQueryAccept方法。

       对于Filter开发人员来说,还需注意一下CBaseInputPin::Receive的实现:

    这个函数首先调用CheckStreaming来检查Filter的运行状态,其次就是读取Sample的属性(保存到成员变量AM_SAMPLE2_PROPERTIES m_SampleProps中),判断媒体类型是否已经改变。如果媒体类型改变了,会重新调用CheckMediaType进行检查,这就是SDK基类提供给我们的(也是仅有的)、对媒体类型动态改变的“免疫能力”。其实,对于媒体类型动态改变的支持,还需要处理更多的细节问题(如,可能需要重新初始化等),这要因具体的Filter任务而异。

从下往上要求媒体类型改变,但传送数据使用的内存不需增大

       这里的从下往上要求媒体类型改变有一个前提条件,那就是Filter B必须拥有自己的Sample分配器,如果Filter A与Filter B连接双方的Pin使用的Sample分配器是Filter A内部创建的,则Filter B将不能支持这种媒体类型的改变。

       媒体类型的改变由Filter B的输入Pin发起。首先Filter B调用Filter A的输出Pin的QueryAccept,看是否支持新的媒体类型。如果支持,则Filter B内部通过私有方法来改变Sample管理器中的下一个空Sample的媒体类型(因为这个分配器是Filter B创建的,所以Filter B有办法对下一个空Sample调用IMediaSample::SetMediaType,而无需通过公有接口方法IMemAllocator::GetBuffer先将下一个空Sample取出)。接下来当Filter A调用IMemAllocator::GetBuffer取得一个空Sample时,就可以通过IMediaSample::GetMediaType来得到新的媒体类型,此时Filter A必须根据新的媒体类型产生Sample数据,然后再传给Filter B。最后,Filter B在接收到Sample时,会确认媒体类型的改变。

        如果Filter A能够接受新的媒体类型,那么它应该有能力恢复到原来的媒体类型,这种媒体类型的动态改变,最常见与传统的Video Renderer上。Video Renderer在连接时用的媒体类型时RGB,但运行以后有时候却变成了YUV,它的工作原理就如上所述。Video Renderer首先使用RGB的媒体类型连接,是为了兼顾GDI函数的应用。为了高效地画图,当能够使用DirectDraw时,Video Renderer会要求上一级Filter输出YUV格式;DirectDraw表面(Surface)也有可能中途丢失,Video Renderer会再次要求上一级Filter恢复RGB格式,以便使用GDI函数画图。

输出Pin往下要求新的媒体类型,而且需要使用更大的内存以传送新的Sample

       在上述两种情况中,虽然媒体类型改变了,但是Sample使用的内存大小并不需要扩大,当新的媒体类型要求更大的内存用于传送数据时,上一级的Filter必须进行如下处理:

  1. 调用下一级Filter的输入Pin上的IPin::ReceiveConnection。如果成功返回,且需要改变Sample内存大小,则在输出Pin上调用IMemAllocator::SetProperties,以使用新的内存大小进行Sample内存的重新分配;
  2. 调用输入Pin上的IMemInputPin::NotifyAllocator通知使用新的Sample分配器。

对于Filter开发人员来说,还需要注意的一点是,在做出上述改变之前,确信已将多有旧媒体类型的Sample都发送出去了。

动态删除或增加Filter

如图所示,假设我们想将Filter2动态移走。

       要实现动态删除或增加Filter,有两个必要条件:(1)Filter 3(Pin D)必须支持IPinConnection接口(这个接口保证Filter在非停止状态下也能进行Pin的重连);(2)Filter在重连的时候不允许数据的传输,所以要阻塞数据线程。如果“重连”是有Filter 1发起的(在Filter内部完成),那么Filter 1要永不这个数据发送线程;如果“重连”是由应用程序完成的,则要求Filter 1(Pin A)实现IPinFlowControl接口。

动态重连的一般步骤如下:

(1)在Filter 1上阻塞数据流线程。IPinFlowControl::Block可以工作在同步和异步两种模式下。不要在应用程序主线程下使用Block函数的同步模式,因为这样可能会引起线程死锁(Dead-lock)。应该另外使用一个工作者线程,或者使用Block函数的异步模式。代码如下:

//创建一个同步事件对象
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (hEvent != NULL)
{
	//阻塞数据流线程
	hr = pFlowControl->Block(AM_PIN_FLOW_CONTROL_BLOCK, hEvent);
	if(SUCCEEDED(hr))
	{
		//等待完成阻塞工作
		DWORD dwRes = WaitForSingleObject(hEvent, dwMilliseconds);
	}
}

(2)重连Pin A和Pin D,必要时插入新的Filter。这里Pin的重连可以使用IGraphConfig::Reconnect或IGraphConfig::Reconfigure(IGraphConfig接口可以从Filter Graph Manager上获得)。Reconnect比Reconfigure使用起来要简单,它主要做如下几件事:将Filter 2置于停止状态,然后将其移走;加入新的Filter;重新连接相关的各个Pin;将新加入的Filter置于暂停或者运行状态,以使其与所在的Filter Graph同步。代码如下:

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。使用Reconfigure方法时,必须在应用程序里实现IGraphConfigCallback接口;在调用Reconfigure之前,还必须依次调用Filter 3(Pin D)上的IPinConnection::NotifyEndOfStream和Filter 2(Pin B)上的IPin::EndOfStream,以使得还没处理完的数据全部发送下去(这些IGraphConfig::Reconnect会自动完成)。

(3)再次启动Filter 1(Pin A)上的数据发送线程。只需要调用IPinFlowControl::Block即可,代码如下:

pFlowControl->Block(0, NULL);

Filter Chain操作

       Filter Chain是相互连接着的一条Filter链路,并且链路中的每个Filter至多有一个处于“已连接”状态的输入Pin,至多有一个处于“已连接”状态的输出Pin。这条Filter链路中的数据流不依赖链路外的其他Filter。图中的A-B,C-D,F-G-H,F-G,G-H都是Filter Chain,而任何含有E的都不是Filter Chain。

       Filter Chain通过IFilterChain接口来进行相关的操作。该接口可以从Filter Graph Manager上获得,它的所有接口方法如下:

接口方法 描述
StarChain 将指定起始Filter和结束Filter的Filter Chain置于运行状态
StopChain 将指定起始Filter和结束Filter的Filter Chain置于运行状态
RemoveChain 将指定起始Filter和结束Filter的Filter Chain中的所有Filter从Filter Graph中删除
PauseChain 将指定起始Filter和结束Filter的Filter Chain置于暂停状态

       当Filter Graph处于运行状态下,Filter Chain可以在运行和停止状态之间切换;当Filter Graph处于暂停状态下,Filter Chain可以在暂停和停止状态之间切换。以上是Filter Chain仅有的两种状态转换。

        在使用IFilterChain接口之前,还需要确认要操作的Filter是否支持Filter Chain操作,否则很容易引起线程死锁。一般为了使用Filter Chain特性,需要特殊设计一些Filter。

         在这里提到Filter Chain是因为接口方法IFilterChain::RemoveChain能够一次移走好几个Filter;而且,移走一个Filter Chain的步骤与上述第2种情形类似。

猜你喜欢

转载自blog.csdn.net/Small_SaltedFish/article/details/81943494