Uso del lettore di origine in modalità asincrona

Questo argomento descrive come usare il lettore di origine in modalità asincrona. In modalità asincrona, l'applicazione fornisce un'interfaccia di callback, usata per notificare all'applicazione che i dati sono disponibili.

In questo argomento si presuppone che sia già stato letto l'argomento Uso del lettore di origine per elaborare i dati multimediali.

Uso della modalità asincrona

Il lettore di origine opera in modalità sincrona o asincrona. L'esempio di codice illustrato nella sezione precedente presuppone che il lettore di origine usi la modalità sincrona, ovvero l'impostazione predefinita. In modalità sincrona il metodo IMFSourceReader::ReadSample blocca mentre l'origine multimediale produce l'esempio successivo. Un'origine multimediale acquisisce in genere dati da un'origine esterna (ad esempio un file locale o una connessione di rete), in modo che il metodo possa bloccare il thread chiamante per un periodo di tempo evidente.

In modalità asincrona, ReadSample restituisce immediatamente e il lavoro viene eseguito su un altro thread. Al termine dell'operazione, il lettore di origine chiama l'applicazione tramite l'interfaccia di callback FMSourceReaderCallback . Per usare la modalità asincrona, è necessario specificare un puntatore di callback quando si crea prima il lettore di origine, come indicato di seguito:

  1. Creare un archivio attributi chiamando la funzione MFCreateAttributes .
  2. Impostare l'attributo MF_SOURCE_READER_ASYNC_CALLBACK nell'archivio attributi. Il valore dell'attributo è un puntatore all'oggetto callback.
  3. Quando si crea il lettore di origine, passare l'archivio attributi alla funzione di creazione nel parametro pAttributes . Tutte le funzioni per creare il lettore di origine hanno questo parametro.

Nell'esempio seguente sono illustrati i passaggi per l'operazione.

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

Dopo aver creato il lettore di origine, non è possibile cambiare modalità tra sincrona e asincrona.

Per ottenere i dati in modalità asincrona, chiamare il metodo ReadSample , ma impostare gli ultimi quattro parametri su NULL, come illustrato nell'esempio seguente.

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

Quando il metodo ReadSample viene completato in modo asincrono, il lettore di origine chiama il metodo FMSourceReaderCallback::OnReadSample . Questo metodo include cinque parametri:

  • hrStatus: contiene un valore HRESULT . Si tratta dello stesso valore restituito da ReadSample in modalità sincrona. Se hrStatus contiene un codice di errore, è possibile ignorare i parametri rimanenti.
  • dwStreamIndex, dwStreamFlags, llTimestamp e pSample: questi tre parametri sono equivalenti agli ultimi tre parametri in ReadSample. Contengono rispettivamente il numero di flusso, i flag di stato e il puntatore FMSample .
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

Inoltre, l'interfaccia di callback definisce due altri metodi:

  • OnEvent. Notifica all'applicazione quando si verificano determinati eventi nell'origine multimediale, ad esempio buffering o eventi di connessione di rete.
  • OnFlush. Chiamato al termine del metodo Flush .

Implementazione dell'interfaccia callback

L'interfaccia di callback deve essere thread-safe, perché OnReadSample e gli altri metodi di callback vengono chiamati dai thread di lavoro.

Esistono diversi approcci che è possibile adottare quando si implementa il callback. Ad esempio, è possibile eseguire tutte le operazioni all'interno del callback oppure usare il callback per notificare all'applicazione (ad esempio, segnalando un handle eventi) e quindi eseguire operazioni dal thread dell'applicazione.

Il metodo OnReadSample verrà chiamato una volta per ogni chiamata eseguita al metodo IMFSourceReader::ReadSample . Per ottenere l'esempio successivo, chiamare di nuovo ReadSample . Se si verifica un errore, OnReadSample viene chiamato con un codice di errore per il parametro hrStatus .

Nell'esempio seguente viene illustrata un'implementazione minima dell'interfaccia di callback. In primo luogo, ecco la dichiarazione di una classe che implementa l'interfaccia.

#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 questo esempio non siamo interessati ai metodi OnEvent e OnFlush , quindi restituiscono semplicemente S_OK. La classe usa un handle eventi per segnalare l'applicazione; questo handle viene fornito tramite il costruttore.

In questo esempio minimo, il metodo OnReadSample stampa solo il timestamp nella finestra della console. Archivia quindi il codice di stato e il flag end-of-stream e segnala l'handle eventi:

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

Il codice seguente mostra che l'applicazione userebbe questa classe di callback per leggere tutti i fotogrammi video da un file multimediale:

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

Lettore di origine