前几小节陆续介绍了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添加到视图中。
构建Capture Graph Builder
通过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,
CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum,
(void**)&pCreateDevEnum);
if (hr != NOERROR) return false;
IEnumMoniker *pEm;
hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEm, 0);
if (hr != NOERROR) return false;
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);
if (FAILED(hr)) return hr;
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视频采集程序就可以完成了。