Étude de cas : MPEG-1 Media Source

Dans Microsoft Media Foundation, l’objet qui introduit des données multimédias dans le pipeline de données est appelé une source multimédia. Cette rubrique examine de manière approfondie l’exemple de KIT de développement logiciel (SDK) source multimédia MPEG-1 .

Prérequis

Avant de lire cette rubrique, vous devez comprendre les concepts media foundation suivants :

Vous devez également avoir une compréhension de base de l’architecture Media Foundation, en particulier le rôle des sources multimédias dans le pipeline. (Pour plus d’informations, consultez Sources multimédias.)

En outre, vous pouvez lire la rubrique Écriture d’une source multimédia personnalisée, qui donne une vue d’ensemble plus générale des étapes décrites ici.

Cette rubrique ne reproduit pas tout le code de l’exemple sdk, car l’exemple est assez volumineux.

Classes C++ utilisées dans la source MPEG-1

L’exemple de source MPEG-1 est implémenté avec les classes C++ suivantes :

  • MPEG1ByteStreamHandler. Implémente le gestionnaire de flux d’octets pour la source multimédia. Avec un flux d’octets, le gestionnaire de flux d’octets crée une instance de la source.
  • MPEG1Source. Implémente la source multimédia.
  • MPEG1Stream. Implémente les objets de flux multimédia. La source multimédia crée un MPEG1Stream objet pour chaque flux audio ou vidéo dans le flux de bits MPEG-1.
  • Parser. Analyse le flux MPEG-1 bits. Dans la plupart des cas, les détails de cette classe ne sont pas pertinents pour les API Media Foundation.
  • SourceOp, OpQueue : ces deux classes gèrent les opérations asynchrones dans la source multimédia. (Voir Opérations asynchrones).

D’autres classes d’assistance diverses sont décrites plus loin dans la rubrique.

Gestionnaire Byte-Stream

Le gestionnaire de flux d’octets est l’objet qui crée la source multimédia. Le gestionnaire de flux d’octets est créé par le programme de résolution source ; les applications n’interagissent pas directement avec le gestionnaire de flux d’octets. Le programme de résolution source découvre le gestionnaire de flux d’octets en recherchant dans le Registre. Les gestionnaires sont inscrits par extension de nom de fichier ou par type MIME. Pour la source MPEG-1, le gestionnaire de flux d’octets est inscrit pour l’extension de nom de fichier « .mpg ».

Notes

Si vous souhaitez prendre en charge des schémas d’URL personnalisés, vous pouvez également écrire un gestionnaire de schéma. La source MPEG-1 est conçue pour les fichiers locaux, et Media Foundation fournit déjà un gestionnaire de schéma pour les URL « file:// ».

 

Le gestionnaire de flux d’octets implémente l’interface IMFByteStreamHandler . Cette interface a deux méthodes les plus importantes qui doivent être implémentées :

Deux autres méthodes sont facultatives et non implémentées dans l’exemple sdk :

  • CancelObjectCreation. Annule la méthode BeginCreateObject . Cette méthode est utile pour une source réseau qui peut avoir une latence élevée au démarrage.
  • GetMaxNumberOfBytesRequiredForResolution. Obtient le nombre maximal d’octets que le gestionnaire lit à partir du flux source. Implémentez cette méthode si vous connaissez la quantité de données du gestionnaire de flux d’octets avant de pouvoir créer la source multimédia. Sinon, il vous suffit de retourner E_NOTIMPL.

Voici l’implémentation de la méthode 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;
}

La méthode effectue les étapes suivantes :

  1. Crée une nouvelle instance de l'objet MPEG1Source.
  2. Créez un objet de résultat asynchrone. Cet objet est utilisé ultérieurement pour appeler la méthode de rappel du programme de résolution source.
  3. Appelle MPEG1Source::BeginOpen, une méthode asynchrone définie dans la MPEG1Source classe .
  4. Définit ppIUnknownCancelCookie sur NULL, ce qui informe l’appelant que CancelObjectCreation n’est pas pris en charge.

La MPEG1Source::BeginOpen méthode effectue le travail réel de lecture du flux d’octets et d’initialisation de l’objet MPEG1Source . Cette méthode ne fait pas partie de l’API publique. Vous pouvez définir n’importe quel mécanisme entre le gestionnaire et la source multimédia qui répond à vos besoins. En plaçant la majeure partie de la logique dans la source multimédia, le gestionnaire d’octets de flux d’octets est relativement simple.

Brièvement, BeginOpen procédez comme suit :

  1. Appelle IMFByteStream::GetCapabilities pour vérifier que le flux d’octets source est à la fois lisible et recherché.
  2. Appelle IMFByteStream::BeginRead pour démarrer une demande d’E/S asynchrone.

Le reste de l’initialisation se produit de manière asynchrone. La source multimédia lit suffisamment de données du flux pour analyser les en-têtes de séquence MPEG-1. Ensuite, il crée un descripteur de présentation, qui est l’objet utilisé pour décrire les flux audio et vidéo dans le fichier. (Pour plus d’informations, consultez Descripteur de présentation.) Une fois l’opération BeginOpen terminée, le gestionnaire de flux d’octets appelle la méthode de rappel du programme de résolution source. À ce stade, le programme de résolution source appelle IMFByteStreamHandler::EndCreateObject. La méthode EndCreateObject retourne le status de l’opération.

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 une erreur se produit à tout moment pendant ce processus, le rappel est appelé avec une erreur status code.

Descripteur de présentation

Le descripteur de présentation décrit le contenu du fichier MPEG-1, y compris les informations suivantes :

  • Nombre de flux.
  • Format de chaque flux.
  • Identificateurs de flux.
  • La sélection status de chaque flux (sélectionné ou désélectionné).

En termes d’architecture Media Foundation, le descripteur de présentation contient un ou plusieurs descripteurs de flux. Chaque descripteur de flux contient un gestionnaire de type multimédia, qui est utilisé pour obtenir ou définir des types de médias sur le flux. Media Foundation fournit des implémentations d’actions pour le descripteur de présentation et le descripteur de flux ; ils conviennent à la plupart des sources multimédias.

Pour créer un descripteur de présentation, procédez comme suit :

  1. Pour chaque flux :
    1. Fournissez un ID de flux et un tableau de types de médias possibles. Si le flux prend en charge plusieurs types de médias, classez la liste des types de médias par préférence, le cas échéant. (Placez le type optimal en premier et le type optimal le moins en dernier.)
    2. Appelez MFCreateStreamDescriptor pour créer le descripteur de flux.
    3. Appelez IMFStreamDescriptor::GetMediaTypeHandler sur le descripteur de flux nouvellement créé.
    4. Appelez IMFMediaTypeHandler::SetCurrentMediaType pour définir le format par défaut du flux. S’il existe plusieurs types de média, vous devez généralement définir le premier type dans la liste.
  2. Appelez MFCreatePresentationDescriptor et passez le tableau de pointeurs de descripteurs de flux.
  3. Pour chaque flux, appelez IMFPresentationDescriptor::SelectStream ou DeselectStream pour définir l’état de sélection par défaut. S’il existe plusieurs flux du même type (audio ou vidéo), un seul doit être sélectionné par défaut.

L’objet MPEG1Source crée le descripteur de présentation dans sa InitPresentationDescriptor méthode :

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’application obtient le descripteur de présentation en appelant IMFMediaSource::CreatePresentationDescriptor. Cette méthode crée une copie superficielle du descripteur de présentation en appelant IMFPresentationDescriptor::Clone. (La copie contient des pointeurs vers les descripteurs de flux d’origine.) L’application peut utiliser le descripteur de présentation pour définir le type de média, sélectionner un flux ou désélectionner un flux.

Si vous le souhaitez, les descripteurs de présentation et les descripteurs de flux peuvent contenir des attributs qui fournissent des informations supplémentaires sur la source. Pour obtenir la liste de ces attributs, consultez les rubriques suivantes :

Un attribut mérite une mention spéciale : l’attribut MF_PD_DURATION contient la durée totale de la source. Définissez cet attribut si vous connaissez la durée à l’avance ; par exemple, la durée peut être spécifiée dans les en-têtes de fichier, en fonction du format de fichier. L’application peut afficher cette valeur ou l’utiliser pour définir une barre de progression ou une barre de recherche.

États de streaming

Une source multimédia définit les états suivants :

State Description
Démarré La source accepte et traite les exemples de demandes.
Suspendu La source accepte les exemples de demandes, mais ne les traite pas. Les demandes sont mises en file d’attente jusqu’à ce que la source soit démarrée.
Arrêté. La source rejette les exemples de requêtes.

 

Démarrer

La méthode IMFMediaSource::Start démarre la source multimédia. Les paramètres suivants sont pris en compte :

  • Descripteur de présentation.
  • GUID de format de temps.
  • Position de début.

L’application doit obtenir le descripteur de présentation en appelant CreatePresentationDescriptor sur la source. Il n’existe aucun mécanisme défini pour valider un descripteur de présentation. Si l’application spécifie le descripteur de présentation incorrect, les résultats ne sont pas définis.

Le GUID de format de temps spécifie comment interpréter la position de départ. Le format standard est d’unités de 100 nanosecondes (ns), indiquées par GUID_NULL. Chaque source multimédia doit prendre en charge des unités de 100 ns. Si vous le souhaitez, une source peut prendre en charge d’autres unités de temps, telles que le numéro d’image ou le code d’heure. Toutefois, il n’existe aucun moyen standard d’interroger une source multimédia pour connaître la liste des formats de temps qu’elle prend en charge.

La position de début est donnée sous la forme d’un PROPVARIANT, ce qui permet d’utiliser différents types de données en fonction du format d’heure. Pour 100 ns, le type PROPVARIANT est VT_I8 ou VT_EMPTY. Si VT_I8, propVARIANT contient la position de début en unités 100 ns. La valeur VT_EMPTY a la signification spéciale « commencer à la position actuelle ».

Implémentez la méthode Start comme suit :

  1. Validez les paramètres et l’état :
    • Recherchez les paramètres NULL .
    • Vérifiez le GUID de format de temps. Si la valeur n’est pas valide, retournez MF_E_UNSUPPORTED_TIME_FORMAT.
    • Vérifiez le type de données du PROPVARIANT qui contient la position de début.
    • Validez la position de début. S’il n’est pas valide, retournez MF_E_INVALIDREQUEST.
    • Si la source a été arrêtée, retournez MF_E_SHUTDOWN.
  2. Si aucune erreur ne se produit à l’étape 1, mettre en file d’attente une opération asynchrone. Tout ce qui suit cette étape se produit sur un thread de file d’attente de travail.
  3. Pour chaque flux :
    1. Vérifiez si le flux est déjà actif à partir d’une demande de démarrage précédente.

    2. Appelez IMFPresentationDescriptor::GetStreamDescriptorByIndex pour case activée si l’application a sélectionné ou désélectionné le flux.

    3. Si un flux précédemment sélectionné est maintenant désélectionné, videz tous les exemples non remis pour ce flux.

    4. Si le flux est actif, la source multimédia (et non le flux) envoie l’un des événements suivants :

      Pour les deux événements, les données d’événement sont le pointeur IMFMediaStream pour le flux.

    5. Si la source redémarre à partir de l’état suspendu, des exemples de demandes peuvent être en attente. Si c’est le cas, livrez-les maintenant.

    6. Si la source recherche une nouvelle position, chaque objet stream envoie un événement MEStreamSeeked . Sinon, chaque flux envoie un événement MEStreamStarted .

  4. Si la source recherche une nouvelle position, la source multimédia envoie un événement MESourceSeeked . Sinon, il envoie un événement MESourceStarted .

Si une erreur se produit à tout moment après l’étape 2, la source envoie un événement MESourceStarted avec un code d’erreur. Cela avertit l’application que la méthode Start a échoué de manière asynchrone.

Le code suivant montre les étapes 1 à 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;
}

Les étapes restantes sont illustrées dans l’exemple suivant :

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

Suspendre

La méthode IMFMediaSource::P ause interrompt la source multimédia. Implémentez cette méthode comme suit :

  1. Mettre en file d’attente une opération asynchrone.
  2. Chaque flux actif envoie un événement MEStreamPaused .
  3. La source multimédia envoie un événement MESourcePaused .

Pendant la pause, la source met en file d’attente les exemples de requêtes sans les traiter. (Voir Exemples de demandes.)

Arrêter

La méthode IMFMediaSource::Stop arrête la source multimédia. Implémentez cette méthode comme suit :

  1. Mettre en file d’attente une opération asynchrone.
  2. Chaque flux actif envoie un événement MEStreamStopped .
  3. Effacez tous les exemples et demandes mis en file d’attente.
  4. La source multimédia envoie un événement MESourceStopped .

Lors de l’arrêt, la source rejette toutes les demandes d’exemples.

Si la source est arrêtée pendant qu’une demande d’E/S est en cours, la demande d’E/S peut se terminer une fois que la source est arrêtée. Dans ce cas, la source doit ignorer le résultat de cette demande d’E/S.

Exemple de demande

Media Foundation utilise un modèle d’extraction , dans lequel le pipeline demande des exemples à partir de la source multimédia. Cela diffère du modèle utilisé par DirectShow, dans lequel les sources « poussent » les exemples.

Pour demander un nouvel exemple, le pipeline Media Foundation appelle IMFMediaStream::RequestSample. Cette méthode prend un pointeur IUnknown qui représente un objet de jeton . L’implémentation de l’objet jeton appartient à l’appelant ; il permet simplement à l’appelant de suivre des exemples de demandes. Le paramètre token peut également être NULL.

En supposant que la source utilise des demandes d’E/S asynchrones pour lire des données, la génération d’exemples ne sera pas synchronisée avec les exemples de demandes. Pour synchroniser des exemples de demandes avec la génération d’exemples, une source de média effectue les opérations suivantes :

  1. Les jetons de requête sont placés dans une file d’attente.
  2. À mesure que des exemples sont générés, ils sont placés dans une deuxième file d’attente.
  3. La source multimédia termine un exemple de demande en extrayant un jeton de requête de la première file d’attente et un exemple de la deuxième file d’attente.
  4. La source multimédia envoie un événement MEMediaSample. L’événement contient un pointeur vers l’exemple et l’exemple contient un pointeur vers le jeton.

Le diagramme suivant montre la relation entre l’événement MEMediaSample , l’exemple et le jeton de requête.

diagramme montrant memediasample et un exemple de file d’attente pointant vers imfsample; imfsample et la file d’attente des requêtes pointent vers iunknown

L’exemple de source MPEG-1 implémente ce processus comme suit :

  1. La méthode RequestSample place la requête dans une file d’attente FIFO.
  2. À mesure que les demandes d’E/S sont terminées, la source multimédia crée de nouveaux exemples et les place dans une deuxième file d’attente FIFO. (Cette file d’attente a une taille maximale, pour empêcher la source de lire trop à l’avance.)
  3. Chaque fois que ces deux files d’attente ont au moins un élément (une requête et un exemple), la source multimédia termine la première requête de la file d’attente des demandes en envoyant le premier exemple de l’exemple de file d’attente.
  4. Pour remettre un exemple, l’objet de flux (et non l’objet source) envoie un événement MEMediaSample .

À ce stade, il existe trois possibilités :

  • Il existe un autre exemple dans la file d’attente de l’exemple, mais aucune demande correspondante.
  • Il existe une demande, mais aucun exemple.
  • Les deux files d’attente sont vides ; il n’y a pas d’échantillons ni de demandes.

Si l’exemple de file d’attente est vide, la source recherche la fin du flux (voir Fin du flux). Sinon, il démarre une autre demande d’E/S pour les données. Si une erreur se produit pendant ce processus, le flux envoie un événement MEError .

Le code suivant implémente la méthode 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;
}

La DispatchSamples méthode extrait des échantillons de l’exemple de file d’attente, les met en correspondance avec des exemples de requêtes en attente et met en file d’attente les événements 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;
}

La DispatchSamples méthode est appelée dans les circonstances suivantes :

  • Dans la méthode RequestSample .
  • Lorsque la source multimédia redémarre à partir de l’état suspendu.
  • Lorsqu’une demande d’E/S se termine.

Fin du flux

Lorsqu’un flux n’a plus de données et que tous les exemples de ce flux ont été remis, les objets de flux envoient un événement MEEndOfStream .

Lorsque tous les flux actifs sont terminés, la source multimédia envoie un événement MEEndOfPresentation .

Opérations asynchrones

La partie la plus difficile de l’écriture d’une source multimédia est peut-être de comprendre le modèle asynchrone Media Foundation.

Toutes les méthodes sur une source multimédia qui contrôlent la diffusion en continu sont asynchrones. Dans chaque cas, la méthode effectue une validation initiale, comme la vérification des paramètres. La source distribue ensuite le reste du travail dans une file d’attente de travail. Une fois l’opération terminée, la source multimédia renvoie un événement à l’appelant, via l’interface IMFMediaEventGenerator de la source multimédia. Il est donc important de comprendre les files d’attente de travail.

Pour placer un élément dans une file d’attente de travail, vous pouvez appeler MFPutWorkItem ou MFPutWorkItemEx. La source MPEG-1 utilise MFPutWorkItem, mais les deux fonctions font la même chose. La fonction MFPutWorkItem prend les paramètres suivants :

  • Valeur DWORD qui identifie la file d’attente de travail. Vous pouvez créer une file d’attente de travail privée ou utiliser MFASYNC_CALLBACK_QUEUE_STANDARD.
  • Pointeur vers l’interface IMFAsyncCallback . Cette interface de rappel est appelée pour effectuer le travail.
  • Objet d’état facultatif, qui doit implémenter IUnknown.

La file d’attente de travail est prise en charge par un ou plusieurs threads de travail qui extrayent en continu l’élément de travail suivant de la file d’attente et appellent la méthode IMFAsyncCallback::Invoke de l’interface de rappel.

Il n’est pas garanti que les éléments de travail s’exécutent dans le même ordre que celui où vous les placez dans la file d’attente. N’oubliez pas que plusieurs threads peuvent traiter la même file d’attente de travail. Les appels d’appel peuvent donc se chevaucher ou se produire dans le désordre. Par conséquent, il appartient à la source multimédia de conserver l’état interne correct, en envoyant les éléments de file d’attente de travail dans le bon ordre. Ce n’est qu’une fois l’opération précédente terminée que la source démarre l’opération suivante.

Pour représenter les opérations en attente, la source MPEG-1 définit une classe nommée 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’énumération Operation identifie l’opération en attente. La classe contient également un PROPVARIANT pour transmettre des données supplémentaires pour l’opération.

File d’attente des opérations

Pour sérialiser des opérations, la source multimédia gère une file d’attente d’objets SourceOp . Il utilise une classe d’assistance pour gérer la file d’attente :

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 est conçue pour être héritée par le composant qui effectue des éléments de travail asynchrones. Le paramètre de modèle OP_TYPE est le type d’objet utilisé pour représenter les éléments de travail dans la file d’attente( dans ce cas, OP_TYPE sera SourceOp. La OpQueue classe implémente les méthodes suivantes :

  • QueueOperation place un nouvel élément dans la file d’attente.
  • ProcessQueue distribue l’opération suivante à partir de la file d’attente. Cette méthode est asynchrone.
  • ProcessQueueAsync termine la méthode asynchrone ProcessQueue .

Deux autres méthodes doivent être implémentées par la classe dérivée :

  • ValidateOperation vérifie s’il est valide d’effectuer une opération spécifiée, compte tenu de l’état actuel de la source multimédia.
  • DispatchOperation exécute l’élément de travail asynchrone.

La file d’attente des opérations est utilisée comme suit :

  1. Le pipeline Media Foundation appelle une méthode asynchrone sur la source multimédia, telle que IMFMediaSource::Start.
  2. La méthode asynchrone appelle QueueOperation, qui place l’opération Start sur la file d’attente et appelle ProcessQueue (sous la forme d’un SourceOp objet ).
  3. ProcessQueue appelle MFPutWorkItem.
  4. Le thread de file d’attente de travail appelle ProcessQueueAsync.
  5. La ProcessQueueAsync méthode appelle ValidateOperation et DispatchOperation.

Le code suivant met en file d’attente une nouvelle opération sur la source 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;
}

Le code suivant traite la file d’attente :

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

La ValidateOperation méthode vérifie si la source MPEG-1 peut distribuer l’opération suivante dans la file d’attente. Si une autre opération est en cours, ValidateOperation retourne MF_E_NOTACCEPTING. Cela garantit que n’est DispatchOperation pas appelé pendant qu’une autre opération est en attente.

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

La méthode DispatchOperation bascule sur le type d’opération :

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

Pour récapituler :

  1. Le pipeline appelle une méthode asynchrone, telle que IMFMediaSource::Start.
  2. La méthode asynchrone appelle OpQueue::QueueOperation, en passant un pointeur vers un SourceOp objet .
  3. La QueueOperation méthode place l’opération dans la file d’attente m_OpQueue et appelle OpQueue::ProcessQueue.
  4. La ProcessQueue méthode appelle MFPutWorkItem. À partir de ce point, tout se passe sur un thread de file d’attente de travail Media Foundation. La méthode asynchrone retourne à l’appelant.
  5. Le thread de file d’attente de travail appelle la OpQueue::ProcessQueueAsync méthode .
  6. La ProcessQueueAsync méthode appelle MPEG1Source:ValidateOperation pour valider l’opération.
  7. La ProcessQueueAsync méthode appelle MPEG1Source::DispatchOperation pour traiter l’opération.

Cette conception présente plusieurs avantages :

  • Les méthodes étant asynchrones, elles ne bloquent pas le thread de l’application appelante.
  • Les opérations sont distribuées sur un thread de file d’attente de travail Media Foundation, qui est partagé entre les composants du pipeline. Par conséquent, la source multimédia ne crée pas son propre thread, ce qui réduit le nombre total de threads créés.
  • La source multimédia ne se bloque pas en attendant la fin des opérations. Cela réduit le risque qu’une source multimédia provoque accidentellement un interblocage et permet de réduire le basculement de contexte.
  • La source multimédia peut utiliser des E/S asynchrones pour lire le fichier source (en appelant IMFByteStream::BeginRead). La source multimédia n’a pas besoin de se bloquer en attendant la fin de la routine d’E/S.

Si vous suivez le modèle indiqué dans l’exemple du SDK, vous pouvez vous concentrer sur les détails spécifiques de votre source multimédia.

Sources multimédias

Écriture d’une source multimédia personnalisée