支援在來源篩選中搜尋
[與此頁面相關聯的功能,DirectShow是舊版功能。 它已被 MediaPlayer、imfMediaEngine 取代,並在媒體基金會 音訊/視訊擷取。 這些功能已針對 Windows 10 和 Windows 11 進行優化。 Microsoft強烈建議新程式代碼盡可能在媒體 基礎中使用 MediaPlayer、IMFMediaEngine 和 音訊/視訊擷取,而不是 DirectShow。 Microsoft建議使用舊版 API 的現有程式代碼,盡可能改寫成使用新的 API。]
本主題描述如何在 Microsoft DirectShow 來源篩選中實作搜尋。 它會使用 球形濾器 範例作為起點,並描述支援在此濾器中定位所需的額外程式碼。
球濾鏡 範例是一種來源濾鏡,可用於創建動畫彈跳球。 本文說明如何將搜尋功能新增至此篩選。 在您新增這項功能之後,您可以在 GraphEdit 中渲染篩選器,然後通過拖曳 GraphEdit 的滑桿來控制球。
本主題包含下列各節:
- 在 DirectShow 中的搜尋概觀
- 球體過濾器快速總覽
- 修改球篩選條件以尋找
- CSourceSeeking 類別 限制
在 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 巨集在零售版本中會編譯成空字串。 Pin 直接繼承 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);
}
方法稱為「NonDelegating」,是因為 DirectShow 基類支援元件物件模型 (COM) 的聚合方式。 如需詳細資訊,請參閱 DirectShow SDK 中的<如何實作 IUnknown>主題。
搜尋方法
CSourceSeeking 類別會維護與搜尋相關的數個成員變數。
變數 | 描述 | 預設值 |
---|---|---|
m_rtStart | 開始時間 | 零 |
m_rtStop | 暫停時間 | _I64_MAX / 2 |
m_dRateSeeking | 播放速率 | 1.0 |
CSourceSeeking 實作 IMediaSeeking::SetPositions 更新開始和停止時間,然後在衍生類別上呼叫兩個純虛擬方法,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 方法會在 呼叫 changeRate之前,先將 m_dRateSeeking 設定為新的速率(捨棄舊值)。 不幸的是,如果呼叫端提供的速率無效,例如小於零,那麼當呼叫 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_bDiscontinuityTRUE,則方法會在樣本上設置不連續標誌。
還有另一個小差異。 因為原始版本依賴只有一個緩衝區,所以當串流開始時,它會以零填滿整個緩衝區一次。 之後,它只是將球從之前的位置清除掉。 不過,此優化揭示球篩選條件中的小錯誤。 當 CBaseOutputPin::DecideAllocator 方法呼叫 IMemInputPin::NotifyAllocator時,它會將只讀旗標設定為 FALSE。 因此,下游過濾器可以自由寫入緩衝區。 其中一個解決方案是覆寫 DecideAllocator,以便將唯讀旗標設定為 TRUE。 不過,為了簡單起見,新版本只會完全移除優化。 相反地,這個版本每次都以零填滿緩衝區。
其他變更
在新版本中,這兩行會從 CBall 建構函式中移除:
m_iRandX = rand();
m_iRandY = rand();
原始的 Ball 篩選會使用這些值,將一些隨機性新增至初始球位置。 為了我們的目的,我們希望球具有確定性。 此外,某些變數已從 CRefTime 物件變更為 REFERENCE_TIME 變數。 (CRefTime 類別是 REFERENCE_TIME 值的精簡包裝函式。最後,已稍微修改 IQualityControl::Notify 實作:您可以參考原始碼以取得詳細資料。
CSourceSeeking 類別的限制
CSourceSeeking 類別不適合用於具有多個輸出引腳的過濾器,因為這會引發引腳之間通訊的問題。 例如,假設剖析器篩選器接收到交錯的音訊視訊串流,將串流分割為音訊和視訊元件,並透過一個輸出釘選傳遞視訊,經由另一個輸出釘選傳遞音訊。 這兩個輸出針腳都會接收每個搜尋命令,但篩選條件應該只搜尋每個搜尋命令一次。 解決方案是指定其中一個針腳來控制搜尋,並忽略另一個針腳所接收的搜尋命令。
不過,在 seek 命令之後,這兩個針腳都應該清空數據。 為了進一步使問題複雜化,搜尋命令會在應用程式線程上發生,而不是串流線程。 因此,您必須確定兩個針腳都不會遭到封鎖,並等候 IMemInputPin::Receive 呼叫傳回,否則可能會導致死結。 如需有關線程安全清理記憶體針腳的更多資訊,請參閱 線程和重要區段。
相關主題