在最近的一个项目中,需要在.NET CF中进行实时视频分析。然而,.NET API只允许拍摄静态图片或录制视频,并没有提供直接访问视频帧缓冲区的方法,以便能即时解析来自摄像头的帧。因此,决定使用DirectShowAPI,它提供了更好的视频流控制,但在Windows完整版DirectShow库中才有的ISampleGrabber接口在.NET CF中并不可用。剩下的选择是编写一个自定义的DirectShow过滤器,该过滤器实现了ISampleGrabber接口,允许开发者访问视频缓冲区数据。在本文中,将尝试解释在这个过程中所学到的知识。
可以使用示例代码作为编写自己的过滤器的起点,或者直接使用准备好的SampleGrabber过滤器在项目中。虽然在项目中使用了多个在线参考资料,但据所知,目前还没有一个完整的“如何”指南来解决这个问题。
在设置Visual Studio项目之前,需要安装Windows Mobile SDK、Windows CE 5.0和Windows CE 5.0 Platform Builder,后者包含了BaseClasses库。一般来说,不一定需要使用BaseClasses,但它绝对可以让工作变得更加轻松,所以利用了它。
首先创建一个支持ATL的智能设备DLL项目。还需要将Windows Mobile SDK和Platform Builder的头文件添加到包含路径中。
在这个例子中,将使用TransInPlaceFilter,这是TransformFilter的简化版本。不需要以任何方式更改数据,只需要将其传递给客户端应用程序。因此创建了一个CSampleGrabber类,它还实现了自定义接口以及任何附加函数。添加了RegisterCallback函数,该函数从客户端应用程序传递一个函数指针。这个函数将在MediaSample(一个视频帧)通过过滤器时被调用,因此客户端可以复制数据并进行一些处理。
class CSampleGrabber : public CTransInPlaceFilter, public ISampleGrabber {
private:
MANAGEDCALLBACKPROC callback;
long m_Width;
long m_Height;
long m_SampleSize;
long m_Stride;
public:
// instantiation
CSampleGrabber(IUnknown *pOuter, HRESULT *phr, BOOL ModifiesData);
~CSampleGrabber();
static CUnknown *WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr);
// IUnknown
DECLARE_IUNKNOWN;
STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv);
// CTransInPlaceFilter
HRESULT CheckInputType(const CMediaType *pmt);
HRESULT SetMediaType(PIN_DIRECTION direction, const CMediaType *pmt);
HRESULT Transform(IMediaSample *pMediaSample);
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties);
HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) {
return NOERROR;
}
// ISampleGrabber
STDMETHODIMP RegisterCallback(MANAGEDCALLBACKPROC mdelegate);
};
可以像实现任何其他过滤器一样实现所有接口。有趣的部分是Transform和RegisterCallback函数:
STDMETHODIMP CSampleGrabber::RegisterCallback(MANAGEDCALLBACKPROC mdelegate) {
// Set pointer to managed delegate
callback = mdelegate;
return S_OK;
}
HRESULT CSampleGrabber::Transform(IMediaSample *pMediaSample) {
long Size = 0;
BYTE *pData;
if (!pMediaSample)
return E_FAIL;
// Get pointer to the video buffer data
if (FAILED(pMediaSample->GetPointer(&pData)))
return E_FAIL;
Size = pMediaSample->GetSize();
// invoke managed delegate
if (callback)
callback(pData, Size);
return S_OK;
}
可以直接在DirectShow应用程序中使用SampleGrabber过滤器,就像使用任何其他过滤器一样。在尝试实例化它之前,重要的是要注册DLL。
// Create and initialize the SampleGrabber filter
CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void**)&pSampleGrabber);
m_pFilterGraph->AddFilter(pSampleGrabber, FILTERNAME);
// Get a pointer to the ISampleGrabber interface
pSampleGrabber->QueryInterface(IID_ISampleGrabber, (void**)&m_pISampleGrabber);
// Register the client callback
if (m_pISampleGrabber)
m_pISampleGrabber->RegisterCallback(&CGraphBuilder::OnSampleProcessed);
当客户端接收到帧样本时,它应该尽快将其复制到本地缓冲区并返回。这允许过滤器继续进行,而不必等待客户端处理数据,这将显著减慢整个图。
也可以创建一个C++ DLL,该DLL创建过滤器图并管理它,并通过P/Invoke从.NET CF应用程序中调用它。直接在.NET CF中创建过滤器图可能会更棘手,因为.NET CF缺乏C++ .NET支持,但也许是可能的。
一篇详细介绍编写DirectShowTransform过滤器的DirectShow和COM文章。
来自RadScorpion的博客的DirectShow信息。
来自Microsoft的Windows Mobile开发者中心。
来自MSDN的创建Transform过滤器。
来自MSDN的如何从Microsoft DirectShow过滤器图获取数据。
2008年8月23日 创建文章
2008年8月26日 v1.1 创建。
修复: