本文总结了 DirectShow Filter 的开发经验,重点介绍了 Source Filter、In-Place Transform Filter 和 Render Filter 的实现方法。通过使用 DirectShow 提供的类,可以简化 Filter 的开发过程。
在 DirectShow 中,Filter 主要分为三类:Source Filter、Transform Filter 和 Render Filter。这三类 Filter 的主要区别在于它们包含的 Pin 类型。Source Filter 至少有一个输出 Pin 而没有输入 Pin;Transform Filter 至少有一个输入 Pin 和一个输出 Pin;Render Filter 至少有一个输入 Pin。Pin 是一种 COM 组件,负责连接两个 Filter 并传输数据。如下图所示:
两个 Pin 之间的连接过程实际上是一个媒体类型协商过程,包括以下几个步骤:
- 调用输出 Pin 上的 Connect 方法。
- 调用输入 Pin 上的 ReceiveConnection 方法。
- 如果上述两个方法都成功,则连接成功。
- 在输出 Pin 上调用 CompleteConnect 方法,进行内存分配和管理。
完成上述步骤后,两个 Filter 之间就可以进行数据传输了。数据传输的具体形式是通过 Sample 实现的。Sample 是数据的载体,由 Allocator 创建和管理。数据传输时,输出 Pin 会调用 IMemAllocator::GetBuffer 方法获取一个 Sample,然后使用 IMediaSample::GetPointer 获取 Sample 的内存地址,填充数据并传递给下一个 Filter 的输入 Pin。
数据传输方式主要有两种:推模式(Push mode)和拉模式(Pull mode)。
推模式
推模式主要用于实时源,如摄像头图像采集。与之连接的下级 Filter 需要实现 IMemInputPin 接口。在数据传输时,Source Filter 完成数据读取后,会主动调用下级 Filter 的 IMemInputPin::Receive 或 IMemInputPin::ReceiveMultiple 方法传递数据。
拉模式
拉模式主要用于文件源,如视频文件的回放。要使用拉模式,需要实现 IAsyncReader 接口。数据传输时间与推模式相反,即在需要数据时,由上级 Filter 的输入 Pin 主动请求数据,调用 IAsyncReader 接口上的方法。IAsyncReader 数据处理方法包括:Request(异步)、SyncReadAligned(同步对齐)和 SyncRead(同步)。
推模式和拉模式只是数据传输的两种方式,开发过程中可以根据实际需求选择。通常推荐在实时源时使用推模式,在文件源时使用拉模式,以减少不必要的工作。
当一个 Source Filter 结束发送数据流时,它会调用连接的 Filter 的输入 Pin 的 IPin::EndOfStream 方法,然后下游的 Filter 依次通知与之相连的 Filter。当 EndOfStream 方法调用到 Render Filter 时,最后一个 Filter 会给 Filter 图表管理器发送一个 EC_COMPLETE 事件通知。如果 Render Filter 有多个输入 Pin,当所有输入 Pin 都接收到 EndOfStream 通知时,它才会给 Filter 图表管理器发送 EC_COMPLETE 事件通知。
Source Filter 实现
Source Filter 是一个提供数据的 Filter,数据可以是本地音视频流、图片数据、实时采集数据等。这里分别给出推模式和拉模式的例子。
推模式示例(BMP 图片)
尽管推模式主要用于实时源,但这里给出一个用推模式推送文件源的例子。这个 Filter 的功能是主动向下一个 Filter 推送图片数据,最终由系统的 Render Filter 显示。
- Filter 注册。创建至少一个 GUID,并在 CPP 文件中写入相关代码。
- 选择一个基类,这里选择 CSource。Source Filter 包含一个或多个输出 Pin(这里为 1 个)。这个输出 Pin 派生于 CSourceStream(这里为 CFileStream)。
- 实现必要的方法,包括 CSourceStream::GetMediaType、CSourceStream::CheckMediaType、CBaseOutputPin::DecideBufferSize 和 CSourceStream::FillBuffer。
- 在 SourceFilter 类中实现 CreateInstance 方法。
- 在 SourceFilter 类中定义一个 CSourceStream 的指针成员变量 m_pPin,并在构造函数中创建输出 Pin 对象。
接下来,具体实现 CFileStream 类:
- 在 CFileStream 的头文件中添加成员变量。
- 在 CFileStream 的构造函数中初始化成员变量。
- 实现 CFileStream::GetMediaType 方法,设置推送数据的媒体类型。
- 实现 CFileStream::DecideBufferSize 方法,决定 Buffer 的大小。
- 实现 CFileStream::FillBuffer 方法,为 Buffer 填充数据。
至此,一个推模式的 Source Filter 就完成了。总体来看,实现这个 Filter 只需重写几个关键方法:CSourceStream::GetMediaType、CBaseOutputPin::DecideBufferSize 和 CSourceStream::FillBuffer。
拉模式示例(视频播放)
拉模式的 Source Filter 比推模式的 Source Filter 复杂一些,但通过基类来实现也并不困难。在拉模式中,推荐使用 CBaseFilter 和 CBasePin,且必须实现 IAsyncReader 接口。
- 实现 CBaseFilter 的步骤包括:
- 从 CBaseFilter 派生一个新类。
- 为新类添加一个继承自 CBasePin 的 Pin 成员变量。
- 重写 CBaseFilter::GetPin 方法,返回 Filter 上的 Pin。
- 重写 CBaseFilter::GetPinCount 方法,返回 Filter 上的 Pin 数量。
- 实现 CBasePin 的步骤包括:
- 重写 CBasePin::CheckMediaType 方法,检查媒体类型。
- 重写 CBasePin::GetMediaType 方法,获取媒体类型。
- 重写 IPin::BeginFlush 和 IPin::EndFlush 方法,用于清除数据。
- 重写 IAsyncReader 的所有方法,包括:
- IAsyncReader::BeginFlush 和 IAsyncReader::EndFlush,用于清除数据。
- IAsyncReader::Length,返回数据流的总长度和可用长度。
- IAsyncReader::RequestAllocator,用于 Pin 连接时的内存分配和管理。
- IAsyncReader::Request,异步请求数据。
- IAsyncReader::SyncReadAligned,同步对齐请求数据。
- IAsyncReader::SyncRead,同步请求数据。
- IAsyncReader::WaitForNext,等待请求的执行完成。
通过上述步骤,可以实现一个拉模式的 Source Filter,用于视频文件的回放。