5. DirectShow-MediaSample的生命周期管理

MediaSample的分配

另一个 COM 对象称为 分配器,负责创建和管理MediaSample。 分配器公开 IMemAllocator 接口。 每当filter需要具有空缓冲区的MediaSample时,它会调用 IMemAllocator::GetBuffer 方法,该方法返回指向MediaSample的指针。 每个pin连接共享一个分配器。 当两个pin连接时,它们决定哪个filter将提供分配器。 pin还会在分配器上设置属性,例如缓冲区数和每个缓冲区的大小。 (有关详细信息,请参阅 filter如何连接 和 协商分配器。)

image.png

分配器创建有限的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传递到下一个filterMediaSample可能会遍历图形的整个长度,每个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取消提交其分配器。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容