Share via


Caso práctico: Origen multimedia MPEG-1

En Microsoft Media Foundation, el objeto que introduce datos multimedia en la canalización de datos se denomina origen multimedia. En este tema se examina en profundidad el ejemplo del SDK de origen multimedia MPEG-1 .

Requisitos previos

Antes de leer este tema, debe comprender los siguientes conceptos de Media Foundation:

También debe tener un conocimiento básico de la arquitectura de Media Foundation, especialmente el rol de los orígenes multimedia en la canalización. (Para obtener más información, vea Orígenes de medios).

Además, puede que desee leer el tema Escritura de un origen multimedia personalizado, que proporciona una visión general más general de los pasos descritos aquí.

Este tema no reproduce todo el código del ejemplo del SDK, ya que el ejemplo es bastante grande.

Clases de C++ usadas en el origen MPEG-1

El origen MPEG-1 de ejemplo se implementa con las siguientes clases de C++:

  • MPEG1ByteStreamHandler. Implementa el controlador de secuencia de bytes para el origen multimedia. Dado un flujo de bytes, el controlador de secuencia de bytes crea una instancia del origen.
  • MPEG1Source. Implementa el origen multimedia.
  • MPEG1Stream. Implementa los objetos de secuencia multimedia. El origen multimedia crea un MPEG1Stream objeto para cada secuencia de audio o vídeo en la secuencia de bits MPEG-1.
  • Parser. Analiza la secuencia de bits MPEG-1. Por lo general, los detalles de esta clase no son relevantes para las API de Media Foundation.
  • SourceOp, OpQueue: estas dos clases administran operaciones asincrónicas en el origen multimedia. (Consulte Operaciones asincrónicas).

Otras clases auxiliares varias se describen más adelante en el tema.

controlador de Byte-Stream

El controlador de secuencia de bytes es el objeto que crea el origen multimedia. El solucionador de origen crea el controlador de secuencias de bytes; Las aplicaciones no interactúan directamente con el controlador de flujo de bytes. El solucionador de origen detecta el controlador de flujo de bytes buscando en el registro. El controlador se registra por extensión de nombre de archivo o tipo MIME. Para el origen MPEG-1, el controlador de secuencia de bytes se registra para la extensión de nombre de archivo ".mpg".

Nota

Si quiere admitir esquemas de direcciones URL personalizados, también puede escribir un controlador de esquemas. El origen MPEG-1 está diseñado para archivos locales y Media Foundation ya proporciona un controlador de esquema para direcciones URL de "file://".

 

El controlador de flujo de bytes implementa la interfaz IMFByteStreamHandler . Esta interfaz tiene dos métodos más importantes que se deben implementar:

Otros dos métodos son opcionales y no se implementan en el ejemplo del SDK:

  • CancelObjectCreation. Cancela el método BeginCreateObject . Este método es útil para un origen de red que podría tener una latencia alta en el inicio.
  • GetMaxNumberOfBytesRequiredForResolution. Obtiene el número máximo de bytes que leerá el controlador de la secuencia de origen. Implemente este método si sabe cuánto datos tiene el controlador de secuencia de bytes para poder crear el origen multimedia. De lo contrario, simplemente devuelva E_NOTIMPL.

Esta es la implementación del método BeginCreateObject :

HRESULT MPEG1ByteStreamHandler::BeginCreateObject(
        /* [in] */ IMFByteStream *pByteStream,
        /* [in] */ LPCWSTR pwszURL,
        /* [in] */ DWORD dwFlags,
        /* [in] */ IPropertyStore *pProps,
        /* [out] */ IUnknown **ppIUnknownCancelCookie,  // Can be NULL
        /* [in] */ IMFAsyncCallback *pCallback,
        /* [in] */ IUnknown *punkState                  // Can be NULL
        )
{
    if (pByteStream == NULL)
    {
        return E_POINTER;
    }

    if (pCallback == NULL)
    {
        return E_POINTER;
    }

    if ((dwFlags & MF_RESOLUTION_MEDIASOURCE) == 0)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;

    IMFAsyncResult *pResult = NULL;
    MPEG1Source    *pSource = NULL;

    // Create an instance of the media source.
    hr = MPEG1Source::CreateInstance(&pSource);

    // Create a result object for the caller's async callback.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateAsyncResult(NULL, pCallback, punkState, &pResult);
    }

    // Start opening the source. This is an async operation.
    // When it completes, the source will invoke our callback
    // and then we will invoke the caller's callback.
    if (SUCCEEDED(hr))
    {
        hr = pSource->BeginOpen(pByteStream, this, NULL);
    }

    if (SUCCEEDED(hr))
    {
        if (ppIUnknownCancelCookie)
        {
            *ppIUnknownCancelCookie = NULL;
        }

        m_pSource = pSource;
        m_pSource->AddRef();

        m_pResult = pResult;
        m_pResult->AddRef();
    }

// cleanup
    SafeRelease(&pSource);
    SafeRelease(&pResult);
    return hr;
}

El método realiza los pasos siguientes:

  1. Crea una nueva instancia del objeto MPEG1Source.
  2. Cree un objeto de resultado asincrónico. Este objeto se usa más adelante para invocar el método de devolución de llamada del solucionador de origen.
  3. Llama a MPEG1Source::BeginOpen, un método asincrónico definido en la MPEG1Source clase .
  4. Establece ppIUnknownCancelCookie en NULL, que informa al autor de la llamada de que No se admite CancelObjectCreation .

El MPEG1Source::BeginOpen método realiza el trabajo real de leer la secuencia de bytes e inicializar el MPEG1Source objeto. Este método no forma parte de la API pública. Puede definir cualquier mecanismo entre el controlador y el origen multimedia que se adapte a sus necesidades. Al colocar la mayoría de la lógica en el origen multimedia, el controlador de secuencias de bytes es relativamente sencillo.

En breve, BeginOpen hace lo siguiente:

  1. Llama a IMFByteStream::GetCapabilities para comprobar que la secuencia de bytes de origen es legible y buscable.
  2. Llama a IMFByteStream::BeginRead para iniciar una solicitud de E/S asincrónica.

El resto de la inicialización se produce de forma asincrónica. El origen multimedia lee suficientes datos de la secuencia para analizar los encabezados de secuencia MPEG-1. A continuación, crea un descriptor de presentación, que es el objeto que se usa para describir las secuencias de audio y vídeo en el archivo. (Para obtener más información, vea Descriptor de presentación). Cuando se completa la BeginOpen operación, el controlador de secuencia de bytes invoca el método de devolución de llamada del solucionador de origen. En ese momento, el solucionador de origen llama a IMFByteStreamHandler::EndCreateObject. El método EndCreateObject devuelve el estado de la operación.

HRESULT MPEG1ByteStreamHandler::EndCreateObject(
        /* [in] */ IMFAsyncResult *pResult,
        /* [out] */ MF_OBJECT_TYPE *pObjectType,
        /* [out] */ IUnknown **ppObject)
{
    if (pResult == NULL || pObjectType == NULL || ppObject == NULL)
    {
        return E_POINTER;
    }

    HRESULT hr = S_OK;

    *pObjectType = MF_OBJECT_INVALID;
    *ppObject = NULL;

    hr = pResult->GetStatus();

    if (SUCCEEDED(hr))
    {
        *pObjectType = MF_OBJECT_MEDIASOURCE;

        assert(m_pSource != NULL);

        hr = m_pSource->QueryInterface(IID_PPV_ARGS(ppObject));
    }

    SafeRelease(&m_pSource);
    SafeRelease(&m_pResult);
    return hr;
}

Si se produce un error en cualquier momento durante este proceso, la devolución de llamada se invoca con un código de estado de error.

Descriptor de presentación

El descriptor de presentación describe el contenido del archivo MPEG-1, incluida la siguiente información:

  • Número de secuencias.
  • Formato de cada secuencia.
  • Identificadores de flujo.
  • Estado de selección de cada secuencia (seleccionada o deseleccionada).

En términos de la arquitectura de Media Foundation, el descriptor de presentación contiene uno o varios descriptores de secuencia. Cada descriptor de secuencia contiene un controlador de tipo multimedia, que se usa para obtener o establecer tipos de medios en la secuencia. Media Foundation proporciona implementaciones de existencias para el descriptor de presentación y el descriptor de secuencia; son adecuados para la mayoría de los orígenes multimedia.

Para crear un descriptor de presentación, realice los pasos siguientes:

  1. Para cada secuencia:
    1. Proporcione un identificador de secuencia y una matriz de posibles tipos de medios. Si la secuencia admite más de un tipo de medio, ordene la lista de tipos de medios por preferencia, si existe. (Coloque primero el tipo óptimo y el tipo menos óptimo último).
    2. Llame a MFCreateStreamDescriptor para crear el descriptor de secuencia.
    3. Llame a IMFStreamDescriptor::GetMediaTypeHandler en el descriptor de secuencia recién creado.
    4. Llame a IMFMediaTypeHandler::SetCurrentMediaType para establecer el formato predeterminado para la secuencia. Si hay más de un tipo de medio, generalmente debe establecer el primer tipo de la lista.
  2. Llame a MFCreatePresentationDescriptor y pase la matriz de punteros del descriptor de secuencia.
  3. Para cada secuencia, llame a IMFPresentationDescriptor::SelectStream o DeselectStream para establecer el estado de selección predeterminado. Si hay más de una secuencia del mismo tipo (audio o vídeo), solo se debe seleccionar una de forma predeterminada.

El MPEG1Source objeto crea el descriptor de presentación en su InitPresentationDescriptor método :

HRESULT MPEG1Source::InitPresentationDescriptor()
{
    HRESULT hr = S_OK;
    DWORD cStreams = 0;

    assert(m_pPresentationDescriptor == NULL);
    assert(m_state == STATE_OPENING);

    if (m_pHeader == NULL)
    {
        return E_FAIL;
    }

    // Get the number of streams, as declared in the MPEG-1 header, skipping
    // any streams with an unsupported format.
    for (DWORD i = 0; i < m_pHeader->cStreams; i++)
    {
        if (IsStreamTypeSupported(m_pHeader->streams[i].type))
        {
            cStreams++;
        }
    }

    // How many streams do we actually have?
    if (cStreams > m_streams.GetCount())
    {
        // Keep reading data until we have seen a packet for each stream.
        return S_OK;
    }

    // We should never create a stream we don't support.
    assert(cStreams == m_streams.GetCount());

    // Ready to create the presentation descriptor.

    // Create an array of IMFStreamDescriptor pointers.
    IMFStreamDescriptor **ppSD =
            new (std::nothrow) IMFStreamDescriptor*[cStreams];

    if (ppSD == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    ZeroMemory(ppSD, cStreams * sizeof(IMFStreamDescriptor*));

    // Fill the array by getting the stream descriptors from the streams.
    for (DWORD i = 0; i < cStreams; i++)
    {
        hr = m_streams[i]->GetStreamDescriptor(&ppSD[i]);
        if (FAILED(hr))
        {
            goto done;
        }
    }

    // Create the presentation descriptor.
    hr = MFCreatePresentationDescriptor(cStreams, ppSD,
        &m_pPresentationDescriptor);

    if (FAILED(hr))
    {
        goto done;
    }

    // Select the first video stream (if any).
    for (DWORD i = 0; i < cStreams; i++)
    {
        GUID majorType = GUID_NULL;

        hr = GetStreamMajorType(ppSD[i], &majorType);
        if (FAILED(hr))
        {
            goto done;
        }

        if (majorType == MFMediaType_Video)
        {
            hr = m_pPresentationDescriptor->SelectStream(i);
            if (FAILED(hr))
            {
                goto done;
            }
            break;
        }
    }

    // Switch state from "opening" to stopped.
    m_state = STATE_STOPPED;

    // Invoke the async callback to complete the BeginOpen operation.
    hr = CompleteOpen(S_OK);

done:
    // clean up:
    if (ppSD)
    {
        for (DWORD i = 0; i < cStreams; i++)
        {
            SafeRelease(&ppSD[i]);
        }
        delete [] ppSD;
    }
    return hr;
}

La aplicación obtiene el descriptor de presentación llamando a IMFMediaSource::CreatePresentationDescriptor. Este método crea una copia superficial del descriptor de presentación llamando a IMFPresentationDescriptor::Clone. (La copia contiene punteros a los descriptores de secuencia originales). La aplicación puede usar el descriptor de presentación para establecer el tipo de medio, seleccionar una secuencia o anular la selección de una secuencia.

Opcionalmente, los descriptores de presentación y los descriptores de flujo pueden contener atributos que proporcionan información adicional sobre el origen. Para obtener una lista de estos atributos, consulte los temas siguientes:

Un atributo merece mención especial: el atributo MF_PD_DURATION contiene la duración total del origen. Establezca este atributo si conoce la duración por adelantado; por ejemplo, la duración podría especificarse en los encabezados de archivo, en función del formato de archivo. La aplicación puede mostrar este valor o usarlo para establecer una barra de progreso o una barra de búsqueda.

Estados de streaming

Un origen multimedia define los siguientes estados:

State Descripción
Iniciado El origen acepta y procesa solicitudes de ejemplo.
En pausa El origen acepta solicitudes de ejemplo, pero no las procesa. Las solicitudes se ponen en cola hasta que se inicia el origen.
Detenido. El origen rechaza las solicitudes de ejemplo.

 

Inicio

El método IMFMediaSource::Start inicia el origen multimedia. Toma los parámetros siguientes:

  • Descriptor de presentación.
  • UN GUID de formato de hora.
  • Posición inicial.

La aplicación debe obtener el descriptor de presentación llamando a CreatePresentationDescriptor en el origen. No hay ningún mecanismo definido para validar un descriptor de presentación. Si la aplicación especifica el descriptor de presentación incorrecto, los resultados no están definidos.

El GUID de formato de hora especifica cómo interpretar la posición inicial. El formato estándar es de 100 nanosegundos (ns), indicado por GUID_NULL. Cada origen multimedia debe admitir unidades de 100 ns. Opcionalmente, un origen puede admitir otras unidades de tiempo, como el número de fotogramas o el código de tiempo. Sin embargo, no hay ninguna manera estándar de consultar un origen multimedia para la lista de formatos de hora que admite.

La posición inicial se da como PROPVARIANT, lo que permite diferentes tipos de datos en función del formato de hora. Para 100-ns, el tipo PROPVARIANT es VT_I8 o VT_EMPTY. Si VT_I8, el PROPVARIANT contiene la posición inicial en unidades de 100 ns. El valor VT_EMPTY tiene el significado especial "empezar en la posición actual".

Implemente el método Start de la siguiente manera:

  1. Valide los parámetros y el estado:
    • Compruebe si hay parámetros NULL .
    • Compruebe el GUID de formato de hora. Si el valor no es válido, devuelva MF_E_UNSUPPORTED_TIME_FORMAT.
    • Compruebe el tipo de datos del PROPVARIANT que contiene la posición inicial.
    • Valide la posición inicial. Si no es válido, devuelva MF_E_INVALIDREQUEST.
    • Si se ha apagado el origen, devuelva MF_E_SHUTDOWN.
  2. Si no se produce ningún error en el paso 1, pone en cola una operación asincrónica. Todo después de este paso se produce en un subproceso de cola de trabajo.
  3. Para cada secuencia:
    1. Compruebe si la secuencia ya está activa desde una solicitud start anterior.

    2. Llame a IMFPresentationDescriptor::GetStreamDescriptorByIndex para comprobar si la aplicación seleccionó o deseleccionó la secuencia.

    3. Si ahora se deselecciona una secuencia seleccionada anteriormente, vacíe los ejemplos no entregados para esa secuencia.

    4. Si la secuencia está activa, el origen multimedia (no el flujo) envía uno de los siguientes eventos:

      En ambos eventos, los datos del evento son el puntero IMFMediaStream para la secuencia.

    5. Si el origen se está reiniciando desde el estado en pausa, es posible que haya solicitudes de ejemplo pendientes. Si es así, entregue estos ahora.

    6. Si el origen busca una nueva posición, cada objeto de secuencia envía un evento MEStreamSeeked . De lo contrario, cada secuencia envía un evento MEStreamStarted .

  4. Si el origen busca una nueva posición, el origen multimedia envía un evento MESourceSeeked . De lo contrario, envía un evento MESourceStarted .

Si se produce un error en cualquier momento después del paso 2, el origen envía un evento MESourceStarted con un código de error. Esto alerta a la aplicación de que el método Start no se pudo realizar de forma asincrónica.

En el código siguiente se muestran los pasos 1 a 2:

HRESULT MPEG1Source::Start(
        IMFPresentationDescriptor* pPresentationDescriptor,
        const GUID* pguidTimeFormat,
        const PROPVARIANT* pvarStartPos
    )
{

    HRESULT hr = S_OK;
    SourceOp *pAsyncOp = NULL;

    // Check parameters.

    // Start position and presentation descriptor cannot be NULL.
    if (pvarStartPos == NULL || pPresentationDescriptor == NULL)
    {
        return E_INVALIDARG;
    }

    // Check the time format.
    if ((pguidTimeFormat != NULL) && (*pguidTimeFormat != GUID_NULL))
    {
        // Unrecognized time format GUID.
        return MF_E_UNSUPPORTED_TIME_FORMAT;
    }

    // Check the data type of the start position.
    if ((pvarStartPos->vt != VT_I8) && (pvarStartPos->vt != VT_EMPTY))
    {
        return MF_E_UNSUPPORTED_TIME_FORMAT;
    }

    EnterCriticalSection(&m_critSec);

    // Check if this is a seek request. This sample does not support seeking.

    if (pvarStartPos->vt == VT_I8)
    {
        // If the current state is STOPPED, then position 0 is valid.
        // Otherwise, the start position must be VT_EMPTY (current position).

        if ((m_state != STATE_STOPPED) || (pvarStartPos->hVal.QuadPart != 0))
        {
            hr = MF_E_INVALIDREQUEST;
            goto done;
        }
    }

    // Fail if the source is shut down.
    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    // Fail if the source was not initialized yet.
    hr = IsInitialized();
    if (FAILED(hr))
    {
        goto done;
    }

    // Perform a basic check on the caller's presentation descriptor.
    hr = ValidatePresentationDescriptor(pPresentationDescriptor);
    if (FAILED(hr))
    {
        goto done;
    }

    // The operation looks OK. Complete the operation asynchronously.
    hr = SourceOp::CreateStartOp(pPresentationDescriptor, &pAsyncOp);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pAsyncOp->SetData(*pvarStartPos);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = QueueOperation(pAsyncOp);

done:
    SafeRelease(&pAsyncOp);
    LeaveCriticalSection(&m_critSec);
    return hr;
}

Los pasos restantes se muestran en el ejemplo siguiente:

HRESULT MPEG1Source::DoStart(StartOp *pOp)
{
    assert(pOp->Op() == SourceOp::OP_START);

    IMFPresentationDescriptor *pPD = NULL;
    IMFMediaEvent  *pEvent = NULL;

    HRESULT     hr = S_OK;
    LONGLONG    llStartOffset = 0;
    BOOL        bRestartFromCurrentPosition = FALSE;
    BOOL        bSentEvents = FALSE;

    hr = BeginAsyncOp(pOp);

    // Get the presentation descriptor from the SourceOp object.
    // This is the PD that the caller passed into the Start() method.
    // The PD has already been validated.
    if (SUCCEEDED(hr))
    {
        hr = pOp->GetPresentationDescriptor(&pPD);
    }

    // Because this sample does not support seeking, the start
    // position must be 0 (from stopped) or "current position."

    // If the sample supported seeking, we would need to get the
    // start position from the PROPVARIANT data contained in pOp.

    if (SUCCEEDED(hr))
    {
        // Select/deselect streams, based on what the caller set in the PD.
        // This method also sends the MENewStream/MEUpdatedStream events.
        hr = SelectStreams(pPD, pOp->Data());
    }

    if (SUCCEEDED(hr))
    {
        m_state = STATE_STARTED;

        // Queue the "started" event. The event data is the start position.
        hr = m_pEventQueue->QueueEventParamVar(
            MESourceStarted,
            GUID_NULL,
            S_OK,
            &pOp->Data()
            );
    }

    if (FAILED(hr))
    {
        // Failure. Send the error code to the application.

        // Note: It's possible that QueueEvent itself failed, in which case it
        // is likely to fail again. But there is no good way to recover in
        // that case.

        (void)m_pEventQueue->QueueEventParamVar(
            MESourceStarted, GUID_NULL, hr, NULL);
    }

    CompleteAsyncOp(pOp);

    SafeRelease(&pEvent);
    SafeRelease(&pPD);
    return hr;
}

Pausar

El método IMFMediaSource::P ause pausa la fuente multimedia. Implemente este método de la siguiente manera:

  1. Poner en cola una operación asincrónica.
  2. Cada secuencia activa envía un evento MEStreamPaused .
  3. El origen multimedia envía un evento MESourcePaused .

Mientras está en pausa, las solicitudes de ejemplo de colas de origen sin procesarlas. (Consulte Solicitudes de ejemplo).

Stop

El método IMFMediaSource::Stop detiene la fuente multimedia. Implemente este método de la siguiente manera:

  1. Poner en cola una operación asincrónica.
  2. Cada secuencia activa envía un evento MEStreamStopped .
  3. Borre todos los ejemplos en cola y las solicitudes de ejemplo.
  4. El origen multimedia envía un evento MESourceStopped .

Mientras se detiene, el origen rechaza todas las solicitudes de muestras.

Si el origen se detiene mientras una solicitud de E/S está en curso, es posible que la solicitud de E/S se complete después de que el origen entre en estado detenido. En ese caso, el origen debe descartar el resultado de esa solicitud de E/S.

Solicitudes de ejemplo

Media Foundation usa un modelo de extracción , en el que la canalización solicita ejemplos del origen multimedia. Esto difiere del modelo utilizado por DirectShow, en el que los orígenes "insertan" muestras.

Para solicitar un nuevo ejemplo, la canalización de Media Foundation llama a IMFMediaStream::RequestSample. Este método toma un puntero IUnknown que representa un objeto token . La implementación del objeto token depende del autor de la llamada; simplemente proporciona una manera de que el autor de la llamada realice un seguimiento de las solicitudes de ejemplo. El parámetro token también puede ser NULL.

Suponiendo que el origen usa solicitudes de E/S asincrónicas para leer datos, la generación de muestras no se sincronizará con las solicitudes de ejemplo. Para sincronizar las solicitudes de ejemplo con la generación de muestras, un origen multimedia hace lo siguiente:

  1. Los tokens de solicitud se colocan en una cola.
  2. A medida que se generan ejemplos, se colocan en una segunda cola.
  3. El origen multimedia completa una solicitud de ejemplo mediante la extracción de un token de solicitud de la primera cola y un ejemplo de la segunda cola.
  4. El origen multimedia envía un evento MEMediaSample. El evento contiene un puntero al ejemplo y el ejemplo contiene un puntero al token.

En el diagrama siguiente se muestra la relación entre el evento MEMediaSample , el ejemplo y el token de solicitud.

diagrama que muestra memediasample y una cola de ejemplo que apunta a imfsample; imfsample y el punto de cola de solicitudes a iunknown

El origen MPEG-1 de ejemplo implementa este proceso de la siguiente manera:

  1. El método RequestSample coloca la solicitud en una cola FIFO.
  2. A medida que se completan las solicitudes de E/S, el origen de medios crea nuevos ejemplos y los coloca en una segunda cola FIFO. (Esta cola tiene un tamaño máximo, para evitar que el origen lea demasiado lejos).
  3. Cada vez que ambas colas tienen al menos un elemento (una solicitud y un ejemplo), el origen multimedia completa la primera solicitud de la cola de solicitudes enviando el primer ejemplo de la cola de ejemplo.
  4. Para entregar un ejemplo, el objeto stream (no el objeto de origen) envía un evento MEMediaSample .
    • Los datos del evento son un puntero a la interfaz IMFSample de la muestra.
    • Si la solicitud incluía un token, adjunte el token al ejemplo estableciendo el atributo MFSampleExtension_Token en el ejemplo.

En este punto, hay tres posibilidades:

  • Hay otro ejemplo en la cola de ejemplo, pero no hay ninguna solicitud coincidente.
  • Hay una solicitud, pero no hay ningún ejemplo.
  • Ambas colas están vacías; no hay ejemplos y ninguna solicitud.

Si la cola de ejemplo está vacía, el origen comprueba el final de la secuencia (consulte End of Stream). De lo contrario, inicia otra solicitud de E/S para los datos. Si se produce algún error durante este proceso, la secuencia envía un evento MEError .

El código siguiente implementa el método IMFMediaStream::RequestSample :

HRESULT MPEG1Stream::RequestSample(IUnknown* pToken)
{
    HRESULT hr = S_OK;
    IMFMediaSource *pSource = NULL;

    // Hold the media source object's critical section.
    SourceLock lock(m_pSource);

    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    if (m_state == STATE_STOPPED)
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    if (!m_bActive)
    {
        // If the stream is not active, it should not get sample requests.
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    if (m_bEOS && m_Samples.IsEmpty())
    {
        // This stream has already reached the end of the stream, and the
        // sample queue is empty.
        hr = MF_E_END_OF_STREAM;
        goto done;
    }

    hr = m_Requests.InsertBack(pToken);
    if (FAILED(hr))
    {
        goto done;
    }

    // Dispatch the request.
    hr = DispatchSamples();
    if (FAILED(hr))
    {
        goto done;
    }

done:
    if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
    {
        // An error occurred. Send an MEError even from the source,
        // unless the source is already shut down.
        hr = m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
    }
    return hr;
}

El DispatchSamples método extrae ejemplos de la cola de ejemplo, los coincide con solicitudes de ejemplo pendientes y pone en cola eventos MEMediaSample :

HRESULT MPEG1Stream::DispatchSamples()
{
    HRESULT hr = S_OK;
    BOOL bNeedData = FALSE;
    BOOL bEOS = FALSE;

    SourceLock lock(m_pSource);

    // An I/O request can complete after the source is paused, stopped, or
    // shut down. Do not deliver samples unless the source is running.
    if (m_state != STATE_STARTED)
    {
        return S_OK;
    }

    IMFSample *pSample = NULL;
    IUnknown  *pToken = NULL;

    // Deliver as many samples as we can.
    while (!m_Samples.IsEmpty() && !m_Requests.IsEmpty())
    {
        // Pull the next sample from the queue.
        hr = m_Samples.RemoveFront(&pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Pull the next request token from the queue. Tokens can be NULL.
        hr = m_Requests.RemoveFront(&pToken);
        if (FAILED(hr))
        {
            goto done;
        }

        if (pToken)
        {
            // Set the token on the sample.
            hr = pSample->SetUnknown(MFSampleExtension_Token, pToken);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Send an MEMediaSample event with the sample.
        hr = m_pEventQueue->QueueEventParamUnk(
            MEMediaSample, GUID_NULL, S_OK, pSample);

        if (FAILED(hr))
        {
            goto done;
        }

        SafeRelease(&pSample);
        SafeRelease(&pToken);
    }

    if (m_Samples.IsEmpty() && m_bEOS)
    {
        // The sample queue is empty AND we have reached the end of the source
        // stream. Notify the pipeline by sending the end-of-stream event.

        hr = m_pEventQueue->QueueEventParamVar(
            MEEndOfStream, GUID_NULL, S_OK, NULL);

        if (FAILED(hr))
        {
            goto done;
        }

        // Notify the source. It will send the end-of-presentation event.
        hr = m_pSource->QueueAsyncOperation(SourceOp::OP_END_OF_STREAM);
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (NeedsData())
    {
        // The sample queue is empty; the request queue is not empty; and we
        // have not reached the end of the stream. Ask for more data.
        hr = m_pSource->QueueAsyncOperation(SourceOp::OP_REQUEST_DATA);
        if (FAILED(hr))
        {
            goto done;
        }
    }

done:
    if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
    {
        // An error occurred. Send an MEError even from the source,
        // unless the source is already shut down.
        m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
    }

    SafeRelease(&pSample);
    SafeRelease(&pToken);
    return S_OK;
}

Se DispatchSamples llama al método en las siguientes circunstancias:

  • Dentro del método RequestSample .
  • Cuando el origen multimedia se reinicia desde el estado en pausa.
  • Cuando se completa una solicitud de E/S.

Fin de la secuencia

Cuando una secuencia no tiene más datos y todos los ejemplos de esa secuencia se han entregado, los objetos de secuencia envían un evento MEEndOfStream .

Cuando se realizan todas las secuencias activas, el origen multimedia envía un evento MEEndOfPresentation .

Operaciones asincrónicas

Quizás la parte más difícil de escribir un origen multimedia es comprender el modelo asincrónico de Media Foundation.

Todos los métodos de un origen multimedia que controlan el streaming son asincrónicos. En cada caso, el método realiza alguna validación inicial, como comprobar parámetros. A continuación, el origen envía el resto del trabajo a una cola de trabajo. Una vez completada la operación, el origen de medios devuelve un evento al autor de la llamada a través de la interfaz IMFMediaEventGenerator del origen multimedia. Por lo tanto, es importante comprender las colas de trabajo.

Para colocar un elemento en una cola de trabajo, puede llamar a MFPutWorkItem o MFPutWorkItemEx. El origen MPEG-1 se produce para usar MFPutWorkItem, pero las dos funciones hacen lo mismo. La función MFPutWorkItem toma los parámetros siguientes:

  • Valor DWORD que identifica la cola de trabajo. Puede crear una cola de trabajo privada o usar MFASYNC_CALLBACK_QUEUE_STANDARD.
  • Puntero a la interfaz IMFAsyncCallback . Esta interfaz de devolución de llamada se invoca para realizar el trabajo.
  • Objeto de estado opcional, que debe implementar IUnknown.

Uno o varios subprocesos de trabajo que extraen continuamente el siguiente elemento de trabajo de la cola e invocan el método IMFAsyncCallback::Invoke de la interfaz de devolución de llamada.

No se garantiza que los elementos de trabajo se ejecuten en el mismo orden en que se colocan en la cola. Recuerde que más de un subproceso puede atender la misma cola de trabajo, por lo que las llamadas invoke pueden superponerse o producirse fuera de orden. Por lo tanto, es necesario que el origen multimedia mantenga el estado interno correcto mediante el envío de elementos de cola de trabajo en el orden correcto. Solo cuando se completa la operación anterior, el origen inicia la siguiente operación.

Para representar operaciones pendientes, el origen MPEG-1 define una clase denominada SourceOp:

// Represents a request for an asynchronous operation.

class SourceOp : public IUnknown
{
public:

    enum Operation
    {
        OP_START,
        OP_PAUSE,
        OP_STOP,
        OP_REQUEST_DATA,
        OP_END_OF_STREAM
    };

    static HRESULT CreateOp(Operation op, SourceOp **ppOp);
    static HRESULT CreateStartOp(IMFPresentationDescriptor *pPD, SourceOp **ppOp);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    SourceOp(Operation op);
    virtual ~SourceOp();

    HRESULT SetData(const PROPVARIANT& var);

    Operation Op() const { return m_op; }
    const PROPVARIANT& Data() { return m_data;}

protected:
    long        m_cRef;     // Reference count.
    Operation   m_op;
    PROPVARIANT m_data;     // Data for the operation.
};

La Operation enumeración identifica qué operación está pendiente. La clase también contiene un PROPVARIANT para transmitir datos adicionales para la operación.

Cola de operaciones

Para serializar las operaciones, el origen multimedia mantiene una cola de SourceOp objetos. Usa una clase auxiliar para administrar la cola:

template <class OP_TYPE>
class OpQueue : public IUnknown
{
public:

    typedef ComPtrList<OP_TYPE>   OpList;

    HRESULT QueueOperation(OP_TYPE *pOp);

protected:

    HRESULT ProcessQueue();
    HRESULT ProcessQueueAsync(IMFAsyncResult *pResult);

    virtual HRESULT DispatchOperation(OP_TYPE *pOp) = 0;
    virtual HRESULT ValidateOperation(OP_TYPE *pOp) = 0;

    OpQueue(CRITICAL_SECTION& critsec)
        : m_OnProcessQueue(this, &OpQueue::ProcessQueueAsync),
          m_critsec(critsec)
    {
    }

    virtual ~OpQueue()
    {
    }

protected:
    OpList                  m_OpQueue;         // Queue of operations.
    CRITICAL_SECTION&       m_critsec;         // Protects the queue state.
    AsyncCallback<OpQueue>  m_OnProcessQueue;  // ProcessQueueAsync callback.
};

La OpQueue clase está diseñada para ser heredada por el componente que realiza elementos de trabajo asincrónicos. El parámetro de plantilla OP_TYPE es el tipo de objeto que se usa para representar elementos de trabajo en la cola; en este caso, OP_TYPE será SourceOp. La OpQueue clase implementa los métodos siguientes:

  • QueueOperation coloca un nuevo elemento en la cola.
  • ProcessQueue distribuye la siguiente operación desde la cola. Este método es asincrónico.
  • ProcessQueueAsync completa el método asincrónico ProcessQueue .

La clase derivada debe implementar otros dos métodos:

  • ValidateOperation comprueba si es válido para realizar una operación especificada, dado el estado actual del origen multimedia.
  • DispatchOperation lleva a cabo el elemento de trabajo asincrónico.

La cola de operaciones se usa de la siguiente manera:

  1. La canalización de Media Foundation llama a un método asincrónico en el origen multimedia, como IMFMediaSource::Start.
  2. El método asincrónico llama a QueueOperation, que coloca la operación Start en la cola y llama ProcessQueue a (en forma de objeto SourceOp ).
  3. ProcessQueue llama a MFPutWorkItem.
  4. El subproceso de cola de trabajo llama a ProcessQueueAsync.
  5. El ProcessQueueAsync método llama a ValidateOperation y DispatchOperation.

El código siguiente pone en cola una nueva operación en el origen MPEG-1:

template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::QueueOperation(OP_TYPE *pOp)
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_critsec);

    hr = m_OpQueue.InsertBack(pOp);
    if (SUCCEEDED(hr))
    {
        hr = ProcessQueue();
    }

    LeaveCriticalSection(&m_critsec);
    return hr;
}

El código siguiente procesa la cola:

template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::ProcessQueue()
{
    HRESULT hr = S_OK;
    if (m_OpQueue.GetCount() > 0)
    {
        hr = MFPutWorkItem(
            MFASYNC_CALLBACK_QUEUE_STANDARD,    // Use the standard work queue.
            &m_OnProcessQueue,                  // Callback method.
            NULL                                // State object.
            );
    }
    return hr;
}

El ValidateOperation método comprueba si el origen MPEG-1 puede enviar la siguiente operación en la cola. Si hay otra operación en curso, ValidateOperation devuelve MF_E_NOTACCEPTING. Esto garantiza que DispatchOperation no se llame mientras hay otra operación pendiente.

HRESULT MPEG1Source::ValidateOperation(SourceOp *pOp)
{
    if (m_pCurrentOp != NULL)
    {
        return MF_E_NOTACCEPTING;
    }
    return S_OK;
}

El método DispatchOperation cambia en el tipo de operación:

//-------------------------------------------------------------------
// DispatchOperation
//
// Performs the asynchronous operation indicated by pOp.
//
// NOTE:
// This method implements the pure-virtual OpQueue::DispatchOperation
// method. It is always called from a work-queue thread.
//-------------------------------------------------------------------

HRESULT MPEG1Source::DispatchOperation(SourceOp *pOp)
{
    EnterCriticalSection(&m_critSec);

    HRESULT hr = S_OK;

    if (m_state == STATE_SHUTDOWN)
    {
        LeaveCriticalSection(&m_critSec);

        return S_OK; // Already shut down, ignore the request.
    }

    switch (pOp->Op())
    {

    // IMFMediaSource methods:

    case SourceOp::OP_START:
        hr = DoStart((StartOp*)pOp);
        break;

    case SourceOp::OP_STOP:
        hr = DoStop(pOp);
        break;

    case SourceOp::OP_PAUSE:
        hr = DoPause(pOp);
        break;

    // Operations requested by the streams:

    case SourceOp::OP_REQUEST_DATA:
        hr = OnStreamRequestSample(pOp);
        break;

    case SourceOp::OP_END_OF_STREAM:
        hr = OnEndOfStream(pOp);
        break;

    default:
        hr = E_UNEXPECTED;
    }

    if (FAILED(hr))
    {
        StreamingError(hr);
    }

    LeaveCriticalSection(&m_critSec);
    return hr;
}

En resumen:

  1. La canalización llama a un método asincrónico, como IMFMediaSource::Start.
  2. El método asincrónico llama a OpQueue::QueueOperation, pasando un puntero a un SourceOp objeto .
  3. El QueueOperation método coloca la operación en la cola de m_OpQueue y llama a OpQueue::ProcessQueue.
  4. El ProcessQueue método llama a MFPutWorkItem. Desde este punto, todo sucede en un subproceso de cola de trabajo de Media Foundation. El método asincrónico vuelve al autor de la llamada.
  5. El subproceso de cola de trabajo llama al OpQueue::ProcessQueueAsync método .
  6. El ProcessQueueAsync método llama MPEG1Source:ValidateOperation a para validar la operación.
  7. El ProcessQueueAsync método llama MPEG1Source::DispatchOperation a para procesar la operación.

Este diseño tiene varias ventajas:

  • Los métodos son asincrónicos, por lo que no bloquean el subproceso de la aplicación que realiza la llamada.
  • Las operaciones se envían en un subproceso de cola de trabajo de Media Foundation, que se comparte entre los componentes de canalización. Por lo tanto, el origen multimedia no crea su propio subproceso, lo que reduce el número total de subprocesos que se crean.
  • El origen multimedia no se bloquea mientras espera a que se completen las operaciones. Esto reduce la posibilidad de que un origen multimedia cause accidentalmente un interbloqueo y ayuda a reducir el cambio de contexto.
  • El origen multimedia puede usar E/S asincrónica para leer el archivo de origen (llamando a IMFByteStream::BeginRead). El origen multimedia no necesita bloquearse mientras espera a que se complete la rutina de E/S.

Si sigue el patrón que se muestra en el ejemplo del SDK, puede centrarse en los detalles concretos del origen multimedia.

Orígenes multimedia

Escritura de un origen multimedia personalizado