1

I have been trying to use SampleGrabber to grab a frame from a webcam and save it as a bitmap but haven't had any luck. I used the Directshow video capture example on MSDN using ICaptureGraphBuilder2 along with the Sample grabber example as well.

I intended my filter graph to look like the following:

Webcam (source) -> Sample Grabber -> Null Renderer

Since I'm not interested in seeing a preview, I decided that a null renderer would be good enough for me.

The plan was to find my webcam by searching for a specific VID and PID (webcamCapture::findWebCam()), create my samplegrabber, create the null renderer and then connect them all together using pGraphBuilder->RenderStream(). Since I would need only one frame, I used the OneShot method and then used GetCurrentBuffer to retrieve the frame.

The problem occurs at this line:

pEvent->WaitForCompletion(2000, &evCode);

The HRESULT code returned was E_ABORT. Setting the timeout to INFINITE caused the program to freeze up possibly meaning that the filter never stops. The HRESULT from pControl->Run() was S_FALSE, meaning that the not all of the filters are running. I am not sure why that is happening and I have a feeling that something went wrong when building these filters.

Below is a dump of my source. I have created a class to handle the webcam capture. In use, basically two things get called:

webcamCapture::buildFilterGraph()
webcamCapture::runFilter()

runFilter would run the filter and save the frame as a bitmap file. I'm new to directshow and I'm pretty sure I'm missing something simple. Any insight would greatly appreciated!

Source

    HRESULT webcamCapture::buildFilterGraph(void){
    HRESULT hr;

    if (FAILED(hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) {
        return hr;
    }

    createCOMInstances();

    //  Use findWebCam() to find the webcam and add it to our capture graph
    hr = findWebCam();
    if(FAILED(hr)){
        return hr;
    }

    //  Add the sample grabber to the graph
    hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
    if(FAILED(hr)){
        return hr;
    }

    hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
    if(FAILED(hr)){
        return hr;
    }

    //  Set media type for the sample grabber (24-bit RGB Uncompressed video)
    ZeroMemory(&mt, sizeof(mt));
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_RGB24;

    hr = pGrabber->SetMediaType(&mt);
    if(FAILED(hr)){
        return hr;
    }

    hr = pGraph->AddFilter(pNullF, L"Null Filter");
    if(FAILED(hr)){
        return hr;
    }

    //  Connect source (pCapF), sample grabber (pGrabberF) and null renderer (pNullF)
    pGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCapF,pGrabberF, pNullF);

    hr = pGrabber->SetOneShot(TRUE);
    hr = pGrabber->SetBufferSamples(TRUE);


    return S_OK;
    }

    HRESULT webcamCapture::runFilter(){
    HRESULT hr;
    FILTER_STATE filterState;

    pBuffer = NULL;



    hr = pControl->Run();
    if(FAILED(hr)){
        goto done;
    }

    pControl->GetState(1000, (OAFilterState*)&filterState);

    long evCode;
    hr = pEvent->WaitForCompletion(2000, &evCode);
    if(FAILED(hr)){
        goto done;
    }

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

    if((mt.formattype == FORMAT_VideoInfo) && (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) && (mt.pbFormat != NULL)){

        VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;
        writeBitmap(L"test_image.bmp", &pVih->bmiHeader, mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
    }

    //FreeMediaType(mt);

    done:
        CoTaskMemFree(pBuffer);
        pNullF->Release();
        pNullF = NULL;
        pCapF->Release();
        pCapF = NULL;
        pGrabber->Release();
        pGrabber = NULL;
        pGrabberF->Release();
        pGrabberF = NULL;
        pControl->Release();
        pControl = NULL;
        pEvent->Release();
        pEvent = NULL;
        pGraph->Release();
        pGraph = NULL;
        CoUninitialize();


    return hr;
    }

    HRESULT webcamCapture::createCOMInstances(void){
    HRESULT hr;

    //  Instructions to create a Capture Graph using CaptureGraphBuilder2 can be found at https://msdn.microsoft.com/en-us/library/windows/desktop/dd373396(v=vs.85).aspx

    //  Create Capture Graph Builder
    hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pGraphBuilder));
    if(FAILED(hr)){
        return hr;
    }

    //  Create Graph Manager
    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pGraph));
    if(FAILED(hr)){
        return hr;
    }

    //  Initialize Capture Graph Builder by referencing pGraph (Graph Manager)
    hr = pGraphBuilder->SetFiltergraph(pGraph);
    if(FAILED(hr)){
        pGraphBuilder->Release();
        return hr;
    }

    //  Set pointer to Media Control Interface
    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
    if(FAILED(hr)){
        return hr;
    }

    //  Set pointer to Media Event Interface
    hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
    if(FAILED(hr)){
        return hr;
    }

    //  Create Sample Grabber
    //  NOTE: ISampleGrabber is depreciated and may not be supported in later versions of Windows!
    hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pGrabberF));
    if(FAILED(hr)){
        return hr;
    }

    //  Create the null renderer filter.  We don't be previewing the camera feed so we can just drop the frames when we've converted them to bitmaps
    hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pNullF));
    if(FAILED(hr)){
        return hr;
    }

    TRACE(_T("Interface Creation Successful\n"));
    return S_OK;
    }

    //  Function to find the MS HD3000 webcam (VID=0x045E PID=0x0779)
    //  For more info on finding devices for Video/Audio capture, see https://msdn.microsoft.com/en-us/library/windows/desktop/dd377566(v=vs.85).aspx
    HRESULT webcamCapture::findWebCam(void){

    HRESULT hr;
    ICreateDevEnum *pDevEnum;
    IEnumMoniker *pEnum;
    IMoniker *pMoniker;
    BOOL isDeviceFound = FALSE;

    WORD wNumCameras = 0;

    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
    if(FAILED(hr)){
        return hr;
    }

    hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
    if(hr == S_FALSE){
        return VFW_E_NOT_FOUND;
    }

    pDevEnum->Release();

    //  Go through each device moniker and read their Device Path properties.  This is how we will find our webcam of interest
    while(pEnum->Next(1, &pMoniker, NULL) == S_OK){

        IPropertyBag *pPropBag;
        VARIANT var;

        hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));

        if(FAILED(hr)){
            pMoniker->Release();
            continue;
        }

        VariantInit(&var);

        hr = pPropBag->Read(L"DevicePath", &var, 0);
        if(SUCCEEDED(hr)){
            //  String search variables
            CString csVidToCompare = _T("");
            CString csPidToCompare = _T("");
            WORD wVidSearchIndex = 0;
            WORD wPidSearchIndex = 0;

            CString csDevPath = var.bstrVal;
            csDevPath.MakeLower();

            wVidSearchIndex = csDevPath.Find(_T("vid_"), 0);
            wPidSearchIndex = csDevPath.Find(_T("pid_"), 0);

            csVidToCompare = csDevPath.Mid(wVidSearchIndex + 4, 4);
            csPidToCompare = csDevPath.Mid(wPidSearchIndex + 4, 4);

            //  If MS Device is found
            if(!csVidToCompare.Compare(_T("045e"))){
                //  If the 3000HD camera is found
                if((!csPidToCompare.Compare(_T("0779"))) && (wNumCameras == 0)){
                    wNumCameras++;
                    hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCapF);
                    if(FAILED(hr)){
                        return hr;
                    }

                    isDeviceFound = TRUE;
                    //  Add the webcam to the filter graph
                    hr = pGraph->AddFilter(pCapF, L"Capture Filter");

                    if(FAILED(hr)){
                        TRACE(_T("Failed to add webcam to filter graph\n"));
                        return hr;
                    }

                    TRACE(_T("Webcam found and added to filter!\n"));

                }

                else if((!csPidToCompare.Compare(_T("0779"))) && (wNumCameras > 0)){
                    TRACE(_T("More than one HD3000 camera found!\n"));
                    continue;
                }

                else{
                    TRACE(_T("MS Device found, but not the camera\n"));
                    continue;
                }
            }
        }

        pPropBag->Release();
        pMoniker->Release();
    }

    if(isDeviceFound == FALSE){
        TRACE(_T("Webcam was not found\n"));
        pEnum->Release();
        return E_FAIL;
    }

    pEnum->Release();

    return S_OK;
}
Roman R.
  • 68,205
  • 6
  • 94
  • 158

1 Answers1

0

The code is about right. Yes, you can build a graph like this and sample grabber in one shot mode accepts a video frame and indicates completion.

A checklist of potential issues includes:

  1. your video device does not deliver frames, sample grabber waits forever
  2. you requested conversion to 24-bit RGB and your camera is more likely to support other video formats, so you perhaps have another converter filter inserted for you - video frames might leave camera filter and are lost in converter; you are interested in reviewing effective graph you built
  3. you built graph incorrectly and your sample grabber is not actually connected right

In any event your first thing to do is simply to break in with debugger and check threads, you might be seeing something wrong right on the spot. Then your second thing to do is to find out how to review your filter graphs. This is a must for any DirectShow development.

S_FALSE return is typical for graphs having a live source in topology. This is fine.

Community
  • 1
  • 1
Roman R.
  • 68,205
  • 6
  • 94
  • 158