Uso di Sample Grabber

[La funzionalità associata a questa pagina, DirectShow, è una funzionalità legacy. È stata sostituita da MediaPlayer, IMFMediaEngine e Audio/Video Capture in Media Foundation. Queste funzionalità sono state ottimizzate per Windows 10 e Windows 11. Microsoft consiglia vivamente che il nuovo codice usi MediaPlayer, IMFMediaEngine e Audio/Video Capture in Media Foundation invece di DirectShow, quando possibile. Microsoft suggerisce che il codice esistente che usa le API legacy venga riscritto per usare le nuove API, se possibile.

[Questa API non è supportata e potrebbe essere modificata o non disponibile in futuro.]

Il filtro Grabber di esempio è un filtro di trasformazione che può essere usato per acquisire campioni multimediali da un flusso mentre passano attraverso il grafico del filtro.

Se si vuole semplicemente afferrare una bitmap da un file video, è più semplice usare l'oggetto Media Detector (MediaDet). Per informazioni dettagliate , vedere Afferrare un frame poster . Sample Grabber è tuttavia più flessibile, perché funziona quasi con qualsiasi tipo di supporto (vedere ISampleGrabber::SetMediaType per le poche eccezioni) e offre un maggiore controllo all'applicazione.

Creare Filter Graph Manager

Per iniziare, creare Filter Graph Manager ed eseguire query per le interfacce IMediaControl e 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;
    }

Aggiungere sample Grabber al grafico dei filtri

Creare un'istanza del filtro Sample Grabber e aggiungere l'elemento al grafico del filtro. Eseguire una query sul filtro Sample Grabber per l'interfaccia 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;
    }

Impostare il tipo di supporto

Quando si crea per la prima volta Sample Grabber, non ha un tipo di supporto preferito. Ciò significa che è possibile connettersi a quasi tutti i filtri nel grafico, ma non si avrà alcun controllo sul tipo di dati ricevuti. Prima di compilare il resto del grafo, è quindi necessario impostare un tipo di supporto per Sample Grabber chiamando il metodo ISampleGrabber::SetMediaType .

Quando Sample Grabber si connette, confronta questo tipo di supporto con il tipo di supporto offerto dall'altro filtro. Gli unici campi che controlla sono il tipo principale, il sottotipo e il tipo di formato. Per uno di questi, il valore GUID_NULL significa "accettare qualsiasi valore". Nella maggior parte dei casi, è consigliabile impostare il tipo principale e il sottotipo. Ad esempio, il codice seguente specifica un video RGB non compresso a 24 bit:

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

Compilare il grafico dei filtri

È ora possibile compilare il resto del grafico dei filtri. Poiché Sample Grabber si connetterà solo usando il tipo di supporto specificato, questo consente di sfruttare i meccanismi intelligent Connect di Filter Graph Manager quando si compila il grafo.

Ad esempio, se è stato specificato un video non compresso, è possibile connettere un filtro di origine a Sample Grabber e Filter Graph Manager aggiungerà automaticamente il parser di file e il decodificatore. L'esempio seguente usa la funzione helper ConnectFilters, elencata in Connect Two Filters:

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

Sample Grabber è un filtro di trasformazione, quindi il pin di output deve essere connesso a un altro filtro. Spesso, è possibile rimuovere semplicemente gli esempi dopo averli eseguiti. In tal caso, connettere Sample Grabber al filtro renderer Null, che rimuove i dati ricevuti.

L'esempio seguente connette Sample Grabber al filtro 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;
    }

Tenere presente che l'inserimento di Sample Grabber tra un decodificatore video e il renderer video può compromettere significativamente le prestazioni di rendering. Sample Grabber è un filtro trans-in-place, il che significa che il buffer di output è lo stesso del buffer di input. Per il rendering video, è probabile che il buffer di output si trovi nella scheda grafica, in cui le operazioni di lettura sono molto più lente rispetto alle operazioni di lettura nella memoria principale.

Eseguire il grafo

Sample Grabber opera in una delle due modalità seguenti:

  • La modalità di memorizzazione nel buffer crea una copia di ogni esempio prima di recapitare l'esempio downstream.
  • La modalità di callback richiama una funzione di callback definita dall'applicazione in ogni esempio.

Questo articolo descrive la modalità di memorizzazione nel buffer. Prima di usare la modalità di callback, tenere presente che la funzione di callback deve essere piuttosto limitata. In caso contrario, può ridurre drasticamente le prestazioni o persino causare deadlock. Per altre informazioni, vedere ISampleGrabber::SetCallback. Per attivare la modalità di buffering, chiamare il metodo ISampleGrabber::SetBufferSamples con il valore TRUE.

Facoltativamente, chiamare il metodo ISampleGrabber::SetOneShot con il valore TRUE. In questo modo, Sample Grabber viene interrotto dopo la ricezione del primo campione multimediale, utile se si vuole acquisire un singolo fotogramma dal flusso. Cercare il tempo desiderato, eseguire il grafico e attendere l'evento EC_COMPLETE . Si noti che il livello di accuratezza dei fotogrammi dipende dall'origine. Ad esempio, la ricerca di un file MPEG non è spesso accurata.

Per eseguire il grafico il più velocemente possibile, disattivare l'orologio del grafico come descritto in Impostazione dell'orologio del grafo.

L'esempio seguente abilita la modalità one-shot e la modalità di memorizzazione nel buffer, esegue il grafico del filtro e attende il completamento.

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

Acquisire l'esempio

In modalità buffering, Sample Grabber archivia una copia di ogni esempio. Il metodo ISampleGrabber::GetCurrentBuffer copia il buffer in una matrice allocata dal chiamante. Per determinare le dimensioni della matrice necessaria, chiamare innanzitutto GetCurrentBuffer con un puntatore NULL per l'indirizzo della matrice. Allocare quindi la matrice e chiamare il metodo una seconda volta per copiare il buffer. Nell'esempio seguente sono illustrati i passaggi per l'operazione.

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

Sarà necessario conoscere il formato esatto dei dati nel buffer. Per ottenere queste informazioni, chiamare il metodo ISampleGrabber::GetConnectedMediaType . Questo metodo compila una struttura AM_MEDIA_TYPE con il formato .

Per un flusso video non compresso, le informazioni sul formato sono contenute in una struttura VIDEOINFOHEADER . L'esempio seguente illustra come ottenere le informazioni sul formato per un flusso video non compresso.

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

Nota

Sample Grabber non supporta VIDEOINFOHEADER2.

 

Codice di esempio

Ecco il codice completo per gli esempi precedenti.

Nota

In questo esempio viene usata la funzione SafeRelease per rilasciare puntatori all'interfaccia.

 

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

Uso dei servizi di modifica DirectShow