Case Study: origine multimediale MPEG-1

In Microsoft Media Foundation l'oggetto che introduce i dati multimediali nella pipeline di dati viene chiamata origine multimediale. Questo argomento illustra in modo approfondito l'esempio MPEG-1 Media Source SDK.

Prerequisiti

Prima di leggere questo argomento, è necessario comprendere i concetti di Media Foundation seguenti:

È inoltre necessario avere una conoscenza di base dell'architettura di Media Foundation, in particolare il ruolo delle origini multimediali nella pipeline. Per altre informazioni, vedere Origini multimediali.

Inoltre, è possibile leggere l'argomento Scrittura di un'origine multimediale personalizzata, che offre una panoramica più generale dei passaggi descritti qui.

Questo argomento non riproduce tutto il codice dell'esempio SDK, perché l'esempio è abbastanza grande.

Classi C++ usate nell'origine MPEG-1

L'origine MPEG-1 di esempio viene implementata con le classi C++ seguenti:

  • MPEG1ByteStreamHandler. Implementa il gestore di byte-stream per l'origine multimediale. Dato un flusso di byte, il gestore di flusso byte crea un'istanza dell'origine.
  • MPEG1Source. Implementa l'origine multimediale.
  • MPEG1Stream. Implementa gli oggetti di flusso multimediale. L'origine multimediale crea un MPEG1Stream oggetto per ogni flusso audio o video nel flusso bit MPEG-1.
  • Parser. Analizza il bitstream MPEG-1. Per la maggior parte, i dettagli di questa classe non sono rilevanti per le API di Media Foundation.
  • SourceOp, OpQueue: queste due classi gestiscono operazioni asincrone nell'origine multimediale. Vedere Operazioni asincrone.

Altre classi helper varie sono descritte più avanti nell'argomento.

Byte-Stream gestore

Il gestore di byte-stream è l'oggetto che crea l'origine multimediale. Il gestore del flusso byte viene creato dal resolver di origine; le applicazioni non interagiscono direttamente con il gestore di flusso byte. Il resolver di origine individua il gestore di flusso byte cercando nel Registro di sistema. Il gestore viene registrato dall'estensione del nome file o dal tipo MIME. Per l'origine MPEG-1, il gestore del flusso byte viene registrato per l'estensione del nome file ".mpg".

Nota

Se si desidera supportare schemi URL personalizzati, è anche possibile scrivere un gestore dello schema. L'origine MPEG-1 è progettata per i file locali e Media Foundation fornisce già un gestore di schemi per gli URL "file://".

 

Il gestore di flusso byte implementa l'interfaccia FMByteStreamHandler . Questa interfaccia include due metodi più importanti che devono essere implementati:

Due altri metodi sono facoltativi e non implementati nell'esempio SDK:

  • CancelObjectCreation. Annulla il metodo BeginCreateObject . Questo metodo è utile per un'origine di rete che potrebbe avere una latenza elevata all'avvio.
  • GetMaxNumberOfBytesRequiredForResolution. Ottiene il numero massimo di byte letti dal gestore dal flusso di origine. Implementare questo metodo se si conosce la quantità di dati del gestore di flusso byte prima di poter creare l'origine multimediale. In caso contrario, è sufficiente restituire E_NOTIMPL.

Ecco l'implementazione del metodo 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;
}

Il metodo esegue i passaggi seguenti:

  1. Crea una nuova istanza dell'oggetto MPEG1Source.
  2. Creare un oggetto risultato asincrono. Questo oggetto viene usato in seguito per richiamare il metodo di callback del resolver di origine.
  3. Chiama MPEG1Source::BeginOpen, un metodo asincrono definito nella MPEG1Source classe .
  4. Imposta ppIUnknownCancelCookie su NULL, che informa il chiamante che CancelObjectCreation non è supportato.

Il MPEG1Source::BeginOpen metodo esegue il lavoro effettivo della lettura del flusso di byte e dell'inizializzazione dell'oggetto MPEG1Source . Questo metodo non fa parte dell'API pubblica. È possibile definire qualsiasi meccanismo tra il gestore e l'origine multimediale adatta alle proprie esigenze. L'inserimento della maggior parte della logica nell'origine multimediale mantiene il gestore di byte-stream relativamente semplice.

Brevemente, BeginOpen esegue le operazioni seguenti:

  1. Chiama IMFByteStream::GetCapabilities per verificare che il flusso di byte di origine sia leggibile che ricercabile.
  2. Chiama FMIByteStream::BeginRead per avviare una richiesta di I/O asincrona.

Il resto dell'inizializzazione si verifica in modo asincrono. L'origine multimediale legge dati sufficienti dal flusso per analizzare le intestazioni di sequenza MPEG-1. Crea quindi un descrittore di presentazione, che è l'oggetto usato per descrivere i flussi audio e video nel file. Per altre informazioni, vedere Descrittore presentazione. Al termine dell'operazione BeginOpen , il gestore del flusso byte richiama il metodo di callback del resolver di origine. A quel punto, il resolver di origine chiama FMByteStreamHandler::EndCreateObject. Il metodo EndCreateObject restituisce lo stato dell'operazione.

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

Se si verifica un errore in qualsiasi momento durante questo processo, il callback viene richiamato con un codice di stato di errore.

Descrittore di presentazione

Il descrittore di presentazione descrive il contenuto del file MPEG-1, incluse le informazioni seguenti:

  • Numero dei flussi.
  • Formato di ogni flusso.
  • Identificatori di flusso.
  • Stato di selezione di ogni flusso (selezionato o deselezionato).

In termini di architettura di Media Foundation, il descrittore di presentazione contiene uno o più descrittori di flusso. Ogni descrittore di flusso contiene un gestore di tipi multimediali, che viene usato per ottenere o impostare tipi di supporti nel flusso. Media Foundation fornisce implementazioni azionarie per il descrittore di presentazione e il descrittore di flusso; sono adatti per la maggior parte delle origini multimediali.

Per creare un descrittore di presentazione, seguire questa procedura:

  1. Per ogni flusso:
    1. Specificare un ID di flusso e una matrice di tipi di supporti possibili. Se il flusso supporta più di un tipo di supporto, ordinare l'elenco di tipi di supporti per preferenza, se presente. Posizionare il tipo ottimale per primo e il tipo meno ottimale per ultimo.
    2. Chiamare MFCreateStreamDescriptor per creare il descrittore di flusso.
    3. Chiamare IMFStreamDescriptor::GetMediaTypeHandler nel descrittore di flusso appena creato.
    4. Chiamare IMFMediaTypeHandler::SetCurrentMediaType per impostare il formato predefinito per il flusso. Se è presente più di un tipo di supporto, è in genere consigliabile impostare il primo tipo nell'elenco.
  2. Chiamare MFCreatePresentationDescriptor e passare la matrice di puntatori del descrittore di flusso.
  3. Per ogni flusso, chiamare IMFPresentationDescriptor::SelectStream o DeselectStream per impostare lo stato di selezione predefinito. Se sono presenti più flussi dello stesso tipo (audio o video), per impostazione predefinita è necessario selezionare solo uno.

L'oggetto MPEG1Source crea il descrittore di presentazione nel relativo InitPresentationDescriptor metodo:

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

L'applicazione ottiene il descrittore di presentazione chiamando IMFMediaSource::CreatePresentationDescriptor. Questo metodo crea una copia superficiale del descrittore di presentazione chiamando IMFPresentationDescriptor::Clone. La copia contiene puntatori ai descrittori di flusso originali. L'applicazione può usare il descrittore di presentazione per impostare il tipo di supporto, selezionare un flusso o deselezionare un flusso.

Facoltativamente, i descrittori di presentazione e i descrittori di flusso possono contenere attributi che forniscono informazioni aggiuntive sull'origine. Per un elenco di tali attributi, vedere gli argomenti seguenti:

Un attributo merita una menzione speciale: l'attributo MF_PD_DURATION contiene la durata totale dell'origine. Impostare questo attributo se si conosce la durata in anticipo; Ad esempio, la durata potrebbe essere specificata nelle intestazioni di file, a seconda del formato di file. L'applicazione potrebbe visualizzare questo valore o usarlo per impostare un indicatore di stato o una barra di ricerca.

Stati di streaming

Un'origine multimediale definisce gli stati seguenti:

State Descrizione
Avviato L'origine accetta ed elabora richieste di esempio.
Paused L'origine accetta richieste di esempio, ma non le elabora. Le richieste vengono accodate fino all'avvio dell'origine.
Arrestato. L'origine rifiuta le richieste di esempio.

 

Avvio

Il metodo IMFMediaSource::Start avvia l'origine multimediale. È necessario specificare i seguenti parametri:

  • Descrittore di presentazione.
  • GUID in formato ora.
  • Posizione iniziale.

L'applicazione deve ottenere il descrittore di presentazione chiamando CreatePresentationDescriptor nell'origine. Non esiste alcun meccanismo definito per convalidare un descrittore di presentazione. Se l'applicazione specifica il descrittore di presentazione errato, i risultati non sono definiti.

Il GUID in formato ora specifica come interpretare la posizione iniziale. Il formato standard è di 100 nanosecondi (ns), indicato da GUID_NULL. Ogni origine multimediale deve supportare 100-ns unità. Facoltativamente, un'origine può supportare altre unità di tempo, ad esempio il numero di fotogrammi o il codice ora. Tuttavia, non esiste un modo standard per eseguire una query su un'origine multimediale per l'elenco di formati temporali supportati.

La posizione iniziale viene assegnata come PROPVARIANT, consentendo tipi di dati diversi a seconda del formato dell'ora. Per 100-ns, il tipo PROPVARIANT è VT_I8 o VT_EMPTY. Se VT_I8, PROPVARIANT contiene la posizione iniziale in 100-ns unità. Il valore VT_EMPTY ha il significato speciale "inizia in corrispondenza della posizione corrente".

Implementare il metodo Start come segue:

  1. Convalidare i parametri e lo stato:
    • Verificare la presenza di parametri NULL .
    • Controllare il GUID in formato ora. Se il valore non è valido, restituire MF_E_UNSUPPORTED_TIME_FORMAT.
    • Controllare il tipo di dati del PROPVARIANT che contiene la posizione iniziale.
    • Convalidare la posizione iniziale. Se non è valido, restituire MF_E_INVALIDREQUEST.
    • Se l'origine è stata arrestata, restituire MF_E_SHUTDOWN.
  2. Se non si verifica alcun errore nel passaggio 1, accoda un'operazione asincrona. Tutti gli elementi successivi a questo passaggio si verificano in un thread della coda di lavoro.
  3. Per ogni flusso:
    1. Controllare se il flusso è già attivo da una richiesta start precedente.

    2. Chiamare IMFPresentationDescriptor::GetStreamDescriptorByIndex per verificare se l'applicazione selezionata o deselezionata il flusso.

    3. Se un flusso selezionato in precedenza è ora deselezionato, scaricare tutti gli esempi non recapitati per tale flusso.

    4. Se il flusso è attivo, l'origine multimediale (non il flusso) invia uno degli eventi seguenti:

      Per entrambi gli eventi, i dati dell'evento sono il puntatore IMFMediaStream per il flusso.

    5. Se l'origine viene riavviata dallo stato sospeso, potrebbero essere presenti richieste di esempio in sospeso. In tal caso, consegnarli ora.

    6. Se l'origine cerca una nuova posizione, ogni oggetto flusso invia un evento MEStreamSeeked . In caso contrario, ogni flusso invia un evento MEStreamStarted .

  4. Se l'origine cerca una nuova posizione, l'origine multimediale invia un evento MESourceSeeked . In caso contrario, invia un evento MESourceStarted .

Se si verifica un errore in qualsiasi momento dopo il passaggio 2, l'origine invia un evento MESourceStarted con un codice di errore. In questo modo l'applicazione segnala che il metodo Start non è riuscito in modo asincrono.

Il codice seguente illustra i passaggi da 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;
}

I passaggi rimanenti sono illustrati nell'esempio seguente:

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

Sospendi

Il metodo IMFMediaSource::P ause sospende l'origine multimediale. Implementare questo metodo come segue:

  1. Accoda un'operazione asincrona.
  2. Ogni flusso attivo invia un evento MEStreamPaused .
  3. L'origine multimediale invia un evento MESourcePaused .

Durante la sospensione, l'origine accoda le richieste di esempio senza elaborarle. Vedere Richieste di esempio.

Stop

Il metodo IMFMediaSource::Stop arresta l'origine multimediale. Implementare questo metodo come segue:

  1. Accoda un'operazione asincrona.
  2. Ogni flusso attivo invia un evento MEStreamStopped .
  3. Cancellare tutti gli esempi in coda e le richieste di esempio.
  4. L'origine multimediale invia un evento MESourceStopped .

Durante l'arresto, l'origine rifiuta tutte le richieste di campioni.

Se l'origine viene arrestata mentre è in corso una richiesta di I/O, la richiesta di I/O potrebbe essere completata dopo che l'origine entra nello stato arrestato. In tal caso, l'origine deve eliminare il risultato della richiesta di I/O.

Richieste di esempio

Media Foundation usa un modello di pull , in cui la pipeline richiede esempi dall'origine multimediale. Ciò è diverso dal modello usato da DirectShow, in cui le origini "esee" esempi.

Per richiedere un nuovo esempio, la pipeline di Media Foundation chiama IMFMediaStream::RequestSample. Questo metodo accetta un puntatore IUnknown che rappresenta un oggetto token . L'implementazione dell'oggetto token è fino al chiamante; consente semplicemente al chiamante di tenere traccia delle richieste di esempio. Il parametro del token può anche essere NULL.

Supponendo che l'origine usi richieste di I/O asincrone per leggere i dati, la generazione di esempio non verrà sincronizzata con le richieste di esempio. Per sincronizzare le richieste di esempio con la generazione di esempio, un'origine multimediale esegue le operazioni seguenti:

  1. I token di richiesta vengono inseriti in una coda.
  2. Man mano che vengono generati esempi, vengono posizionati in una seconda coda.
  3. L'origine multimediale completa una richiesta di esempio eseguendo il pull di un token di richiesta dalla prima coda e un esempio dalla seconda coda.
  4. L'origine multimediale invia un evento MEMediaSample. L'evento contiene un puntatore all'esempio e l'esempio contiene un puntatore al token.

Il diagramma seguente illustra la relazione tra l'evento MEMediaSample , l'esempio e il token di richiesta.

diagramma che mostra memediasample e una coda di esempio che punta a fmsample; fmsample e il punto di coda della richiesta a iunknown

L'esempio di origine MPEG-1 implementa questo processo come segue:

  1. Il metodo RequestSample inserisce la richiesta in una coda FIFO.
  2. Al termine delle richieste di I/O, l'origine multimediale crea nuovi esempi e li inserisce in una seconda coda FIFO. Questa coda ha una dimensione massima, per evitare che l'origine venga letto troppo avanti.
  3. Ogni volta che entrambe queste code hanno almeno un elemento (una richiesta e un esempio), l'origine multimediale completa la prima richiesta dalla coda della richiesta inviando il primo esempio dalla coda di esempio.
  4. Per recapitare un esempio, l'oggetto di flusso (non l'oggetto di origine) invia un evento MEMediaSample .
    • I dati dell'evento sono un puntatore all'interfaccia FMSample dell'esempio.
    • Se la richiesta include un token, collegare il token all'esempio impostando l'attributo MFSampleExtension_Token nell'esempio.

A questo punto, ci sono tre possibilità:

  • Esiste un altro esempio nella coda di esempio, ma non è richiesta corrispondente.
  • Esiste una richiesta, ma nessun esempio.
  • Entrambe le code sono vuote; non sono presenti esempi e nessuna richiesta.

Se la coda di esempio è vuota, l'origine verifica la fine del flusso (vedere Fine del flusso). In caso contrario, avvia un'altra richiesta di I/O per i dati. Se si verifica un errore durante questo processo, il flusso invia un evento MEError .

Il codice seguente implementa il metodo 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;
}

Il DispatchSamples metodo esegue il pull di esempi dalla coda di esempio, li corrisponde alle richieste di esempio in sospeso e alle code gli eventi 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;
}

Il DispatchSamples metodo viene chiamato nelle circostanze seguenti:

  • All'interno del metodo RequestSample .
  • Quando l'origine multimediale viene riavviata dallo stato sospeso.
  • Al termine di una richiesta di I/O.

Fine del flusso

Quando un flusso non ha più dati e tutti gli esempi per tale flusso sono stati recapitati, gli oggetti di flusso invia un evento MEEndOfStream .

Al termine di tutti i flussi attivi, l'origine multimediale invia un evento MEEndOfPresentation .

Operazioni asincrone

Forse la parte più difficile della scrittura di un'origine multimediale è comprendere il modello asincrono di Media Foundation.

Tutti i metodi in un'origine multimediale che controllano lo streaming sono asincroni. In ogni caso, il metodo esegue una convalida iniziale, ad esempio i parametri di controllo. L'origine invia quindi il resto del lavoro a una coda di lavoro. Al termine dell'operazione, l'origine multimediale invia un evento al chiamante tramite l'interfaccia FMMediaEventGenerator dell'origine multimediale. È quindi importante comprendere le code di lavoro.

Per inserire un elemento in una coda di lavoro, è possibile chiamare MFPutWorkItem o MFPutWorkItemEx. L'origine MPEG-1 usa MFPutWorkItem, ma le due funzioni fanno la stessa cosa. La funzione MFPutWorkItem accetta i parametri seguenti:

  • Valore DWORD che identifica la coda di lavoro. È possibile creare una coda di lavoro privata o usare MFASYNC_CALLBACK_QUEUE_STANDARD.
  • Puntatore all'interfaccia IMFAsyncCallback . Questa interfaccia di callback viene richiamata per eseguire il lavoro.
  • Oggetto stato facoltativo, che deve implementare IUnknown.

La coda di lavoro viene gestita da uno o più thread di lavoro che eseguono il pull continuo dell'elemento di lavoro successivo dalla coda e richiamano il metodo IMFAsyncCallback::Invoke dell'interfaccia di callback.

Gli elementi di lavoro non sono garantiti per l'esecuzione nello stesso ordine in cui vengono inseriti nella coda. Tenere presente che più thread possono eseguire il servizio della stessa coda di lavoro, quindi richiamare le chiamate possono sovrapporsi o verificarsi in modo non ordinato. Pertanto, è necessario che l'origine multimediale mantenga lo stato interno corretto, inviando elementi della coda di lavoro nell'ordine corretto. Solo quando l'operazione precedente viene completata, l'origine avvia l'operazione successiva.

Per rappresentare operazioni in sospeso, l'origine MPEG-1 definisce una classe denominata 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.
};

L'enumerazione Operation identifica l'operazione in sospeso. La classe contiene anche un PROPVARIANT per trasmettere eventuali dati aggiuntivi per l'operazione.

Coda di operazioni

Per serializzare le operazioni, l'origine multimediale gestisce una coda di SourceOp oggetti. Usa una classe helper per gestire la coda:

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 classe è progettata per essere ereditata dal componente che esegue elementi di lavoro asincroni. Il parametro modello di OP_TYPE è il tipo di oggetto usato per rappresentare gli elementi di lavoro nella coda, in questo caso , OP_TYPE sarà SourceOp. La OpQueue classe implementa i metodi seguenti:

  • QueueOperation inserisce un nuovo elemento nella coda.
  • ProcessQueue invia l'operazione successiva dalla coda. Questo metodo è asincrono.
  • ProcessQueueAsync completa il metodo asincrono ProcessQueue .

Altri due metodi devono essere implementati dalla classe derivata:

  • ValidateOperation verifica se è valido eseguire un'operazione specificata, dato lo stato corrente dell'origine multimediale.
  • DispatchOperation esegue l'elemento di lavoro asincrono.

La coda di operazioni viene usata come segue:

  1. La pipeline di Media Foundation chiama un metodo asincrono nell'origine multimediale, ad esempio FMMediaSource::Start.
  2. Il metodo asincrono chiama QueueOperation, che inserisce l'operazione Start sulla coda e le chiamate ProcessQueue (sotto forma di SourceOp oggetto).
  3. ProcessQueue chiama MFPutWorkItem.
  4. Il thread della coda di lavoro chiama ProcessQueueAsync.
  5. Il ProcessQueueAsync metodo chiama ValidateOperation e DispatchOperation.

Il codice seguente accoda una nuova operazione nell'origine 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;
}

Il codice seguente elabora la coda:

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

Il ValidateOperation metodo verifica se l'origine MPEG-1 può inviare l'operazione successiva nella coda. Se un'altra operazione è in corso, ValidateOperation restituisce MF_E_NOTACCEPTING. Ciò garantisce che DispatchOperation non venga chiamato mentre esiste un'altra operazione in sospeso.

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

Il metodo DispatchOperation attiva il tipo di operazione:

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

Per concludere:

  1. La pipeline chiama un metodo asincrono, ad esempio FMMediaSource::Start.
  2. Il metodo asincrono chiama OpQueue::QueueOperation, passando un puntatore a un SourceOp oggetto.
  3. Il QueueOperation metodo inserisce l'operazione nella coda di m_OpQueue e chiama OpQueue::ProcessQueue.
  4. Il ProcessQueue metodo chiama MFPutWorkItem. Da questo punto, tutto avviene su un thread della coda di lavoro di Media Foundation. Il metodo asincrono restituisce al chiamante.
  5. Il thread della coda di lavoro chiama il OpQueue::ProcessQueueAsync metodo .
  6. Il ProcessQueueAsync metodo chiama MPEG1Source:ValidateOperation per convalidare l'operazione.
  7. Il ProcessQueueAsync metodo chiama MPEG1Source::DispatchOperation per elaborare l'operazione.

Esistono diversi vantaggi per questa progettazione:

  • I metodi sono asincroni, quindi non bloccano il thread dell'applicazione chiamante.
  • Le operazioni vengono inviate in un thread della coda di lavoro di Media Foundation, che viene condiviso tra i componenti della pipeline. Pertanto, l'origine multimediale non crea il proprio thread, riducendo il numero totale di thread creati.
  • L'origine multimediale non blocca durante l'attesa del completamento delle operazioni. Ciò riduce la possibilità che un'origine multimediale causa accidentalmente un deadlock e consente di ridurre il cambio di contesto.
  • L'origine multimediale può usare L/O asincrona per leggere il file di origine (chiamando FMByteStream::BeginRead). L'origine multimediale non deve bloccare durante l'attesa del completamento della routine di I/O.

Se si segue il modello illustrato nell'esempio sdk, è possibile concentrarsi sui dettagli specifici dell'origine multimediale.

Origini multimediali

Scrittura di un'origine multimediale personalizzata