如何实现DirectShow source filter

2018-08-25  本文已影响0人  叶迎宪

实现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
上一篇 下一篇

猜你喜欢

热点阅读