MediaSample的分配
另一个 COM 对象称为 分配器,负责创建和管理MediaSample
。 分配器公开 IMemAllocator 接口。 每当filter
需要具有空缓冲区的MediaSample
时,它会调用 IMemAllocator::GetBuffer 方法,该方法返回指向MediaSample
的指针。 每个pin
连接共享一个分配器。 当两个pin
连接时,它们决定哪个filter
将提供分配器。 pin
还会在分配器上设置属性,例如缓冲区数和每个缓冲区的大小。 (有关详细信息,请参阅 filter
如何连接 和 协商分配器。)
分配器创建有限的MediaSample
池。 随时可以使用某些MediaSample
,而其他MediaSample
可用于 GetBuffer 调用。 分配器使用引用计数来跟踪MediaSample
。 GetBuffer 方法返回引用计数为 1 的MediaSample
。 如果引用计数为零,则MediaSample
将返回到分配器的池中,可在下一个 GetBuffer 调用中使用。 只要引用计数保持在零以上, GetBuffer 就不能使用MediaSample
。 如果属于分配器的每个MediaSample
都在使用中, 则 GetBuffer 方法会阻塞,直到MediaSample
可用。
例如,假设输入pin
接收MediaSample
。 如果它在 Receive 方法内同步处理MediaSample
,则不会递增引用计数。 Receive 返回后,输出pin
释放MediaSample
,引用计数为零,MediaSample
返回到分配器的池。 另一方面,如果输入pin
在工作线程上处理MediaSample
,它会在离开 Receive 方法之前递增引用计数。 引用计数现在为 2。 当输出pin
释放MediaSample
时,计数为 1。MediaSample
尚未返回到池。 工作线程完成MediaSample
后,它将调用 Release 以释放MediaSample
。 现在,MediaSample
返回到池。
当pin
收到MediaSample
时,它可以将数据复制到另一个MediaSample
,也可以修改原始MediaSample
,并将该MediaSample
传递到下一个filter
。 MediaSample
可能会遍历图形的整个长度,每个filter
依次调用 AddRef 和 Release 。 因此,输出pin
在调用 Receive 后不得重复使用MediaSample
,因为下游filter
可能正在使用该MediaSample
。 输出pin
必须始终调用 GetBuffer 以获取新MediaSample
。
此机制可减少内存分配量,因为filter
会重复使用相同的缓冲区。 它还可防止filter
意外写入尚未处理的数据,因为分配器维护可用MediaSample
的列表。
filter
可以将单独的分配器用于输入和输出。 如果它将输入数据 (展开,则可能会执行此操作,例如,将其解压缩) 。 如果输出不大于输入,filter
可能会就地处理数据,而无需将其复制到新MediaSample
。 在这种情况下,两个或多个pin
连接可以共享一个分配器。
当filter
首次创建分配器时,分配器未保留任何内存缓冲区。 此时,对 GetBuffer 方法的任何调用都将失败。 当流式处理启动时,输出pin
调用 IMemAllocator::Commit,这将提交分配器,使其分配内存。 pin
现在可以调用 GetBuffer。
当流式处理停止时,pin
调用 IMemAllocator::Decommit,这会取消分配器的提交。 对 GetBuffer 的所有后续调用都失败,直到再次提交分配器。 此外,如果当前阻塞对 GetBuffer 的任何 调用等待MediaSample
,它们将立即返回失败代码。 Decommit 方法可能释放内存,也可能不释放内存,具体取决于实现。 例如, CMemAllocator 类将等待其析构函数方法释放内存。
MediaSample的流动过程
filter
有三种可能的状态:已停止、暂停和正在运行。 暂停状态的目的是在filter Graph
中提示数据,以便运行命令立即响应。 filter
关系图管理器控制所有状态转换。 当应用程序调用 IMediaControl::Run、 IMediaControl::P ause 或 IMediaControl::Stop 时,Filter Graph 管理器对所有filter
调用相应的 IMediaFilter 方法。 停止和运行之间的转换始终会经历暂停状态,因此,如果应用程序在已停止的图形上调用 “运行 ”,filter
图形管理器会在运行图形之前暂停图形。
对于大多数filter
,正在运行和暂停状态是相同的。 请考虑以下filter
图:
源 > 转换 > 呈现器
现在假设源filter
不是实时捕获源。 当源filter
暂停时,它会创建一个线程来生成新数据,并尽快将其写入MediaSample
。 线程通过在转换filter
的输入pin
上调用 IMemInputPin::Receive 将MediaSample
“推送”到下游。 转换filter
接收源filter
线程上的MediaSample
。 它可以使用工作线程将MediaSample
传送到呈现器,但通常在同一线程上传递MediaSample
。 呈现器暂停时,它会等待接收MediaSample
。 收到一个MediaSample
后,它会无限期阻塞并保留该MediaSample
。 如果是视频呈现器,则会将MediaSample
显示为海报图像,并根据需要重新绘制图像。
此时,流已完全提示并准备好呈现。 如果图形保持暂停状态,MediaSample
将“堆积”在第一个MediaSample
后面的图形中,直到 Receive 或 IMemAllocator::GetBuffer 中阻塞每个filter
。 不过,不会丢失任何数据。 取消阻塞源线程后,它只会从阻塞的点恢复。
源filter
和转换filter
忽略从暂停到正在运行的转换,它们只是继续尽可能快地处理数据。 但是,当呈现器运行时,它将开始呈现MediaSample
。 首先,它呈现暂停时保存的MediaSample
。 然后,每次收到新MediaSample
时,它都会计算MediaSample
的呈现时间。 (有关详细信息,请参阅 DirectShow 中的时间和时钟。) 呈现器将保留每个MediaSample
,直到呈现时间,此时呈现MediaSample
。 在等待演示时间时,它会在 Receive 方法中阻塞,或者接收具有队列的工作线程上的新MediaSample
。 来自呈现器上游的filter
不涉及计划。
实时源(如捕获设备)是此常规体系结构的例外。 使用实时源时,不宜提前提示任何数据。 应用程序可能会暂停图形,然后等待很长时间后再运行它。 图形不应呈现“过时”MediaSample
。 因此,实时源在暂停时不生成任何MediaSample
,仅在运行时生成。 为了向filter
关系图管理器发出这一事实信号,源filter
的 IMediaFilter::GetState 方法返回VFW_S_CANT_CUE。 此返回代码指示filter
已切换到暂停状态,即使呈现器未收到任何数据。
当filter
停止时,它会拒绝传递给它的任何更多MediaSample
。 源filter
关闭其流式处理线程,其他filter
关闭它们可能已创建的任何工作线程。 pin
取消提交其分配器。