说明:本周的主要工作就是从摄像头获取YUV数据用于后期处理,从来没有用过DirectShow,做个记录方便以后参考。DirectShow本身并不提供采集到的图像数据的输出,根据文档,视频CAPTURE后只能输出为AVI或ASF格式,所以原始的YUV数据只能通过例子中附带的ISampleGrabberCB类得到。
1.参考程序
安装WindowsSDK后在安装目录下有.\Samples\multimedia\directshow\capture\amcap,其中是官方提供的音、视频采集例程。
2.数据类型及定义
ICaptureGraphBuilder2 *pCGB2;
IGraphBuilder *pGB;
IMoniker *pVid; // 视频采集设备指针
IBaseFilter *pVCap; // 用于绑定pVid的filter指针
ISampleGrabber *pVGrab; // 截取数据类指针
IBaseFilter *pVGrabFilter; // pVGrab中的filter指针
GrabberCB *pVGrabCB; // 截取数据后的callback类
其中ISampleGrabber是MS提供的用于截取数据的,与ISampleGrabberCB配合使用
class GrabberCB : public ISampleGrabberCB {
public:
ULONG STDMETHODCALLTYPE AddRef();
ULONG STDMETHODCALLTYPE Release();
HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject);
STDMETHODIMP SampleCB(double, IMediaSample *);
STDMETHODIMP BufferCB(double, BYTE *, long);
...
};
GrabberCB子类继承自ISampleGrabberCB,类中必须定义父类中的几个虚函数,前三个可以直接return,SampleCB定义了收到IMediaSample数据后的动作,而BufferCB则定义了收到原始数据后的动作
3.采集流程
3.1
实例化pCGB2和pGB,设置pGB为pCGB2中的graph
pCGB2->SetFiltergraph(pGB)
3.2
获取采集设备指针pVid,具体获取方式可以用枚举设备的方式,或者枚举时记录设备名称然后调用MkParseDisplayName函数
3.3
pVid->BindToObject(NULL, NULL, IID_IBaseFilter, (void **)&pVCap)
绑定pVCap到pVid,graph中只能用IBaseFilter
3.4
将pVCap加入graph中
pGB->AddFilter(pVCap, L"Video cap")
——————————–此处开始与amcap不同——————————–
3.5
实例化pVGrab并获取pVGrab的IBaseFilter,调用函数
pVGrab->QueryInterface(IID_IBaseFilter, (void **)&pVGrabFilter)
3.6
把pVGrabFilter加入graph
pGB->AddFilter(pVGrabFilter, L"Grabber")
这样就完成了graph的创建工作,如果与graphedt中的操作对应,以上完成的操作就是把采集设备和截取数据设备加入到视图中
3.7
连接graph中的filter
pCGB2->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pVCap, NULL, pVGrabFilter)
函数的最后三个参数是三个filter,如果把pVGrabFilter放在第二个位置而把第三个赋值NULL,则DirectShow自动添加默认输出,运行时会弹出预览窗口
3.8
设置pVGrab
pVGrab->SetBufferSamples(FALSE)
pVGrab->SetOneShot(FALSE)
MSDN上专门有ISampleGrabber的使用说明,第一个函数置FALSE,表明直接利用源数据而不另外copy一份;第二个函数置FALSE,表明连续截取数据
3.9
设置pVGrabCB
pVGrabCB = new GrabberCB;
pVGrab->SetCallback(pVGrabCB, 1);
创建pVGrabCB并在pVGrab指定回调类,其中第二个参数为0时调用SampleCB函数,为1时调用BufferCB函数
3.10
至此一个能够截取视频原始数据的graph已经完成了
IMediaControl *pMC = NULL;
pGB->QueryInterface(IID_IMediaControl, (void **)&pMC);
pMC->Run();
pMC->Release();
如上得到pGB的控制后运行即可
4.Q&A
Q1 怎么用ISampleGrabber和ISampleGrabberCB?
这两个类定义在qedit.h中,该头文件是较早版本的directx SDK中的,搜索下载即可,但是注意使用方法
#define __IDxtCompositor_INTERFACE_DEFINED__
#define __IDxtAlphaSetter_INTERFACE_DEFINED__
#define __IDxtJpeg_INTERFACE_DEFINED__
#define __IDxtKey_INTERFACE_DEFINED__
#include "qedit.h"
先定义前面的宏,否则编译时出错
Q2 用了DeleteMediaType()函数编译时出现错误”wcsrchr already defined in xxx”?
可以用以下方式释放内存
if (pMt->cbFormat)
{
CoTaskMemFree(LPVOID(pMt->pbFormat));
pMt->cbFormat = 0;
pMt->pbFormat = NULL;
}
if (pMt->pUnk)
{
pMt->pUnk->Release();
pMt->pUnk = NULL;
}
CoTaskMemFree(pMt);
pMt = NULL;
编译时遇到此问题时可以尝试
Q3 怎么使用硬件自身的配置窗口?
视频采集设备配置窗口分为两种:亮度、白平衡等设置窗口和分辨率、采集格式及帧率设置窗口,前者调用代码为
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
pVCap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
pSpec->GetPages(&cauuid);
OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&pVCap,
cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL);
CoTaskMemFree(cauuid.pElems);
pSpec->Release();
其中ghwndApp为程序窗口句柄,后面两个参数设置弹出窗口相对程序窗口的位置
后者调用代码
IAMStreamConfig *pSC;
pGB->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pVCap, IID_IAMStreamConfig, (void **)&pSC);
ISpecifyPropertyPages *pSpec;
CAUUID cauuid;
hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
...(同上)
可以看出前者直接从IBaseFilter即采集设备获取设置窗口,后者查询采集设备上的流设置信息并从该信息获取设置窗口
Q4 怎么直接设置视频采集分辨率、格式等参数?
// 设置参数,p1=宽,p2=高,p3=帧率
AM_MEDIA_TYPE *p = NULL;
IAMStreamConfig *pSC = NULL;
pCGB2->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pVCap,
IID_IAMStreamConfig, (void **)&pSC);
pSC->GetFormat(&p);
VIDEOINFOHEADER *pHd = (VIDEOINFOHEADER *)p->pbFormat;
// 单位为100ns,所以每帧(10^7/p3)*100ns
pHd->AvgTimePerFrame = 10000000 / p3;
// I420格式
pHd->bmiHeader.biCompression = MAKEFOURCC('I', '4', '2', '0');
pHd->bmiHeader.biWidth = p1;
pHd->bmiHeader.biHeight = p2;
// 一个像素点12位
pHd->bmiHeader.biBitCount = 12;
// 图像大小
pHd->bmiHeader.biSizeImage = p1 * p2 * 3 / 2;
// 图像传输率,单位bps
pHd->dwBitRate = pHd->bmiHeader.biSizeImage * 8 * p3;
// defined in wmsdkidl.h
p->subtype = WMMEDIASUBTYPE_I420;
p->lSampleSize = pHd->bmiHeader.biSizeImage;
pSC->SetFormat(p);
// free p
FreeAM_MEDIA_TYPE(p);
pSC->Release();