DirectShow 视频捕获(5) 基类如何实现连接

 How the Base Classes Implement Connection

原文:http://msdn.microsoft.com/en-us/library/ms899460.aspx

 

CBasePin类和它的派生类CBaseOutputPinCBaseInputPin实现了大部分共同的连接情况下的机制,大部分都可以在派生类中重写,以便更多的过程控制。

连接过程依赖于下面4个接口的实现

  • IPin,由CBasePin实现,并且被CBaseInputPin和CBaseOutputPin 继承。
  • IEnumMediaTypes,由CEnumMediaTypes类实现,并通过IPin::EnumMediaTypes方法传递出去。
  • IMemInputPin,由CBaseInputPin类实现。
  • IMemAllocator,由CBaseAllocator 实现,并通过IMemInputPin::GetAllocator方法传递。

只要输入引脚的过滤器(叫做下游过滤器)期望提供共享内存分配器来传输采样,IMemInputPin 和 IMemAllocator就是必须的。然而,在CBaseInputPin类中的实现假设这种情况,并且在IMemInputPin实现中提供一个分配对象给连接的输出引脚的请求。

默认的基类在连接的时候,派生于CBaseInputPin 和 CBaseOutputPin的引脚类只需要重写和实现少部分成员函数,并让基类做剩下的工作。从这些类派生的基类如CTransformInputPinCTransformOutputPin做大部分所需的工作来提供一个默认的连接方案。

从CBaseInputPin和CBaseOutputPin继承的引脚类只需要重写下面的成员函数使得能够连接引脚。

  • CBasePin::CheckMediaType,…  。重写的成员函数必须接受或者拒绝目标媒体类型。
  • CBasePin::GetMediaType,被输出引脚枚举器的媒体类型调用,用来建议已经通过传输过滤器的输入引脚达成一致的媒体类型。这个成员函数也用来显示源过滤器会产生的媒体类型。

并且,从CBaseOutputPin继承的输出引脚必须重写CBaseOutputPin::DecideBufferSize成员函数。这个函数通过基类调用,用来输入引脚通知所有取得的分配器,它会提供的媒体采样的大小和类型。这个由过滤器的输出引脚完成,因为派生的类应该知道将要发送到相连过滤器输入引脚的数据的大小和类型。

明白重载函数的上下文,对于在类库中连接代码的执行的单步调试很有帮助。所有连接在一个CBasePin::Connect成员函数中发生。

这部分包含如下主题。

  • Filter Graph Manager 开始连接
  • 和CBasePin::AgreeMediaType协商媒体类型
  • 和CBasePin::TryMediaTypes决定一个媒体类型

Filter Graph Manager 开始连接

当Filter Graph Manager 在输出引脚调用IPin::Connect方法开始连接,传递一个指向要连接的输入引脚的指针。Filter Graph Manager 已经提前获取了直线两个过滤器的IPin指针,例如在连接过滤器的时候调用IBaseFilter::EnumPins方法。EnumPins方法创建一个CEnumPins对象来枚举引脚,枚举器通过重复调用派生过滤器必须实现的CBaseFilter::GetPin成员函数。

CBasePin::Connect实现IPin::Connect的方法,并在做了很多工作。它调用下面的方法。

  • CheckConnect,被CBaseOutputPin重写
  • AgreeMediaType,由CBasePin实现

CBasePin::CheckConnect简单实现判断引脚的方向是否不同。重写的CBaseOutputPin::CheckConnect成员函数调用连接的输入引脚的IUnknown::QueryInterface方法获取一个指向那个引脚的IMemInputPin接口的指针。这回在后面的连接过程中被使用来向连接的输入引脚请求一个分配器。(如果输出引脚已经有一个分配器,你的派生类可以重写CBaseOutputPin::CheckConnect方法并且忽略获取IMemInputPin接口;例如想从上游过滤器使用分配器来消除拷贝。)

和CBasePin::AgreeMediaType协商媒体类型

下一步调用CBasePin::AgreeMediaType成员函数,尝试协商两个引脚都适合的媒体类型。这通过尝试从连接的输入引脚上找一个符合输出引脚的媒体类型。如果这样找失败了,找一个输出引脚提供的连接的输入引脚符合的媒体类型。

CBasePin::AgreeMediaType 调用下面的成员函数和方法

  • IPin::EnumMediaTypes 在已连接的引脚
  • CBasePin::TryMediaTypes 在派生的输出引脚类

调用 连接的输入引脚的IPin::EnumMediaTypes 方法返回一个媒体枚举器(IEnumMediaTypes).这允许输出引脚检查输入引脚首选的媒体类型。

枚举器的IEnumMediaTypes::Next 方法调用派生的输入引脚的GetMediaType成员函数获取每一个媒体类型。如果GetMediaType 没有实现,基类的实现返回一个错误,但这不是打断连接的充分条件。(引脚不要求有一个首选的媒体类型,如果一个引脚或者其他可以建议一个它们都能接受的媒体类型。如果两个引脚都不能建议一个类型,连接就会失败)

和CBasePin::TryMediaTypes决定一个媒体类型

然后,CBasePin::AgreeMediaType 调用 CBasePin::TryMediaTypes。TryMediaTypes成员函数在连接的输入引脚的首先媒体类型中循环,并且为每一个找到的媒体类型调用派生的输出引脚类的CBasePin::CheckMediaType成员函数。派生的输出引脚类必须实现CheckMediaType。如果CheckMediaType访问媒体类型,调用连接输入引脚的IPin::ReceiveConnection方法和媒体类型来决定连接的输入引脚是否接受这个媒体类型。如果这样,TryMediaTypes调用CBaseOutputPin::CompleteConnect成员函数来结束连接到输入引脚。

如果输入引脚没有输出类型可用的媒体类型,CBasePin::AgreeMediaType重复整个过程,使用枚举器枚举输出引脚的媒体类型。(换言之,它获取自己的枚举器并且为每个首选的媒体类型调用TryMediaTypes。)再次,枚举器为每个在列表中的媒体类型调用GetMediaType。在这种情况下,GetMediaType必须实现,以便提供媒体类型。如果过滤器是一个源过滤器,它会暴露一个明确的媒体类型。如果过滤器是转换过滤器,媒体类型将会在过滤器的输入引脚和它相连的引脚间建立;转换过滤器必须查询这个媒体类型或者简单的使用上游过滤器的枚举器(除非转换过滤器改变输入到输出的媒体类型)。

CBasePin::TryMediaTypes 调用CheckMediaType,即使当TryMediaTypes枚举输出引脚的首选媒体类型列表。这是因为拥有的过滤器可能是一个透明过滤器,就是简单使用上游过滤器的媒体类型(和枚举器);这样直线是否决定的媒体类型是否兼容。这种输入引脚的转换过滤器也许服从连接时选择的媒体类型,在这种情况下直到转换过滤器的输出引脚类确定媒体类型是否和这个转换是否兼容。

如果可以建立一个媒体类型,TryMediaTypes最终调用CBaseOutputPin::CompleteConnect成员函数类协商内存分配器。

首先,CBaseOutputPin::CompleteConnect成员函数调用CBaseOutputPin::DecideAllocator成员函数。这个成员函数协商一个和输入引脚共享的内存分配器。通过调用连接的输入引脚的IMemInputPin::GetAllocator方法,这样获得一个指向一个有输入引脚提供的IMemAllocator interface接口指针。

然后,CompleteConnect调用纯虚函数CBaseOutputPin::DecideBufferSize,这个函数你必须在输出引脚类中必须重写和实现,因为只有派生类可以为它自己的媒体类型决定请求的缓冲区大小。

最后,CompleteConnect调用连接引脚的IMemInputPin::NotifyAllocator方法来通知分配器的输入引脚使用并提供用一个指向这个的指针给它。在输出引脚可以用一个不同的分配器重试或者连接失败的情况下,输入引脚可以拒绝这个分配器。如果你的派生类没有使用连接的输入引脚的分配器,在你的派生类中重写 CBaseOutputPin::DecideAllocator用一个分配器调用NotifyAllocator成员函数。

什么时候应该重连

重连总是通过Filter Graph Manager 的IFilterGraph接口进行。通过调用IFilterGraph2::ReconnectEx或者IFilterGraph::Reconnect方法进行重连。两个方法都传递两个引脚的IPin接口来重连的。ReconnectEx方法指定了一个媒体类型,因此不要记住那个类型去连接引脚,这样就变得重连更容易成功。

典型的过滤器都先连接上游的过滤器,然后连接下游的过滤器。所以,过滤器在通知可以连接输出引脚之前和输入引脚进行协商。当过滤器的输出引脚连接时,也许为过滤器的输入引脚建立的媒体类型和分配器变得明显不可用。在这种情况下,可以打断输入连接或者重连。

例如,考虑下面的连接情况。一个音频特效过滤器(例如,混音效果)要插入到MPEG音频减压过滤器和另外一个音频特效过滤器之间。在于上游的减压过滤器连接时,媒体类型比如选择22.05kHz、16位声道。然而,在这种情况下,当混音过滤器连接它的输出引脚时,下游过滤器只能接受11.025kHz、16位声道的媒体类型。因此,在连接下游过滤器后,混音特效过滤器必须和上游过滤器重连并且为11.025kHz媒体类型协商。

但是,媒体类型不是重连的唯一因素。在很多情况下,过滤器是一个原位转换过滤器,也就是过滤器不要求改变媒体类型或者拷贝数据。这样的过滤器可以设计出使用其他过滤器(如上游或者下游过滤器)的分配器,同样的使用其他过滤器的媒体类型。换句话说,这个过滤器在另一个过滤器的缓冲区中做转换(例如,源过滤器的文件缓冲区或者渲染过滤器的视频缓冲区)。

通常的规则是这种类型的过滤器应提供下游过滤器的分配器给上游的过滤器,来和输出引脚建立分配器。当上游的输出引脚引脚请输入引脚的一个分配器时,要求和输入引脚重连,以便提供一个通过转换过滤器的输出引脚获得的下游过滤器的分配器。因此原位转换过滤器 总是要重连。

需要重连要遵守两个重要的规则。

首先,过滤器一次从不要求一个重连,除非它能保证重连能够成功。如果重连失败了,会导致在过滤器图表中产生一个不能确定清除的异步错误。产生的任何错误(例如,不相容的媒体类型)都因该在第一次连接时产生,那时(至少通过过滤器图表管理器或者应用程序)在多个级别有足够的重试选择可用。

其次,过滤器应该在和调用IPin::Connect同一个线程中请求重连。例如下面的情况在不同的线程中尝试重连会导致错误。

  • 过滤器图表管理器在一个引脚调用Connect。
  • 过滤器引脚实行 Connect方法并创建一个线程来检查连接准备是否做好。
  • 过滤器图表管理器返回应用程序
  • 应用程序调用过滤器图表管理器的IMediaControl::RunIMediaControl::Run方法来开始过滤器图表,过滤器开始运行。
  • 线程从开始连接调用IFilterGraph2::ReconnectEx或者IFilterGraph::Reconnect方法,而且过滤器图表管理器开始重连。
  • 失败产生,因为过滤器在运行状态下不能重连。

在过滤器图表还在执行IGraphBuilder::Connect方法的过程中,只要IFilterGraph2::ReconnectEx或者IFilterGraph::Reconnect开始执行了,过滤器图表没有方法来阻止这个失败。在IPin::Connect返回之前掉要过滤器图表来重连是确保这个错误不会发生的最好方法。达到这个最好的方法是所有运行在同一个线程中。

 

 

你可能感兴趣的:(DirectShow 视频捕获(5) 基类如何实现连接)