示例和分配器

[与此页面关联的功能 DirectShow 是一项旧功能。 它已被 MediaPlayerIMFMediaEngine媒体基金会中的音频/视频捕获取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能使用 MediaPlayerIMFMediaEngineMedia Foundation 中的音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]

当引脚将媒体数据传送到另一个引脚时,它不会将直接指针传递给内存缓冲区。 相反,它传递指向管理内存的 COM 对象的指针。 此对象称为 媒体示例,公开 IMediaSample 接口。 接收引脚通过调用 IMediaSample::GetPointer、IMediaSample::GetSizeIMediaSample::GetActualDataLength 等方法来访问内存缓冲区。

样本始终从输出引脚到输入引脚向下游移动。 在推送模型中,输出引脚通过在输入引脚上调用 IMemInputPin::Receive 来提供示例。 输入引脚将完全在 Receive 方法) 内同步 (处理数据,或在工作线程上异步处理数据。 如果需要等待资源,则允许输入引脚在 Receive 方法中阻止。

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

下图显示了分配器、媒体示例和筛选器之间的关系。

媒体示例和分配器

媒体示例引用计数

分配器创建有限的样本池。 随时可以使用某些示例,而其他示例可用于 GetBuffer 调用。 分配器使用引用计数来跟踪样本。 GetBuffer 方法返回引用计数为 1 的示例。 如果引用计数为零,则示例将返回到分配器的池中,可在下一个 GetBuffer 调用中使用。 只要引用计数保持在零以上, GetBuffer 就不能使用样本。 如果属于分配器的每个样本都在使用中, 则 GetBuffer 方法会阻止,直到样本可用。

例如,假设输入引脚接收样本。 如果它在 Receive 方法内同步处理示例,则不会递增引用计数。 Receive 返回后,输出引脚释放样本,引用计数为零,样本返回到分配器的池。 另一方面,如果输入引脚在工作线程上处理样本,它会在离开 Receive 方法之前递增引用计数。 引用计数现在为 2。 当输出引脚释放示例时,计数为 1;示例尚未返回到池。 工作线程完成示例后,它将调用 Release 以释放示例。 现在,示例返回到池。

当引脚收到样本时,它可以将数据复制到另一个样本,也可以修改原始示例,并将该示例传递到下一个筛选器。 示例可能会遍历图形的整个长度,每个筛选器依次调用 AddRefRelease 。 因此,输出引脚在调用 Receive 后不得重复使用示例,因为下游筛选器可能正在使用该示例。 输出引脚必须始终调用 GetBuffer 以获取新示例。

此机制可减少内存分配量,因为筛选器会重复使用相同的缓冲区。 它还可防止筛选器意外写入尚未处理的数据,因为分配器维护可用示例的列表。

筛选器可以将单独的分配器用于输入和输出。 如果它将输入数据 (展开,则可能会执行此操作,例如,将其解压缩) 。 如果输出不大于输入,筛选器可能会就地处理数据,而无需将其复制到新样本。 在这种情况下,两个或多个引脚连接可以共享一个分配器。

提交和取消提交分配器

当筛选器首次创建分配器时,分配器未保留任何内存缓冲区。 此时,对 GetBuffer 方法的任何调用都将失败。 当流式处理启动时,输出引脚调用 IMemAllocator::Commit,这将提交分配器,使其分配内存。 引脚现在可以调用 GetBuffer

当流式处理停止时,引脚调用 IMemAllocator::D ecommit,这会取消分配器的提交。 对 GetBuffer 的所有后续调用都失败,直到再次提交分配器。 此外,如果当前阻止对 GetBuffer 的任何 调用等待示例,它们将立即返回失败代码。 Decommit 方法可能释放内存,也可能不释放内存,具体取决于实现。 例如, CMemAllocator 类将等待其析构函数方法释放内存。

筛选器图中的数据流