如何实现DirectShow source filter
实现DirectShow source filter有两大类方法
一、从CSource类派生出一个source filter。在这个类中内嵌一个CSourceStream的派生类用于实际数据帧的产生与传递。这类实现可以参考DirectShow SDK push source的例子
https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/multimedia/directshow/filters/pushsource/PushSourceBitmap.cpp
https://msdn.microsoft.com/en-us/library/windows/desktop/dd757807(v=vs.85).aspx
http://blog.csdn.net/rageliu/article/details/621157
二、从CBaseFilter和CBaseOutputPin两个更基础的类派生,从而实现source filter。这类实现可以参考codeproject的文章和pjsip的实现
https://www.codeproject.com/articles/158053/directshow-filters-development-part-live-source
https://github.com/chakrit/pjsip/blob/master/pjmedia/src/pjmedia-videodev/dshowclasses.cpp
先研究第一种实现方法。从DirectShow SDK push source的例子来看,CSource派生类的实现都非常简单,无非在构造函数中生成一个CSourceStream派生的pin对象,在析构函数中将pin删除。主要的代码实现都在CSourceStream的派生类。而CSourceStream要重载的主要方法包括
HRESULT GetMediaType(CMediaType *pMediaType);
HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest);
HRESULT FillBuffer(IMediaSample *pSample);
GetMediaType接口用于返回pin接受的媒体格式。GetMediaType有两种形式的接口,一种是一个参数的,另外一种是两个参数的
HRESULT GetMediaType(CMediaType *pMediaType);
HRESULT GetMediaType(int iPosition, CMediaType *pmt);
一个参数的版本适用于pin只提供一种输出格式的情况。如果pin支持多种格式的输出,需要使用第二种接口,同时重载CSourceStream::CheckMediaType接口。
CMediaType类是对AM_MEDIA_TYPE结构体的封装。这个结构体要赋值的成员还是挺多的,如何设置可以参考例子当中的GetMediaType函数
http://blog.csdn.net/zhengxinwcking/article/details/30475869
DecideBufferSize接口用于设置需要多大的buffer。CSourceStream类的父类CBasePin中有个m_mt的成员保存了GetMediaType设置的媒体格式。一份buffer的大小通常就是GetMediaType中设置的pmt->lSampleSize。一般用一份buffer也够了。
FillBuffer接口用于填写每一帧的音视频数据。这个接口一般先调用IMediaSample::GetPointer获得新一帧数据的缓冲,然后把数据写入这个缓冲。接着调用IMediaSample::SetTime设置帧的时间戳。最后调用IMediaSample::SetSyncPoint设置该帧是否为关键帧。对于提供非压缩数据的source filter,每一帧都应该设置为TRUE。
如果source filter的下游是vmr7(vmr9测试没有这个问题),还需要多实现两个接口
HRESULT CheckMediaType(const CMediaType *pMediaType)
STDMETHODIMP Notify(IBaseFilter *pSelf, Quality q)
vmr7要自己实现CheckMediaType的原因是,如果不重载CheckMediaType,DecideBufferSize函数中调用IMemAllocator::SetProperties会报错,返回E_FAIL,从而导致FillBuffer不会被调用(更正,如果IMemAllocator::SetProperties,则DecideBufferSize也应该报错,这样仍可以运行)。stackoverflow上面有一个类似的问题
https://stackoverflow.com/questions/7928828/negotiating-an-allocator-between-directshow-filters-fails
上面的解释是,VMR/EVR filter所需要的buffer stride会跟source filter GetMediaType设置的图像宽度不一致(显存需要字节对齐),导致格式发生改变。当VMR/EVR改变格式数据的时候,它会调用IPin::QueryAccept询问上游filter是否接受这个格式。CSourceStream实现的CheckMediaType是这样的
HRESULT CSourceStream::CheckMediaType(const CMediaType *pMediaType)
{
CAutoLock lock(m_pFilter->pStateLock());
CMediaType mt;
GetMediaType(&mt);
if (mt == *pMediaType) {
return NOERROR;
}
return E_FAIL;
}
它会调用我们派生类实现的GetMediaType获得一个CMediaType,然后将这个CMediaType和下游传过来的CMediaType作比较。如果不一致,就返回E_FAIL。而CMediaType的==运算符是这样定义的
BOOL
CMediaType::operator == (const CMediaType& rt) const
{
// I don't believe we need to check sample size or
// temporal compression flags, since I think these must
// be represented in the type, subtype and format somehow. They
// are pulled out as separate flags so that people who don't understand
// the particular format representation can still see them, but
// they should duplicate information in the format block.
return ((IsEqualGUID(majortype,rt.majortype) == TRUE) &&
(IsEqualGUID(subtype,rt.subtype) == TRUE) &&
(IsEqualGUID(formattype,rt.formattype) == TRUE) &&
(cbFormat == rt.cbFormat) &&
( (cbFormat == 0) ||
pbFormat != NULL && rt.pbFormat != NULL &&
(memcmp(pbFormat, rt.pbFormat, cbFormat) == 0)));
}
通过调试,发现上游传过来的格式是用VIDEOINFO结构体的,而且biHeight是负数,因此会被误判为格式不相同。因此需要我们自己重载实现CheckMediaType
Notify虽然没什么鸟用,但是必须实现。否则CBasePin::Notify会抛出断言错误
CBasePin::Notify(IBaseFilter * pSender, Quality q)
{
UNREFERENCED_PARAMETER(q);
UNREFERENCED_PARAMETER(pSender);
DbgBreak("IQualityControl::Notify not over-ridden from CBasePin. (IGNORE is OK)");
return E_NOTIMPL;
} //Notify