支援在來源篩選中搜尋

[與此頁面 相關的功能 DirectShow是舊版功能。 它已被 MediaPlayerIMFMediaEngineMedia Foundation 中的音訊/視訊擷取取代。 這些功能已針對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 宏會編譯為零售組建中的空字串。 針腳會直接繼承 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::SetPositionsCSourceSeeking實作會更新開始和停止時間,然後在衍生類別上呼叫兩個純虛擬方法:CSourceSeeking::ChangeStart 和 CSourceSeeking::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 方法會將 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_bDiscontinuityTRUE,方法會在範例上設定不連續旗標。

有另一個次要差異。 因為原始版本依賴只有一個緩衝區,所以當串流開始時,它會以零填滿整個緩衝區一次。 之後,它只會從先前的位置清除球。 不過,此優化會顯示 [球形] 篩選準則中的一些錯誤。 當 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 呼叫傳回,否則可能會導致死結。 如需針腳中安全排清執行緒的詳細資訊,請參閱 執行緒和重要章節

撰寫來源篩選