DirectShow开发快速入门之二 -- 数据流的流动

Directshow数据流动概述
  Filter之间的数据是通过Sample来传送的。Sample是一个COM组件,拥有自己的一段数据缓冲buffer,这个com组件暴露了IMediaSample接口。这个sample一般都有一个叫做内存分配器(alloctor)的com对象来创建管理,这个对象具有IMemAllocator接口。如下图所示:
DirectShow开发快速入门之二 -- 数据流的流动_第1张图片
图1 
  两个Filter之间的连接都要指定一个allocator,有时也有几个Filter连接同用一个allocator。每一个allocator都要创建一个media sample池,并且给每一个sample分配一个内存buffer。每当一个Filter需要一个buffer来填充数据,它就通过allocator的函数IMemAllocator::GetBuffer.来获得一个sample。如果分配器allocator正好有空闲的sample,GetBuffer立即返回一个指向该sample的指针。如果没有空闲的sample,该方法就阻塞,直到有一个sample可用为止。当该函数返回一个sample时,Filter就将数据填充到buffer里,设置好标识,然后就将sample传递给下一个Filter。
  当一个Render Filter接收到一个sample时,它就检查该sample的时间戳,直到Fliter Graph的参考时钟表明该数据可用播放,Filter就开始播放该数据。当数据播放完毕,Filter释放sample,直到所有的Filter都释放对该sample的引用,该sample的引用计数为0时,这个sample才返回到sample池。

图2 
  有时也许数据流的上游对buffer的填充比播放要快,即使这样,render Filter也要按照时间戳播放数据,这样sample池中的sample数量就少,从而填充的速度减慢。
  下面描述了在流中只有一个allocator的情景,实际上,在每条数据流中总是有好几个allocator,当一个sample被释放的时候,也许此时有好几个allocator都在等着该sample,这就有新的问题了,也许有的alloctor永远都不能被分配sample,陷入互锁状态。下面的图就演示了这种情形,Decoder有数据需要压缩,因此它在等待Renderer释放sample,但是,Parser也在请求sample,它在等待decoder释放sample。
DirectShow开发快速入门之二 -- 数据流的流动_第2张图片
图3 
  传输(Transports)
  为了在过滤器图表中传送媒体数据,DirectShow过滤器需要支持一些协议,称之为传输协议(transport)。相连的过滤器必须支持同样的传输协议,否则不能交换媒体数据。
  大多数的DirectShow过滤器把媒体数据保存在主存储器中,并通过引脚把数据提交给其它的过滤器,这种传输称为局部存储器传输(local memory transport)。虽然局部存储器传输在DirectShow中最常用,但并不是所有的过滤器都使用它。例如,有些过滤器通过硬件传送媒体数据,引脚只是用来提交控制信息,如IOverlay接口。
  DirectShow为局部存储器传输定义了两种机制:推模式(push model)和拉模式(pull model)。在推模式中,源过滤器生成数据并提交给下一级过滤器。下一级过滤器被动的接收数据,完成处理后再传送给再下一级过滤器。在拉模式中,源过滤器与一个Render过滤器相连。Render过滤器向源过滤器请求数据后,源过滤器才传送数据以响应请求。推模式使用的是IMemInputPin接口,拉模式使用IAsyncReader接口,推模式比拉模式要更常用。
Samples和Allocators
  当一个pin向另一个pin传递数据的时候,它并不是直接将内存块的指针传递下一个pin,实际上,它将传递一个管理内存的com对象的指针给下一个pin。这个com对象称为media sample。暴露了IMediaSample接口。接收pin通过调用IMediaSample的方法来对内存进行操作,比如方法IMediaSample::GetSize, IMediaSample::GetActualDataLength以及IMediaSample::GetPointer。
  Sample一般都是从源filter开始,通过输出pin传递到下一个filter的输入pin,一路传递下去一直到render filter。在拉模式中,输出pin通过调用输入pin上的IMemInputPin::Receive方法传递sample,输入pin或者在Receive函数同步处理数据,或者另外采用一个工作线程异步出来的方式。如果在Recive方法中需要等待资源的话,也可以阻塞。
  另外一个com对象,叫做allocator,用来创建和管理sample的。暴露了IMemAllocator接口。当一个filter需要一个空的buffer的时候,就可以调用IMemAllocator::GetBuffer,该方法返回一个指向sample的指针。每一个pin连接都共享一个allocator,当两个pin连接的时候,他们会决定由哪个filter来提供allocator,通过pin还可以设置allocator的属性,例如,buffer的数量和大小。
  下面的图表显示了allocator,sample和filter之间的关系。
 
DirectShow开发快速入门之二 -- 数据流的流动_第3张图片
图4 
  Media sample引用计数
  Allocator创建了一个sample池。因此 ,当某个Filter调用GetBuffer函数时,一些sample被使用,其他空闲的sample可以响应。Allocator通过引用计数来跟踪samples。Filter调用Getbuffer返回的sample的引用计数是1。当sample的引用计数为0时,sample就返回内存池,成为空闲的sample,可以再次响应Getbuffer的调用。如果所有的sample都处于繁忙状态,Getbuffer就会阻塞,直到有一个sample空闲。
  例如,假设一个输入pin接到一个sample,如果它在Receive方法里同步的处理这个sample,没有增加该sample的引用计数,等到Receive返回后,输出pin就释放这个sample,引用计数为0,sample就返回到内存池中。如果输入pin的线程还要处理该sample,引用计数增加1,成为2,输出pin返回,释放,计数成1。
  当一个输入pin接收一个sample时,它可以将数据复制到另一个sample中,也可以将这个sample传递到下一个Filter。一个sample可以流遍整个filter graph。不过引用计数要保持大于0。 当一个输出pin调用了Release以后,就不应该再次使用该sample,因为也许下游还有filter正在使用该sample。输出pin必须调用GetBuffer获取新的sample。
  这种机制减少 了内存分配的,因为buffer可以重用。也防止了数据没有被处理的sample被重新写入。
  当一个Filter创建一个allocator的时候,allocator还没有保留任何的内存,如果这个时候有人Getbuffer,就会失败。只有当数据流开始的时候,输出pin调用IMemAllocator::Commit,提交allocator,现在才能分配内存。
  当数据流停止的时候,pin就调用IMemAllocator::Decommit,来销毁allocator。在allocator再次committ之前,所有调用GetBuffer方法都会失败。当然,如果有一个GetBuffer阻塞调用在等待sample的时候,遇到Decommit方法,会立即返回一个错误码。
  Filter的状态
  Filter有三种状态,停止,暂停,运行。
  过滤器图表管理器 控制着Filter的所有状态的转换。当应用程序调用IMediaControl::Run, IMediaControl::Pause, or IMediaControl::Stop时, 过滤器图表管理器就调用Filter相应的IMediaFilter方法。停止,运行状态的切换总是要经过暂停,因此,当一个应用程序对一个停止的Graph 调用RUN命令时,过滤器图表管理器 在run之前首先要暂停。
  对于大多数的filter来说,running和paused状态是一样的。看下面的Graph Source > Transform > Renderer
  当一个Filter停止时,它拒绝发送给它的任何samples,源filter关闭他们的stream线程,其他filter也关闭他们创建的其他线程,pin decommit他们的内存分配器。
  过滤器图表管理器按照逆流的方向来切换Filter的状态,从Renderer Filter到源filter,这种方式可以防止死锁。最关键的状态切换是暂停和停止之间。
  从停止到暂停,当filter暂停时,它就做好了接收sample的准备,源filter是最后一个切换到暂停的。它开始创建streaming线程,发送sample,因为下游的filter的状态都已经切换到暂停了,所以,所有的filter都可以接收sample。只有当所有的flter都接收到sample,过滤器图表管理器才算完成了状态的切换
  从暂停到停止。当一个filter停止时,它要释放它拥有的所有的samples。当图表管理器试图停掉上游的一个filter时,这个filter不会阻塞在Getbuffer和receive方法里,它会立即响应stop命令。上游的filter也许在执行stop命令前还会讲少量的sample传递下去,但是下游的filter会拒绝的,因为他们已经停止了。

你可能感兴趣的:(工作,Stream,filter,存储,Graph,buffer)