Capturando uma imagem de um pino de imagem parada

[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.]

Algumas câmeras podem produzir uma imagem parada separada do fluxo de captura e, muitas vezes, a imagem parada é de maior qualidade do que as imagens produzidas pelo fluxo de captura. A câmera pode ter um botão que atua como um gatilho de hardware ou pode dar suporte ao disparo de software. Uma câmera que dá suporte a imagens paradas exporá um pino de imagem parada, que é a categoria de pino PIN_CATEGORY_STILL.

A maneira recomendada de obter imagens paradas do dispositivo é usar as APIs de WIA (Aquisição de Imagens do Windows). Para obter mais informações, confira "Aquisição de imagens do Windows" na documentação do SDK da plataforma. No entanto, você também pode usar o DirectShow para capturar uma imagem.

Para disparar o pino ainda, use o método IAMVideoControl::SetMode quando o grafo estiver em execução, da seguinte maneira:

IAMVideoControl *pAMVidControl = NULL;

hr = pControl->Run(); // Run the graph.
if (FAILED(hr))
{
    // Handle error.
}

hr = pCap->QueryInterface(IID_IAMVideoControl, (void**)&pAMVidControl);

if (SUCCEEDED(hr))
{
    // Find the still pin.
    IPin *pPin = NULL;

    // pBuild is an ICaptureGraphBuilder2 pointer.

    hr = pBuild->FindPin(
        pCap,                  // Filter.
        PINDIR_OUTPUT,         // Look for an output pin.
        &PIN_CATEGORY_STILL,   // Pin category.
        NULL,                  // Media type (don't care).
        FALSE,                 // Pin must be unconnected.
        0,                     // Get the 0'th pin.
        &pPin                  // Receives a pointer to thepin.
        );

    if (SUCCEEDED(hr))
    {
        hr = pAMVidControl->SetMode(pPin, VideoControlFlag_Trigger);
        pPin->Release();
    }
    pAMVidControl->Release();
}

Consulte o filtro de captura para IAMVideoControl. Se houver suporte para a interface , obtenha um ponteiro para a interface IPin do pino ainda chamando o método ICaptureGraphBuilder2::FindPin , conforme mostrado no exemplo anterior. Em seguida, chame IAMVideoControl::SetMode com o ponteiro IPin e o sinalizador VideoControlFlag_Trigger.

Observação

Dependendo da câmera, talvez seja necessário renderizar o pino de captura (PIN_CATEGORY_CAPTURE) antes que o pino ainda se conecte.

 

Exemplo: usando o filtro de captura de exemplo

Uma maneira de capturar a imagem é com o filtro De exemplo grabber . O Sample Grabber usa uma função de retorno de chamada definida pelo aplicativo para processar a imagem. Para obter mais informações sobre o filtro De exemplo grabber, consulte Using the Sample Grabber.

O exemplo a seguir pressupõe que o pino ainda fornece uma imagem RGB descompactada. Primeiro, defina uma classe que implementará a interface de retorno de chamada do Sample Grabber, ISampleGrabberCB:

// Class to hold the callback function for the Sample Grabber filter.
class SampleGrabberCallback : public ISampleGrabberCB
{
    // Implementation is described later.
}

// Global instance of the class.
SampleGrabberCallback g_StillCapCB;

A implementação da classe é descrita em breve.

Em seguida, conecte o pino ainda ao Capturador de Exemplo e conecte o Sample Grabber ao filtro Renderizador Nulo . O Renderizador Nulo simplesmente descarta exemplos de mídia que recebe; o trabalho real será feito dentro do retorno de chamada. (O único motivo para o Renderizador Nulo é conectar o pino de saída do Sample Grabber a algo.) Chame CoCreateInstance para criar os filtros Grabber de Exemplo e Renderizador Nulo e chame IFilterGraph::AddFilter para adicionar os dois filtros ao grafo:

// Add the Sample Grabber filter to the graph.
IBaseFilter *pSG_Filter;
hr = CoCreateInstance(
    CLSID_SampleGrabber, 
    NULL, 
    CLSCTX_INPROC_SERVER,
    IID_IBaseFilter, 
    (void**)&pSG_Filter
    );

hr = pGraph->AddFilter(pSG_Filter, L"SampleGrab");

// Add the Null Renderer filter to the graph.
IBaseFilter *pNull;

hr = CoCreateInstance(
    CLSID_NullRenderer, 
    NULL, 
    CLSCTX_INPROC_SERVER,
    IID_IBaseFilter, 
    (void**)&pNull
    );

hr = pGraph->AddFilter(pNull, L"NullRender");

Você pode usar o método ICaptureGraphBuilder2::RenderStream para conectar todos os três filtros em uma chamada de método, indo do pino ainda para o Grabber de Exemplo e do Grabber de Exemplo para o Renderizador Nulo:

hr = pBuild->RenderStream(
    &PIN_CATEGORY_STILL, // Connect this pin ...
    &MEDIATYPE_Video,    // with this media type ...
    pCap,                // on this filter ...
    pSG_Filter,          // to the Sample Grabber ...
    pNull);              // ... and finally to the Null Renderer.

Agora, use a interface ISampleGrabber para configurar o Sample Grabber para que ele armague amostras em buffer:

// Configure the Sample Grabber.
ISampleGrabber *pSG = NULL;

hr = pSG_Filter->QueryInterface(IID_ISampleGrabber, (void**)&pSG);
if (SUCCEEDED(hr))
{
    hr = pSG->SetOneShot(FALSE);
    hr = pSG->SetBufferSamples(TRUE);

    ...

Defina a interface de retorno de chamada com um ponteiro para o objeto de retorno de chamada:

hr = pSG->SetCallback(&g_StillCapCB, 0); // 0 = Use the SampleCB callback method.

Obtenha o tipo de mídia que o pino ainda usou para se conectar com o Grabber de Exemplo:

// Store the media type for later use.
AM_MEDIA_TYPE g_StillMediaType;

hr = pSG->GetConnectedMediaType(&g_StillMediaType);
pSG->Release();

Esse tipo de mídia conterá a estrutura BITMAPINFOHEADER que define o formato da imagem parada. Libere o tipo de mídia antes que o aplicativo saia:

// On exit, remember to release the media type.
FreeMediaType(g_StillMediaType);

O que se segue é um exemplo da classe de retorno de chamada. Observe que a classe implementa IUnknown, que herda por meio da interface ISampleGrabber , mas não mantém uma contagem de referência. Isso é seguro porque o aplicativo cria o objeto na pilha e o objeto permanece no escopo durante todo o tempo de vida do grafo de filtro.

Todo o trabalho ocorre no método BufferCB , que é chamado pelo Sample Grabber sempre que ele obtém um novo exemplo. No exemplo a seguir, o método grava o bitmap em um arquivo:

class SampleGrabberCallback : public ISampleGrabberCB
{
public:
    // Fake referance counting.
    STDMETHODIMP_(ULONG) AddRef() { return 1; }
    STDMETHODIMP_(ULONG) Release() { return 2; }

    STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject)
    {
        if (NULL == ppvObject) return E_POINTER;
        if (riid == __uuidof(IUnknown))
        {
            *ppvObject = static_cast<IUnknown*>(this);
             return S_OK;
        }
        if (riid == __uuidof(ISampleGrabberCB))
        {
            *ppvObject = static_cast<ISampleGrabberCB*>(this);
             return S_OK;
        }
        return E_NOTIMPL;
    }

    STDMETHODIMP SampleCB(double Time, IMediaSample *pSample)
    {
        return E_NOTIMPL;
    }

    STDMETHODIMP BufferCB(double Time, BYTE *pBuffer, long BufferLen)
    {
        if ((g_StillMediaType.majortype != MEDIATYPE_Video) ||
            (g_StillMediaType.formattype != FORMAT_VideoInfo) ||
            (g_StillMediaType.cbFormat < sizeof(VIDEOINFOHEADER)) ||
            (g_StillMediaType.pbFormat == NULL))
        {
            return VFW_E_INVALIDMEDIATYPE;
        }
        HANDLE hf = CreateFile("C:\\Example.bmp", GENERIC_WRITE, 
        FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
        if (hf == INVALID_HANDLE_VALUE)
        {
            return E_FAIL;
        }
        long cbBitmapInfoSize = g_StillMediaType.cbFormat - SIZE_PREHEADER;
        VIDEOINFOHEADER *pVideoHeader =
           (VIDEOINFOHEADER*)g_StillMediaType.pbFormat;

        BITMAPFILEHEADER bfh;
        ZeroMemory(&bfh, sizeof(bfh));
        bfh.bfType = 'MB';  // Little-endian for "BM".
        bfh.bfSize = sizeof( bfh ) + BufferLen + cbBitmapInfoSize;
        bfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + cbBitmapInfoSize;
        
        // Write the file header.
        DWORD dwWritten = 0;
        WriteFile( hf, &bfh, sizeof( bfh ), &dwWritten, NULL );
        WriteFile(hf, HEADER(pVideoHeader), cbBitmapInfoSize, &dwWritten, NULL);        
        WriteFile( hf, pBuffer, BufferLen, &dwWritten, NULL );
        CloseHandle( hf );
        return S_OK;

    }
};

Tarefas de Captura de Vídeo