Utilisation de l’exemple Grabber

[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 API n’est pas prise en charge et peut être modifiée ou indisponible à l’avenir.]

Le filtre Sample Grabber est un filtre de transformation qui peut être utilisé pour récupérer des exemples multimédias à partir d’un flux à mesure qu’ils passent par le graphe de filtre.

Si vous souhaitez simplement récupérer une bitmap à partir d’un fichier vidéo, il est plus facile d’utiliser l’objet Media Detector (MediaDet). Pour plus d’informations, consultez Capture d’un cadre d’affiche . L’exemple Grabber est toutefois plus flexible, car il fonctionne avec presque n’importe quel type de média (voir ISampleGrabber::SetMediaType pour les quelques exceptions) et offre plus de contrôle à l’application.

Créer le Gestionnaire de graphe de filtre

Pour commencer, créez le Gestionnaire de graphes de filtre et interrogez les interfaces IMediaControl et IMediaEventEx .

    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEventEx *pEvent = NULL;


    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, 
        CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
    if (FAILED(hr))
    {
        goto done;
    }

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

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

Ajouter l’exemple de capture au graphe de filtre

Créez un instance du filtre Sample Grabber et ajoutez-le au graphe de filtre. Interrogez le filtre Sample Grabber pour l’interface ISampleGrabber .

    IBaseFilter *pGrabberF = NULL;
    ISampleGrabber *pGrabber = NULL;


    // Create the Sample Grabber filter.
    hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&pGrabberF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
    if (FAILED(hr))
    {
        goto done;
    }

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

Définir le type de média

Lorsque vous créez l’exemple de capture pour la première fois, il n’a pas de type de média préféré. Cela signifie que vous pouvez vous connecter à presque n’importe quel filtre dans le graphique, mais que vous n’avez aucun contrôle sur le type de données qu’il a reçues. Avant de générer le reste du graphe, vous devez donc définir un type de média pour l’exemple Grabber, en appelant la méthode ISampleGrabber::SetMediaType .

Lorsque l’exemple Grabber se connecte, il compare ce type de média au type de média proposé par l’autre filtre. Les seuls champs qu’il vérifie sont le type principal, le sous-type et le type de format. Pour l’un de ces éléments, la valeur GUID_NULL signifie « accepter n’importe quelle valeur ». La plupart du temps, vous devez définir le type principal et le sous-type. Par exemple, le code suivant spécifie une vidéo RVB 24 bits non compressée :

    AM_MEDIA_TYPE mt;
    ZeroMemory(&mt, sizeof(mt));
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_RGB24;

    hr = pGrabber->SetMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

Générer le graphe de filtre

Vous pouvez maintenant générer le reste du graphe de filtre. Étant donné que l’exemple De capture se connecte uniquement à l’aide du type de média que vous avez spécifié, cela vous permet de tirer parti des mécanismes de connexion intelligente du Gestionnaire de graphes de filtre lorsque vous générez le graphe.

Par exemple, si vous avez spécifié une vidéo non compressée, vous pouvez connecter un filtre source à l’exemple d’capture, et le Gestionnaire de graphes de filtre ajoute automatiquement l’analyseur de fichiers et le décodeur. L’exemple suivant utilise la fonction d’assistance ConnectFilters, qui est répertoriée dans Connecter deux filtres :

    IBaseFilter *pSourceF = NULL;
    IEnumPins *pEnum = NULL;
    IPin *pPin = NULL;


    hr = pGraph->AddSourceFilter(pszVideoFile, L"Source", &pSourceF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pSourceF->EnumPins(&pEnum);
    if (FAILED(hr))
    {
        goto done;
    }

    while (S_OK == pEnum->Next(1, &pPin, NULL))
    {
        hr = ConnectFilters(pGraph, pPin, pGrabberF);
        SafeRelease(&pPin);
        if (SUCCEEDED(hr))
        {
            break;
        }
    }

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

L’exemple Grabber étant un filtre de transformation, la broche de sortie doit être connectée à un autre filtre. Souvent, vous pouvez simplement ignorer les échantillons une fois que vous en avez terminé. Dans ce cas, connectez l’exemple de capture au filtre de rendu Null, qui ignore les données qu’il reçoit.

L’exemple suivant connecte l’exemple de capture au filtre Renderer Null :

    IBaseFilter *pNullF = NULL;


    hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, 
        IID_PPV_ARGS(&pNullF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pNullF, L"Null Filter");
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConnectFilters(pGraph, pGrabberF, pNullF);
    if (FAILED(hr))
    {
        goto done;
    }

N’oubliez pas que le fait de placer l’exemple de capture entre un décodeur vidéo et le convertisseur vidéo peut considérablement nuire aux performances de rendu. L’exemple De capture est un filtre trans-in-place, ce qui signifie que la mémoire tampon de sortie est identique à la mémoire tampon d’entrée. Pour le rendu vidéo, la mémoire tampon de sortie est susceptible de se trouver sur les carte graphiques, où les opérations de lecture sont beaucoup plus lentes que les opérations de lecture dans main mémoire.

Exécuter le graphique

L’exemple Grabber fonctionne dans l’un des deux modes suivants :

  • Le mode de mise en mémoire tampon effectue une copie de chaque exemple avant de remettre l’exemple en aval.
  • Le mode de rappel appelle une fonction de rappel définie par l’application sur chaque exemple.

Cet article décrit le mode de mise en mémoire tampon. (Avant d’utiliser le mode de rappel, n’oubliez pas que la fonction de rappel doit être assez limitée. Sinon, cela peut réduire considérablement les performances ou même provoquer des blocages. Pour plus d’informations, consultez ISampleGrabber::SetCallback.) Pour activer le mode de mise en mémoire tampon, appelez la méthode ISampleGrabber::SetBufferSamples avec la valeur TRUE.

Si vous le souhaitez, appelez la méthode ISampleGrabber::SetOneShot avec la valeur TRUE. Cela entraîne l’arrêt de l’sample grabber après avoir reçu le premier exemple multimédia, ce qui est utile si vous souhaitez récupérer une seule image à partir du flux. Recherchez l’heure souhaitée, exécutez le graphique et attendez l’événement EC_COMPLETE . Notez que le niveau de précision des images dépend de la source. Par exemple, la recherche d’un fichier MPEG n’est souvent pas exacte.

Pour exécuter le graphe aussi rapidement que possible, désactivez l’horloge du graphe comme décrit dans Définition de l’horloge du graphique.

L’exemple suivant active le mode one-shot et le mode de mise en mémoire tampon, exécute le graphe de filtre et attend la fin.

    hr = pGrabber->SetOneShot(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetBufferSamples(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pControl->Run();
    if (FAILED(hr))
    {
        goto done;
    }

    long evCode;
    hr = pEvent->WaitForCompletion(INFINITE, &evCode);

Récupérer l’exemple

En mode mise en mémoire tampon, l’exemple De capture stocke une copie de chaque exemple. La méthode ISampleGrabber::GetCurrentBuffer copie la mémoire tampon dans un tableau alloué à l’appelant. Pour déterminer la taille du tableau nécessaire, appelez d’abord GetCurrentBuffer avec un pointeur NULL pour l’adresse du tableau. Allouez ensuite le tableau et appelez la méthode une deuxième fois pour copier la mémoire tampon. L'exemple suivant affiche ces étapes.

    // Find the required buffer size.
    long cbBuffer;
    hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
    if (!pBuffer) 
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

Vous devez connaître le format exact des données dans la mémoire tampon. Pour obtenir ces informations, appelez la méthode ISampleGrabber::GetConnectedMediaType . Cette méthode remplit une structure AM_MEDIA_TYPE au format .

Pour un flux vidéo non compressé, les informations de format sont contenues dans une structure VIDEOINFOHEADER . L’exemple suivant montre comment obtenir les informations de format d’un flux vidéo non compressé.

    // Examine the format block.
    if ((mt.formattype == FORMAT_VideoInfo) && 
        (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
        (mt.pbFormat != NULL)) 
    {
        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;

        hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader, 
            mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
    }
    else 
    {
        // Invalid format.
        hr = VFW_E_INVALIDMEDIATYPE; 
    }

Notes

L’exemple Grabber ne prend pas en charge VIDEOINFOHEADER2.

 

Exemple de code

Voici le code complet des exemples précédents.

Notes

Cet exemple utilise la fonction SafeRelease pour libérer les pointeurs d’interface.

 

#include <windows.h>
#include <dshow.h>
#include "qedit.h"

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}



HRESULT WriteBitmap(PCWSTR, BITMAPINFOHEADER*, size_t, BYTE*, size_t);

HRESULT GrabVideoBitmap(PCWSTR pszVideoFile, PCWSTR pszBitmapFile)
{
    IGraphBuilder *pGraph = NULL;
    IMediaControl *pControl = NULL;
    IMediaEventEx *pEvent = NULL;
    IBaseFilter *pGrabberF = NULL;
    ISampleGrabber *pGrabber = NULL;
    IBaseFilter *pSourceF = NULL;
    IEnumPins *pEnum = NULL;
    IPin *pPin = NULL;
    IBaseFilter *pNullF = NULL;

    BYTE *pBuffer = NULL;

    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, 
        CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
    if (FAILED(hr))
    {
        goto done;
    }

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

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

    // Create the Sample Grabber filter.
    hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&pGrabberF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pGrabberF, L&quot;Sample Grabber&quot;);
    if (FAILED(hr))
    {
        goto done;
    }

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

    AM_MEDIA_TYPE mt;
    ZeroMemory(&mt, sizeof(mt));
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_RGB24;

    hr = pGrabber->SetMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddSourceFilter(pszVideoFile, L&quot;Source&quot;, &pSourceF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pSourceF->EnumPins(&pEnum);
    if (FAILED(hr))
    {
        goto done;
    }

    while (S_OK == pEnum->Next(1, &pPin, NULL))
    {
        hr = ConnectFilters(pGraph, pPin, pGrabberF);
        SafeRelease(&pPin);
        if (SUCCEEDED(hr))
        {
            break;
        }
    }

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

    hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, 
        IID_PPV_ARGS(&pNullF));
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGraph->AddFilter(pNullF, L&quot;Null Filter&quot;);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConnectFilters(pGraph, pGrabberF, pNullF);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetOneShot(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->SetBufferSamples(TRUE);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pControl->Run();
    if (FAILED(hr))
    {
        goto done;
    }

    long evCode;
    hr = pEvent->WaitForCompletion(INFINITE, &evCode);

    // Find the required buffer size.
    long cbBuffer;
    hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
    if (!pBuffer) 
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pGrabber->GetConnectedMediaType(&mt);
    if (FAILED(hr))
    {
        goto done;
    }

    // Examine the format block.
    if ((mt.formattype == FORMAT_VideoInfo) && 
        (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
        (mt.pbFormat != NULL)) 
    {
        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;

        hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader, 
            mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
    }
    else 
    {
        // Invalid format.
        hr = VFW_E_INVALIDMEDIATYPE; 
    }

    FreeMediaType(mt);

done:
    CoTaskMemFree(pBuffer);
    SafeRelease(&pPin);
    SafeRelease(&pEnum);
    SafeRelease(&pNullF);
    SafeRelease(&pSourceF);
    SafeRelease(&pGrabber);
    SafeRelease(&pGrabberF);
    SafeRelease(&pControl);
    SafeRelease(&pEvent);
    SafeRelease(&pGraph);
    return hr;
};

// Writes a bitmap file
//  pszFileName:  Output file name.
//  pBMI:         Bitmap format information (including pallete).
//  cbBMI:        Size of the BITMAPINFOHEADER, including palette, if present.
//  pData:        Pointer to the bitmap bits.
//  cbData        Size of the bitmap, in bytes.

HRESULT WriteBitmap(PCWSTR pszFileName, BITMAPINFOHEADER *pBMI, size_t cbBMI,
    BYTE *pData, size_t cbData)
{
    HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL, 
        CREATE_ALWAYS, 0, NULL);
    if (hFile == NULL)
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    BITMAPFILEHEADER bmf = { };

    bmf.bfType = &#39;MB&#39;;
    bmf.bfSize = cbBMI+ cbData + sizeof(bmf); 
    bmf.bfOffBits = sizeof(bmf) + cbBMI; 

    DWORD cbWritten = 0;
    BOOL result = WriteFile(hFile, &bmf, sizeof(bmf), &cbWritten, NULL);
    if (result)
    {
        result = WriteFile(hFile, pBMI, cbBMI, &cbWritten, NULL);
    }
    if (result)
    {
        result = WriteFile(hFile, pData, cbData, &cbWritten, NULL);
    }

    HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32(GetLastError());

    CloseHandle(hFile);

    return hr;
}

Utilisation de DirectShow Editing Services