如何为 DirectShow 编写源筛选器

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

本主题介绍如何为 DirectShow 编写自定义源筛选器。

注意

本主题仅介绍推送源;它不描述拉取源,例如异步读取器筛选器或连接到拉取源的拆分器筛选器。 有关推送源和拉取源之间的区别,请参阅筛选器开发人员数据流

DirectShow 流式处理模型

编写源筛选器时,请务必了解推送源与实时源不同。 实时源从某些外部源(例如相机或网络流)获取数据。 通常,实时源无法控制传入数据速率。 如果下游筛选器使用数据的速度不够快,则源将需要删除样本。

但推送源不必是实时源。 例如,推送源可以从本地文件读取数据。 在这种情况下,下游呈现器筛选器根据参考时钟和示例时间戳确定它们从源中消耗数据的速度。 源筛选器会尽快提供样本,但实际数据流受到呈现器的限制。 筛选器开发人员的数据流中介绍了用于对数据流进行门控的机制。

源筛选器上的每个输出引脚都会创建一个名为 流式处理线程的线程。 引脚在流式处理线程上提供示例。 通常,所有解码、处理和呈现都发生在此线程上,尽管某些下游筛选器可能会创建其他线程来对其输出样本进行排队。

流式处理线程运行具有以下结构的循环:

until (stopped)
  1. Get a media sample from the allocator.
  2. Fill the sample with data.
  3. Time stamp the sample. 
  4. Deliver the sample downstream.

如果没有可用的示例,则步骤 1 将阻止,直到示例变为可用。 步骤 4 也可以阻止;例如,在暂停图形时,它可以阻止。

循环运行速度尽可能快,但受呈现器筛选器呈现每个样本的速度的限制。 假设筛选器图具有引用时钟,则速率由样本的呈现时间决定。 如果没有参考时钟,呈现器会尽快使用样本。

使用 CSource 和 CSourceStream

DirectShow 基类包括两个支持推送源的类: CSourceCSourceStream

输出引脚

源筛选器可以有多个输出引脚。 在筛选器的构造函数方法中,创建一个或多个派生自 CSourceStream 的引脚, (每个输出流) 一个引脚。 无需存储指向引脚的指针;引脚在创建时会自动将自身添加到筛选器。

输出格式

输出引脚使用以下 CSourceStream 方法处理格式协商:

方法 说明
GetMediaType 从输出引脚获取媒体类型。
引脚必须至少建议一种介质类型,因为下游筛选器可能不会建议任何类型。 在大多数情况下,下游筛选器将是解码器或呈现器,具体取决于源筛选器是提供压缩数据还是未压缩数据。 呈现器筛选器通常需要完整的媒体类型,其中包含呈现流所需的所有格式信息。 对于解码器,媒体类型中所需的信息量在很大程度上取决于编码格式。
CheckMediaType 检查输出引脚是否接受给定的媒体类型。 替代此方法是可选的,具体取决于实现 GetMediaType 的方式。

GetMediaType 方法已重载:

如果源筛选器的输出引脚仅支持一种媒体格式,则应重写 (1) 以使用该格式初始化 CMediaType 对象。 保留 (2 的默认实现) ,并保留 CheckMediaType 的默认实现。

如果引脚支持多种格式,请替代 (2) 。 根据索引变量的值初始化 CMediaType 对象。 引脚应以有序列表的形式返回格式。 在这种情况下,还必须重写 CheckMediaType,以根据格式列表检查媒体类型。

对于未压缩的视频格式,请记住,下游筛选器可以建议具有各种步幅值的格式。 筛选器应接受任何有效的步幅值。 有关详细信息,请参阅 BITMAPINFOHEADER

还必须重写纯虚拟 CBaseOutputPin::D ecideBufferSize 方法。 使用此方法可设置示例缓冲区的大小。

流式处理

CSourceStream 类为引脚创建流式处理线程。 线程过程在 CSourceStream::D oBufferProcessingLoop 方法中实现。 此方法调用纯虚拟 CSourceStream::FillBuffer 方法,派生类必须重写该方法。 此方法是引脚用数据填充缓冲区的位置。 例如,如果筛选器提供未压缩的视频,则可以在此处绘制视频帧。

当筛选器暂停或停止时,基类会在正确的时间自动启动和停止线程循环。 发生这种情况时, CSourceStream 类会调用一些方法来通知派生类:

如果需要添加任何特殊处理,可以重写这些方法。 否则,默认实现仅返回 S_OK

寻求

如果源筛选器具有一个输出引脚,则可以使用 CSourceSeeking 类作为实现查找的起点。 从 CSourceStreamCSourceSeeking 继承固定类。

注意

对于具有多个输出引脚的筛选器,不建议使用 CSourceSeeking。 main问题是,只有一个引脚应响应请求。 这通常需要引脚和筛选器之间的通信。

CSourceSeeking 类管理播放速率、开始时间、停止时间和持续时间。 派生类应设置初始停止时间和持续时间。 每当其中一个值发生更改时,将根据需要调用 CSourceSeeking::ChangeRateCSourceSeeking::ChangeStartCSourceSeeking::ChangeStop 方法。 这些方法都是纯虚拟方法。 派生的引脚类重写这些方法以执行以下操作:

  1. 在下游引脚上调用 IPin::BeginFlush 。 这会导致下游筛选器释放它们保存的样本,并拒绝新样本。
  2. 调用 CSourceStream::Stop 以停止流式处理线程。 源筛选器暂停生成新数据。
  3. 在下游引脚上调用 IPin::EndFlush 。 这向下游筛选器发出信号以接受新数据。
  4. 使用新的开始和停止时间和速率调用 IPin::NewSegment
  5. 在下一个示例中设置不连续属性。

有关详细信息,请参阅 支持在源筛选器中查找

如果筛选器支持查找,则流位置现在与演示时间无关。 查找后,时间戳将重置为零。 时间戳的一般公式为:

  • 示例开始时间 = 已用时间/播放速率
  • 样本结束时间 = 样本开始时间 + 每帧 (时间/播放速率)

其中 ,经过的时间 是自筛选器开始运行或自上次查找命令以来经过的时间。

查找的时间格式

默认情况下,seek 命令的单位为 100 纳秒。 源筛选器可以支持其他时间格式,例如按帧编号查找。 每次由 GUID 标识格式时;请参阅 时间格式 GUID

若要支持其他时间格式,必须在输出引脚上实现以下方法:

如果应用程序设置了新的时间格式,则 IMediaSeeking 方法中的所有位置参数都会根据新的时间格式进行解释。 例如,如果时间格式为帧, 则 IMediaSeeking::GetDuration 方法必须返回以帧为单位的持续时间。

实际上,很少有 DirectShow 筛选器支持其他时间格式,因此,很少有 DirectShow 应用程序使用此功能。

编写源筛选器