Usando o grabber de exemplo

[O recurso associado a esta página, DirectShow, é um recurso herdado. Foi substituído por MediaPlayer, IMFMediaEngine e Audio/Video Capture in Media Foundation. Esses recursos foram otimizados para Windows 10 e Windows 11. A Microsoft recomenda fortemente que o novo código use MediaPlayer, IMFMediaEngine e Audio/Video Capture in 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.]

[Não há suporte para essa API e pode ser alterada ou indisponível no futuro.]

O filtro De captura de exemplo é um filtro de transformação que pode ser usado para capturar amostras de mídia de um fluxo conforme eles passam pelo grafo de filtro.

Se você simplesmente quiser pegar um bitmap de um arquivo de vídeo, será mais fácil usar o objeto MediaDet (Media Detector ). Consulte Pegando um quadro de pôster para obter detalhes. No entanto, o Sample Grabber é mais flexível 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 de Grafo de Filtro

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

Crie uma instância do filtro De exemplo grabber e adicione-o ao grafo de filtro. Consulte o filtro De exemplo 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 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 descompactado:

    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 Sample Grabber só se conectará usando o tipo de mídia especificado, isso permite que você aproveite os mecanismos de Conexão Inteligente do Gerenciador de Gráficos de Filtro ao criar o grafo.

Por exemplo, se você especificou um vídeo descompactado, poderá conectar um filtro de origem ao Analisador de Exemplo e o Gerenciador de Gráficos de Filtro 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 os exemplos depois de terminar com eles. Nesse caso, conecte o Sample Grabber ao Filtro de Renderizador Nulo, que descarta os dados que ele recebe.

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 da renderização. O Sample Grabber é um filtro trans-in-loco, o que significa que o buffer de saída é o mesmo que o buffer de entrada. Para a renderização de vídeo, é provável que o buffer de saída esteja localizado nos elementos gráficos cartão, em que as operações de leitura são muito mais lentas, em comparação com as operações de leitura na memória main.

Executar o grafo

O Grabber de Exemplo opera em um dos dois modos:

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

Este artigo descreve o modo de 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 pode 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 Grabber de Exemplo pare depois de receber o primeiro exemplo de mídia, o que é útil se você quiser pegar um único quadro do fluxo. Procure o tempo desejado, execute o grafo e aguarde o evento EC_COMPLETE . Observe que o nível de precisão do quadro depende da origem. Por exemplo, a busca de um arquivo MPEG geralmente não tem a precisão do quadro.

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

O exemplo a seguir habilita o modo único 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 amostra. O método ISampleGrabber::GetCurrentBuffer copia o buffer em 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 estas 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&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;
}

Usando o DirectShow Editing Services