Supporto della ricerca in un filtro di origine

[La funzionalità associata a questa pagina, DirectShow, è una funzionalità legacy. È stata sostituita da MediaPlayer, IMFMediaEngine e Audio/Video Capture in Media Foundation. Queste funzionalità sono state ottimizzate per Windows 10 e Windows 11. Microsoft consiglia vivamente che il nuovo codice usi MediaPlayer, IMFMediaEngine e Audio/Video Capture in Media Foundation invece di DirectShow, quando possibile. Microsoft suggerisce che il codice esistente che usa le API legacy venga riscritto per usare le nuove API, se possibile.

Questo argomento descrive come implementare la ricerca in un filtro di origine Microsoft DirectShow. Usa l'esempio filtro a sfera come punto iniziale e descrive il codice aggiuntivo necessario per supportare la ricerca in questo filtro.

L'esempio di filtro a sfera è un filtro di origine che crea una palla di rimbalzo animata. Questo articolo descrive come aggiungere funzionalità di ricerca a questo filtro. Dopo aver aggiunto questa funzionalità, è possibile eseguire il rendering del filtro in GraphEdit e controllare la palla trascinando il dispositivo di scorrimento GraphEdit.

In questo argomento sono incluse le sezioni seguenti:

Panoramica della ricerca in DirectShow

Un'applicazione cerca il grafico del filtro chiamando un metodo IMediaSeeking in Filter Graph Manager. Filter Graph Manager distribuisce quindi la chiamata a ogni renderer nel grafo. Ogni renderer invia la chiamata upstream, tramite il pin di output del filtro upstream successivo. La chiamata viaggia a monte fino a raggiungere un filtro in grado di eseguire il comando seek, in genere un filtro di origine o un filtro parser. In generale, il filtro che ha origine i timestamp gestisce anche la ricerca.

Un filtro risponde a un comando seek come indicato di seguito:

  1. Il filtro scarica il grafico. In questo modo vengono cancellati i dati non aggiornati dal grafico, migliorando così la velocità di risposta. In caso contrario, gli esempi memorizzati nel buffer prima del comando seek potrebbero essere recapitati.
  2. Il filtro chiama IPin::NewSegment per informare i filtri downstream della nuova ora di arresto, ora di inizio e velocità di riproduzione.
  3. Il filtro imposta quindi il flag di discontinuità nel primo esempio dopo il comando seek.

I timestamp iniziano da zero dopo qualsiasi comando seek (incluse le modifiche alla frequenza).

Panoramica rapida del filtro a sfera

Il filtro Ball è un'origine push, il che significa che usa un thread di lavoro per recapitare campioni downstream, anziché un'origine pull, che attende passivamente un filtro downstream per richiedere campioni. Il filtro Ball viene compilato dalla classe CSource e il relativo pin di output viene compilato dalla classe CSourceStream . La classe CSourceStream crea il thread di lavoro che determina il flusso di dati. Questo thread entra in un ciclo che ottiene campioni dall'allocatore, li riempie di dati e li recapita a valle.

La maggior parte dell'azione in CSourceStream si verifica nel metodo CSourceStream::FillBuffer , implementato dalla classe derivata. L'argomento di questo metodo è un puntatore all'esempio da recapitare. L'implementazione del filtro Ball di FillBuffer recupera l'indirizzo del buffer di esempio e disegna direttamente nel buffer impostando singoli valori pixel. Il disegno viene eseguito da una classe helper, CBall, in modo da poter ignorare i dettagli relativi a profondità di bit, tavolozze e così via.

Modifica del filtro a sfera per la ricerca

Per rendere ricercabile il filtro Ball, usare la classe CSourceSeeking , progettata per implementare la ricerca nei filtri con un pin di output. Aggiungere la classe CSourceSeeking all'elenco di ereditarietà per la classe CBallStream:

class CBallStream :  // Defines the output pin.
    public CSourceStream, public CSourceSeeking

Sarà anche necessario aggiungere un inizializzatore per CSourceSeeking al costruttore CBallStream:

    CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),

Questa istruzione chiama il costruttore di base per CSourceSeeking. I parametri sono un nome, un puntatore al pin proprietario, un valore HRESULT e l'indirizzo di un oggetto sezione critica. Il nome viene usato solo per il debug e la macro NAME viene compilata in una stringa vuota nelle compilazioni retail. Il pin eredita direttamente CSourceSeeking, quindi il secondo parametro è un puntatore a se stesso, eseguito il cast su un puntatore IPin . Il valore HRESULT viene ignorato nella versione corrente delle classi di base; rimane per compatibilità con le versioni precedenti. La sezione critica protegge i dati condivisi, ad esempio l'ora di inizio corrente, l'ora di arresto e la frequenza di riproduzione.

Aggiungere l'istruzione seguente all'interno del costruttore CSourceSeeking :

m_rtStop = 60 * UNITS;

La variabile m_rtStop specifica l'ora di arresto. Per impostazione predefinita, il valore è _I64_MAX / 2, che è di circa 14.600 anni. L'istruzione precedente lo imposta su un valore più conservativo di 60 secondi.

È necessario aggiungere due variabili membro aggiuntive a CBallStream:

BOOL            m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME  m_rtBallPosition; // Position of the ball. 

Dopo ogni comando seek, il filtro deve impostare il flag di discontinuità nell'esempio successivo chiamando IMediaSample::SetDiscontinuity. La variabile m_bDiscontinuity continuerà a tenere traccia di questa situazione. La variabile m_rtBallPosition specifica la posizione della palla all'interno del fotogramma video. Il filtro ball originale calcola la posizione dall'ora del flusso, ma il tempo del flusso viene reimpostato su zero dopo ogni comando seek. In un flusso ricercabile, la posizione assoluta è indipendente dal tempo del flusso.

QueryInterface

La classe CSourceSeeking implementa l'interfaccia IMediaSeeking . Per esporre questa interfaccia ai client, eseguire l'override del metodo NonDelegatingQueryInterface :

STDMETHODIMP CBallStream::NonDelegatingQueryInterface
    (REFIID riid, void **ppv)
{
    if( riid == IID_IMediaSeeking ) 
    {
        return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
    }
    return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}

Il metodo viene chiamato "NonDelegating" a causa del modo in cui le classi di base DirectShow supportano l'aggregazione COM (Component Object Model). Per altre informazioni, vedere l'argomento "Come implementare IUnknown" in DirectShow SDK.

Ricerca di metodi

La classe CSourceSeeking gestisce diverse variabili membro relative alla ricerca.

Variabile Descrizione Valore predefinito
m_rtStart Ora di inizio Zero
m_rtStop Ora di arresto _I64_MAX / 2
m_dRateSeeking Velocità di riproduzione 1,0

 

L'implementazione CSourceSeeking di IMediaSeeking::SetPositions aggiorna i tempi di avvio e arresto e quindi chiama due metodi virtuali puri nella classe derivata, CSourceSeeking::ChangeStart e CSourceSeeking::ChangeStop. L'implementazione di IMediaSeeking::SetRate è simile: aggiorna la frequenza di riproduzione e quindi chiama il metodo virtuale puro CSourceSeeking::ChangeRate. In ognuno di questi metodi virtuali, il pin deve eseguire le operazioni seguenti:

  1. Chiamare IPin::BeginFlush per avviare lo scaricamento dei dati.
  2. Interrompere il thread di streaming.
  3. Chiama IPin::EndFlush.
  4. Riavviare il thread di streaming.
  5. Chiama IPin::NewSegment.
  6. Impostare il flag di discontinuità nell'esempio successivo.

L'ordine di questi passaggi è fondamentale, perché il thread di streaming può bloccarsi mentre attende di recapitare un campione o ottenere un nuovo esempio. Il metodo BeginFlush garantisce che il thread di streaming non sia bloccato e pertanto non si blocchi nel passaggio 2. La chiamata EndFlush indica ai filtri downstream di aspettarsi nuovi campioni, in modo da non rifiutarli quando il thread viene avviato di nuovo nel passaggio 4.

Il metodo privato seguente esegue i passaggi da 1 a 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();
    }
}

Quando il thread di streaming viene avviato di nuovo, chiama il metodo CSourceStream::OnThreadStartPlay . Eseguire l'override di questo metodo per eseguire i passaggi 5 e 6:

HRESULT CBallStream::OnThreadStartPlay()
{
    m_bDiscontinuity = TRUE;
    return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}

Nel metodo ChangeStart impostare l'ora del flusso su zero e la posizione della palla sulla nuova ora di inizio. Chiamare quindi CBallStream::UpdateFromSeek:

HRESULT CBallStream::ChangeStart( )
{
    {
        CAutoLock lock(CSourceSeeking::m_pLock);
        m_rtSampleTime = 0;
        m_rtBallPosition = m_rtStart;
    }
    UpdateFromSeek();
    return S_OK;
}

Nel metodo ChangeStop chiamare CBallStream::UpdateFromSeek se il nuovo tempo di arresto è minore della posizione corrente. In caso contrario, il tempo di arresto è ancora in futuro, quindi non è necessario scaricare il grafico.

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;
}

Per le modifiche alla frequenza, il metodo CSourceSeeking::SetRate imposta m_dRateSeeking sulla nuova frequenza (ignorando il valore precedente) prima di chiamare ChangeRate. Purtroppo, se il chiamante ha dato una frequenza non valida, ad esempio inferiore a zero, è troppo tardi quando viene chiamato ChangeRate . Una soluzione consiste nell'eseguire l'override di SetRate e verificare le tariffe valide:

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; }

Disegno nel buffer

Ecco la versione modificata di CSourceStream::FillBuffer, la routine che disegna la palla su ogni fotogramma:

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;
}

Le principali differenze tra questa versione e l'originale sono le seguenti:

  • Come accennato in precedenza, la variabile m_rtBallPosition viene usata per impostare la posizione della palla, anziché il tempo di flusso.
  • Dopo aver premuto la sezione critica, il metodo controlla se la posizione corrente supera il tempo di arresto. In tal caso, restituisce S_FALSE, che segnala alla classe di base di interrompere l'invio dei dati e recapitare una notifica end-of-stream.
  • I timestamp sono divisi per la frequenza corrente.
  • Se m_bDiscontinuity è TRUE, il metodo imposta il flag di discontinuità nell'esempio.

C'è un'altra differenza minore. Poiché la versione originale si basa sulla presenza di esattamente un buffer, riempie l'intero buffer con zeri una sola volta, all'inizio del flusso. Dopo questo, cancella solo la palla dalla sua posizione precedente. Tuttavia, questa ottimizzazione rivela un leggero bug nel filtro Palla. Quando il metodo CBaseOutputPin::D ecideAllocator chiama IMemInputPin::NotifyAllocator, imposta il flag di sola lettura su FALSE. Di conseguenza, il filtro downstream è libero di scrivere nel buffer. Una soluzione consiste nell'eseguire l'override di DecideAllocator in modo che imposti il flag di sola lettura su TRUE. Per semplicità, tuttavia, la nuova versione rimuove semplicemente l'ottimizzazione. Invece, questa versione riempie il buffer con zeri ogni volta.

Modifiche varie

Nella nuova versione queste due righe vengono rimosse dal costruttore CBall:

    m_iRandX = rand();
    m_iRandY = rand();

Il filtro ball originale usa questi valori per aggiungere una certa casualità alla posizione iniziale della palla. Ai nostri fini, vogliamo che la palla sia deterministica. Inoltre, alcune variabili sono state modificate da oggetti CRefTime a variabili REFERENCE_TIME . La classe CRefTime è un wrapper sottile per un valore REFERENCE_TIME . Infine, l'implementazione di IQualityControl::Notify è stata leggermente modificata; è possibile fare riferimento al codice sorgente per informazioni dettagliate.

Limitazioni della classe CSourceSeeking

La classe CSourceSeeking non è destinata ai filtri con più pin di output, a causa di problemi di comunicazione tra pin. Si supponga, ad esempio, che un filtro parser che riceve un flusso audio-video interleaved, suddivide il flusso nei relativi componenti audio e video e distribuisce video da un pin di output e audio da un altro. Entrambi i pin di output riceveranno ogni comando seek, ma il filtro deve cercare una sola volta per ogni comando seek. La soluzione consiste nel designare uno dei pin per controllare la ricerca e ignorare i comandi seek ricevuti dall'altro pin.

Dopo il comando seek, tuttavia, entrambi i pin devono scaricare i dati. Per complicare ulteriormente l'operazione, il comando seek si verifica nel thread dell'applicazione, non nel thread di streaming. Pertanto, è necessario assicurarsi che nessuno dei pin sia bloccato e in attesa che venga restituita una chiamata IMemInputPin::Receive oppure potrebbe causare un deadlock. Per altre informazioni sullo scaricamento thread-safe nei pin, vedere Thread e sezioni critiche.

Scrittura di filtri di origine