Partilhar via


Usando o "Sample Grabber"

[O recurso associado a esta página, DirectShow, é um recurso herdado. Foi substituído por MediaPlayer, IMFMediaEnginee Audio/Video Capture in Media Foundation. Esses recursos foram otimizados para Windows 10 e Windows 11. A Microsoft recomenda vivamente que o novo código utilize MediaPlayer, IMFMediaEngine e Captura de Áudio/Vídeo no Media Foundation em vez de DirectShow, quando possível. A Microsoft sugere que o código existente que usa as APIs herdadas seja reescrito para usar as novas APIs, se possível.]

[Esta API não é suportada e pode ser alterada ou indisponível no futuro.]

O filtroSample Grabber é um filtro de transformação que pode ser usado para capturar amostras de mídia de um fluxo à medida que passam pelo gráfico de filtro.

Se você quiser simplesmente pegar um bitmap de um arquivo de vídeo, é mais fácil usar o Media Detetor (MediaDet) objeto. Consulte Pegando um quadro de pôster para obter detalhes. O Sample Grabber é mais flexível, no entanto, porque funciona com praticamente qualquer tipo de mídia (consulte ISampleGrabber::SetMediaType para as poucas exceções) e oferece mais controle para o aplicativo.

Criar o Gerenciador de gráficos de filtro

Para começar, crie o Filter Graph Manager e consulte as interfaces 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;
    }

Adicionar o Sample Grabber ao gráfico de filtro

Crie uma instância do filtro Sample Grabber e adicione-a ao gráfico de filtro. Consulte o filtro Sample Grabber para a 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;
    }

Definir o tipo de mídia

Quando você cria o Sample Grabber pela primeira vez, ele não tem nenhum tipo de mídia preferido. Isso significa que você pode se conectar a praticamente qualquer filtro no gráfico, mas não teria controle sobre o tipo de dados recebidos. Antes de criar o resto do gráfico, portanto, você deve definir um tipo de mídia para o Sample Grabber, chamando o ISampleGrabber::SetMediaType método.

Quando o Sample Grabber se conectar, ele comparará esse tipo de mídia com o tipo de mídia oferecido pelo outro filtro. Os únicos campos que ele verifica são o tipo principal, subtipo e tipo de formato. Para qualquer um deles, o valor GUID_NULL significa "aceitar qualquer valor". Na maioria das vezes, você vai querer definir o tipo principal e subtipo. Por exemplo, o código a seguir especifica vídeo RGB de 24 bits não compactado:

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

Criar o gráfico de filtro

Agora você pode criar o resto do gráfico de filtro. Como o Sample Grabber só se conectará usando o tipo de mídia que você especificou, isso permite que você aproveite os mecanismos de Intelligent Connect do Filter Graph Manager ao criar o gráfico.

Por exemplo, se você especificou vídeo não compactado, você pode conectar um filtro de origem ao Sample Grabber, e o Filter Graph Manager adicionará automaticamente o analisador de arquivos e o decodificador. O exemplo a seguir usa a função auxiliar ConnectFilters, listada em Conectar dois filtros:

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

O Sample Grabber é um filtro de transformação, portanto, o pino de saída deve ser conectado a outro filtro. Muitas vezes, você pode simplesmente querer descartar as amostras depois de terminar de usá-las. Nesse caso, conecte o Sample Grabber ao Null Renderer Filter, que descarta os dados recebidos.

O exemplo a seguir conecta o Sample Grabber ao filtro Null Renderer:

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

Lembre-se de que colocar o Sample Grabber entre um decodificador de vídeo e o renderizador de vídeo pode prejudicar significativamente o desempenho de renderização. O Sample Grabber é um filtro trans-local, o que significa que o buffer de saída é o mesmo que o buffer de entrada. Para renderização de vídeo, é provável que o buffer de saída esteja localizado na placa gráfica, onde as operações de leitura são muito mais lentas, em comparação com as operações de leitura na memória principal.

Executar o gráfico

O Sample Grabber opera em um de dois modos:

  • O modo de buffer faz uma cópia de cada amostra antes de entregar a amostra a jusante.
  • O modo de retorno de chamada invoca uma função de retorno de chamada definida pela aplicação em cada amostra.

Este artigo descreve o modo de armazenamento em buffer. (Antes de usar o modo de retorno de chamada, esteja ciente de que a função de retorno de chamada deve ser bastante limitada. Caso contrário, pode reduzir drasticamente o desempenho ou até mesmo causar impasses. Para obter mais informações, consulte ISampleGrabber::SetCallback.) Para ativar o modo de buffer, chame o método ISampleGrabber::SetBufferSamples com o valor TRUE.

Opcionalmente, chame o método ISampleGrabber::SetOneShot com o valor TRUE. Isso faz com que o Sample Grabber pare depois de receber a primeira amostra de mídia, o que é útil se você quiser pegar um único quadro do fluxo. Procure o tempo desejado, execute o gráfico e aguarde o evento EC_COMPLETE. Observe que o nível de precisão do quadro depende da fonte. Por exemplo, procurar um ficheiro MPEG frequentemente não é preciso em termos de fotogramas.

Para executar o gráfico o mais rápido possível, desligue o relógio do gráfico conforme descrito em Definindo o relógio do gráfico.

O exemplo a seguir habilita o modo one-shot e o modo buffering, executa o gráfico de filtro e aguarda a conclusão.

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

Pegue a amostra

No modo de buffer, o Sample Grabber armazena uma cópia de cada amostra. O método ISampleGrabber::GetCurrentBuffer copia o buffer para uma matriz alocada pelo chamador. Para determinar o tamanho da matriz necessária, primeiro chame GetCurrentBuffer com um ponteiro NULL para o endereço da matriz. Em seguida, aloque a matriz e chame o método uma segunda vez para copiar o buffer. O exemplo a seguir mostra essas etapas.

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

Você precisará saber o formato exato dos dados no buffer. Para obter essas informações, chame o ISampleGrabber::GetConnectedMediaType método. Este método preenche uma estrutura AM_MEDIA_TYPE com o formato.

Para um fluxo de vídeo não compactado, as informações de formato estão contidas numa estrutura VIDEOINFOHEADER. O exemplo a seguir mostra como obter as informações de formato para um fluxo de vídeo não compactado.

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

Observação

O Sample Grabber não suporta VIDEOINFOHEADER2.

 

Código de exemplo

Aqui está o código completo para os exemplos anteriores.

Observação

Este exemplo usa a função SafeRelease para liberar ponteiros de 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"Sample Grabber");
    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"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;
    }

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

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

Usando o DirectShow Serviços de Edição