Compartilhar via


Usando o Capturador de Amostras

[O recurso associado a esta página, DirectShow, é um recurso herdado. Foi substituído pelo MediaPlayer, IMFMediaEngine e Captura de áudio/vídeo no Media Foundation. Esses recursos foram otimizados para Windows 10 e Windows 11. A Microsoft recomenda fortemente que o novo código use o MediaPlayer, o IMFMediaEngine e a Captura de áudio/vídeo no Media Foundation em vez do 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.]

[Essa API não tem suporte e pode ser alterada ou indisponível no futuro.]

O filtro Sample Grabber é um filtro de transformação que pode ser usado para capturar amostras de mídia de um fluxo enquanto passam pelo grafo de filtro.

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

Criar o Gerenciador do Grafo de Filtro

Para começar, crie o Gerenciador de Grafos de Filtro 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 seletor de exemplo ao grafo de filtro

Crie uma instância do filtro de Captura de Exemplo e adicione-a ao grafo de filtro. Consulte o filtro de Captura de Exemplo 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 preferencial. Isso significa que você pode se conectar a quase qualquer filtro no grafo, mas não teria controle sobre o tipo de dados que ele recebeu. Antes de criar o restante do grafo, portanto, você deve definir um tipo de mídia para o Sample Grabber chamando o método ISampleGrabber::SetMediaType .

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, o subtipo e o tipo de formato. Para qualquer um deles, o valor GUID_NULL significa "aceitar qualquer valor". Na maioria das vezes, você desejará definir o tipo e o subtipo principais. Por exemplo, o código a seguir especifica um 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 grafo de filtro

Agora você pode criar o restante do grafo de filtro. Como o Seletor de Exemplo se conectará apenas usando o tipo de mídia especificado, isso permite que você aproveite os mecanismos de Conexão Inteligente do Gerenciador de Grafo de Filtro ao criar o grafo.

Por exemplo, se você especificou um vídeo não compactado, 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, que está 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 estar conectado a outro filtro. Muitas vezes, você pode simplesmente querer descartar as amostras depois de terminar com elas. Nesse caso, conecte o Sample Grabber ao Filtro do Renderizador Nulo, que descarta os dados recebidos.

O exemplo a seguir conecta o Sample Grabber ao filtro Renderizador Nulo:

    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-in-place, 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, em que 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 Grafo

O Sample Grabber opera em um dos dois modos:

  • O modo de buffer cria uma cópia de cada amostra antes de entregar a amostra downstream.
  • O modo de retorno de chamada invoca uma função definida pelo aplicativo em cada amostra.

Este artigo descreve o modo de armazenamento em buffer. (Antes de usar o modo de retorno de chamada, lembre-se de que a função de retorno de chamada deve ser bastante limitada. Caso contrário, ele poderá reduzir drasticamente o desempenho ou até mesmo causar deadlocks. 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 interrompe após receber o primeiro exemplo de mídia, o que é útil se você quiser capturar um único quadro do fluxo. Procure o tempo desejado, execute o grafo e aguarde o evento EC_COMPLETE . Note que o nível de precisão do frame depende da origem. Por exemplo, a busca de um arquivo MPEG geralmente não é precisa por quadros.

Para executar o grafo o mais rápido possível, desative o relógio de grafo conforme descrito em Definir o relógio de grafo.

O exemplo a seguir habilita o modo de captura única e o modo de buffer, executa o grafo 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);

Capturar o exemplo

No modo de buffer, o Sample Grabber armazena uma cópia de cada exemplo. O método ISampleGrabber::GetCurrentBuffer copia o buffer para uma matriz alocada por 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 método ISampleGrabber::GetConnectedMediaType . Esse método preenche uma estrutura AM_MEDIA_TYPE com o formato.

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

    // 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 dá suporte a 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 os Serviços de Edição do DirectShow