数据与数据缓冲区
数据保存在缓冲区中,缓冲区只是字节数组。 每个缓冲区由名为 MediaSample
的 COM 对象包装,该对象实现 IMediaSample 接口。 MediaSample
由另一种类型的对象(称为分配器)创建,该对象实现 IMemAllocator 接口。 虽然两个或更多个pin
连接可能共享同一分配器,但为每个pin
连接分配一个分配器。 下图演示了此过程。
MediaSample
是承载数据的载体
每个分配器创建MediaSample
池,并为每个MediaSample
分配缓冲区。 每当filter
需要使用数据填充缓冲区时,它都会通过调用 IMemAllocator::GetBuffer 从分配器请求MediaSample
。 如果分配器具有其他filter
当前未使用的任何MediaSample
, GetBuffer 方法将立即返回,并返回指向MediaSample
的指针。 如果分配器的所有MediaSample
都在使用中,则 方法将阻塞,直到MediaSample
可用。 当 方法返回MediaSample
时,filter
会将数据放入MediaSample
里面的缓冲区, (设置相应的标志,通常包括时间戳) ,并将MediaSample
传送到下游。
当呈现器filter
收到MediaSample
时,它会检查时间戳并保留MediaSample
,直到filter
图的引用时钟指示应呈现数据。 filter
呈现数据后,会释放MediaSample
。 在MediaSample
的引用计数为零之前,MediaSample
不会返回到分配器的MediaSample
池中,这意味着每个filter
都释放了MediaSample
。 下图演示了此过程。
MediaSample
可以作为上游生产者和下游消费者中间的缓冲帧
上游filter
可能在呈现器之前运行,也就是说,它填充缓冲区的速度可能比呈现器使用缓冲区快。 即便如此,MediaSample
也不会提前呈现,因为呈现器会保留每个MediaSample
,直到其呈现时间为止。 此外,上游filter
不会意外覆盖缓冲区,因为 GetSample 仅返回未使用的示例。 上游filter
可以提前运行的数量取决于分配器池中的MediaSample
数。
上图仅显示一个分配器,但通常每个流有多个分配器。 因此,当呈现器释放示例时,它可以具有级联效果。 下图显示了解码器在等待呈现器释放示例时保存压缩的视频帧的情况。 分析器filter
也在等待解码器发布示例。
当呈现器释放其示例时,解码器对 GetBuffer 的挂起调用将返回。 然后,解码器可以解码压缩的视频帧并释放它所持有的MediaSample
,从而解除阻塞程序挂起的 GetBuffer 调用。
本地内存的数据传输机制
DirectShow 定义了本地内存传输的两种机制:推送模型和拉取模型。
在推送模型中,源filter
生成数据并将其传递到下游的下一个filter
。 该filter
被动接收数据、处理数据,并将数据发送到下游。
在拉取模型中,源filter
连接到分析器filter
。 分析程序filter
从源filter
请求数据。 源filter
通过传递数据来响应请求。
推送模型使用 IMemInputPin 接口,拉取模型使用 IAsyncReader 接口。
推送模型比拉取模型更常见。 因此,以下文章采用推送模型。 本部分的最后一篇文章 “拉取模型”介绍了 IAsyncReader 接口与 IMemInputPin 的区别。
当pin
将媒体数据传送到另一个pin
时,它不会将直接指针传递给内存缓冲区。 相反,它传递指向管理内存的 COM 对象的指针。 此对象称为 MediaSample
,公开 IMediaSample 接口。 接收pin
通过调用 IMediaSample::GetPointer、IMediaSample::GetSize 和 IMediaSample::GetActualDataLength 等方法来访问内存缓冲区。
MediaSample
始终从输出pin
到输入pin
向下游移动。 在推送模型中,输出pin
通过在输入pin
上调用 IMemInputPin::Receive 来提供MediaSample
。 输入pin
将完全在 Receive 方法内同步 (处理数据) ,或在工作线程上异步处理数据。 如果需要等待资源,则允许输入pin
在 Receive 方法中阻塞。
在 IMemInputPin 接口中,上游filter
确定要发送的数据,并将数据推送到下游filter
。 对于某些filter
, 拉取模型更合适。 此处,下游filter
从上游filter
请求数据。 MediaSample
仍会从输出pin
到输入pin
向下游移动,但下游filter
会启动数据流。 这种类型的连接使用 IAsyncReader 接口。
拉取模型的典型用途是在文件播放中。 例如,在 AVI 播放图中, 异步文件源 filter
执行一般文件读取操作,并将数据作为字节流传递,而没有格式信息。 AVI 拆分器filter
读取 AVI 标头并将流分析为视频和音频MediaSample
。 与异步文件源filter
相比,AVI 拆分器可以确定所需的数据,因此它使用 IAsyncReader 而不是 IMemInputPin。
若要从输出pin
请求数据,输入pin
调用以下方法之一:
IAsyncReader::Request
IAsyncReader::SyncRead
IAsyncReader::SyncReadAligned。
第一种方法是异步的,用于支持多个重叠读取。 其他是同步的。
理论上,任何filter
都可以支持 IAsyncReader,但在实践中,它专为连接到分析程序filter
的源filter
而设计。 分析程序的作用非常类似于推送模型中的源filter
。 暂停时,它会创建一个流式处理线程,该线程从 IAsyncReader 连接拉取数据并将其推送到下游。 输出pin
使用 IMemInputPin,图形的其余部分使用标准推送模型。