Partager via


Prise en charge de la recherche dans un filtre source

[La fonctionnalité associée à cette page, DirectShow, est une fonctionnalité héritée. Il a été remplacé par MediaPlayer, IMFMediaEngine et Audio/Video Capture dans Media Foundation. Ces fonctionnalités ont été optimisées pour Windows 10 et Windows 11. Microsoft recommande vivement au nouveau code d’utiliser MediaPlayer, IMFMediaEngine et La capture audio/vidéo dans Media Foundation au lieu de DirectShow, lorsque cela est possible. Microsoft suggère que le code existant qui utilise les API héritées soit réécrit pour utiliser les nouvelles API si possible.]

Cette rubrique explique comment implémenter la recherche dans un filtre source Microsoft DirectShow. Il utilise l’exemple de filtre à billes comme point de départ et décrit le code supplémentaire nécessaire pour prendre en charge la recherche dans ce filtre.

L’exemple de filtre à billes est un filtre source qui crée une boule de rebond animée. Cet article explique comment ajouter des fonctionnalités de recherche à ce filtre. Une fois que vous avez ajouté cette fonctionnalité, vous pouvez afficher le filtre dans GraphEdit et contrôler la boule en faisant glisser le curseur GraphEdit.

Cette rubrique contient les sections suivantes :

Vue d’ensemble de la recherche dans DirectShow

Une application recherche le graphe de filtre en appelant une méthode IMediaSeeking sur le Gestionnaire de graphes de filtre. Le Gestionnaire de graphiques de filtre distribue ensuite l’appel à chaque renderer du graphe. Chaque convertisseur envoie le amont d’appel via la broche de sortie du filtre amont suivant. L’appel se déplace amont jusqu’à atteindre un filtre qui peut exécuter la commande seek, généralement un filtre source ou un filtre d’analyseur. En général, le filtre à l’origine des horodatages gère également la recherche.

Un filtre répond à une commande seek comme suit :

  1. Le filtre vide le graphe. Cela efface toutes les données obsolètes du graphique, ce qui améliore la réactivité. Sinon, les exemples qui ont été mis en mémoire tampon avant la commande seek peuvent être remis.
  2. Le filtre appelle IPin::NewSegment pour informer les filtres en aval de l’heure d’arrêt, de l’heure de début et de la vitesse de lecture.
  3. Le filtre définit ensuite l’indicateur de discontinuité sur le premier exemple après la commande seek.

Les horodatages commencent à zéro après toute commande de recherche (y compris les modifications de débit).

Vue d’ensemble rapide du filtre à billes

Le filtre Ball est une source push, ce qui signifie qu’il utilise un thread worker pour fournir des échantillons en aval, par opposition à une source d’extraction, qui attend passivement qu’un filtre en aval demande des exemples. Le filtre Ball est généré à partir de la classe CSource et sa broche de sortie est générée à partir de la classe CSourceStream . La classe CSourceStream crée le thread de travail qui pilote le flux de données. Ce thread entre dans une boucle qui obtient des exemples à partir de l’allocateur, les remplit de données et les remet en aval.

La plupart de l’action dans CSourceStream se produit dans la méthode CSourceStream::FillBuffer , que la classe dérivée implémente. L’argument de cette méthode est un pointeur vers l’exemple à remettre. L’implémentation de FillBuffer par le filtre Ball récupère l’adresse de l’exemple de mémoire tampon et dessine directement dans la mémoire tampon en définissant des valeurs de pixels individuelles. (Le dessin est effectué par une classe d’assistance, CBall, de sorte que vous pouvez ignorer les détails concernant les profondeurs de bits, les palettes, etc.)

Modification du filtre à billes pour la recherche

Pour que le filtre Ball soit recherché, utilisez la classe CSourceSeeking , qui est conçue pour implémenter la recherche dans des filtres avec une broche de sortie. Ajoutez la classe CSourceSeeking à la liste d’héritage de la classe CBallStream :

class CBallStream :  // Defines the output pin.
    public CSourceStream, public CSourceSeeking

En outre, vous devez ajouter un initialiseur pour CSourceSeeking au constructeur CBallStream :

    CSourceSeeking(NAME("SeekBall"), (IPin*)this, phr, &m_cSharedState),

Cette instruction appelle le constructeur de base pour CSourceSeeking. Les paramètres sont un nom, un pointeur vers la broche propriétaire, une valeur HRESULT et l’adresse d’un objet de section critique. Le nom est utilisé uniquement pour le débogage, et la macro NAME est compilée dans une chaîne vide dans les builds de vente au détail. La broche hérite directement de CSourceSeeking, de sorte que le deuxième paramètre est un pointeur vers lui-même, cast vers un pointeur IPin . La valeur HRESULT est ignorée dans la version actuelle des classes de base ; il reste pour la compatibilité avec les versions précédentes. La section critique protège les données partagées, telles que l’heure de début, l’heure d’arrêt et la vitesse de lecture actuelles.

Ajoutez l’instruction suivante à l’intérieur du constructeur CSourceSeeking :

m_rtStop = 60 * UNITS;

La variable m_rtStop spécifie l’heure d’arrêt. Par défaut, la valeur est _I64_MAX/2, soit environ 14 600 ans. L’instruction précédente la définit sur une durée plus conservatrice de 60 secondes.

Deux variables membres supplémentaires doivent être ajoutées à CBallStream :

BOOL            m_bDiscontinuity; // If true, set the discontinuity flag.
REFERENCE_TIME  m_rtBallPosition; // Position of the ball. 

Après chaque commande de recherche, le filtre doit définir l’indicateur de discontinuité sur l’exemple suivant en appelant IMediaSample::SetDiscontinuity. La variable m_bDiscontinuity en fait le suivi. La variable m_rtBallPosition spécifie la position de la balle dans le cadre vidéo. Le filtre Ball d’origine calcule la position à partir de la durée du flux, mais le temps de flux est réinitialisé à zéro après chaque commande de recherche. Dans un flux recherché, la position absolue est indépendante de la durée du flux.

QueryInterface

La classe CSourceSeeking implémente l’interface IMediaSeeking . Pour exposer cette interface aux clients, remplacez la méthode NonDelegatingQueryInterface :

STDMETHODIMP CBallStream::NonDelegatingQueryInterface
    (REFIID riid, void **ppv)
{
    if( riid == IID_IMediaSeeking ) 
    {
        return CSourceSeeking::NonDelegatingQueryInterface( riid, ppv );
    }
    return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}

La méthode est appelée « NonDelegating » en raison de la façon dont les classes de base DirectShow prennent en charge l’agrégation COM (Component Object Model). Pour plus d’informations, consultez la rubrique « Comment implémenter IUnknown » dans le Kit de développement logiciel (SDK) DirectShow.

Recherche de méthodes

La classe CSourceSeeking gère plusieurs variables membres relatives à la recherche.

Variable Description Valeur par défaut
m_rtStart Heure de début Zéro
m_rtStop Heure d’arrêt _I64_MAX / 2
m_dRateSeeking Taux de lecture 1.0

 

L’implémentation CSourceSeekingd’IMediaSeeking::SetPositions met à jour les heures de début et d’arrêt, puis appelle deux méthodes virtuelles pures sur la classe dérivée, CSourceSeeking::ChangeStart et CSourceSeeking::ChangeStop. L’implémentation de IMediaSeeking::SetRate est similaire : elle met à jour le taux de lecture, puis appelle la méthode virtuelle pure CSourceSeeking::ChangeRate. Dans chacune de ces méthodes virtuelles, le code pin doit effectuer les opérations suivantes :

  1. Appelez IPin::BeginFlush pour commencer à vider les données.
  2. Arrêtez le thread de diffusion en continu.
  3. Appelez IPin::EndFlush.
  4. Redémarrez le thread de streaming.
  5. Appelez IPin::NewSegment.
  6. Définissez l’indicateur de discontinuité sur l’exemple suivant.

L’ordre de ces étapes est crucial, car le thread de streaming peut se bloquer pendant qu’il attend de remettre un exemple ou d’obtenir un nouvel exemple. La méthode BeginFlush garantit que le thread de diffusion en continu n’est pas bloqué et ne sera donc pas bloqué à l’étape 2. L’appel EndFlush informe les filtres en aval de s’attendre à de nouveaux exemples, afin qu’ils ne les rejettent pas lorsque le thread redémarre à l’étape 4.

La méthode privée suivante effectue les étapes 1 à 4 :

void CBallStream::UpdateFromSeek()
{
    if (ThreadExists()) 
    {
        DeliverBeginFlush();
        // Shut down the thread and stop pushing data.
        Stop();
        DeliverEndFlush();
        // Restart the thread and start pushing data again.
        Pause();
    }
}

Lorsque le thread de streaming redémarre, il appelle la méthode CSourceStream::OnThreadStartPlay . Remplacez cette méthode pour effectuer les étapes 5 et 6 :

HRESULT CBallStream::OnThreadStartPlay()
{
    m_bDiscontinuity = TRUE;
    return DeliverNewSegment(m_rtStart, m_rtStop, m_dRateSeeking);
}

Dans la méthode ChangeStart , définissez l’heure de flux sur zéro et la position de la balle sur la nouvelle heure de début. Appelez ensuite CBallStream::UpdateFromSeek :

HRESULT CBallStream::ChangeStart( )
{
    {
        CAutoLock lock(CSourceSeeking::m_pLock);
        m_rtSampleTime = 0;
        m_rtBallPosition = m_rtStart;
    }
    UpdateFromSeek();
    return S_OK;
}

Dans la méthode ChangeStop , appelez CBallStream::UpdateFromSeek si la nouvelle heure d’arrêt est inférieure à la position actuelle. Sinon, l’heure d’arrêt étant toujours à l’avenir, il n’est pas nécessaire de vider le graphique.

HRESULT CBallStream::ChangeStop( )
{
    {
        CAutoLock lock(CSourceSeeking::m_pLock);
        if (m_rtBallPosition < m_rtStop)
        {
            return S_OK;
        }
    }

    // We're already past the new stop time. Flush the graph.
    UpdateFromSeek();
    return S_OK;
}

Pour les modifications de débit, la méthode CSourceSeeking::SetRate définit m_dRateSeeking au nouveau taux (en ignorant l’ancienne valeur) avant d’appeler ChangeRate. Malheureusement, si l’appelant a donné un taux non valide(par exemple, inférieur à zéro), il est trop tard au moment où ChangeRate est appelé. Une solution consiste à remplacer SetRate et case activée pour les tarifs valides :

HRESULT CBallStream::SetRate(double dRate)
{
    if (dRate <= 1.0)
    {
        return E_INVALIDARG;
    }
    {
        CAutoLock lock(CSourceSeeking::m_pLock);
        m_dRateSeeking = dRate;
    }
    UpdateFromSeek();
    return S_OK;
}
// Now ChangeRate won't ever be called, but it's pure virtual, so it needs
// a dummy implementation.
HRESULT CBallStream::ChangeRate() { return S_OK; }

Dessin dans la mémoire tampon

Voici la version modifiée de CSourceStream::FillBuffer, la routine qui dessine la balle sur chaque frame :

HRESULT CBallStream::FillBuffer(IMediaSample *pMediaSample)
{
    BYTE *pData;
    long lDataLen;
    pMediaSample->GetPointer(&pData);
    lDataLen = pMediaSample->GetSize();
    {
        CAutoLock cAutoLockShared(&m_cSharedState);
        if (m_rtBallPosition >= m_rtStop) 
        {
            // End of the stream.
            return S_FALSE;
        }
        // Draw the ball in its current position.
        ZeroMemory( pData, lDataLen );
        m_Ball->MoveBall(m_rtBallPosition);
        m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize);
        
        // The sample times are modified by the current rate.
        REFERENCE_TIME rtStart, rtStop;
        rtStart = static_cast<REFERENCE_TIME>(
                      m_rtSampleTime / m_dRateSeeking);
        rtStop  = rtStart + static_cast<int>(
                      m_iRepeatTime / m_dRateSeeking);
        pMediaSample->SetTime(&rtStart, &rtStop);

        // Increment for the next loop.
        m_rtSampleTime += m_iRepeatTime;
        m_rtBallPosition += m_iRepeatTime;
    }
    pMediaSample->SetSyncPoint(TRUE);
    if (m_bDiscontinuity) 
    {
        pMediaSample->SetDiscontinuity(TRUE);
        m_bDiscontinuity = FALSE;
    }
    return NOERROR;
}

Les principales différences entre cette version et l’original sont les suivantes :

  • Comme mentionné précédemment, la variable m_rtBallPosition est utilisée pour définir la position de la balle, plutôt que la durée du flux.
  • Après avoir tenu la section critique, la méthode vérifie si la position actuelle dépasse l’heure d’arrêt. Si c’est le cas, elle retourne S_FALSE, ce qui indique à la classe de base d’arrêter l’envoi de données et de remettre une notification de fin de flux.
  • Les horodatages sont divisés par le taux actuel.
  • Si m_bDiscontinuity a la valeur TRUE, la méthode définit l’indicateur de discontinuité sur l’exemple.

Il y a une autre différence mineure. Étant donné que la version d’origine repose sur la présence d’une seule mémoire tampon, elle remplit la mémoire tampon entière avec des zéros une fois, lorsque la diffusion en continu commence. Après cela, il efface simplement la balle de sa position précédente. Toutefois, cette optimisation révèle un léger bogue dans le filtre Ball. Lorsque la méthode CBaseOutputPin::D ecideAllocator appelle IMemInputPin::NotifyAllocator, elle définit l’indicateur en lecture seule sur FALSE. Par conséquent, le filtre en aval est libre d’écrire sur la mémoire tampon. Une solution consiste à remplacer DecideAllocator afin qu’il définisse l’indicateur en lecture seule sur TRUE. Toutefois, par souci de simplicité, la nouvelle version supprime simplement l’optimisation. Au lieu de cela, cette version remplit la mémoire tampon avec des zéros à chaque fois.

Modifications diverses

Dans la nouvelle version, ces deux lignes sont supprimées du constructeur CBall :

    m_iRandX = rand();
    m_iRandY = rand();

Le filtre De bille d’origine utilise ces valeurs pour ajouter un caractère aléatoire à la position initiale de la bille. Pour nos besoins, nous voulons que la balle soit déterministe. En outre, certaines variables sont passées d’objets CRefTime à des variables REFERENCE_TIME . (La classe CRefTime est un wrapper fin pour une valeur REFERENCE_TIME .) Enfin, l’implémentation de IQualityControl::Notify a été légèrement modifiée ; vous pouvez vous référer au code source pour plus d’informations.

Limitations de la classe CSourceSeeking

La classe CSourceSeeking n’est pas destinée aux filtres avec plusieurs broches de sortie, en raison de problèmes de communication entre broches. Par exemple, imaginez un filtre d’analyseur qui reçoit un flux audio-vidéo entrelacé, fractionne le flux en ses composants audio et vidéo, et diffuse la vidéo à partir d’une broche de sortie et l’audio d’une autre. Les deux broches de sortie recevront chaque commande de recherche, mais le filtre ne doit rechercher qu’une seule fois par commande de recherche. La solution consiste à désigner l’une des broches pour contrôler la recherche et ignorer les commandes de recherche reçues par l’autre broche.

Toutefois, après la commande seek, les deux broches doivent vider les données. Pour compliquer davantage les choses, la commande seek se produit sur le thread d’application, et non sur le thread de diffusion en continu. Par conséquent, vous devez vous assurer qu’aucune broche n’est bloquée et qu’elle attend qu’un appel IMemInputPin::Receive soit retourné, sinon cela peut provoquer un blocage. Pour plus d’informations sur le vidage thread-safe dans les broches, consultez Threads et sections critiques.

Écriture de filtres sources