共用方式為


支援在來源篩選中搜尋

[與此頁面相關聯的功能,DirectShow是舊版功能。 它已被 MediaPlayer、imfMediaEngine 取代,並在媒體基金會 音訊/視訊擷取。 這些功能已針對 Windows 10 和 Windows 11 進行優化。 Microsoft強烈建議新程式代碼盡可能在媒體 基礎中使用 MediaPlayerIMFMediaEngine 音訊/視訊擷取,而不是 DirectShow。 Microsoft建議使用舊版 API 的現有程式代碼,盡可能改寫成使用新的 API。]

本主題描述如何在 Microsoft DirectShow 來源篩選中實作搜尋。 它會使用 球形濾器 範例作為起點,並描述支援在此濾器中定位所需的額外程式碼。

球濾鏡 範例是一種來源濾鏡,可用於創建動畫彈跳球。 本文說明如何將搜尋功能新增至此篩選。 在您新增這項功能之後,您可以在 GraphEdit 中渲染篩選器,然後通過拖曳 GraphEdit 的滑桿來控制球。

本主題包含下列各節:

在 DirectShow 中尋找的概觀

應用程式會藉由在 Filter Graph Manager 上呼叫 IMediaSeeking 方法來搜尋篩選圖形。 篩選圖形管理員接著會將呼叫散發給圖形中的每個轉譯器。 每個渲染器都會透過下一個上游濾鏡的輸出針腳,將呼叫傳送到上游。 請求沿著上游傳遞,直到到達可執行搜尋命令的篩選器,通常是來源篩選器或剖析篩選器。 一般而言,生成時間戳的篩選器也負責處理搜尋操作。

篩選條件會回應搜尋命令,如下所示:

  1. 篩選會排清圖形。 這會清除圖形中任何過時的數據,進而改善回應性。 否則,在 seek 命令之前緩衝的範例有可能會被傳遞。
  2. 篩選會呼叫 IPin::NewSegment,以通知下游篩選新的停止時間、開始時間和播放速率。
  3. 濾波器接著會在 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::ChangeStartCSourceSeeking::ChangeStop IMediaSeeking::SetRate 的實作類似:它會更新播放速率,然後呼叫純虛擬方法 CSourceSeeking::ChangeRate。 在這些虛擬方法中,針腳必須執行下列動作:

  1. 呼叫 IPin::BeginFlush 開始排清數據。
  2. 停止串流線程。
  3. 呼叫 IPin::EndFlush
  4. 重新啟動串流線程。
  5. 呼叫 IPin::NewSegment
  6. 在下一個範例上設定不連續旗標。

這些步驟的順序非常重要,因為串流線程可以在等候傳遞範例或取得新範例時封鎖。 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 呼叫傳回,否則可能會導致死結。 如需有關線程安全清理記憶體針腳的更多資訊,請參閱 線程和重要區段

撰寫來源篩選