Share via


Uso del lector de origen en modo asincrónico

En este tema se describe cómo usar el Lector de origen en modo asincrónico. En modo asincrónico, la aplicación proporciona una interfaz de devolución de llamada, que se usa para notificar a la aplicación que los datos están disponibles.

En este tema se supone que ya ha leído el tema Uso del lector de origen para procesar datos multimedia.

Uso del modo asincrónico

El Lector de origen funciona en modo sincrónico o en modo asincrónico. En el ejemplo de código que se muestra en la sección anterior se supone que el Lector de origen usa el modo sincrónico, que es el valor predeterminado. En modo sincrónico, el método IMFSourceReader::ReadSample se bloquea mientras que el origen multimedia genera el ejemplo siguiente. Normalmente, un origen multimedia adquiere datos de algún origen externo (como un archivo local o una conexión de red), por lo que el método puede bloquear el subproceso de llamada durante un período de tiempo notable.

En modo asincrónico, ReadSample devuelve inmediatamente y el trabajo se realiza en otro subproceso. Una vez completada la operación, el Lector de origen llama a la aplicación a través de la interfaz de devolución de llamada IMFSourceReaderCallback . Para usar el modo asincrónico, debe proporcionar un puntero de devolución de llamada al crear por primera vez el Lector de origen, como se indica a continuación:

  1. Cree un almacén de atributos mediante una llamada a la función MFCreateAttributes .
  2. Establezca el atributo MF_SOURCE_READER_ASYNC_CALLBACK en el almacén de atributos. El valor del atributo es un puntero al objeto de devolución de llamada.
  3. Al crear el Lector de origen, pase el almacén de atributos a la función de creación en el parámetro pAttributes . Todas las funciones para crear el Lector de origen tienen este parámetro.

En el ejemplo siguiente se muestran estos pasos.

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

Después de crear el Lector de origen, no puede cambiar los modos entre sincrónico y asincrónico.

Para obtener datos en modo asincrónico, llame al método ReadSample , pero establezca los cuatro últimos parámetros en NULL, como se muestra en el ejemplo siguiente.

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

Cuando el método ReadSample se completa de forma asincrónica, el lector de origen llama al método IMFSourceReaderCallback::OnReadSample . Este método tiene cinco parámetros:

  • hrStatus: contiene un valor HRESULT . Este es el mismo valor que ReadSample devolvería en modo sincrónico. Si hrStatus contiene un código de error, puede omitir los parámetros restantes.
  • dwStreamIndex, dwStreamFlags, llTimestamp y pSample: estos tres parámetros son equivalentes a los tres últimos parámetros de ReadSample. Contienen el número de secuencia, las marcas de estado y el puntero IMFSample , respectivamente.
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

Además, la interfaz de devolución de llamada define otros dos métodos:

  • OnEvent. Notifica a la aplicación cuándo se producen determinados eventos en el origen multimedia, como el almacenamiento en búfer o los eventos de conexión de red.
  • OnFlush. Se llama cuando se completa el método Flush .

Implementación de la interfaz de devolución de llamada

La interfaz de devolución de llamada debe ser segura para subprocesos, ya que onReadSample y los otros métodos de devolución de llamada se llaman desde subprocesos de trabajo.

Hay varios enfoques diferentes que puede tomar al implementar la devolución de llamada. Por ejemplo, puede realizar todo el trabajo dentro de la devolución de llamada, o bien puede usar la devolución de llamada para notificar a la aplicación (por ejemplo, mediante la señalización de un identificador de eventos) y, a continuación, realizar el trabajo desde el subproceso de la aplicación.

Se llamará al método OnReadSample una vez por cada llamada que realice al método IMFSourceReader::ReadSample . Para obtener el ejemplo siguiente, vuelva a llamar a ReadSample . Si se produce un error, se llama a OnReadSample con un código de error para el parámetro hrStatus .

En el ejemplo siguiente se muestra una implementación mínima de la interfaz de devolución de llamada. En primer lugar, esta es la declaración de una clase que implementa la interfaz .

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

};

En este ejemplo, no estamos interesados en los métodos OnEvent y OnFlush , por lo que simplemente devuelven S_OK. La clase usa un identificador de eventos para indicar la aplicación; este identificador se proporciona a través del constructor .

En este ejemplo mínimo, el método OnReadSample simplemente imprime la marca de tiempo en la ventana de la consola. A continuación, almacena el código de estado y la marca de fin de secuencia y señala el identificador del evento:

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

En el código siguiente se muestra que la aplicación usaría esta clase de devolución de llamada para leer todos los fotogramas de vídeo de un archivo multimedia:

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

Lector de origen