DirectShow Samples和Allocators
在看本节前,我们首先明确2个概念。通过前面的章节可知:
- Samples是DirectShow各过滤器之间传输的数据的封包。
- Allocators是用于创建Samples的分配器。
本节将详细介绍Allocators和Sample的原理。
当一个过滤器通过其输出管脚将多媒体数据传输给另一个过滤器(通过其输入管脚)时,它不直接传递内存指针,而是传递一个指向管理内存的COM对象的指针,此对象称为media sample,类型为IMediaSample。通过IMediaSample接口提供的方法如IMediaSample::GetPointer, IMediaSample::GetSize和IMediaSample::GetActualDataLength进行数据访问。
sample总是从输出管脚到输入管脚向下游传输。
在push模型中,输出管脚通过调用输入管脚上的IMemInputPin::Receive来传递。输入pin要么同步处理数据(即在Receive方法内部),要么在工作线程上异步处理数据。如果需要等待资源,则允许输入pin在Receive方法中阻塞。
负责IMediaSample的构建的COM对象叫做分配器,类型为IMemAllocator。每当过滤器需要空缓冲区的sample时,调用IMemAllocator::GetBuffer 方法来分配并返回一个IMediaSample指针。每个管脚连接共享一个分配器。当两个管脚连接时,它们必须指定哪个过滤器将提供分配器。这些管脚还需要设置分配器的属性,例如缓冲区的数量和每个缓冲区的大小。
下图显示了分配器、Media sample和过滤器之间的关系。
Media Sample引用计数
分配器创建一个有限的Sample池。在任何时候,一些Sample可能正在使用中,而其他Sample可用于GetBuffer调用创建。分配器使用引用计数来跟踪Sample。GetBuffer方法返回一个引用计数为1的Sample。如果引用计数变为零,则Sample将返回分配器的池中,在下一个GetBuffer调用中可以使用它。只要引用计数保持在零以上,Sample就不可用于GetBuffer。如果属于分配器的每个Sample都在使用中,GetBuffer方法将阻塞,直到有一个Sample可用为止。
当一个pin接收到一个Sample时,它可以将数据复制到另一个Sample,也可以修改原始Sample并将其传递到下一个过滤器。一个Sample可能会遍历图形过滤管理器中所有过滤器,每个过滤器依次调用AddRef和Release。因此,输出管脚在调用Receive后决不能重复使用Sample,因为下游过滤器可能正在使用Sample。输出引脚必须始终调用GetBuffer以获取新Sample。
这种机制减少了内存分配量,因为过滤器重复使用相同的缓冲区。它还可以防止过滤器意外写入尚未处理的数据,因为分配器维护可用Sample的列表。
过滤器可以对输入和输出管脚使用单独的分配器。例如可以对输入管脚的数据进行扩充(例如,通过解压缩),它可能会这样做。如果输出脚脚的数据小于输入输入管脚的数据长度,则过滤器可能就地处理数据,而不将其复制到新Sample。在这种情况下,两个或多个管脚连接可以共享一个分配器。
提交和取消提交分配器 Committing and Decommitting Allocators
当筛选器第一次创建分配器时,分配器没有保留任何内存缓冲区。此时,对GetBuffer方法的任何调用都将失败。当流开始时,输出pin调用IMemAllocator::Commit,它提交分配器,使其分配内存。Pins现在可以调用GetBuffer。
当流停止时,pin调用IMemAllocator::Decommit,这将使分配器停止工作。在再次提交分配器之前,对GetBuffer的所有后续调用都将失败。另外,如果对GetBuffer的任何调用在等待样本时被阻塞,它们会立即返回一个失败代码。Decommit方法可以释放内存,也可以不释放内存,这取决于实现。例如,CMemAllocator类等待直到其析构函数方法释放内存。