流式处理和应用程序线程
[与此页面关联的功能 DirectShow 是一项旧功能。 它已被 MediaPlayer、 IMFMediaEngine 和 媒体基金会中的音频/视频捕获取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能使用 MediaPlayer、 IMFMediaEngine 和 Media Foundation 中的音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]
任何 DirectShow 应用程序至少包含两个重要线程:应用程序线程和一个或多个流式处理线程。 示例在流式处理线程上传递,状态更改在应用程序线程上发生。 main流式处理线程由源或分析程序筛选器创建。 其他筛选器可能会创建提供示例的工作线程,这些线程也被视为流式处理线程。
某些方法在应用程序线程上调用,而其他方法在流式处理线程上调用。 例如:
- 流式处理线程 () : IMemInputPin::Receive、 IMemInputPin::ReceiveMultiple、 IPin::EndOfStream、 IMemAllocator::GetBuffer。
- 应用程序线程: IMediaFilter::P ause、 IMediaFilter::Run、 IMediaFilter::Stop、 IMediaSeeking::SetPositions、 IPin::BeginFlush、 IPin::EndFlush。
- 任一: IPin::NewSegment。
拥有单独的流式处理线程允许数据在应用程序线程等待用户输入时流经图形。 但是,多个线程的危险在于,筛选器在应用程序线程) 上暂停 (时可能会创建资源,在流式处理方法中使用资源,并在停止 (应用程序线程) 时销毁资源。 如果不小心,流式处理线程可能会在资源被销毁后尝试使用这些资源。 解决方案是使用关键部分保护资源,并将流式处理方法与状态更改同步。
筛选器需要一个关键部分来保护筛选器状态。 CBaseFilter 类具有此关键节的成员变量 CBaseFilter::m_pLock。 此关键部分称为筛选器锁。 此外,每个输入引脚都需要一个关键部分来保护流式处理线程使用的资源。 这些关键部分称为流式处理锁;必须在派生的 pin 类中声明它们。 最简单的方法是使用 CCritSec 类,该类包装 Windows CRITICAL_SECTION 对象,并且可以使用 CAutoLock 类锁定。 CCritSec 类还提供一些有用的调试函数。 有关详细信息,请参阅 关键部分调试函数。
当筛选器停止或刷新时,它必须将应用程序线程与流式处理线程同步。 若要避免死锁,它必须首先取消阻止流式处理线程,该线程可能由于以下几个原因而受阻:
- 它正在等待 在 IMemAllocator::GetBuffer 方法中获取示例,因为分配器的所有样本都在使用中。
- 它正在等待另一个筛选器从流式处理方法返回,例如 Receive。
- 它正在等待其自己的流式处理方法之一,以便某些资源变得可用。
- 它是等待下一个示例的呈现时间的呈现器筛选器
- 它是暂停时在 Receive 方法内等待的呈现器筛选器。
因此,当筛选器停止或刷新时,它必须执行以下操作:
- 出于任何原因释放它持有的任何示例。 这样做会取消阻止 GetBuffer 方法。
- 尽快从任何流式处理方法返回。 如果流式处理方法正在等待资源,则必须立即停止等待。
- 开始拒绝 接收中的示例,以便流式处理线程不再访问任何资源。 (CBaseInputPin 类自动处理此问题。)
- Stop 方法必须取消提交筛选器的所有分配器。 (CBaseInputPin 类自动处理此问题。)
刷新和停止都发生在应用程序线程上。 筛选器停止以响应 IMediaControl::Stop 方法。 Filter Graph 管理器按上游顺序发出 stop 命令,从呈现器开始,向后工作到源筛选器。 stop 命令完全发生在筛选器的 CBaseFilter::Stop 方法中。 当方法返回时,筛选器应处于停止状态。
刷新通常是由于 seek 命令而发生的。 刷新命令从源或分析程序筛选器开始,并流向下游。 刷新分两个阶段进行: IPin::BeginFlush 方法通知筛选器放弃所有挂起的数据和传入数据; IPin::EndFlush 方法向筛选器发出信号,以再次接受数据。 刷新需要两个阶段,因为 BeginFlush 调用位于应用程序线程上,在此期间,流式处理线程将继续传送数据。 因此,某些样本可能会在 BeginFlush 调用后到达。 筛选器应放弃这些内容。 在 EndFlush 调用后到达的任何样本都保证是新的,并且应该传递。
以下各节包含代码示例,演示如何通过避免死锁和争用条件的方式实现最重要的筛选器方法,如 Pause、 Receive 等。 但是,每个筛选器都有不同的要求,因此需要根据特定筛选器调整这些示例。
注意
CTransformFilter 和 CTransInPlaceFilter 基类可处理本文中所述的许多问题。 如果要编写转换筛选器,并且筛选器不等待流式处理方法中的事件,或者不保留 Receive 外部的样本,则这些基类就足够了。