支持在源筛选器中查找
[与此页面关联的功能 DirectShow 是一项旧功能。 它已被 MediaPlayer、 IMFMediaEngine 和 媒体基金会中的音频/视频捕获取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能使用 MediaPlayer、 IMFMediaEngine 和 Media Foundation 中的音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]
本主题介绍如何在 Microsoft DirectShow 源筛选器中实现查找。 它使用 Ball Filter 示例作为起点,并描述支持在此筛选器中查找所需的其他代码。
球筛选器示例是创建动画弹跳球的源筛选器。 本文介绍如何向此筛选器添加查找功能。 添加此功能后,可以在 GraphEdit 中呈现筛选器,并通过拖动 GraphEdit 滑块来控制球。
本主题包含以下各节:
在 DirectShow 中查找概述
应用程序通过在筛选器关系图管理器上调用 IMediaSeeking 方法来查找筛选器图。 然后,筛选器关系图管理器将调用分发到图形中的每个呈现器。 每个呈现器通过下一个上游筛选器的输出引脚发送调用上游。 调用上游,直到到达可以执行 seek 命令的筛选器,通常是源筛选器或分析器筛选器。 通常,源自时间戳的筛选器也会处理查找。
筛选器响应 seek 命令,如下所示:
- 筛选器刷新图形。 这将清除图形中的任何过时数据,从而提高响应能力。 否则,可能会传递在 seek 命令之前缓冲的样本。
- 筛选器调用 IPin::NewSegment ,以通知下游筛选器新的停止时间、开始时间和播放速率。
- 然后,筛选器在 seek 命令后对第一个样本设置不连续标志。
在任何 seek 命令 (包括速率更改) 之后,时间戳从零开始。
球过滤器快速概述
Ball 筛选器是一个推送源,这意味着它使用工作线程在下游传递样本,而不是被动地等待下游筛选器请求样本的拉取源。 Ball 筛选器是从 CSource 类生成的,其输出引脚是从 CSourceStream 类生成的。 CSourceStream 类创建驱动数据流的工作线程。 此线程进入一个循环,该循环从分配器获取样本,用数据填充这些样本,并将它们传送到下游。
CSourceStream 中的大多数操作发生在派生类实现的 CSourceStream::FillBuffer 方法中。 此方法的参数是指向要传递的示例的指针。 Ball 筛选器的 FillBuffer 实现检索示例缓冲区的地址,并通过设置单个像素值直接绘制到缓冲区。 (绘图由帮助程序类 CBall 完成,因此你可以忽略有关位深度、调色板等的详细信息。)
修改用于查找的球筛选器
若要使 Ball 筛选器可查找,请使用 CSourceSeeking 类,该类旨在通过一个输出引脚实现在筛选器中查找。 将 CSourceSeeking 类添加到 CBallStream 类的继承列表:
class CBallStream : // Defines the output pin.
public CSourceStream, public CSourceSeeking
此外,还需要将 CSourceSeeking 的初始值设定项添加到 CBallStream 构造函数:
CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),
此语句调用 CSourceSeeking 的基本构造函数。 参数是名称、指向所属引脚的指针、 HRESULT 值和关键节对象的地址。 该名称仅用于调试, NAME 宏在零售版本中编译为空字符串。 引脚直接继承 CSourceSeeking,因此第二个参数是指向自身的指针,强制转换为 IPin 指针。 在当前版本的基类中忽略 HRESULT 值;保留它是为了与以前的版本兼容。 关键部分保护共享数据,例如当前开始时间、停止时间和播放速率。
在 CSourceSeeking 构造函数中添加以下语句:
m_rtStop = 60 * UNITS;
m_rtStop变量指定停止时间。 默认情况下,该值为 _I64_MAX/2,大约为 14,600 年。 上一语句将其设置为更保守的 60 秒。
必须向 CBallStream 添加两个附加成员变量:
BOOL m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME m_rtBallPosition; // Position of the ball.
在每次查找命令后,筛选器必须通过调用 IMediaSample::SetDiscontinuity 来设置下一个示例的不连续标志。 m_bDiscontinuity变量将对此进行跟踪。 m_rtBallPosition变量将指定球在视频帧中的位置。 原始 Ball 筛选器根据流时间计算位置,但在每次查找命令后,流时间重置为零。 在可查找流中,绝对位置与流时间无关。
QueryInterface
CSourceSeeking 类实现 IMediaSeeking 接口。 若要向客户端公开此接口,请重写 NonDelegatingQueryInterface 方法:
STDMETHODIMP CBallStream::NonDelegatingQueryInterface
(REFIID riid, void **ppv)
{
if( riid == IID_IMediaSeeking )
{
return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
}
return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}
由于 DirectShow 基类支持组件对象模型 (COM) 聚合的方式,因此该方法称为“NonDelegating”。 有关详细信息,请参阅 DirectShow SDK 中的“如何实现 IUnknown”主题。
查找方法
CSourceSeeking 类维护与查找相关的多个成员变量。
变量 | 说明 | 默认值 |
---|---|---|
m_rtStart | 开始时间 | 零个 |
m_rtStop | 停止时间 | _I64_MAX / 2 |
m_dRateSeeking | 播放速率 | 1.0 |
IMediaSeeking::SetPositions 的 CSourceSeeking 实现更新开始和停止时间,然后在派生类上调用两个纯虚拟方法:CSourceSeeking::ChangeStart 和 CSourceSeeking::ChangeStop。 IMediaSeeking::SetRate 的实现类似:它更新播放速率,然后调用纯虚拟方法 CSourceSeeking::ChangeRate。 在每个虚拟方法中,引脚必须执行以下操作:
- 调用 IPin::BeginFlush 开始刷新数据。
- 停止流式处理线程。
- 调用 IPin::EndFlush。
- 重启流式处理线程。
- 调用 IPin::NewSegment。
- 在下一个示例上设置不连续标志。
这些步骤的顺序至关重要,因为流式处理线程在等待提供示例或获取新示例时可能会阻塞。 BeginFlush 方法可确保流式处理线程不会被阻止,因此不会在步骤 2 中死锁。 EndFlush 调用通知下游筛选器预期新样本,因此当线程在步骤 4 中再次启动时,它们不会拒绝这些样本。
以下私有方法执行步骤 1 到 4:
void CBallStream::UpdateFromSeek()
{
if (ThreadExists())
{
DeliverBeginFlush();
// Shut down the thread and stop pushing data.
Stop();
DeliverEndFlush();
// Restart the thread and start pushing data again.
Pause();
}
}
当流式处理线程再次启动时,它将调用 CSourceStream::OnThreadStartPlay 方法。 重写此方法以执行步骤 5 和 6:
HRESULT CBallStream::OnThreadStartPlay()
{
m_bDiscontinuity = TRUE;
return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}
在 ChangeStart 方法中,将流时间设置为零,将球的位置设置为新的开始时间。 然后调用 CBallStream::UpdateFromSeek:
HRESULT CBallStream::ChangeStart( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_rtSampleTime = 0;
m_rtBallPosition = m_rtStart;
}
UpdateFromSeek();
return S_OK;
}
在 ChangeStop 方法中,如果新的停止时间小于当前位置,则调用 CBallStream::UpdateFromSeek。 否则,停止时间仍在将来,因此无需刷新图形。
HRESULT CBallStream::ChangeStop( )
{
{
CAutoLock lock(CSourceSeeking::m_pLock);
if (m_rtBallPosition < m_rtStop)
{
return S_OK;
}
}
// We're already past the new stop time. Flush the graph.
UpdateFromSeek();
return S_OK;
}
对于速率更改, CSourceSeeking::SetRate 方法 将m_dRateSeeking 设置为新速率, (在调用 ChangeRate 之前放弃旧值) 。 遗憾的是,如果调用方给出的速率无效(例如,小于零),那么在调用 ChangeRate 时为时已晚。 一种解决方案是替代 SetRate,并针对有效速率检查:
HRESULT CBallStream::SetRate(double dRate)
{
if (dRate <= 1.0)
{
return E_INVALIDARG;
}
{
CAutoLock lock(CSourceSeeking::m_pLock);
m_dRateSeeking = dRate;
}
UpdateFromSeek();
return S_OK;
}
// Now ChangeRate won't ever be called, but it's pure virtual, so it needs
// a dummy implementation.
HRESULT CBallStream::ChangeRate() { return S_OK; }
在缓冲区中绘图
下面是 CSourceStream::FillBuffer 的修改版本,该例程在每个帧上绘制球:
HRESULT CBallStream::FillBuffer(IMediaSample *pMediaSample)
{
BYTE *pData;
long lDataLen;
pMediaSample->GetPointer(&pData);
lDataLen = pMediaSample->GetSize();
{
CAutoLock cAutoLockShared(&m_cSharedState);
if (m_rtBallPosition >= m_rtStop)
{
// End of the stream.
return S_FALSE;
}
// Draw the ball in its current position.
ZeroMemory( pData, lDataLen );
m_Ball->MoveBall(m_rtBallPosition);
m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize);
// The sample times are modified by the current rate.
REFERENCE_TIME rtStart, rtStop;
rtStart = static_cast<REFERENCE_TIME>(
m_rtSampleTime / m_dRateSeeking);
rtStop = rtStart + static_cast<int>(
m_iRepeatTime / m_dRateSeeking);
pMediaSample->SetTime(&rtStart, &rtStop);
// Increment for the next loop.
m_rtSampleTime += m_iRepeatTime;
m_rtBallPosition += m_iRepeatTime;
}
pMediaSample->SetSyncPoint(TRUE);
if (m_bDiscontinuity)
{
pMediaSample->SetDiscontinuity(TRUE);
m_bDiscontinuity = FALSE;
}
return NOERROR;
}
此版本与原始版本之间的主要区别如下:
- 如前所述, m_rtBallPosition 变量用于设置球的位置,而不是流时间。
- 保留关键部分后,方法检查当前位置是否超过停止时间。 如果是这样,它将返回 S_FALSE,这指示基类停止发送数据并发送流结束通知。
- 时间戳除以当前速率。
- 如果m_bDiscontinuity为 TRUE,则 该方法在示例上设置不连续标志。
还有一个细微的区别。 由于原始版本依赖于恰好有一个缓冲区,因此当流式处理开始时,它会用零填充整个缓冲区一次。 之后,它只是将球从先前的位置上抹去。 但是,此优化在 Ball 筛选器中显示一个轻微的 bug。 当 CBaseOutputPin::D ecideAllocator 方法调用 IMemInputPin::NotifyAllocator 时,它将只读标志设置为 FALSE。 因此,下游筛选器可以自由地在缓冲区上写入。 一种解决方案是重写 DecideAllocator ,以便将只读标志设置为 TRUE。 但是,为简单起见,新版本只是完全删除优化。 相反,此版本每次都会用零填充缓冲区。
其他更改
在新版本中,这两行将从 CBall 构造函数中删除:
m_iRandX = rand();
m_iRandY = rand();
原始的 Ball 筛选器使用这些值向初始球位置添加一些随机性。 出于我们的目的,我们希望球是确定性的。 此外,某些变量已从 CRefTime 对象更改为 REFERENCE_TIME 变量。 (CRefTime 类是 REFERENCE_TIME 值的精简包装器。) 最后, 对 IQualityControl::Notify 的实现进行了轻微修改;有关详细信息,请参阅源代码。
CSourceSeeking 类的限制
CSourceSeeking 类不适用于具有多个输出引脚的筛选器,因为跨引脚通信存在问题。 例如,假设一个分析器筛选器接收交错的音频视频流,将流拆分为其音频和视频组件,并从一个输出引脚传送视频,从另一个输出引脚传送音频。 这两个输出引脚将接收每个搜寻命令,但筛选器应为每个搜寻命令只查找一次。 解决方法是指定其中一个引脚来控制查找,并忽略另一个引脚收到的查找命令。
但是,在 seek 命令之后,两个引脚都应刷新数据。 使问题进一步复杂化,seek 命令发生在应用程序线程上,而不是流式处理线程上。 因此,必须确保这两个引脚都不会被阻止,也不会等待 IMemInputPin::Receive 调用返回,否则可能会导致死锁。 有关引脚中线程安全刷新的详细信息,请参阅 线程和关键部分。
相关主题