支援在來源篩選中搜尋
[與此頁面 相關的功能 DirectShow是舊版功能。 它已被 MediaPlayer、 IMFMediaEngine和 Media Foundation 中的音訊/視訊擷取取代。 這些功能已針對Windows 10和Windows 11進行優化。 Microsoft 強烈建議新程式碼盡可能使用 MediaPlayer、 IMFMediaEngine 和 音訊/視訊擷取 ,而不是 DirectShow。 Microsoft 建議盡可能重寫使用舊版 API 的現有程式碼,以使用新的 API。]
本主題描述如何在 Microsoft DirectShow 來源篩選中實作搜尋。 它會使用 球篩選 範例作為起點,並描述支援在此篩選準則中搜尋所需的額外程式碼。
球球篩選範例是一種來源篩選準則,可建立動畫彈跳球。 本文說明如何將搜尋功能新增至此篩選。 新增這項功能之後,您可以藉由拖曳 GraphEdit 滑杆來轉譯 GraphEdit 中的篩選,並控制球。
本主題包含下列幾節:
在 DirectShow 中搜尋的概觀
應用程式會藉由在 Filter Graph Manager 上呼叫 IMediaSeeking 方法來搜尋篩選圖表。 篩選圖形管理員接著會將呼叫散發給圖形中的每個轉譯器。 每個轉譯器都會透過下一個上游篩選的輸出針腳,傳送呼叫上游。 呼叫會流向上游,直到到達可執行搜尋命令的篩選,通常是來源篩選準則或剖析器篩選準則。 一般而言,源自時間戳記的篩選準則也會處理搜尋。
篩選準則會回應搜尋命令,如下所示:
- 篩選會排清圖形。 這會清除圖形中任何過時的資料,以改善回應性。 否則,在 seek 命令之前緩衝的範例可能會傳遞。
- 篩選準則會呼叫 IPin::NewSegment ,以通知下游篩選新的停止時間、開始時間和播放速率。
- 篩選接著會在 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」。 For more information, see the "How to Implement IUnknown" topic in the DirectShow SDK.
搜尋方法
CSourceSeeking類別會維護與搜尋相關的數個成員變數。
變數 | Description | 預設值 |
---|---|---|
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,方法會在範例上設定不連續旗標。
有另一個次要差異。 因為原始版本依賴只有一個緩衝區,所以當串流開始時,它會以零填滿整個緩衝區一次。 之後,它只會從先前的位置清除球。 不過,此優化會顯示 [球形] 篩選準則中的一些錯誤。 當 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 呼叫傳回,否則可能會導致死結。 如需針腳中安全排清執行緒的詳細資訊,請參閱 執行緒和重要章節。
相關主題