Share via


Verwenden des Quelllesers im asynchronen Modus

In diesem Thema wird beschrieben, wie Sie den Quellleser im asynchronen Modus verwenden. Im asynchronen Modus stellt die Anwendung eine Rückrufschnittstelle bereit, die verwendet wird, um die Anwendung zu benachrichtigen, dass Daten verfügbar sind.

In diesem Thema wird davon ausgegangen, dass Sie das Thema Verwenden des Quelllesers zum Verarbeiten von Mediendaten bereits gelesen haben.

Verwenden des asynchronen Modus

Der Quellleser arbeitet entweder im synchronen Modus oder im asynchronen Modus. Das im vorherigen Abschnitt gezeigte Codebeispiel geht davon aus, dass der Quellleser den synchronen Modus verwendet, der standardmäßig ist. Im synchronen Modus blockiert die IMFSourceReader::ReadSample-Methode , während die Medienquelle das nächste Beispiel erzeugt. Eine Medienquelle erfasst in der Regel Daten aus einer externen Quelle (z. B. einer lokalen Datei oder einer Netzwerkverbindung), sodass die Methode den aufrufenden Thread für eine spürbare Zeit blockieren kann.

Im asynchronen Modus wird readSample sofort zurückgegeben, und die Arbeit wird in einem anderen Thread ausgeführt. Nach Abschluss des Vorgangs ruft der Quellleser die Anwendung über die IMFSourceReaderCallback-Rückrufschnittstelle auf. Um den asynchronen Modus zu verwenden, müssen Sie einen Rückrufzeiger bereitstellen, wenn Sie den Quellleser wie folgt erstellen:

  1. Erstellen Sie einen Attributspeicher, indem Sie die MFCreateAttributes-Funktion aufrufen.
  2. Legen Sie das attribut MF_SOURCE_READER_ASYNC_CALLBACK für den Attributspeicher fest. Der Attributwert ist ein Zeiger auf Ihr Rückrufobjekt.
  3. Wenn Sie den Quellleser erstellen, übergeben Sie den Attributspeicher an die Erstellungsfunktion im pAttributes-Parameter . Alle Funktionen zum Erstellen des Quelllesers verfügen über diesen Parameter.

Das folgende Beispiel zeigt die erforderlichen Schritte:

HRESULT CreateSourceReaderAsync(
    PCWSTR pszURL, 
    IMFSourceReaderCallback *pCallback, 
    IMFSourceReader **ppReader)
{
    HRESULT hr = S_OK;
    IMFAttributes *pAttributes = NULL;

    hr = MFCreateAttributes(&pAttributes, 1);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = MFCreateSourceReaderFromURL(pszURL, pAttributes, ppReader);

done:
    SafeRelease(&pAttributes);
    return hr;
}

Nachdem Sie den Quellleser erstellt haben, können Sie nicht zwischen synchron und asynchron wechseln.

Um Daten im asynchronen Modus abzurufen, rufen Sie die ReadSample-Methode auf, legen aber die letzten vier Parameter auf NULL fest, wie im folgenden Beispiel gezeigt.

    // Request the first sample.
    hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 
        0, NULL, NULL, NULL, NULL);

Wenn die ReadSample-Methode asynchron abgeschlossen wird, ruft der Quellleser Ihre IMFSourceReaderCallback::OnReadSample-Methode auf. Diese Methode verfügt über fünf Parameter:

  • hrStatus: Enthält einen HRESULT-Wert . Dies ist der gleiche Wert, den ReadSample im synchronen Modus zurückgeben würde. Wenn hrStatus einen Fehlercode enthält, können Sie die verbleibenden Parameter ignorieren.
  • dwStreamIndex, dwStreamFlags, llTimestamp und pSample: Diese drei Parameter entsprechen den letzten drei Parametern in ReadSample. Sie enthalten die Streamnummer, status Flags und den IMFSample-Zeiger.
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

Darüber hinaus definiert die Rückrufschnittstelle zwei weitere Methoden:

  • OnEvent. Benachrichtigt die Anwendung, wenn bestimmte Ereignisse in der Medienquelle auftreten, z. B. Pufferungs- oder Netzwerkverbindungsereignisse.
  • OnFlush. Wird aufgerufen, wenn die Flush-Methode abgeschlossen ist.

Implementieren der Rückrufschnittstelle

Die Rückrufschnittstelle muss threadsicher sein, da OnReadSample und die anderen Rückrufmethoden von Workerthreads aufgerufen werden.

Es gibt verschiedene Ansätze, die Sie bei der Implementierung des Rückrufs verfolgen können. Beispielsweise können Sie die gesamte Arbeit innerhalb des Rückrufs erledigen, oder Sie können den Rückruf verwenden, um die Anwendung zu benachrichtigen (z. B. durch Signalisieren eines Ereignishandles), und dann arbeiten Sie im Anwendungsthread.

Die OnReadSample-Methode wird für jeden Aufruf, den Sie an die IMFSourceReader::ReadSample-Methode vornehmen, einmal aufgerufen. Um das nächste Beispiel zu erhalten, rufen Sie ReadSample erneut auf. Wenn ein Fehler auftritt, wird OnReadSample mit einem Fehlercode für den hrStatus-Parameter aufgerufen.

Das folgende Beispiel zeigt eine minimale Implementierung der Rückrufschnittstelle. Zunächst sehen Sie hier die Deklaration einer Klasse, die die Schnittstelle implementiert.

#include <shlwapi.h>

class SourceReaderCB : public IMFSourceReaderCallback
{
public:
    SourceReaderCB(HANDLE hEvent) : 
      m_nRefCount(1), m_hEvent(hEvent), m_bEOS(FALSE), m_hrStatus(S_OK)
    {
        InitializeCriticalSection(&m_critsec);
    }

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
    {
        static const QITAB qit[] =
        {
            QITABENT(SourceReaderCB, IMFSourceReaderCallback),
            { 0 },
        };
        return QISearch(this, qit, iid, ppv);
    }
    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_nRefCount);
    }
    STDMETHODIMP_(ULONG) Release()
    {
        ULONG uCount = InterlockedDecrement(&m_nRefCount);
        if (uCount == 0)
        {
            delete this;
        }
        return uCount;
    }

    // IMFSourceReaderCallback methods
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

    STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *)
    {
        return S_OK;
    }

    STDMETHODIMP OnFlush(DWORD)
    {
        return S_OK;
    }

public:
    HRESULT Wait(DWORD dwMilliseconds, BOOL *pbEOS)
    {
        *pbEOS = FALSE;

        DWORD dwResult = WaitForSingleObject(m_hEvent, dwMilliseconds);
        if (dwResult == WAIT_TIMEOUT)
        {
            return E_PENDING;
        }
        else if (dwResult != WAIT_OBJECT_0)
        {
            return HRESULT_FROM_WIN32(GetLastError());
        }

        *pbEOS = m_bEOS;
        return m_hrStatus;
    }
    
private:
    
    // Destructor is private. Caller should call Release.
    virtual ~SourceReaderCB() 
    {
    }

    void NotifyError(HRESULT hr)
    {
        wprintf(L"Source Reader error: 0x%X\n", hr);
    }

private:
    long                m_nRefCount;        // Reference count.
    CRITICAL_SECTION    m_critsec;
    HANDLE              m_hEvent;
    BOOL                m_bEOS;
    HRESULT             m_hrStatus;

};

In diesem Beispiel sind wir nicht an den Methoden OnEvent und OnFlush interessiert, sodass sie einfach S_OK zurückgeben. Die -Klasse verwendet ein Ereignishandle, um die Anwendung zu signalisieren. dieses Handle wird über den Konstruktor bereitgestellt.

In diesem minimalen Beispiel gibt die OnReadSample-Methode einfach den Zeitstempel im Konsolenfenster aus. Anschließend speichert er den status-Code und das Flag "Streamende" und signalisiert das Ereignishandle:

HRESULT SourceReaderCB::OnReadSample(
    HRESULT hrStatus,
    DWORD /* dwStreamIndex */,
    DWORD dwStreamFlags,
    LONGLONG llTimestamp,
    IMFSample *pSample      // Can be NULL
    )
{
    EnterCriticalSection(&m_critsec);

    if (SUCCEEDED(hrStatus))
    {
        if (pSample)
        {
            // Do something with the sample.
            wprintf(L"Frame @ %I64d\n", llTimestamp);
        }
    }
    else
    {
        // Streaming error.
        NotifyError(hrStatus);
    }

    if (MF_SOURCE_READERF_ENDOFSTREAM & dwStreamFlags)
    {
        // Reached the end of the stream.
        m_bEOS = TRUE;
    }
    m_hrStatus = hrStatus;

    LeaveCriticalSection(&m_critsec);
    SetEvent(m_hEvent);
    return S_OK;
}

Der folgende Code zeigt, dass die Anwendung diese Rückrufklasse verwendet, um alle Videoframes aus einer Mediendatei zu lesen:

HRESULT ReadMediaFile(PCWSTR pszURL)
{
    HRESULT hr = S_OK;

    IMFSourceReader *pReader = NULL;
    SourceReaderCB *pCallback = NULL;

    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (hEvent == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto done;
    }

    // Create an instance of the callback object.
    pCallback = new (std::nothrow) SourceReaderCB(hEvent);
    if (pCallback == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    // Create the Source Reader.
    hr = CreateSourceReaderAsync(pszURL, pCallback, &pReader);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConfigureDecoder(pReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM);
    if (FAILED(hr))
    {
        goto done;
    }

    // Request the first sample.
    hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 
        0, NULL, NULL, NULL, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    while (SUCCEEDED(hr))
    {
        BOOL bEOS;
        hr = pCallback->Wait(INFINITE, &bEOS);
        if (FAILED(hr) || bEOS)
        {
            break;
        }
        hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,
            0, NULL, NULL, NULL, NULL);
    }

done:
    SafeRelease(&pReader);
    SafeRelease(&pCallback);
    return hr;
}

Quellleser