Freigeben über


Unterstützung der Suche in einem Quellfilter

[Das dieser Seite zugeordnete Feature DirectShow ist ein Legacyfeature. Es wurde von MediaPlayer, IMFMediaEngine und Audio/Video Capture in Media Foundation abgelöst. Diese Features wurden für Windows 10 und Windows 11 optimiert. Microsoft empfiehlt dringend, dass neuer Code mediaPlayer, IMFMediaEngine und Audio/Video Capture in Media Foundation anstelle von DirectShow verwendet, wenn möglich. Microsoft schlägt vor, dass vorhandener Code, der die Legacy-APIs verwendet, so umgeschrieben wird, dass nach Möglichkeit die neuen APIs verwendet werden.]

In diesem Thema wird beschrieben, wie Sie die Suche in einem Microsoft DirectShow-Quellfilter implementieren. Es verwendet das Beispiel BallFilter als Ausgangspunkt und beschreibt den zusätzlichen Code, der zur Unterstützung der Suche in diesem Filter erforderlich ist.

Das Beispiel ball filter ist ein Quellfilter, der einen animierten Springball erstellt. In diesem Artikel wird beschrieben, wie Sie diesem Filter Suchfunktionen hinzufügen. Nachdem Sie diese Funktionalität hinzugefügt haben, können Sie den Filter in GraphEdit rendern und den Ball steuern, indem Sie den GraphEdit-Schieberegler ziehen.

Dieses Thema enthält folgende Abschnitte:

Übersicht über die Suche in DirectShow

Eine Anwendung sucht das Filterdiagramm, indem sie eine IMediaSeeking-Methode im Filter Graph-Manager aufruft. Der Filter graph-Manager verteilt dann den Aufruf an jeden Renderer im Graphen. Jeder Renderer sendet den Aufruf Upstream über den Ausgabepin des nächsten Upstream Filters. Der Aufruf wird Upstream, bis er einen Filter erreicht, der den Suchbefehl ausführen kann, in der Regel einen Quellfilter oder einen Parserfilter. Im Allgemeinen behandelt der Filter, aus dem die Zeitstempel stammen, auch die Suche.

Ein Filter antwortet auf einen Seek-Befehl wie folgt:

  1. Der Filter leert das Diagramm. Dadurch werden veraltete Daten aus dem Diagramm gelöscht, wodurch die Reaktionsfähigkeit verbessert wird. Andernfalls werden Beispiele, die vor dem Seek-Befehl gepuffert wurden, möglicherweise übermittelt.
  2. Der Filter ruft IPin::NewSegment auf, um nachgeschaltete Filter über die neue Stoppzeit, die Startzeit und die Wiedergaberate zu informieren.
  3. Der Filter legt dann das Diskontinuitätsflag für das erste Beispiel nach dem Seek-Befehl fest.

Zeitstempel beginnen bei 0 (null) nach jedem Seek-Befehl (einschließlich Ratenänderungen).

Schnelle Übersicht über den Ballfilter

Der Ball-Filter ist eine Pushquelle, d. h., er verwendet einen Workerthread, um Samples downstream zu übermitteln, im Gegensatz zu einer Pullquelle, die passiv auf einen Downstreamfilter wartet, um Beispiele anzufordern. Der Ball-Filter wird aus der CSource-Klasse erstellt, und sein Ausgabepin wird aus der CSourceStream-Klasse erstellt. Die CSourceStream-Klasse erstellt den Workerthread, der den Datenfluss steuert. Dieser Thread wechselt in eine Schleife, die Stichproben vom Allocator abruft, sie mit Daten auffüllt und nachgeschaltet übermittelt.

Der Großteil der Aktion in CSourceStream erfolgt in der CSourceStream::FillBuffer-Methode , die von der abgeleiteten Klasse implementiert wird. Das Argument auf diese Methode ist ein Zeiger auf das zu liefernde Beispiel. Die Implementierung des Ball-Filters von FillBuffer ruft die Adresse des Beispielpuffers ab und zeichnet direkt in den Puffer, indem einzelne Pixelwerte festgelegt werden. (Die Zeichnung erfolgt durch die Hilfsklasse CBall, sodass Sie die Details zu Bittiefen, Paletten usw. ignorieren können.)

Ändern des Ballfilters für die Suche

Um den Ball-Filter suchbar zu machen, verwenden Sie die CSourceSeeking-Klasse , die für die Implementierung der Suche in Filtern mit einem Ausgabenadel konzipiert ist. Fügen Sie die CSourceSeeking-Klasse der Vererbungsliste für die CBallStream-Klasse hinzu:

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

Außerdem müssen Sie dem CBallStream-Konstruktor einen Initialisierer für CSourceSeeking hinzufügen:

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

Diese Anweisung ruft den Basiskonstruktor für CSourceSeeking auf. Die Parameter sind ein Name, ein Zeiger auf den besitzenden Pin, ein HRESULT-Wert und die Adresse eines kritischen Abschnittsobjekts. Der Name wird nur zum Debuggen verwendet, und das NAME-Makro wird in Verkaufsbuilds zu einer leeren Zeichenfolge kompiliert. Der Pin erbt direkt CSourceSeeking, sodass der zweite Parameter ein Zeiger auf sich selbst ist, der in einen IPin-Zeiger umgewandelt wird. Der HRESULT-Wert wird in der aktuellen Version der Basisklassen ignoriert. Sie bleibt aus Kompatibilität mit früheren Versionen bestehen. Der kritische Abschnitt schützt freigegebene Daten, z. B. die aktuelle Startzeit, die Stoppzeit und die Wiedergaberate.

Fügen Sie die folgende Anweisung innerhalb des CSourceSeeking-Konstruktors hinzu:

m_rtStop = 60 * UNITS;

Die m_rtStop Variable gibt die Stoppzeit an. Standardmäßig ist der Wert _I64_MAX/2, was ungefähr 14.600 Jahre beträgt. Die vorherige Anweisung legt sie auf konservativere 60 Sekunden fest.

Zwei zusätzliche Membervariablen müssen CBallStream hinzugefügt werden:

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

Nach jedem Seek-Befehl muss der Filter das Diskontinuitätsflag für das nächste Beispiel festlegen, indem IMediaSample::SetDiscontinuity aufgerufen wird. Die m_bDiscontinuity Variable verfolgt dies. Die m_rtBallPosition Variable gibt die Position des Balls innerhalb des Videoframes an. Der ursprüngliche Ball-Filter berechnet die Position aus der Streamzeit, aber die Streamzeit wird nach jedem Suchbefehl auf 0 zurückgesetzt. In einem suchbaren Stream ist die absolute Position unabhängig von der Streamzeit.

QueryInterface

Die CSourceSeeking-Klasse implementiert die IMediaSeeking-Schnittstelle . Um diese Schnittstelle für Clients verfügbar zu machen, überschreiben Sie die NonDelegatingQueryInterface-Methode :

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

Die -Methode wird als "NonDelegating" bezeichnet, da die DirectShow-Basisklassen die Com-Aggregation (Component Object Model) unterstützen. Weitere Informationen finden Sie im Thema "Implementieren von IUnknown" im DirectShow SDK.

Suchen von Methoden

Die CSourceSeeking-Klasse verwaltet mehrere Membervariablen im Zusammenhang mit suchen.

Variable BESCHREIBUNG Standardwert
m_rtStart Startzeit Null
m_rtStop Endzeit _I64_MAX / 2
m_dRateSeeking Wiedergaberate 1.0

 

Die CSourceSeeking-Implementierung von IMediaSeeking::SetPositions aktualisiert die Start- und Endzeiten und ruft dann zwei reine virtuelle Methoden für die abgeleitete Klasse auf, CSourceSeeking::ChangeStart und CSourceSeeking::ChangeStop. Die Implementierung von IMediaSeeking::SetRate ist ähnlich: Sie aktualisiert die Wiedergaberate und ruft dann die reine virtuelle Methode CSourceSeeking::ChangeRate auf. In jeder dieser virtuellen Methoden muss der Pin folgendes ausführen:

  1. Rufen Sie IPin::BeginFlush auf, um mit dem Leeren von Daten zu beginnen.
  2. Halten Sie den Streamingthread an.
  3. Rufen Sie IPin::EndFlush auf.
  4. Starten Sie den Streamingthread neu.
  5. Rufen Sie IPin::NewSegment auf.
  6. Legen Sie im nächsten Beispiel das Diskontinuitätsflag fest.

Die Reihenfolge dieser Schritte ist entscheidend, da der Streamingthread blockieren kann, während er auf die Übermittlung eines Beispiels oder das Abrufen eines neuen Beispiels wartet. Die BeginFlush-Methode stellt sicher, dass der Streamingthread nicht blockiert wird und daher in Schritt 2 nicht deadlockt wird. Der EndFlush-Aufruf informiert die nachgeschalteten Filter, neue Beispiele zu erwarten, sodass sie diese nicht ablehnen, wenn der Thread in Schritt 4 erneut gestartet wird.

Die folgende private Methode führt die Schritte 1 bis 4 aus:

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

Wenn der Streamingthread erneut gestartet wird, wird die CSourceStream::OnThreadStartPlay-Methode aufgerufen. Überschreiben Sie diese Methode, um die Schritte 5 und 6 auszuführen:

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

Legen Sie in der ChangeStart-Methode die Streamzeit auf null und die Position des Balls auf die neue Startzeit fest. Rufen Sie dann CBallStream::UpdateFromSeek auf:

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

Rufen Sie in der ChangeStop-Methode CBallStream::UpdateFromSeek auf, wenn die neue Stoppzeit kleiner als die aktuelle Position ist. Andernfalls liegt die Stoppzeit noch in der Zukunft, sodass das Diagramm nicht geleert werden muss.

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

Bei Ratenänderungen legt die CSourceSeeking::SetRate-Methodem_dRateSeeking auf die neue Rate fest (verwirft den alten Wert), bevor ChangeRate aufgerufen wird. Wenn der Aufrufer eine ungültige Rate angegeben hat ( z. B. kleiner als 0), ist es leider zu spät, wenn ChangeRate aufgerufen wird. Eine Lösung besteht darin, SetRate zu überschreiben und nach gültigen Tarifen zu suchen:

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

Zeichnen im Puffer

Hier ist die geänderte Version von CSourceStream::FillBuffer, der Routine, die den Ball für jeden Frame zeichnet:

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

Die wichtigsten Unterschiede zwischen dieser Version und dem Original sind die folgenden:

  • Wie bereits erwähnt, wird die variable m_rtBallPosition verwendet, um die Position des Balls und nicht die Streamzeit festzulegen.
  • Nach dem Halten des kritischen Abschnitts überprüft die Methode, ob die aktuelle Position die Stoppzeit überschreitet. Wenn ja, wird S_FALSE zurückgegeben, der der Basisklasse signalisiert, das Senden von Daten zu beenden und eine Benachrichtigung zum Ende des Datenstroms zu übermitteln.
  • Die Zeitstempel werden durch den aktuellen Kurs geteilt.
  • Wenn m_bDiscontinuitytrue ist, legt die Methode das Diskontinuitätsflag für das Beispiel fest.

Es gibt einen weiteren geringfügigen Unterschied. Da die ursprüngliche Version auf genau einen Puffer angewiesen ist, füllt sie den gesamten Puffer einmal mit Nullen, wenn das Streaming beginnt. Danach löscht es einfach den Ball von seiner vorherigen Position. Diese Optimierung zeigt jedoch einen leichten Fehler im Ball-Filter. Wenn die CBaseOutputPin::D ecideAllocator-MethodeIMemInputPin::NotifyAllocator aufruft, legt sie das schreibgeschützte Flag auf FALSE fest. Daher kann der Nachgelagerte Filter frei in den Puffer schreiben. Eine Lösung besteht darin, DecideAllocator zu überschreiben, sodass das schreibgeschützte Flag auf TRUE festgelegt wird. Der Einfachheit halber entfernt die neue Version die Optimierung jedoch ganz. Stattdessen füllt diese Version den Puffer jedes Mal mit Nullen.

Sonstige Änderungen

In der neuen Version werden diese beiden Zeilen aus dem CBall-Konstruktor entfernt:

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

Der ursprüngliche Ballfilter verwendet diese Werte, um der anfänglichen Ballposition eine gewisse Zufälligkeit hinzuzufügen. Für unsere Zwecke wollen wir, dass der Ball deterministisch ist. Außerdem wurden einige Variablen von CRefTime-Objekten in REFERENCE_TIME Variablen geändert. (Die CRefTime-Klasse ist ein dünner Wrapper für einen REFERENCE_TIME-Wert .) Zuletzt wurde die Implementierung von IQualityControl::Notify geringfügig geändert; Weitere Informationen finden Sie im Quellcode.

Einschränkungen der CSourceSeeking-Klasse

Die CSourceSeeking-Klasse ist aufgrund von Problemen bei der pinübergreifenden Kommunikation nicht für Filter mit mehreren Ausgabenadeln vorgesehen. Stellen Sie sich z. B. einen Parserfilter vor, der einen verschachtelten Audio-Video-Stream empfängt, den Stream in seine Audio- und Videokomponenten aufteilt und Video von einem Ausgabenadel und Audio von einem anderen sendet. Beide Ausgabepins erhalten jeden Suchbefehl, aber der Filter sollte nur einmal pro Suchbefehl suchen. Die Lösung besteht darin, einen der Pins festzulegen, um die Suche zu steuern und suchbefehle zu ignorieren, die vom anderen Pin empfangen wurden.

Nach dem Befehl seek sollten jedoch beide Pins Daten leeren. Um die Sache weiter zu erschweren, erfolgt der Befehl seek für den Anwendungsthread, nicht für den Streamingthread. Daher müssen Sie sicherstellen, dass keine Pin blockiert ist und darauf wartet, dass ein IMemInputPin::Receive-Aufruf zurückgegeben wird, da dies zu einem Deadlock führen kann. Weitere Informationen zum threadsicheren Spülen in Pins finden Sie unter Threads und kritische Abschnitte.

Schreiben von Quellfiltern