6

After looking at a very similar question and seeing almost identical code, I've decided to ask this question separately. I want to show a video preview of the webcam's video stream to the default window DirectShow uses, and I also want the ability to "take a picture" of the video stream at any given moment.

I started with the DirectShow examples on MSDN, as well as the AMCap sample code, and have something I believe should should the preview part, but does not. I've found no examples of grabbing an image from the video stream except using SampleGrabber, which is deprecated and therefore I am trying not to use it.

Below is my code, line for line. Note that most of the code in EnumerateCameras is commented out. That code would've been for attaching to another window, which I don't want to do. In the MSDN documentation, it explicitly states that the VMR_7 creates its own window to display the video stream. I get no errors in my app, but this window never appears.

My question then is this: What am I doing wrong? Alternatively, if you know of a simple example of what I am trying to do, link me to it. AMCap is not a simple example, for reference.

NOTE: InitalizeVMR is for running in windowless state, which is my ultimate goal (integrating into a DirectX game). For now, however, i just want it to run in the simplest mode possible.

EDIT: The first portion of this question, that is previewing the camera stream, is solved. I am now just looking for an alternative to the deprecated SampleGrabber class so I can snap a photo at any moment and save it to a file.

EDIT: After looking for almost an hour on google, the general concensus seems to be that you HAVE to use ISampleGrabber. Please let me know if you find anything different.

Testing code (main.cpp):

CWebcam* camera = new CWebcam();    
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);    
MessageBox(NULL, L"text", L"caption", NULL);    
if (SUCCEEDED(hr))
{       
camera->Create();       
camera->EnumerateCameras();     
camera->StartCamera();  
}   
int d;  
cin >> d;

Webcam.cpp:

#include "Webcam.h"

CWebcam::CWebcam() {
    HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    //m_pTexInst = nullptr;
    //m_pTexRes = nullptr;
}

CWebcam::~CWebcam() {
    CoUninitialize();
    m_pDeviceMonikers->Release();
    m_pMediaController->Release();
}

BOOL CWebcam::Create() {
    InitCaptureGraphBuilder(&m_pFilterGraph, &m_pCaptureGraph);
    hr = m_pFilterGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaController);
    return TRUE;
}

void CWebcam::Destroy() {
}

void CWebcam::EnumerateCameras() {  
    HRESULT hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &m_pDeviceMonikers);
    if (SUCCEEDED(hr))
    {
        //DisplayDeviceInformation(m_pDeviceMonikers);
        //m_pDeviceMonikers->Release();

        IMoniker *pMoniker = NULL;
        if(m_pDeviceMonikers->Next(1, &pMoniker, NULL) == S_OK)
        {
            hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&m_pCameraFilter);
            if (SUCCEEDED(hr))
            {
                hr = m_pFilterGraph->AddFilter(m_pCameraFilter, L"Capture Filter");
            }
        }

        // connect the output pin to the video renderer
        if(SUCCEEDED(hr))
        {
            hr = m_pCaptureGraph->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, 
                m_pCameraFilter, NULL, NULL);
        }
        //InitializeVMR(hwnd, m_pFilterGraph, &m_pVMRControl, 1, FALSE);
        //get the video window that will be displayed from the filter graph
        IVideoWindow *pVideoWindow = NULL;
        hr = m_pFilterGraph->QueryInterface(IID_IVideoWindow, (void **)&pVideoWindow);
        /*if(hr != NOERROR)
        {
            printf("This graph cannot preview properly");
        }
        else
        {
            //get the video stream configurations
            hr = m_pCaptureGraph->FindInterface(&PIN_CATEGORY_CAPTURE,
                &MEDIATYPE_Video, m_pCameraFilter,
                IID_IAMStreamConfig, (void **)&m_pVideoStreamConfig);

            //Find out if this is a DV stream
            AM_MEDIA_TYPE *pMediaTypeDV;

            //fake window handle
            HWND window = NULL;
            if(m_pVideoStreamConfig && SUCCEEDED(m_pVideoStreamConfig->GetFormat(&pMediaTypeDV)))
            {
                if(pMediaTypeDV->formattype == FORMAT_DvInfo)
                {
                    // in this case we want to set the size of the parent window to that of
                    // current DV resolution.
                    // We get that resolution from the IVideoWindow.
                    IBasicVideo* pBasivVideo;

                    // If we got here, gcap.pVW is not NULL 
                    //ASSERT(pVideoWindow != NULL);
                    hr = pVideoWindow->QueryInterface(IID_IBasicVideo, (void**)&pBasivVideo);

                    /*if(SUCCEEDED(hr))
                    {
                        HRESULT hr1, hr2;
                        long lWidth, lHeight;

                        hr1 = pBasivVideo->get_VideoHeight(&lHeight);
                        hr2 = pBasivVideo->get_VideoWidth(&lWidth);
                        if(SUCCEEDED(hr1) && SUCCEEDED(hr2))
                        {
                            ResizeWindow(lWidth, abs(lHeight));
                        }
                    }
                }
            }

            RECT rc;
            pVideoWindow->put_Owner((OAHWND)window);    // We own the window now
            pVideoWindow->put_WindowStyle(WS_CHILD);    // you are now a child

            GetClientRect(window, &rc);
            pVideoWindow->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
            pVideoWindow->put_Visible(OATRUE);
        }*/
    }   
}

BOOL CWebcam::StartCamera() {
    if(m_bIsStreaming == FALSE)
    {
        m_bIsStreaming = TRUE;
        hr = m_pMediaController->Run();
        if(FAILED(hr))
        {
            // stop parts that ran
            m_pMediaController->Stop();
            return FALSE;
        }
        return TRUE;
    }
    return FALSE;
}

void CWebcam::EndCamera() {
    if(m_bIsStreaming)
    {
        hr = m_pMediaController->Stop();
        m_bIsStreaming = FALSE;
        //invalidate client rect as well so that it must redraw
    }
}

BOOL CWebcam::CaptureToTexture() {
    return TRUE;
}

HRESULT CWebcam::InitCaptureGraphBuilder(
  IGraphBuilder **ppGraph,  // Receives the pointer.
  ICaptureGraphBuilder2 **ppBuild  // Receives the pointer.
)
{
    if (!ppGraph || !ppBuild)
    {
        return E_POINTER;
    }
    IGraphBuilder *pGraph = NULL;
    ICaptureGraphBuilder2 *pBuild = NULL;

    // Create the Capture Graph Builder.
    HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, 
        CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuild );
    if (SUCCEEDED(hr))
    {
        // Create the Filter Graph Manager.
        hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
            IID_IGraphBuilder, (void**)&pGraph);
        if (SUCCEEDED(hr))
        {
            // Initialize the Capture Graph Builder.
            pBuild->SetFiltergraph(pGraph);

            // Return both interface pointers to the caller.
            *ppBuild = pBuild;
            *ppGraph = pGraph; // The caller must release both interfaces.
            return S_OK;
        }
        else
        {
            pBuild->Release();
        }
    }
    return hr; // Failed
}

HRESULT CWebcam::EnumerateDevices(REFGUID category, IEnumMoniker **ppEnum)
{
    // Create the System Device Enumerator.
    ICreateDevEnum *pSystemDeviceEnumerator;
    HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,  
        CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSystemDeviceEnumerator));

    if (SUCCEEDED(hr))
    {
        // Create an enumerator for the category.
        hr = pSystemDeviceEnumerator->CreateClassEnumerator(category, ppEnum, 0);
        if (hr == S_FALSE)
        {
            hr = VFW_E_NOT_FOUND;  // The category is empty. Treat as an error.
        }
        pSystemDeviceEnumerator->Release();
    }
    return hr;
}

void CWebcam::DisplayDeviceInformation(IEnumMoniker *pEnum)
{
    IMoniker *pMoniker = NULL;
    int counter = 0;

    while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
    {
        IPropertyBag *pPropBag;
        HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
        if (FAILED(hr))
        {
            pMoniker->Release();
            continue;  
        } 

        VARIANT var;
        VariantInit(&var);

        // Get description or friendly name.
        hr = pPropBag->Read(L"Description", &var, 0);
        if (FAILED(hr))
        {
            hr = pPropBag->Read(L"FriendlyName", &var, 0);
        }
        if (SUCCEEDED(hr))
        {
            printf("%d: %S\n", counter, var.bstrVal);
            VariantClear(&var); 
        }

        hr = pPropBag->Write(L"FriendlyName", &var);

        // WaveInID applies only to audio capture devices.
        hr = pPropBag->Read(L"WaveInID", &var, 0);
        if (SUCCEEDED(hr))
        {
            printf("%d: WaveIn ID: %d\n", counter, var.lVal);
            VariantClear(&var); 
        }

        hr = pPropBag->Read(L"DevicePath", &var, 0);
        if (SUCCEEDED(hr))
        {
            // The device path is not intended for display.
            printf("%d: Device path: %S\n", counter, var.bstrVal);
            VariantClear(&var); 
        }

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

HRESULT CWebcam::InitializeVMR(
    HWND hwndApp,         // Application window.
    IGraphBuilder* pFG,    // Pointer to the Filter Graph Manager.
    IVMRWindowlessControl** ppWc,  // Receives the interface.
    DWORD dwNumStreams,  // Number of streams to use.
    BOOL fBlendAppImage  // Are we alpha-blending a bitmap?
    )
{
    IBaseFilter* pVmr = NULL;
    IVMRWindowlessControl* pWc = NULL;
    *ppWc = NULL;

    // Create the VMR and add it to the filter graph.
    HRESULT hr = CoCreateInstance(CLSID_VideoMixingRenderer, NULL,
       CLSCTX_INPROC, IID_IBaseFilter, (void**)&pVmr);
    if (FAILED(hr))
    {
        return hr;
    }
    hr = pFG->AddFilter(pVmr, L"Video Mixing Renderer");
    if (FAILED(hr))
    {
        pVmr->Release();
        return hr;
    }

    // Set the rendering mode and number of streams.  
    IVMRFilterConfig* pConfig;
    hr = pVmr->QueryInterface(IID_IVMRFilterConfig, (void**)&pConfig);
    if (SUCCEEDED(hr)) 
    {
        pConfig->SetRenderingMode(VMRMode_Windowless);

        // Set the VMR-7 to mixing mode if you want more than one video
        // stream, or you want to mix a static bitmap over the video.
        // (The VMR-9 defaults to mixing mode with four inputs.)
        if (dwNumStreams > 1 || fBlendAppImage) 
        {
            pConfig->SetNumberOfStreams(dwNumStreams);
        }
        pConfig->Release();

        hr = pVmr->QueryInterface(IID_IVMRWindowlessControl, (void**)&pWc);
        if (SUCCEEDED(hr)) 
        {
            pWc->SetVideoClippingWindow(hwndApp);
            *ppWc = pWc;  // The caller must release this interface.
        }
    }
    pVmr->Release();

    // Now the VMR can be connected to other filters.
    return hr;
}
Roman R.
  • 68,205
  • 6
  • 94
  • 158
Darkhydro
  • 1,992
  • 4
  • 24
  • 43

1 Answers1

4

In windowless mode VMR would not create separate window. Since you started initialization for widnowless mode, you have to follow SetVideoClippingWindow with IVMRWindowlessControl::SetVideoPosition call to provide position within the window, see VMR Windowless Mode on MSDN.

Another sample code snippet for you: http://www.assembla.com/code/roatl-utilities/subversion/nodes/trunk/FullScreenWindowlessVmrSample01/MainDialog.h#ln188

Roman R.
  • 68,205
  • 6
  • 94
  • 158
  • I have not started initialization for windowless mode. InitializeVMR() is commented out. I've taken a quick look at the code sample provided, and was a little confused by all the ATL... calls, since i've never worked with atlcoll.h. I can make since of the code after stripping away these calls, but it looks like the sample is for an application where you create the window, not DirectShow. I would much rather have DirectShow create the window for me if at all possible. – Darkhydro Sep 26 '11 at 05:14
  • I checked your code once again, and what are you doing after StartCamera returns? You have to have a message loop there, so that filter graph could dispatch its messages it might be sending. Are you doing something like this? Maybe you need another MessageBox after StartCamera to check if this is the problem. – Roman R. Sep 26 '11 at 06:00
  • I was unaware I needed to process Windows messages for the windowed (compatability) mode. I am not processing any windows messages at the moment. Is there any processing I need to do or can I just set up a simple message loop and do nothing with the messages? – Darkhydro Sep 26 '11 at 15:36
  • You need just a regular message loop so that posted messages are dispatched. This is what I meant by putting another MessageBox - since it will pump the messages with a single line of code. – Roman R. Sep 26 '11 at 15:49
  • Ill try it and reeport back soon. Thanks – Darkhydro Sep 26 '11 at 19:31
  • Oh man... i did some debugging, and it looks like the error wasn't any of my core code, it was m_bIsStreaming! I never initialized it, so I never called MediaController->Run()... its the little things that count.. Thanks for your help though! I'll give you the answer if you can point me in the direction of an updated SampleGrabber class/API (not the old deprecated SampleGrabber class). – Darkhydro Sep 27 '11 at 00:47
  • There is no updated Sample Grabber class. There is one current which is marked deprecated, and there was another one with source code, in older SDK. The one deprecated is what everybody out there uses. Because of massive use, it will be kept with Windows for really long time. Since sample grabber definitions are taken out of SDK, you can do it like this: http://www.assembla.com/code/roatl-utilities/subversion/nodes/trunk/RenderStreamTest01/RenderStreamTest01.cpp#ln12 – Roman R. Sep 27 '11 at 05:43
  • Thanks for the code sample! I was definitely looking for a solution that didn't require me to include Qedit.h. I'll try putting this in my project and see if it works. I'm still pretty confused by the ATL calls, but from what I can see it's all error handling code and can be removed and replaced right? – Darkhydro Sep 27 '11 at 16:59
  • Exactly, all ATL stuff you see here is CComPtr<>, _ATLTRY, ATLASSERT, and a few other macros you can easily strip or replace with what you are used to. You must admit though that this makes code very compact. – Roman R. Sep 27 '11 at 17:02
  • I'm going to go ahead and try to code the rest of the project using the ISampleGrabber struct you have in your sample, but I do have a question. I understand that it is an interface, but without any underlying functionality how does this class work?? It doesn't seem to me that it will do anything. – Darkhydro Sep 27 '11 at 23:57
  • another status update. I've found a forum post by you on http://social.msdn.microsoft.com/Forums/en/windowsdirectshowdevelopment/thread/fa4ae433-d613-49ee-9552-637de5f7e003, which references using the "standard" sample grabber. Where is this, and it is it available without getting old SDKs? – Darkhydro Sep 28 '11 at 00:38
  • And success! Your code sample led down the right path, and now I have both parts working! Thanks for helping me through this Roman. If you feel like it, take a look at the code line below, which i made up because I didn't know what to do to replace CLSID_SampleGrabber. Not sure if it's safe, but hey it works at least! hr = CoCreateInstance(__uuidof(SampleGrabber), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pGrabberBaseFilter)); – Darkhydro Sep 28 '11 at 02:06
  • I need access to the Null Renderer class in the same manner as the Sample Grabber. Is there a similar way to do this, by just adding a declaration in a header file with a unique uuid? – Darkhydro Oct 21 '11 at 21:36
  • Yes, define CLSID in code and you will be able to instantiate it. You won't need any specific interfaces for `Null Renderer Filter`. – Roman R. Oct 22 '11 at 05:55