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之间的关系。
图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会拒绝的,因为他们已经停止了。
查看本文来源