Écrire un présentateur EVR

[Le composant décrit sur cette page, Convertisseur vidéo amélioré, est une fonctionnalité héritée. Il a été remplacé par le Convertisseur video simple (SVR) exposé à travers des composants MediaPlayer et IMFMediaEngine. Pour lire du contenu vidéo, vous devez envoyer des données dans l’un de ces composants et les autoriser à instancier le nouveau convertisseur vidéo. Ces composants ont été optimisés pour Windows 10 et Windows 11. Microsoft recommande vivement (si possible) que le nouveau code utilise MediaPlayer ou les API de bas niveau IMFMediaEngine pour lire des médias vidéo dans Windows au lieu de l’EVR. Microsoft recommande, si possible, la réécriture du code existant qui utilise les API héritées pour être à jour avec les nouvelles API.]

Cet article explique comment écrire un présentateur personnalisé pour le convertisseur vidéo amélioré (EVR). Un présentateur personnalisé peut être utilisé avec DirectShow et Media Foundation. Les interfaces et le modèle objet sont identiques pour les deux technologies, même si la séquence d’opérations exacte peut varier.

L’exemple de code de cette rubrique est adapté de l’exemple EVRPresenter, fourni dans le kit de développement logiciel (SDK) Windows.

Cette rubrique contient les sections suivantes :

Prérequis

Avant d’écrire un présentateur personnalisé, vous devez bien connaître les technologies suivantes :

  • Convertisseur vidéo amélioré. Voir Convertisseur vidéo amélioré.
  • Graphismes Direct3D. Vous n’avez pas besoin de comprendre les graphismes 3D pour écrire un présentateur. En revanche, vous devez savoir comment créer un appareil Direct3D et gérer les surfaces Direct3D. Si vous ne connaissez pas Direct3D, lisez les sections « Appareils Direct3D » et « Ressources Direct3D » dans la documentation du SDK DirectX Graphics.
  • DirectShow filtre les graphiques ou le pipeline Media Foundation, selon la technologie que votre application va utiliser pour restituer la vidéo.
  • Transformations Media Foundation. Le mélangeur EVR est une transformation Media Foundation et le présentateur appelle les méthodes directement sur le mélangeur.
  • Implémentation d’objets COM. Le présentateur est un objet COM in-process et free-threaded.

Modèle objet du présentateur

Cette section présente le modèle objet et les interfaces du présentateur.

Flux de données dans l’EVR

L’EVR utilise deux composants plug-in pour restituer la vidéo : le mélangeur et le présentateur. Le mélangeur mélange les flux vidéo et désentrelace la vidéo si nécessaire. Le présentateur dessine (ou présente) la vidéo sur le périphérique d’affichage et planifie lorsque chaque image est dessinée. Les applications peuvent remplacer l’un de ces objets par une implémentation personnalisée.

L’EVR a un ou plusieurs flux d’entrée, et le mélangeur a un nombre correspondant de flux d’entrée. Stream 0 est toujours le flux de référence. Les autres flux sont des sous-flux, que le mélangeur soumet à une fusion alpha sur le flux de référence. Le flux de référence détermine la fréquence d’images principale pour la vidéo composée. Pour chaque image de référence, le mélangeur prend l’image la plus récente de chaque sous-flux, les soumet à une fusion alpha sur l’image de référence et génère une image composée unique. Le mélangeur effectue également un désentrelaçage et une conversion de couleur de YUV en RVB si nécessaire. L’EVR insère toujours le mélangeur dans le pipeline vidéo, quel que soit le nombre de flux d’entrée ou le format vidéo. Le schéma suivant illustre ce processus.

diagram showing the reference stream and substream pointing to the mixer, which points to the presenter, which points to the display

Un présentateur effectue les tâches suivantes :

  • Il définit le format de sortie sur le mélangeur. Avant le début de la diffusion en continu, le présentateur définit un type de média sur le flux de sortie du mélangeur. Ce type de média définit le format de l’image composée.
  • Il crée l’appareil Direct3D.
  • Il alloue les surfaces Direct3D. Le mélangeur réalise un blit des images composées sur ces surfaces.
  • Il obtient les données de sortie du mélangeur.
  • Il planifie quand les images sont présentées. L’EVR fournit l’horloge de présentation et le présentateur planifie les images en fonction de cette horloge.
  • Présente chaque image à l’aide de Direct3D.
  • Il exécute la décomposition et le nettoyage d’image.

États du présentateur

À tout moment, le présentateur se trouve dans l’un des états suivants :

  • Démarré. L’horloge de présentation de l’EVR est en cours d’exécution. Le présentateur planifie les images vidéo pour la présentation à mesure qu’elles arrivent.
  • En pause. L’horloge de présentation est en pause. Le présentateur ne présente pas de nouveaux exemples, mais conserve sa file d’attente des exemples planifiés. S’il reçoit de nouveaux exemples, le présentateur les ajoute à la file d’attente.
  • Arrêté. L’horloge de présentation est arrêtée. Le présentateur abandonne tous les exemples qui ont été planifiés.
  • Éteint. Le présentateur libère toutes les ressources liées à la diffusion en continu, comme les surfaces Direct3D. Il s’agit de l’état initial du présentateur et de son état final avant destruction.

Dans l’exemple de code de cette rubrique, l’état du présentateur est représenté par une énumération :

enum RENDER_STATE
{
    RENDER_STATE_STARTED = 1,
    RENDER_STATE_STOPPED,
    RENDER_STATE_PAUSED,
    RENDER_STATE_SHUTDOWN,  // Initial state.
};

Certaines opérations ne sont pas valides lorsque le présentateur se trouve dans l’état « Éteint ». L’exemple de code vérifie cet état en appelant une méthode d’assistance :

    HRESULT CheckShutdown() const
    {
        if (m_RenderState == RENDER_STATE_SHUTDOWN)
        {
            return MF_E_SHUTDOWN;
        }
        else
        {
            return S_OK;
        }
    }

Interfaces du présentateur

Un présentateur est requis pour implémenter les interfaces suivantes :

Interface Description
IMFClockStateSink Avertit le présentateur lorsque l’horloge de l’EVR change d’état. Voir Implémentation d’IMFClockStateSink.
IMFGetService Permet à l’application et d’autres composants du pipeline d’obtenir les interfaces depuis le présentateur.
IMFTopologyServiceLookupClient Permet au présentateur d’obtenir les interfaces à partir de l’EVR ou du mélangeur. Voir Implémentation d’IMFTopologyServiceLookupClient.
IMFVideoDeviceID Garantit que le présentateur et le mélangeur utilisent des technologies compatibles. Voir Implémentation d’IMFVideoDeviceID.
IMFVideoPresenter Traite les messages de l’EVR. Voir Implémentation d’IMFVideoPresenter.

 

Les interfaces suivantes sont facultatives :

Interface Description
IEVRTrustedVideoPlugin Permet au présentateur d’utiliser un média protégé. Implémentez cette interface si le présentateur est un composant approuvé conçu pour fonctionner dans le chemin d’accès multimédia protégé (PMP).
IMFRateSupport Indique la plage de fréquences de lecture que prend en charge le présentateur. Voir Implémentation d’IMFRateSupport.
IMFVideoPositionMapper Mappe les coordonnées sur l’image vidéo de sortie vers les coordonnées sur l’image vidéo d’entrée.
IQualProp Fournit des informations sur les performances. L’EVR utilise ces informations pour la gestion du contrôle de la qualité. Cette interface est documentée dans le SDK DirectShow.

 

Vous pouvez également fournir des interfaces pour que l’application communique avec le présentateur. Le présentateur standard implémente l’interface IMFVideoDisplayControl à cet effet. Vous pouvez implémenter cette interface ou définir la vôtre. L’application obtient les interfaces du présentateur en appelant IMFGetService::GetService sur l’EVR. Lorsque l’identificateur global unique (GUID) de service est MR_VIDEO_RENDER_SERVICE, l’EVR transmet la requête GetService au présentateur.

Implémentation d’IMFVideoDeviceID

L’interface IMFVideoDeviceID contient une méthode, GetDeviceID, qui retourne le GUID d’appareil. Le GUID d’appareil garantit que le présentateur et le mélangeur utilisent des technologies compatibles. Si les GUID d’appareil ne correspondent pas, l’EVR ne parvient pas à réaliser l’initialisation.

Le mélangeur standard et le présentateur utilisent Direct3D 9, avec le GUID d’appareil défini sur IID_IDirect3DDevice9. Si vous envisagez d’utiliser votre présentateur personnalisé avec le mélangeur standard, le GUID d’appareil du présentateur doit être IID_IDirect3DDevice9. Si vous remplacez les deux composants, vous pouvez définir un nouveau GUID pour l’appareil. Pour le reste de cet article, il est supposé que le présentateur utilise Direct3D 9. Voici l’implémentation standard de GetDeviceID :

HRESULT EVRCustomPresenter::GetDeviceID(IID* pDeviceID)
{
    if (pDeviceID == NULL)
    {
        return E_POINTER;
    }

    *pDeviceID = __uuidof(IDirect3DDevice9);
    return S_OK;
}

La méthode doit réussir même lorsque le présentateur est éteint.

Implémentation d’IMFTopologyServiceLookupClient

L’interface IMFTopologyServiceLookupClient permet au présentateur d’obtenir des pointeurs d’interface depuis l’EVR et le mélangeur comme suit :

  1. Lorsque l’EVR initialise le présentateur, il appelle la méthode IMFTopologyServiceLookupClient::InitServicePointers du présentateur. L’argument est un pointeur vers l’interface IMFTopologyServiceLookup de l’EVR.
  2. Le présentateur appelle IMFTopologyServiceLookup::LookupService pour obtenir les pointeurs d’interface depuis l’EVR ou le mélangeur.

La méthode LookupService est similaire à la méthode IMFGetService::GetService. Les deux méthodes prennent le GUID de service et l’identificateur d’interface (IID) comme entrée. Cependant LookupService retourne un tableau de pointeurs d’interface, tandis que GetService retourne un pointeur unique. Toutefois, dans la pratique, vous pouvez toujours définir la taille du tableau sur 1. L’objet interrogé dépend du GUID de service :

  • Si le GUID de service est MR_VIDEO_RENDER_SERVICE, l’EVR est interrogé.
  • Si le GUID de service est MR_VIDEO_RENDER_SERVICE, l’EVR est interrogé.

Dans votre implémentation d’InitServicePointers, obtenez les interfaces suivantes depuis l’EVR :

Interface de l’EVR Description
IMediaEventSink Permet au présentateur d’envoyer des messages à l’EVR. Cette interface est définie dans le SDK DirectShow. Par conséquent, les messages suivent le modèle pour les événements DirectShow (et non celui pour les événements Media Foundation).
IMFClock Représente l’horloge de l’EVR. Le présentateur utilise cette interface pour planifier des exemples pour la présentation. L’EVR peut s’exécuter sans horloge. Cette interface peut donc ne pas être disponible. Si ce n’est pas le cas, ignorez le code d’erreur de LookupService.
L’horloge implémente également l’interface IMFTimer. Dans le pipeline Media Foundation, l’horloge implémente l’interface IMFPresentationClock. Elle n’implémente pas cette interface dans DirectShow.

 

Obtenez les interfaces suivantes depuis le mélangeur :

Interface du mélangeur Description
IMFTransform Permet au présentateur de communiquer avec le mélangeur.
IMFVideoDeviceID Permet au présentateur de valider le GUID d’appareil du mélangeur.

 

Le code suivant implémente la méthode InitServicePointers :

HRESULT EVRCustomPresenter::InitServicePointers(
    IMFTopologyServiceLookup *pLookup
    )
{
    if (pLookup == NULL)
    {
        return E_POINTER;
    }

    HRESULT             hr = S_OK;
    DWORD               dwObjectCount = 0;

    EnterCriticalSection(&m_ObjectLock);

    // Do not allow initializing when playing or paused.
    if (IsActive())
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    // Ask for the clock. Optional, because the EVR might not have a clock.
    dwObjectCount = 1;

    (void)pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL,   // Not used.
        0,                          // Reserved.
        MR_VIDEO_RENDER_SERVICE,    // Service to look up.
        IID_PPV_ARGS(&m_pClock),    // Interface to retrieve.
        &dwObjectCount              // Number of elements retrieved.
        );

    // Ask for the mixer. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_pMixer), &dwObjectCount
        );

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

    // Make sure that we can work with this mixer.
    hr = ConfigureMixer(m_pMixer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Ask for the EVR's event-sink interface. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_pMediaEventSink),
        &dwObjectCount
        );

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

    // Successfully initialized. Set the state to "stopped."
    m_RenderState = RENDER_STATE_STOPPED;

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Lorsque les pointeurs d’interface obtenus avec LookupService ne sont plus valides, l’EVR appelle IMFTopologyServiceLookupClient::ReleaseServicePointers. Avec cette méthode, vous libérez tous les pointeurs d’interface et définissez l’état du présentateur sur « Éteint » :

HRESULT EVRCustomPresenter::ReleaseServicePointers()
{
    // Enter the shut-down state.
    EnterCriticalSection(&m_ObjectLock);

    m_RenderState = RENDER_STATE_SHUTDOWN;

    LeaveCriticalSection(&m_ObjectLock);

    // Flush any samples that were scheduled.
    Flush();

    // Clear the media type and release related resources.
    SetMediaType(NULL);

    // Release all services that were acquired from InitServicePointers.
    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    return S_OK;
}

L’EVR appelle ReleaseServicePointers pour diverses raisons, notamment :

  • Déconnecter ou reconnecter des repères (DirectShow), ajouter ou supprimer des récepteurs de flux (Media Foundation).
  • Modifier le format.
  • Configurer une nouvelle horloge.
  • Éteindre l’EVR.

Pendant la durée de vie du présentateur, l’EVR peut appeler InitServicePointers et ReleaseServicePointers plusieurs fois.

Implémentation d’IMFVideoPresenter

L’interface IMFVideoPresenter hérite d’IMFClockStateSink et ajoute deux méthodes :

Méthode Description
GetCurrentMediaType Retourne le type de média des images vidéo composées.
ProcessMessage Indique au présentateur qu’il doit d’effectuer différentes actions.

 

La méthode GetCurrentMediaType retourne le type de média du présentateur. (Pour plus d’informations sur la définition du type de média, voir Négociation des formats.) Le type de média est retourné en tant que pointeur d’interface IMFVideoMediaType. L’exemple suivant suppose que le présentateur stocke le type de média en tant que pointeur IMFMediaType. Pour obtenir l’interface IMFVideoMediaType à partir du type de média, appelez QueryInterface :

HRESULT EVRCustomPresenter::GetCurrentMediaType(
    IMFVideoMediaType** ppMediaType
    )
{
    HRESULT hr = S_OK;

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

    *ppMediaType = NULL;

    EnterCriticalSection(&m_ObjectLock);

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

    if (m_pMediaType == NULL)
    {
        hr = MF_E_NOT_INITIALIZED;
        goto done;
    }

    hr = m_pMediaType->QueryInterface(IID_PPV_ARGS(ppMediaType));

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

La méthode ProcessMessage est le mécanisme principal pour que l’EVR communique avec le présentateur. Les messages suivants sont définis. Les détails concernant l’implémentation de chaque message sont fournis dans la suite de cette rubrique.

Message Description
MFVP_MESSAGE_INVALIDATEMEDIATYPE Le type de média de la sortie du mélangeur n’est pas valide. Le présentateur doit négocier un nouveau type de média avec le mélangeur. Voir Négociation des formats.
MFVP_MESSAGE_BEGINSTREAMING La diffusion en continu a commencé. Aucune action particulière n’est requise pour ce message. Cependant, vous pouvez l’utiliser pour allouer des ressources.
MFVP_MESSAGE_ENDSTREAMING La diffusion en continu a pris fin. Libérez les ressources que vous avez allouées en réponse au message MFVP_MESSAGE_BEGINSTREAMING.
MFVP_MESSAGE_PROCESSINPUTNOTIFY Le mélangeur a reçu un nouvel exemple d’entrée et peut être capable de générer une nouvelle image de sortie. Le présentateur doit appeler IMFTransform::ProcessOutput sur le mélangeur. Voir Traitement des sorties.
MFVP_MESSAGE_ENDOFSTREAM La présentation a pris fin. Voir Fin de flux.
MFVP_MESSAGE_FLUSH L’EVR vide les données dans le pipeline de rendu. Le présentateur doit abandonner toutes les images vidéo planifiées pour la présentation.
MFVP_MESSAGE_STEP Demande au présentateur de décomposer N images vers l’avant. Le présentateur doit abandonner les N-1 prochaines images et afficher l’image N. Voir Décomposition d’image.
MFVP_MESSAGE_CANCELSTEP Annule la décomposition d’image.

 

Implémentation d’IMFClockStateSink

Le présentateur doit implémenter l’interface IMFClockStateSink dans le cadre de son implémentation d’IMFVideoPresenter, qui hérite de IMFClockStateSink. L’EVR utilise cette interface pour avertir le présentateur chaque fois que l’horloge de l’EVR change d’état. Pour plus d’informations sur les états de l’horloge, voir Horloge de présentation.

Voici plusieurs instructions pour implémenter les méthodes dans cette interface. Toutes ces méthodes doivent échouer lorsque le présentateur est éteint.

Méthode Description
OnClockStart
  1. Définissez l’état du présentateur sur « Démarré ».
  2. Si llClockStartOffset n’est pas PRESENTATION_CURRENT_POSITION, videz la file d’attente des exemples du présentateur. (Cela équivaut à recevoir un message MFVP_MESSAGE_FLUSH.)
  3. Si une précédente demande de décomposition d’image est toujours en attente, traitez la demande (voir Décomposition d’image). Sinon, essayez de traiter la sortie du mélangeur (voir Traitement des sorties).
OnClockStop
  1. Définissez l’état du présentateur sur « Arrêté ».
  2. Videz la file d’attente des exemples du présentateur.
  3. Annulez toute opération de décomposition d’image.
OnClockPause Définissez l’état du présentateur sur « En pause ».
OnClockRestart Fonctionnez comme pour OnClockStart, mais ne videz pas la file d’attente des exemples.
OnClockSetRate
  1. Si la fréquence passe de zéro à une autre valeur non nulle, annulez la décomposition d’image.
  2. Enregistrez la nouvelle fréquence d’horloge. La fréquence d’horloge affecte le moment où les exemples sont présentés. Pour plus d’informations, voir Planification des exemples.

 

Implémentation d’IMFRateSupport

Pour prendre en charge les fréquences de lecture autres que la vitesse 1×, le présentateur doit implémenter l’interface IMFRateSupport. Voici plusieurs instructions pour implémenter les méthodes dans cette interface. Toutes ces méthodes doivent échouer après que le présentateur a été éteint. Pour plus d’informations sur cette interface, voir Contrôle de la fréquence.

Valeur Description
GetSlowestRate Retourne zéro pour indiquer l’absence de fréquence de lecture minimale.
GetFastestRate Pour la lecture non amincie, la fréquence de lecture ne doit pas dépasser la fréquence de rafraîchissement du moniteur : fréquence maximale = fréquence de rafraîchissement (Hz) / fréquence d’images vidéo (fps). La fréquence d’images vidéo est spécifiée dans le type de média du présentateur.
Pour la lecture amincie, la fréquence de lecture n’est pas liée. Retournez la valeur FLT_MAX. Dans la pratique, la source et le décodeur sont les facteurs de limitation pendant une lecture amincie.
Pour une lecture vers l’arrière, retournez la valeur négative de la fréquence maximale.
IsRateSupported Retournez MF_E_UNSUPPORTED_RATE si la valeur absolue de flRate dépasse la fréquence de lecture maximale du présentateur. Calculez la fréquence de lecture maximale, comme décrit pour GetFastestRate.

 

L’exemple suivant illustre l’implémentation de la méthode GetFastestRate.

float EVRCustomPresenter::GetMaxRate(BOOL bThin)
{
    // Non-thinned:
    // If we have a valid frame rate and a monitor refresh rate, the maximum
    // playback rate is equal to the refresh rate. Otherwise, the maximum rate
    // is unbounded (FLT_MAX).

    // Thinned: The maximum rate is unbounded.

    float   fMaxRate = FLT_MAX;
    MFRatio fps = { 0, 0 };
    UINT    MonitorRateHz = 0;

    if (!bThin && (m_pMediaType != NULL))
    {
        GetFrameRate(m_pMediaType, &fps);
        MonitorRateHz = m_pD3DPresentEngine->RefreshRate();

        if (fps.Denominator && fps.Numerator && MonitorRateHz)
        {
            // Max Rate = Refresh Rate / Frame Rate
            fMaxRate = (float)MulDiv(
                MonitorRateHz, fps.Denominator, fps.Numerator);
        }
    }

    return fMaxRate;
}

L’exemple précédent appelle une méthode d’assistance, GetMaxRate, pour calculer la fréquence de lecture maximale vers l’avant :

L’exemple suivant décrit comment implémenter la méthode GetFastestRate.

HRESULT EVRCustomPresenter::IsRateSupported(
    BOOL bThin,
    float fRate,
    float *pfNearestSupportedRate
    )
{
    EnterCriticalSection(&m_ObjectLock);

    float   fMaxRate = 0.0f;
    float   fNearestRate = fRate;  // If we support fRate, that is the nearest.

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

    // Find the maximum forward rate.
    // Note: We have no minimum rate (that is, we support anything down to 0).
    fMaxRate = GetMaxRate(bThin);

    if (fabsf(fRate) > fMaxRate)
    {
        // The (absolute) requested rate exceeds the maximum rate.
        hr = MF_E_UNSUPPORTED_RATE;

        // The nearest supported rate is fMaxRate.
        fNearestRate = fMaxRate;
        if (fRate < 0)
        {
            // Negative for reverse playback.
            fNearestRate = -fNearestRate;
        }
    }

    // Return the nearest supported rate.
    if (pfNearestSupportedRate != NULL)
    {
        *pfNearestSupportedRate = fNearestRate;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Envoi d’événements à l’EVR

Le présentateur doit avertir l’EVR des différents événements. Pour ce faire, il utilise l’interface IMediaEventSink de l’EVR, obtenue lorsque l’EVR appelle la méthode IMFTopologyServiceLookupClient::InitServicePointers du présentateur. (L’interface IMediaEventSink est à l’origine une interface DirectShow, mais elle est utilisée dans l’EVR DirectShow et Media Foundation.) Le code suivant décrit comment envoyer un événement à l’EVR :

    // NotifyEvent: Send an event to the EVR through its IMediaEventSink interface.
    void NotifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2)
    {
        if (m_pMediaEventSink)
        {
            m_pMediaEventSink->Notify(EventCode, Param1, Param2);
        }
    }

Le tableau suivant répertorie les événements envoyés par le présentateur, ainsi que les paramètres d’événement.

Événement Description
EC_COMPLETE Le présentateur a terminé le rendu de toutes les images à l’issue du message MFVP_MESSAGE_ENDOFSTREAM.
  • Param1 : HRESULT indiquant l’état de l’opération.
  • Param2 : inutilisé.
Pour plus d’informations, voir Fin de flux.
EC_DISPLAY_CHANGED L’appareil Direct3D a changé.
  • Param1 : inutilisé.
  • Param2 : inutilisé.
Pour plus d’informations, voir Gestion de l’appareil Direct3D.
EC_ERRORABORT Une erreur s’est produite qui nécessite l’arrêt de la diffusion en continu.
  • Param1 : HRESULT indiquant l’erreur qui s’est produite.
  • Param2 : inutilisé.
EC_PROCESSING_LATENCY Spécifie le temps nécessaire au présentateur pour effectuer le rendu de chaque image. (Facultatif.)
  • Param1 : pointeur vers une valeur LONGLONG constante qui contient la durée de traitement de l’image, en unités de 100 nanosecondes.
  • Param2 : inutilisé.
Pour plus d’informations, voir Traitement des sorties.
EC_SAMPLE_LATENCY Spécifie l’actuel décalage dans les exemples de rendu. Si la valeur est positive, les exemples sont en retard sur la planification. Si la valeur est négative, les exemples sont en avance sur la planification. (Facultatif.)
  • Param1 : pointeur vers une valeur LONGLONG constante qui contient le décalage, en unités de 100 nanosecondes.
  • Param2 : inutilisé.
EC_SCRUB_TIME Envoyé immédiatement après EC_STEP_COMPLETE si la fréquence de lecture est égale à zéro. Cet événement contient l’horodatage de l’image affichée.
  • Param1 : 32 bits inférieurs de l’horodatage.
  • Param2 : 32 bits supérieurs de l’horodatage.
Pour plus d’informations, voir Décomposition d’image.
EC_STEP_COMPLETE Le présentateur a terminé ou annulé une décomposition d’image.
- Param1 : inutilisé.
- Param2 : inutilisé.
Pour plus d’informations, voir Décomposition d’image.
Remarque : une précédente version de la documentation décrivait Param1 de manière incorrecte. Ce paramètre n’est pas utilisé pour cet événement.

 

Négociation des formats

Chaque fois que le présentateur reçoit un message MFVP_MESSAGE_INVALIDATEMEDIATYPE de l’EVR, il doit définir le format de sortie sur le mélangeur, comme suit :

  1. Appelez IMFTransform::GetOutputAvailableType sur le mélangeur pour obtenir un type de sortie possible. Ce type décrit un format que le mélangeur peut produire en fonction des flux d’entrée et des fonctionnalités de traitement vidéo de l’appareil graphique.

  2. Vérifiez si le présentateur peut utiliser ce type de média comme format de rendu. Voici certains éléments à contrôler, même si votre implémentation peut avoir ses propres exigences :

    • La vidéo doit être décompressée.
    • La vidéo doit comporter uniquement des images progressives. Vérifiez que l’attribut MF_MT_INTERLACE_MODE est égal à MFVideoInterlace_Progressive.
    • Le format doit être compatible avec l’appareil Direct3D.

    Si le type n’est pas acceptable, revenez à l’étape 1 et obtenez le prochain type proposé par le mélangeur.

  3. Créez un type de média qui soit un clone du type d’origine, puis modifiez les attributs suivants :

    • Définissez l’attribut MF_MT_FRAME_SIZE de sorte qu’il soit égal à la largeur et la hauteur souhaitées pour les surfaces Direct3D que vous allouez.
    • Définissez l’attribut MF_MT_PAN_SCAN_ENABLED sur FALSE.
    • Définissez l’attribut MF_MT_PIXEL_ASPECT_RATIO sorte qu’il soit égal à la valeur PAR de l’affichage (généralement 1:1).
    • Définissez l’ouverture géométrique (attribut MF_MT_GEOMETRIC_APERTURE) de sorte qu’elle soit égale à un rectangle dans la surface Direct3D. Lorsque le mélangeur génère une image de sortie, il réalise un blit de l’image source sur ce rectangle. L’ouverture géométrique peut être aussi grande que la surface, ou il peut s’agir d’un sous-rectangle dans la surface. Pour plus d’informations, voir Rectangles sources/de destination.
  4. Pour vérifier si le mélangeur accepte le type de sortie modifié, appelez IMFTransform::SetOutputType avec l’indicateur MFT_SET_TYPE_TEST_ONLY. Si le mélangeur rejette le type, revenez à l’étape 1 et obtenez le type suivant.

  5. Allouez un pool de surfaces Direct3D, comme décrit dans Allocation des surfaces Direct3D. Le mélangeur va utiliser ces surfaces lorsqu’il va dessiner les images vidéo composées.

  6. Définissez le type de sortie sur le mélangeur en appelant SetOutputType sans indicateur. Si le premier appel à SetOutputType a réussi à l’étape 4, la méthode doit à nouveau réussir.

Si le mélangeur n’a plus de types, la méthode GetOutputAvailableType retourne MF_E_NO_MORE_TYPES. Si le présentateur ne trouve pas de type de sortie approprié pour le mélangeur, le flux ne peut pas être rendu. Dans ce cas, DirectShow ou Media Foundation peut essayer un autre format de flux. Par conséquent, le présentateur peut recevoir plusieurs messages MFVP_MESSAGE_INVALIDATEMEDIATYPE dans une ligne jusqu’à ce qu’un type valide soit trouvé.

Le mélangeur applique automatiquement un cadre à la vidéo, en tenant compte de la valeur re proportion de pixels (PAR) de la source et de la destination. Pour de meilleurs résultats, la largeur et la hauteur de la surface ainsi que l’ouverture géométrique doivent être égales à la taille réelle dans laquelle vous souhaitez voir apparaître la vidéo sur le périphérique d’affichage. Le schéma suivant illustre ce processus.

diagram showing a composited fram leading to a direct3d surface, which leads to a window

Le code suivant décrit la structure du processus. Certaines des étapes sont intégrées aux fonctions d’assistance, dont les détails exacts dépendent des exigences du présentateur.

HRESULT EVRCustomPresenter::RenegotiateMediaType()
{
    HRESULT hr = S_OK;
    BOOL bFoundMediaType = FALSE;

    IMFMediaType *pMixerType = NULL;
    IMFMediaType *pOptimalType = NULL;
    IMFVideoMediaType *pVideoType = NULL;

    if (!m_pMixer)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Loop through all of the mixer's proposed output types.
    DWORD iTypeIndex = 0;
    while (!bFoundMediaType && (hr != MF_E_NO_MORE_TYPES))
    {
        SafeRelease(&pMixerType);
        SafeRelease(&pOptimalType);

        // Step 1. Get the next media type supported by mixer.
        hr = m_pMixer->GetOutputAvailableType(0, iTypeIndex++, &pMixerType);
        if (FAILED(hr))
        {
            break;
        }

        // From now on, if anything in this loop fails, try the next type,
        // until we succeed or the mixer runs out of types.

        // Step 2. Check if we support this media type.
        if (SUCCEEDED(hr))
        {
            // Note: None of the modifications that we make later in CreateOptimalVideoType
            // will affect the suitability of the type, at least for us. (Possibly for the mixer.)
            hr = IsMediaTypeSupported(pMixerType);
        }

        // Step 3. Adjust the mixer's type to match our requirements.
        if (SUCCEEDED(hr))
        {
            hr = CreateOptimalVideoType(pMixerType, &pOptimalType);
        }

        // Step 4. Check if the mixer will accept this media type.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, MFT_SET_TYPE_TEST_ONLY);
        }

        // Step 5. Try to set the media type on ourselves.
        if (SUCCEEDED(hr))
        {
            hr = SetMediaType(pOptimalType);
        }

        // Step 6. Set output media type on mixer.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, 0);

            assert(SUCCEEDED(hr)); // This should succeed unless the MFT lied in the previous call.

            // If something went wrong, clear the media type.
            if (FAILED(hr))
            {
                SetMediaType(NULL);
            }
        }

        if (SUCCEEDED(hr))
        {
            bFoundMediaType = TRUE;
        }
    }

    SafeRelease(&pMixerType);
    SafeRelease(&pOptimalType);
    SafeRelease(&pVideoType);

    return hr;
}

Pour plus d’informations sur les types de médias vidéo, voir Types de médias vidéo.

Gestion de l’appareil Direct3D

Le présentateur crée l’appareil Direct3D et gère toute perte d’appareil pendant la diffusion en continu. Le présentateur héberge également le gestionnaire d’appareils Direct3D, qui permet aux autres composants d’utiliser le même appareil. Par exemple, le mélangeur utilise l’appareil Direct3D pour mélanger les sous-flux, désentrelacer et procéder aux ajustements de couleur. Les décodeurs peuvent utiliser l’appareil Direct3D pour le décodage vidéo accéléré. (Pour plus d’informations sur l’accélération vidéo, voir Accélération vidéo DirectX 2.0.)

Pour configurer l’appareil Direct3D, procédez comme suit :

  1. Créez l’objet Direct3D en appelant Direct3DCreate9 ou Direct3DCreate9Ex.
  2. Créez l’appareil en appelant IDirect3D9::CreateDevice ou IDirect3D9Ex::CreateDevice.
  3. Créez le gestionnaire d’appareils en appelant DXVA2CreateDirect3DDeviceManager9.
  4. Définissez l’appareil sur le gestionnaire d’appareils en appelant IDirect3DDeviceManager9::ResetDevice.

Si un autre composant de pipeline a besoin du gestionnaire d’appareils, il appelleIMFGetService::GetService sur l’EVR, en spécifiant MR_VIDEO_ACCELERATION_SERVICE pour le GUID de service. L’EVR transmet la requête au présentateur. Une fois que l’objet a obtenu le pointeur IDirect3DDeviceManager9, il peut obtenir un descripteur sur l’appareil en appelant IDirect3DDeviceManager9::OpenDeviceHandle. Lorsque l’objet doit utiliser l’appareil, il transmet le descripteur d’appareil à la méthode IDirect3DDeviceManager9::LockDevice, qui retourne un pointeur IDirect3DDevice9.

Une fois l’appareil créé, si le présentateur détruit l’appareil et en crée un nouveau, le présentateur doit à nouveau appeler ResetDevice. La méthode ResetDevice invalide tous les descripteurs d’appareil existants, ce qui fait que LockDevice retourne DXVA2_E_NEW_VIDEO_DEVICE. Ce code d’erreur indique aux autres objets qui utilisent l’appareil qu’ils doivent ouvrir un nouveau descripteur d’appareil. Pour plus d’informations sur l’utilisation du gestionnaire d’appareils, voir Gestionnaire d’appareils Direct3D.

Le présentateur peut créer l’appareil en mode fenêtré ou en mode exclusif plein écran. Pour le mode fenêtré, vous devez apporter une solution permettant à l’application de spécifier la fenêtre vidéo. Le présentateur standard implémente la méthode IMFVideoDisplayControl::SetVideoWindow à cet effet. Vous devez créer l’appareil à la première création du présentateur. En général, vous ne connaissez pas encore tous les paramètres d’appareil, tels que la fenêtre ou le format de la mémoire tampon d’arrière-plan. Vous pouvez créer un appareil temporaire et le remplacer ultérieurement. Il vous suffit de ne pas oublier d’appeler ResetDevice sur le gestionnaire de périphériques.

Si vous créez un appareil ou si vous appelez IDirect3DDevice9::Reset ou IDirect3DDevice9Ex::ResetEx sur un appareil existant, envoyez un événement EC_DISPLAY_CHANGED à l’EVR. Cet événement avertit l’EVR qu’il doit renégocier le type de média. L’EVR ignore les paramètres d’événement de cet événement.

Allocation des surfaces Direct3D

Une fois que le présentateur a défini le type de média, il peut allouer les surfaces Direct3D, que le mélangeur va utiliser pour écrire les images vidéo. La surface doit correspondre au type de média du présentateur :

  • Le format de surface doit correspondre au sous-type de média. Par exemple, si le sous-type est MFVideoFormat_RGB24, le format de surface doit être D3DFMT_X8R8G8B8. Pour plus d’informations sur les sous-types et les formats Direct3D, voir GUID de sous-types vidéo.
  • La largeur et la hauteur de la surface doivent correspondre aux dimensions indiquées dans l’attribut MF_MT_FRAME_SIZE du type de média.

La méthode recommandée pour allouer des surfaces varie selon que le présentateur s’exécute en mode fenêtré ou plein écran.

Si l’appareil Direct3D est fenêtré, vous pouvez créer plusieurs chaînes d’échange, chacune avec une mémoire tampon d’arrière-plan unique. Cette approche vous permet de présenter chaque surface indépendamment, car la présentation d’une chaîne d’échange n’interfère pas avec les autres chaînes d’échange. Le mélangeur peut écrire les données dans une surface tandis qu’une autre surface est planifiée pour la présentation.

Tout d’abord, décidez du nombre de chaînes d’échange à créer. Un minimum de trois est recommandé. Pour chaque chaîne d’échange, procédez comme suit :

  1. Appelez IDirect3DDevice9::CreateAdditionalSwapChain pour créer la chaîne d’échange.
  2. Appelez IDirect3DSwapChain9::GetBackBuffer pour obtenir un pointeur vers la surface de la mémoire tampon d’arrière-plan de la chaîne d’échange.
  3. Appelez MFCreateVideoSampleFromSurface et transmettez un pointeur à la surface. Cette fonction retourne un pointeur vers un objet d’exemple vidéo. L’objet d’exemple vidéo implémente l’interface IMFSample et le présentateur utilise cette interface pour livrer la surface au mélangeur lorsque le présentateur appelle la méthode IMFTransform::ProcessOutput du mélangeur. Pour plus d’informations sur l’objet d’exemple vidéo, voir Exemples vidéo.
  4. Enregistrez le pointeur IMFSample dans une file d’attente. Le présentateur extrait des exemples de cette file d’attente pendant le traitement, comme décrit Traitement des sorties.
  5. Conservez une référence au pointeur IDirect3DSwapChain9 afin que la chaîne d’échange ne soit pas libérée.

En mode exclusif plein écran, l’appareil ne peut pas avoir plusieurs chaînes d’échange. Cette chaîne d’échange est créée implicitement lorsque vous créez l’appareil plein écran. La chaîne d’échange peut avoir plusieurs mémoires tampon d’arrière-plan. Malheureusement, si vous présentez toutefois une mémoire tampon d’arrière-plan pendant que vous écrivez dans une autre mémoire tampon d’arrière-plan de la même chaîne d’échange, il n’existe aucune solution facile pour coordonner les deux opérations. Cela est dû à la façon dont Direct3D implémente le retournement de surface. Lorsque vous appelez Present, le pilote graphique met à jour les pointeurs de surface dans la mémoire graphique. Si vous conservez un pointeur IDirect3DSurface9 alors que vous appelez Present, celui-ci va pointer vers d’autres mémoires tampons une fois l’appel Present retourné.

L’option la plus simple consiste à créer un exemple vidéo pour la chaîne d’échange. Si vous choisissez cette option, suivez les mêmes étapes que celles indiquées pour le mode fenêtré. La seule différence est que la file d’attente des exemples contient un seul exemple vidéo. Une autre option consiste à créer des surfaces hors écran, puis à réaliser un blit de ces surfaces dans la mémoire tampon d’arrière-plan. Les surfaces que vous créez doivent prendre en charge la méthode IDirectXVideoProcessor::VideoProcessBlt, que le mélangeur utilise pour composer les images de sortie.

Suivi des exemples

Lorsque le présentateur alloue d’abord les exemples vidéo, il les place en file d’attente. Le présentateur puise dans cette file d’attente chaque fois qu’il doit obtenir une nouvelle image du mélangeur. Une fois que le mélangeur génère l’image, le présentateur déplace l’exemple dans une deuxième file d’attente. La deuxième file d’attente est destinée aux exemples qui attendent leur heure de présentation planifiée.

Pour simplifier le suivi de l’état de chaque exemple, l’objet d’exemple vidéo implémente l’interface IMFTrackedSample. Vous pouvez utiliser cette interface comme suit :

  1. Implémentez l’interface IMFAsyncCallback dans votre présentateur.

  2. Avant de placer un exemple dans la file d’attente planifiée, interrogez l’objet d’exemple vidéo pour l’interface IMFTrackedSample.

  3. Appelez IMFTrackedSample::SetAllocator avec un pointeur vers l’interface de rappel.

  4. Lorsque l’exemple est prêt pour la présentation, supprimez-le de la file d’attente planifiée, présentez-le et libérez toutes les références à l’exemple.

  5. L’exemple invoque le rappel. (L’objet d’exemple n’est pas supprimé dans ce cas, car il contient lui-même un nombre de références jusqu’à ce que le rappel soit invoqué.)

  6. Dans le rappel, retournez l’exemple dans la file d’attente disponible.

Un présentateur n’est pas tenu d’utiliser IMFTrackedSample pour suivre des exemples. Vous pouvez implémenter la technique qui fonctionne le mieux pour votre conception. L’un des avantages d’IMFTrackedSample est que vous pouvez déplacer les fonctions de planification et de rendu du présentateur dans des objets d’assistance. Ces objets n’ont pas besoin d’un mécanisme spécial pour rappeler le présentateur lorsqu’ils publient des exemples vidéo, car l’objet d’exemple fournit ce mécanisme.

Le code suivant indique comment configurer le rappel :

HRESULT EVRCustomPresenter::TrackSample(IMFSample *pSample)
{
    IMFTrackedSample *pTracked = NULL;

    HRESULT hr = pSample->QueryInterface(IID_PPV_ARGS(&pTracked));

    if (SUCCEEDED(hr))
    {
        hr = pTracked->SetAllocator(&m_SampleFreeCB, NULL);
    }

    SafeRelease(&pTracked);
    return hr;
}

Dans le rappel, appelez IMFAsyncResult::GetObject sur l’objet de résultat asynchrone pour récupérer un pointeur vers l’exemple :

HRESULT EVRCustomPresenter::OnSampleFree(IMFAsyncResult *pResult)
{
    IUnknown *pObject = NULL;
    IMFSample *pSample = NULL;
    IUnknown *pUnk = NULL;

    // Get the sample from the async result object.
    HRESULT hr = pResult->GetObject(&pObject);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pObject->QueryInterface(IID_PPV_ARGS(&pSample));
    if (FAILED(hr))
    {
        goto done;
    }

    // If this sample was submitted for a frame-step, the frame step operation
    // is complete.

    if (m_FrameStep.state == FRAMESTEP_SCHEDULED)
    {
        // Query the sample for IUnknown and compare it to our cached value.
        hr = pSample->QueryInterface(IID_PPV_ARGS(&pUnk));
        if (FAILED(hr))
        {
            goto done;
        }

        if (m_FrameStep.pSampleNoRef == (DWORD_PTR)pUnk)
        {
            // Notify the EVR.
            hr = CompleteFrameStep(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Note: Although pObject is also an IUnknown pointer, it is not
        // guaranteed to be the exact pointer value returned through
        // QueryInterface. Therefore, the second QueryInterface call is
        // required.
    }

    /*** Begin lock ***/

    EnterCriticalSection(&m_ObjectLock);

    UINT32 token = MFGetAttributeUINT32(
        pSample, MFSamplePresenter_SampleCounter, (UINT32)-1);

    if (token == m_TokenCounter)
    {
        // Return the sample to the sample pool.
        hr = m_SamplePool.ReturnSample(pSample);
        if (SUCCEEDED(hr))
        {
            // A free sample is available. Process more data if possible.
            (void)ProcessOutputLoop();
        }
    }

    LeaveCriticalSection(&m_ObjectLock);

    /*** End lock ***/

done:
    if (FAILED(hr))
    {
        NotifyEvent(EC_ERRORABORT, hr, 0);
    }
    SafeRelease(&pObject);
    SafeRelease(&pSample);
    SafeRelease(&pUnk);
    return hr;
}

Traitement des sorties

Chaque fois que le mélangeur reçoit un nouvel exemple d’entrée, l’EVR envoie un message MFVP_MESSAGE_PROCESSINPUTNOTIFY au présentateur. Ce message indique que le mélangeur peut avoir une nouvelle image vidéo à livrer. En réponse, le présentateur appelle IMFTransform::ProcessOutput sur le mélangeur. Si la méthode réussit, le présentateur planifie l’exemple de la présentation.

Pour obtenir la sortie du mélangeur, procédez comme suit :

  1. Vérifiez l’état de l’horloge. Si l’horloge est en pause, ignorez le message MFVP_MESSAGE_PROCESSINPUTNOTIFY, sauf s’il s’agit de la première image vidéo. Si l’horloge est en cours d’exécution ou s’il s’agit de la première image vidéo, continuez.

  2. Obtenez un exemple à partir de la file d’attente des exemples disponibles. Si la file d’attente est vide, tous les exemples alloués sont actuellement planifiés pour la présentation. Dans ce cas, ignorez le message MFVP_MESSAGE_PROCESSINPUTNOTIFY pour l’instant. Lorsque l’exemple suivant est disponible, répétez les étapes répertoriées ici.

  3. (Facultatif) Si l’horloge est disponible, obtenez l’heure de l’horloge actuelle (T1) en appelant IMFClock::GetCorrelatedTime.

  4. Appelez IMFTransform::ProcessOutput sur le mélangeur. Si ProcessOutput réussit, l’exemple contient une image vidéo. Si la méthode échoue, vérifiez le code de retour. Les codes d’erreur suivants de ProcessOutput ne sont pas des échecs critiques :

    Code d’erreur Description
    MF_E_TRANSFORM_NEED_MORE_INPUT Le mélangeur a besoin d’une entrée supplémentaire pour produire une nouvelle image de sortie.
    Si vous obtenez ce code d’erreur, vérifiez si l’EVR atteint la fin du flux et répond en conséquence, comme décrit dans Fin de flux. Sinon, ignorez le message MF_E_TRANSFORM_NEED_MORE_INPUT. L’EVR en envoie un autre lorsque le mélangeur obtient d’autres entrées.
    MF_E_TRANSFORM_STREAM_CHANGE Le type de sortie du mélangeur n’est plus valide, peut-être en raison d’une modification de format en amont.
    Si vous obtenez ce code d’erreur, définissez le type de média du présentateur sur NULL. L’EVR va demander un nouveau format.
    MF_E_TRANSFORM_TYPE_NOT_SET Le mélangeur exige un nouveau type de média.
    Si vous obtenez ce code d’erreur, renégociez le type de sortie du mélangeur comme décrit dans Négociation des formats.

     

    Si ProcessOutput réussit, continuez.

  5. (Facultatif) Si l’horloge est disponible, obtenez l’heure de l’horloge actuelle (T2). La quantité de latence introduite par le mélangeur est (T2 - T1). Envoyez un événement EC_PROCESSING_LATENCY avec cette valeur à l’EVR. L’EVR utilise cette valeur pour le contrôle de la qualité. Si aucune horloge n’est disponible, il n’existe aucune raison d’envoyer l’événement EC_PROCESSING_LATENCY.

  6. (Facultatif) Interrogez l’exemple pour IMFTrackedSample et appelez IMFTrackedSample::SetAllocator comme décrit dans Suivi des exemples.

  7. Planifiez l’exemple de la présentation.

Cette séquence d’étapes peut se terminer avant que le présentateur n’ait obtenu une sortie du mélangeur. Pour vous assurer qu’aucune demande n’est abandonnée, vous devez répéter ces étapes dans les cas suivants :

  • La méthode IMFClockStateSink::OnClockStart ou IMFClockStateSink::OnClockStart est appelée. Cela englobe le cas où le mélangeur ignore l’entrée, car l’horloge est en pause (étape 1).
  • Le rappel IMFTrackedSample est invoqué. Cela englobe le cas où le mélangeur reçoit l’entrée alors que tous les exemples vidéo du présentateur sont en cours d’utilisation (étape 2).

Les exemples de code suivants décrivent ces étapes plus en détail. Le présentateur appelle la méthode ProcessInputNotify (illustrée dans l’exemple suivant) lorsqu’il obtient le message MFVP_MESSAGE_PROCESSINPUTNOTIFY.

//-----------------------------------------------------------------------------
// ProcessInputNotify
//
// Attempts to get a new output sample from the mixer.
//
// This method is called when the EVR sends an MFVP_MESSAGE_PROCESSINPUTNOTIFY
// message, which indicates that the mixer has a new input sample.
//
// Note: If there are multiple input streams, the mixer might not deliver an
// output sample for every input sample.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessInputNotify()
{
    HRESULT hr = S_OK;

    // Set the flag that says the mixer has a new sample.
    m_bSampleNotify = TRUE;

    if (m_pMediaType == NULL)
    {
        // We don't have a valid media type yet.
        hr = MF_E_TRANSFORM_TYPE_NOT_SET;
    }
    else
    {
        // Try to process an output sample.
        ProcessOutputLoop();
    }
    return hr;
}

La méthode ProcessInputNotify définit un indicateur booléen pour enregistrer le fait que le mélangeur a une nouvelle entrée. Ensuite, il appelle la méthode ProcessOutputLoop, illustrée dans l’exemple suivant. Cette méthode tente d’extraire autant d’exemples que possible à partir du mélangeur :

void EVRCustomPresenter::ProcessOutputLoop()
{
    HRESULT hr = S_OK;

    // Process as many samples as possible.
    while (hr == S_OK)
    {
        // If the mixer doesn't have a new input sample, break from the loop.
        if (!m_bSampleNotify)
        {
            hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
            break;
        }

        // Try to process a sample.
        hr = ProcessOutput();

        // NOTE: ProcessOutput can return S_FALSE to indicate it did not
        // process a sample. If so, break out of the loop.
    }

    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
    {
        // The mixer has run out of input data. Check for end-of-stream.
        CheckEndOfStream();
    }
}

La méthode ProcessOutput, illustrée dans l’exemple suivant, tente d’obtenir une image vidéo unique à partir du mélangeur. Si aucune image vidéo n’est disponible, ProcessSample retourne S_FALSE ou un code d’erreur, qui interrompt la boucle dans ProcessOutputLoop. La plupart du travail est effectué avec la méthode ProcessOutput :

//-----------------------------------------------------------------------------
// ProcessOutput
//
// Attempts to get a new output sample from the mixer.
//
// Called in two situations:
// (1) ProcessOutputLoop, if the mixer has a new input sample.
// (2) Repainting the last frame.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessOutput()
{
    assert(m_bSampleNotify || m_bRepaint);  // See note above.

    HRESULT     hr = S_OK;
    DWORD       dwStatus = 0;
    LONGLONG    mixerStartTime = 0, mixerEndTime = 0;
    MFTIME      systemTime = 0;
    BOOL        bRepaint = m_bRepaint; // Temporarily store this state flag.

    MFT_OUTPUT_DATA_BUFFER dataBuffer;
    ZeroMemory(&dataBuffer, sizeof(dataBuffer));

    IMFSample *pSample = NULL;

    // If the clock is not running, we present the first sample,
    // and then don't present any more until the clock starts.

    if ((m_RenderState != RENDER_STATE_STARTED) &&  // Not running.
         !m_bRepaint &&             // Not a repaint request.
         m_bPrerolled               // At least one sample has been presented.
         )
    {
        return S_FALSE;
    }

    // Make sure we have a pointer to the mixer.
    if (m_pMixer == NULL)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Try to get a free sample from the video sample pool.
    hr = m_SamplePool.GetSample(&pSample);
    if (hr == MF_E_SAMPLEALLOCATOR_EMPTY)
    {
        // No free samples. Try again when a sample is released.
        return S_FALSE;
    }
    else if (FAILED(hr))
    {
        return hr;
    }

    // From now on, we have a valid video sample pointer, where the mixer will
    // write the video data.
    assert(pSample != NULL);

    // (If the following assertion fires, it means we are not managing the sample pool correctly.)
    assert(MFGetAttributeUINT32(pSample, MFSamplePresenter_SampleCounter, (UINT32)-1) == m_TokenCounter);

    if (m_bRepaint)
    {
        // Repaint request. Ask the mixer for the most recent sample.
        SetDesiredSampleTime(
            pSample,
            m_scheduler.LastSampleTime(),
            m_scheduler.FrameDuration()
            );

        m_bRepaint = FALSE; // OK to clear this flag now.
    }
    else
    {
        // Not a repaint request. Clear the desired sample time; the mixer will
        // give us the next frame in the stream.
        ClearDesiredSampleTime(pSample);

        if (m_pClock)
        {
            // Latency: Record the starting time for ProcessOutput.
            (void)m_pClock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
        }
    }

    // Now we are ready to get an output sample from the mixer.
    dataBuffer.dwStreamID = 0;
    dataBuffer.pSample = pSample;
    dataBuffer.dwStatus = 0;

    hr = m_pMixer->ProcessOutput(0, 1, &dataBuffer, &dwStatus);

    if (FAILED(hr))
    {
        // Return the sample to the pool.
        HRESULT hr2 = m_SamplePool.ReturnSample(pSample);
        if (FAILED(hr2))
        {
            hr = hr2;
            goto done;
        }
        // Handle some known error codes from ProcessOutput.
        if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
        {
            // The mixer's format is not set. Negotiate a new format.
            hr = RenegotiateMediaType();
        }
        else if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
        {
            // There was a dynamic media type change. Clear our media type.
            SetMediaType(NULL);
        }
        else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
        {
            // The mixer needs more input.
            // We have to wait for the mixer to get more input.
            m_bSampleNotify = FALSE;
        }
    }
    else
    {
        // We got an output sample from the mixer.

        if (m_pClock && !bRepaint)
        {
            // Latency: Record the ending time for the ProcessOutput operation,
            // and notify the EVR of the latency.

            (void)m_pClock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);

            LONGLONG latencyTime = mixerEndTime - mixerStartTime;
            NotifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latencyTime, 0);
        }

        // Set up notification for when the sample is released.
        hr = TrackSample(pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Schedule the sample.
        if ((m_FrameStep.state == FRAMESTEP_NONE) || bRepaint)
        {
            hr = DeliverSample(pSample, bRepaint);
            if (FAILED(hr))
            {
                goto done;
            }
        }
        else
        {
            // We are frame-stepping (and this is not a repaint request).
            hr = DeliverFrameStepSample(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        m_bPrerolled = TRUE; // We have presented at least one sample now.
    }

done:
    SafeRelease(&pSample);

    // Important: Release any events returned from the ProcessOutput method.
    SafeRelease(&dataBuffer.pEvents);
    return hr;
}

Voici plusieurs remarques concernant cet exemple :

  • La variable m_SamplePool est supposée être un objet de collection qui contient la file d’attente des exemples vidéo disponibles. La méthode GetSample de l’objet retourne MF_E_SAMPLEALLOCATOR_EMPTY si la file d’attente est vide.
  • Si la méthode ProcessOutput du mélangeur retourne MF_E_TRANSFORM_NEED_MORE_INPUT, le mélangeur ne peut pas produire plus de sorties, de sorte que le présentateur efface l’indicateur m_fSampleNotify.
  • La méthode TrackSample, qui définit le rappel IMFTrackedSample, s’affiche dans la section Suivi des exemples.

Repeinture des images

Parfois, le présentateur peut avoir besoin de repeindre l’image vidéo la plus récente. Par exemple, le présentateur standard repeint l’image dans les situations suivantes :

Procédez comme suit pour demander au mélangeur de recréer l’image la plus récente :

  1. Obtenez un exemple vidéo depuis la file d’attente.
  2. Interrogez l’exemple pour l’interface IMFDesiredSample.
  3. Appelez IMFDesiredSample::SetDesiredSampleTimeAndDuration. Spécifiez l’horodatage de l’image vidéo la plus récente. (Vous devez mettre en cache cette valeur et la mettre à jour pour chaque image.)
  4. Appelez ProcessOutput sur le mélangeur.

Lorsque vous repeignez une image, vous pouvez ignorer l’horloge de présentation et présenter immédiatement l’image.

Planification des exemples

Les images vidéo peuvent arriver dans l’EVR à tout moment. Le présentateur est chargé de présenter chaque image au bon moment, en fonction de l’horodatage de l’image. Lorsque le présentateur obtient un nouvel exemple du mélangeur, il place l’exemple dans la file d’attente planifiée. Sur un thread distinct, le présentateur obtient en continu le premier exemple à partir de la tête de la file d’attente et détermine s’il faut :

  • Présenter l’exemple.
  • Conserver l’exemple dans la file d’attente parce qu’il est trop tôt.
  • Abandonner l’exemple parce qu’il est trop tard. Même s’il vaut mieux éviter d’abandonner une image, vous devrez peut-être le faire si le présentateur prend continuellement du retard.

Pour obtenir l’horodatage d’une image vidéo, appelez IMFSample::GetSampleTime sur l’exemple vidéo. L’horodatage dépend de l’horloge de présentation de l’EVR. Pour obtenir l’heure réelle de l’horloge, appelez IMFClock::GetCorrelatedTime. Si l’EVR n’a pas d’horloge de présentation ou si un exemple n’a pas d’horodatage, vous pouvez présenter l’exemple immédiatement après l’avoir obtenu.

Pour obtenir la durée de chaque exemple, appelez IMFSample::GetSampleDuration. Si l’exemple n’a pas de durée, vous pouvez utiliser la fonction MFFrameRateToAverageTimePerFrame pour calculer la durée à partir de la fréquence d’images.

Lorsque vous planifiez des exemples, gardez à l’esprit les points suivants :

  • Si la fréquence de lecture est plus rapide (ou plus lente) que la vitesse normale, l’horloge s’exécute à un rythme plus rapide (ou plus lent). Autrement dit, l’horodatage sur un exemple donne toujours l’heure cible correcte par rapport à l’horloge de présentation. Toutefois, si vous traduisez une heure de présentation en une autre heure (comme celle du compteur de performances haute résolution), vous devez mettre à l’échelle les heures au regard de la vitesse de l’horloge. Si la vitesse de l’horloge change, l’EVR appelle la méthode IMFClockStateSink::OnClockSetRate du présentateur.
  • La fréquence de lecture peut être négative pour la lecture vers l’arrière. Lorsque la fréquence de lecture est négative, l’horloge de présentation s’exécute vers l’arrière. En d’autres termes, l’heure N + 1 se produit avant l’heure N.

L’exemple suivant calcule le début ou la fin d’un exemple, par rapport à l’horloge de présentation :

    LONGLONG hnsPresentationTime = 0;
    LONGLONG hnsTimeNow = 0;
    MFTIME   hnsSystemTime = 0;

    BOOL bPresentNow = TRUE;
    LONG lNextSleep = 0;

    if (m_pClock)
    {
        // Get the sample's time stamp. It is valid for a sample to
        // have no time stamp.
        hr = pSample->GetSampleTime(&hnsPresentationTime);

        // Get the clock time. (But if the sample does not have a time stamp,
        // we don't need the clock time.)
        if (SUCCEEDED(hr))
        {
            hr = m_pClock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
        }

        // Calculate the time until the sample's presentation time.
        // A negative value means the sample is late.
        LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
        if (m_fRate < 0)
        {
            // For reverse playback, the clock runs backward. Therefore, the
            // delta is reversed.
            hnsDelta = - hnsDelta;
        }

L’horloge de présentation est généralement pilotée par l’horloge système ou le convertisseur audio. (Le convertisseur audio détermine l’heure à partir de la fréquence à laquelle la carte audio consomme l’audio.) En général, l’horloge de présentation n’est pas synchronisée sur la fréquence de rafraîchissement du moniteur.

Si vos paramètres de présentation Direct3D spécifient D3DPRESENT_INTERVAL_DEFAULT ou D3DPRESENT_INTERVAL_ONE pour l’intervalle de présentation, l’opération Present attend le retracement vertical du moniteur. Cette solution simple permet d’éviter la déchirure, mais réduit la précision de l’algorithme de planification. À l’inverse, si l’intervalle de présentation est D3DPRESENT_INTERVAL_IMMEDIATE, la méthode Present s’exécute immédiatement, ce qui provoque la déchirure, sauf si l’algorithme de planification est suffisamment précis que l’appel de Present s’effectue uniquement pendant la période de retracement vertical.

Les fonctions suivantes peuvent vous aider à obtenir des informations précises sur la synchronisation :

  • IDirect3DDevice9::GetRasterStatus retourne des informations sur le raster, y compris sur la ligne d’analyse actuelle et sur la position du raster (dans la période de vide vertical ou non).
  • DwmGetCompositionTimingInfo retourne des informations sur la synchronisation pour le gestionnaire de fenêtrage. Ces informations sont utiles si la composition du bureau est activée.

Présentation des exemples

Cette section suppose d’avoir créé une chaîne d’échange distincte pour chaque surface, comme décrit dans Allocation des surfaces Direct3D. Pour présenter un exemple, obtenez la chaîne d’échange depuis l’exemple vidéo comme suit :

  1. Appelez IMFSample::GetBufferByIndex sur l’exemple vidéo pour obtenir la mémoire tampon.
  2. Interrogez la mémoire tampon pour l’interface IMFGetService.
  3. Appelez IMFGetService::GetService pour obtenir l’interface IDirect3DSurface9 de la surface Direct3D. (Vous pouvez regrouper cette étape avec la précédente en appelant MFGetService.)
  4. Appelez IDirect3DSurface9::GetContainer sur la surface pour obtenir un pointeur vers la chaîne d’échange.
  5. Appelez IDirect3DSwapChain9::Present sur la chaîne d’échange.

Le code suivant illustre ces étapes :

HRESULT D3DPresentEngine::PresentSample(IMFSample* pSample, LONGLONG llTarget)
{
    HRESULT hr = S_OK;

    IMFMediaBuffer* pBuffer = NULL;
    IDirect3DSurface9* pSurface = NULL;
    IDirect3DSwapChain9* pSwapChain = NULL;

    if (pSample)
    {
        // Get the buffer from the sample.
        hr = pSample->GetBufferByIndex(0, &pBuffer);
        if (FAILED(hr))
        {
            goto done;
        }

        // Get the surface from the buffer.
        hr = MFGetService(pBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pSurface));
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (m_pSurfaceRepaint)
    {
        // Redraw from the last surface.
        pSurface = m_pSurfaceRepaint;
        pSurface->AddRef();
    }

    if (pSurface)
    {
        // Get the swap chain from the surface.
        hr = pSurface->GetContainer(IID_PPV_ARGS(&pSwapChain));
        if (FAILED(hr))
        {
            goto done;
        }

        // Present the swap chain.
        hr = PresentSwapChain(pSwapChain, pSurface);
        if (FAILED(hr))
        {
            goto done;
        }

        // Store this pointer in case we need to repaint the surface.
        CopyComPointer(m_pSurfaceRepaint, pSurface);
    }
    else
    {
        // No surface. All we can do is paint a black rectangle.
        PaintFrameWithGDI();
    }

done:
    SafeRelease(&pSwapChain);
    SafeRelease(&pSurface);
    SafeRelease(&pBuffer);

    if (FAILED(hr))
    {
        if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG)
        {
            // We failed because the device was lost. Fill the destination rectangle.
            PaintFrameWithGDI();

            // Ignore. We need to reset or re-create the device, but this method
            // is probably being called from the scheduler thread, which is not the
            // same thread that created the device. The Reset(Ex) method must be
            // called from the thread that created the device.

            // The presenter will detect the state when it calls CheckDeviceState()
            // on the next sample.
            hr = S_OK;
        }
    }
    return hr;
}

Rectangles sources/de destination

Le rectangle source est la partie de l’image vidéo à afficher. Il est défini par rapport à un système de coordonnées normalisé, dans lequel l’image vidéo entière occupe un rectangle avec des coordonnées {0, 0, 1, 1}. Le rectangle de destination est la zone de la surface de destination sur laquelle l’image vidéo est représentée. Le présentateur standard permet à une application de définir ces rectangles en appelant IMFVideoDisplayControl::SetVideoPosition.

Plusieurs options permettent d’appliquer les rectangles sources/de destination. La première option consiste à laisser le mélangeur s’en charger :

  • Définissez le rectangle source avec l’attribut VIDEO_ZOOM_RECT. Le mélangeur applique le rectangle source lorsqu’il réalise un blit de la vidéo sur la surface de destination. Par défaut, le rectangle source du mélangeur est l’ensemble de l’image.
  • Définissez le rectangle de destination comme ouverture géométrique dans le type de sortie du mélangeur. Pour plus d’informations, voir Négociation des formats.

La deuxième option consiste à appliquer les rectangles lorsque vous appelez IDirect3DSwapChain9::Present en spécifiant les paramètres pSourceRect et pDestRect dans la méthode Present. Vous pouvez combiner ces options. Par exemple, vous pouvez définir le rectangle source sur le mélangeur, mais appliquer le rectangle de destination dans la méthode Present.

Si l’application modifie le rectangle de destination ou redimensionne la fenêtre, vous aurez peut-être besoin d’allouer de nouvelles surfaces. Dans ce cas, vous devez vous montrer prudent au moment de synchroniser l’opération sur le thread de planification. Videz la file d’attente de planification et abandonnez les anciens exemples avant d’allouer de nouvelles surfaces.

Fin de flux

Lorsque chaque flux d’entrée sur l’EVR se termine, l’EVR envoie un message MFVP_MESSAGE_ENDOFSTREAM au présentateur. Toutefois, lorsque vous recevez le message, il peut subsister quelques images vidéo à traiter. Avant de répondre au message de fin de flux, vous devez vider toutes les sorties du mélangeur et présenter toutes les images restantes. Une fois la dernière image présentée, envoyez un événement EC_COMPLETE à l’EVR.

L’exemple suivant représente une méthode qui envoie l’événement EC_COMPLETE lorsque plusieurs conditions sont remplies. Sinon, elle retourne « S_OK » sans envoyer l’événement :

HRESULT EVRCustomPresenter::CheckEndOfStream()
{
    if (!m_bEndStreaming)
    {
        // The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message.
        return S_OK;
    }

    if (m_bSampleNotify)
    {
        // The mixer still has input.
        return S_OK;
    }

    if (m_SamplePool.AreSamplesPending())
    {
        // Samples are still scheduled for rendering.
        return S_OK;
    }

    // Everything is complete. Now we can tell the EVR that we are done.
    NotifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
    m_bEndStreaming = FALSE;
    return S_OK;
}

Cette méthode vérifie les états suivants :

  • Si la variable m_fSampleNotify a la valeur TRUE, le mélangeur a une ou plusieurs images qui n’ont pas encore été traitées. (Pour plus d’informations, voir Traitement des sorties.)
  • La variable m_fEndStreaming est un indicateur booléen dont la valeur initiale est FALSE. Le présentateur définit l’indicateur sur TRUE lorsque l’EVR envoie le message MFVP_MESSAGE_ENDOFSTREAM.
  • La méthode AreSamplesPending est supposée retourner TRUE tant qu’une ou plusieurs images attendent dans la file d’attente planifiée.

Dans la méthode IMFVideoPresenter::ProcessMessage, définissez m_fEndStreaming sur TRUE et appelez CheckEndOfStream lorsque l’EVR envoie le message MFVP_MESSAGE_ENDOFSTREAM :

HRESULT EVRCustomPresenter::ProcessMessage(
    MFVP_MESSAGE_TYPE eMessage,
    ULONG_PTR ulParam
    )
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_ObjectLock);

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

    switch (eMessage)
    {
    // Flush all pending samples.
    case MFVP_MESSAGE_FLUSH:
        hr = Flush();
        break;

    // Renegotiate the media type with the mixer.
    case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
        hr = RenegotiateMediaType();
        break;

    // The mixer received a new input sample.
    case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
        hr = ProcessInputNotify();
        break;

    // Streaming is about to start.
    case MFVP_MESSAGE_BEGINSTREAMING:
        hr = BeginStreaming();
        break;

    // Streaming has ended. (The EVR has stopped.)
    case MFVP_MESSAGE_ENDSTREAMING:
        hr = EndStreaming();
        break;

    // All input streams have ended.
    case MFVP_MESSAGE_ENDOFSTREAM:
        // Set the EOS flag.
        m_bEndStreaming = TRUE;
        // Check if it's time to send the EC_COMPLETE event to the EVR.
        hr = CheckEndOfStream();
        break;

    // Frame-stepping is starting.
    case MFVP_MESSAGE_STEP:
        hr = PrepareFrameStep(LODWORD(ulParam));
        break;

    // Cancels frame-stepping.
    case MFVP_MESSAGE_CANCELSTEP:
        hr = CancelFrameStep();
        break;

    default:
        hr = E_INVALIDARG; // Unknown message. This case should never occur.
        break;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

En outre, appelez CheckEndOfStream si la méthode IMFTransform::ProcessOutput du mélangeur retourne MF_E_TRANSFORM_NEED_MORE_INPUT. Ce code d’erreur indique que le mélangeur n’a plus d’exemples d’entrée (voir Traitement des sorties).

Décomposition d’image

L’EVR est conçu pour prendre en charge la décomposition d’image dans DirectShow et le nettoyage dans Media Foundation. La décomposition d’image et le nettoyage d’image sont deux concepts similaires. Dans les deux cas, l’application demande une image vidéo à la fois. En interne, le présentateur utilise le même mécanisme pour implémenter les deux fonctionnalités.

La décomposition d’image dans DirectShow fonctionne comme suit :

  • L’application appelle IVideoFrameStep::Step. Le nombre d’étapes est indiqué par le paramètre dwSteps. L’EVR envoie un message MFVP_MESSAGE_STEP au présentateur, dans lequel le paramètre de message (ulParam) correspond au nombre d’étapes.
  • Si l’application appelle IVideoFrameStep::CancelStep ou modifie l’état du graphique (en cours d’exécution, en pause ou arrêté), l’EVR envoie un message MFVP_MESSAGE_CANCELSTEP.

Le nettoyage dans Media Foundation fonctionne comme suit :

  • L’application définit la fréquence de lecture sur zéro en appelant IMFRateControl::SetRate.
  • Pour afficher une nouvelle image, l’application appelle IMFMediaSession::Start avec la position souhaitée. L’EVR envoie un message MFVP_MESSAGE_STEP avec ulParam égal à 1.
  • Pour arrêter le nettoyage, l’application définit la fréquence de lecture sur une valeur non nulle. L’EVR envoie le message MFVP_MESSAGE_CANCELSTEP.

Après avoir reçu le message MFVP_MESSAGE_STEP, le présentateur attend que l’image cible arrive. Si le nombre d’étapes est N, le présentateur abandonne les exemples suivants (N - 1) et présente le Nᵉ exemple. Lorsque le présentateur termine la décomposition d’image, il envoie un événement EC_STEP_COMPLETE à l’EVR avec lParam1 défini sur FALSE. En outre, si la fréquence de lecture est égale à zéro, le présentateur envoie un événement EC_SCRUB_TIME. Si l’EVR annule la décomposition d’image pendant qu’une opération de décomposition d’image est toujours en attente, le présentateur envoie un événement EC_STEP_COMPLETE avec lParam1 défini sur TRUE.

L’application peut effectuer une décomposition ou un nettoyage d’image à plusieurs reprises, de sorte que le présentateur peut recevoir plusieurs messages MFVP_MESSAGE_STEP avant d’obtenir un message MFVP_MESSAGE_CANCELSTEP. En outre, le présentateur peut recevoir le message MFVP_MESSAGE_STEP avant le démarrage de l’horloge ou pendant son exécution.

Implémentation de la décomposition d’image

Cette section décrit un algorithme pour implémenter la décomposition d’image. L’algorithme de décomposition d’image utilise les variables suivantes :

  • step_count. Entier non signé qui spécifie le nombre d’étapes de l’opération de décomposition d’image.

  • step_queue. File d’attente des pointeurs IMFSample.

  • step_state. À tout moment, le présentateur peut se trouver dans l’un des états suivants concernant la décomposition d’image :

    State Description
    PAS DE DÉCOMPOSITION Aucune décomposition d’image.
    EN ATTENTE Le présentateur a reçu le message MFVP_MESSAGE_STEP, mais l’horloge n’a pas démarré.
    PENDING Le présentateur a reçu le message MFVP_MESSAGE_STEP et l’horloge a démarré, mais le présentateur attend de recevoir l’image cible.
    PLANIFIÉ Le présentateur a reçu l’image cible et l’a planifiée pour la présentation, mais l’image n’a pas été présentée.
    TERMINÉ Le présentateur a présenté l’image cible, envoyé l’événement EC_STEP_COMPLETE et attend le message MFVP_MESSAGE_STEP ou MFVP_MESSAGE_CANCELSTEP suivant.

     

    Ces états sont indépendants des états du présentateur répertoriés dans la section États du présentateur.

Les procédures suivantes sont définies pour l’algorithme de décomposition d’image :

Procédure PrepareFrameStep

  1. Incrémentez step_count.
  2. Définissez step_state sur « EN ATTENTE ».
  3. Si l’horloge est en cours d’exécution, appelez « StartFrameStep ».

Procédure StartFrameStep

  1. Si step_state est égal à « EN ATTENTE », définissez step_state sur « EN SUSPENS ». Pour chaque exemple de step_queue, appelez « DeliverFrameStepSample ».
  2. Si step_state est égal à « PAS DE DÉCOMPOSITION », supprimez les exemples de step_queue et planifiez-les pour la présentation.

Procédure CompleteFrameStep

  1. Définissez step_state sur « TERMINÉ ».
  2. Envoyez l’événement « EC_STEP_COMPLETE » avec lParam1 = FALSE.
  3. Si la fréquence d’horloge est égale à zéro, envoyez l’événement EC_SCRUB_TIME avec l’heure de l’exemple.

Procédure DeliverFrameStepSample

  1. Si la fréquence d’horloge est égale à zéro et que l’heure de l’exemple + la durée de l’exemple<l’heure de l’horloge, abandonnez l’exemple. Quitter.
  2. Si step_state est égal à « PLANIFIÉ » ou « TERMINÉ », ajoutez l’exemple à step_queue. Quitter.
  3. Décrémentez step_count.
  4. Si step_count> 0, abandonnez l’exemple. Quitter.
  5. Si step_state est égal à « EN ATTENTE », ajoutez l’exemple à step_queue. Quitter.
  6. Planifiez l’exemple de la présentation.
  7. Définissez step_state sur « PLANIFIÉ ».

Procédure CancelFrameStep

  1. Définissez step_state sur « PAS DE DÉCOMPOSITION ».
  2. Réinitialisez step_count sur zéro.
  3. Si la précédente valeur de step_state était « EN ATTENTE », « EN SUSPENS » ou «PLANIFIÉ », envoyez « EC_STEP_COMPLETE » avec lParam1 = TRUE.

Appelez ces procédures comme suit :

Message ou méthode du présentateur Procédure
Message MFVP_MESSAGE_STEP PrepareFrameStep
Message MFVP_MESSAGE_STEP CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
Rappel IMFTrackedSample CompleteFrameStep
IMFClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

Le graphique de flux suivant présente les procédures de décomposition d’image.

flow chart showing paths that start with mfvp-message-step and mfvp-message-processinputnotify and end at

Configuration du présentateur sur l’EVR

Après avoir implémenté le présentateur, l’étape suivante consiste à configurer l’EVR pour l’utiliser.

Configuration du présentateur dans DirectShow

Dans une application DirectShow, définissez le présentateur sur l’EVR comme suit :

  1. Créez le filtre de l’EVR en appelant CoCreateInstance. Le CLSID est CLSID_EnhancedVideoRenderer.
  2. Ajoutez l’EVR au graphique de filtre.
  3. Créez une instance du présentateur. Le présentateur peut prendre en charge la création d’objets COM standard via IClassFactory, mais ce n’est pas obligatoire.
  4. Interrogez le filtre de l’EVR pour l’interface IMFVideoRenderer.
  5. Appelez IMFVideoRenderer::InitializeRenderer.

Configuration du présentateur dans Media Foundation

Dans Media Foundation, plusieurs options s’offrent à vous (selon que vous créez le récepteur multimédia de l’EVR ou l’objet d’activation de l’EVR). Pour plus d’informations sur les objets d’activation, voir Objets d’activation.

Pour le récepteur multimédia de l’EVR, procédez comme suit :

  1. Appelez MFCreateVideoRenderer pour créer le récepteur multimédia.
  2. Créez une instance du présentateur.
  3. Interrogez le récepteur multimédia de l’EVR pour l’interface IMFVideoRenderer.
  4. Appelez IMFVideoRenderer::InitializeRenderer.

Pour l’objet d’activation de l’EVR, procédez comme suit :

  1. Appelez MFCreateVideoRendererActivate pour créer l’objet d’activation.

  2. Définissez l’un des attributs suivants sur l’objet d’activation :

    Attribut Description
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Pointeur vers un objet d’activation pour le présentateur.
    Avec cet indicateur, vous devez fournir un objet d’activation pour le présentateur. L’objet d’activation doit implémenter l’interface IMFActivate.
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID du présentateur.
    Avec cet indicateur, votre présentateur doit prendre en charge la création d’objets COM standard avec IClassFactory.

     

  3. Si vous le souhaitez, définissez l’attribut MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS sur l’objet d’activation.

Convertisseur vidéo amélioré

Exemple EVRPresenter