天天看点

(DirectX系列05)DirectShow 视频采集系统设备枚举构建Capture Graph Builder设备与滤波器捆绑视频预览、视频保存

    前几小节陆续介绍了Directsound、Directshow音频处理方面的技术,还没有介绍视频方面的技术,从这节开始介绍视频采集方面的技术。今日刚好就介绍Directshow视频采集技术,其实DirectShow视频采集并不复杂,复杂的部分微软已经帮做好了,只需按照顺序将API连接起来即可,如下所叙述;

    系统设备枚举器为我们按类型枚举已注册在系统中的Fitler提供了统一的方法。而且它能够区分不同的硬件设备,即便是同一个Filter支持它们。这对那些使用Windows驱动模型和KSProxy Filter的设备来说是非常有用的。系统设备枚举器对它们按不同的设备实例进行对待。当我们利用系统设备枚举器查询设备的时候,系统设备枚举器为特定类型的设备(如,音频捕获和视频压缩)生成了一张枚举表(Enumerator)。类型枚举器(Category enumerator)为每个这种类型的设备返回一个Moniker,类型枚举器自动把每一种即插即用的设备包含在内。

按如下的步骤使用系统设备枚举器:

1. 调用方法CoCreateInstance生成系统设备枚举器。类标识(CLSID)为CLSID_SystemDeviceEnum。

2. 调用ICreateDevEnum::CreateClassEnumerator方法生成类型枚举器,参数为你想要得到的类型的CLSID,该方法返回一个IEnumMoniker接口指针,如果指定的类型(是空的)或不存在,函数ICreateDevEnum::CreateClassEnumerator将返回S_FALSE而不是错误代码,同时IEnumMoniker指针(译注:通过参数返回)也是空的,这就要求我们在调用CreateClassEnumerator的时候明确用S_OK进行比较而不是使用宏SUCCEEDED。

3. 使用IEnumMoniker::Next方法依次得到IEnumMoniker指针中的每个moniker。该方法返回一个IMoniker接口指针。当Next到达枚举的底部,它的返回值仍然是S_FALSE,这里我们仍需要用S_OK来进行检验。

4. 想要得到该设备较为友好的名称(例如想要在用户界面中进行显示),调用IMoniker::BindToStorage方法。

5. 如果想要生成并初始化管理该设备的Filter调用3返回指针的IMonitor::BindToObject方法,接下来调用IFilterGraph::AddFilter把该Filter添加到视图中。

    通过CoCreateInstance函数一一实例化IGraphBuilder和ICaptureGraphBuilder2接口,并将IGraphBuilder初始化到滤波器链表管理器中,方便控制滤波器链表,如下代码;

// 创建IGraphBuilder接口

hr = CoCreateInstance(CLSID_FilterGraph, NULL,

                    CLSCTX_INPROC_SERVER,

                    IID_IGraphBuilder, (void **)&m_pGB);

// 创建ICaptureGraphBuilder2接口

hr = CoCreateInstance(CLSID_CaptureGraphBuilder2 , NULL,

                    CLSCTX_INPROC,

                    IID_ICaptureGraphBuilder2, (void **)&m_pCapture);

// 初始化滤波器链表管理器IGraphBuilder

m_pCapture->SetFiltergraph(m_pGB);

// 查询媒体控制接口

hr = m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC);

// 查询视频窗口接口

hr = m_pGB->QueryInterface(IID_IVideoWindow, (LPVOID *) &m_pVW);

    通过上面的构建,接下来就要将选择的设备和滤波器进行绑定,绑定的步骤与枚举有点相似,其中还需要一个枚举的过程,其中通过index角标来进行指定设备的绑定,代码如下;

    ICreateDevEnum *pCreateDevEnum;

    HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,

                    IID_ICreateDevEnum,

                    (void**)&pCreateDevEnum);

    if (hr != NOERROR) return false;

    IEnumMoniker *pEm;

    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEm, 0);

    pEm->Reset();

    ULONG cFetched;

    IMoniker *pM;

    int index = 0;

    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK, index     {

        IPropertyBag *pBag;

        hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);

        if(SUCCEEDED(hr))

        {

            VARIANT var;

            var.vt = VT_BSTR;

            hr = pBag->Read(L"FriendlyName", &var, NULL);

            if (hr == NOERROR)

            {

                if (index == deviceId)

                {

      pM->BindToObject(0, 0, IID_IBaseFilter, (void**)pFilter);

                }

                SysFreeString(var.bstrVal);

            }           

            pBag->Release();

        }

        pM->Release();

        index++;

    }

在Directshow中视频预览需要先渲染媒体,把链表中滤波器连接起来,后再设置显示窗口,如下代码;

    // 渲染媒体,把链表中滤波器连接起来

    hr = m_pCapture->RenderStream( &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, m_pBF, NULL, NULL );

    /* 设置视频显示窗口的特性 */ 

    HRESULT hr;

    //ljz

    hr = m_pVW->put_Visible(OAFALSE);

    hr = m_pVW->put_Owner((OAHWND)m_hWnd);

    if (FAILED(hr)) return hr;

    hr = m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPCHILDREN);

    ResizeVideoWindow();

    经过前面的介绍,视频保存更加简单,直接调用SetOutputFileName函数和RenderStream函数即可,如下代码;

    // 先停止视频

    m_pMC->Stop();

    // 设置文件名,注意该函数的第二个参数的类型

    hr = m_pCapture->SetOutputFileName(&MEDIASUBTYPE_Avi, inFileName.AllocSysString(), &pMux, NULL );

    // 渲染媒体,连接所有滤波器

    hr = m_pCapture->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pBF, NULL, pMux );

    pMux->Release();

    // 回复视频

    m_pMC->Run();

经过上面的几步,一个基于DirectShow视频采集程序就可以完成了。

继续阅读