DirectShow Filter开发学习总结

学习DShow Filter的开发,主要针对于SourceFilter、TransInPlaceFilter和RenderFilter方面。Filter的开发并不需要我们从头到尾的编写代码,DShow为我们提供了许多类,通过这些类我们可以快速开发出良好的Filter。下面就这几种Filter的学习总结如下。

 

在DirectShow中,Filter一般被分成三类:SourceFilter、TransformFilter和RenderFilter。区分这三种Filter最直观的是区分它们所包含的Pin。SourceFilter有至少一个的输出Pin,而没有输入Pin;TransformFilter至少有一个输入Pin和至少一个的输出Pin。RenderFilter至少有一个输入Pin。那么什么是Pin呢?Pin和Filter一样,都是一种Com组件。Filter可以被看成是一个容器,每个Filter都至少包含一个Pin。而Pin的工作的就是连接两个Filter,传输和处理数据。两个Filter之间的连接其实就是两个Pin之间的连接。如下图:

而2个Pin之间连接过程实际上是Pin之间媒体类型的协商过程,简单来说:

1.调用输出PIN上的Connect方法

2.调用输入PIN上的ReceiveConnection方法

3.如果这两个方法都成功了,那么则连接成功。

当完成上面的步骤后,两个Filter之间还不能传输数据,因为还没有为它们分配内存空间。当上面步骤成功完成后:

4.在输出PIN上调用CompleteConnect方法,而这个方法就是两个Filter之间的内存的分配与管理。

 

这样,两个Filter之间就可以进行数据传输了。但是,两个Filter之间的数据是以什么样的形式、如何进行传输的呢?

 

Filter之间的数据传输是通过Pin来实现的。在连接着的2个PIN之间有一个Allocator。它是一个Sample分配器。它创建、管理一个或多个Sample。而这个Sample可以看做是数据的载体。我们传输的数据被封装在Sample中,数据传送时,输出PIN会调用IMemAllocator::GetBuffer方法得到一个Sample,然后我们可调用IMediaSample::GetPointer来得到这个Sample的内存地址,把数据填充好,然后传送给下一级Filter的输入PIN。

 

那么这个Sample是如何传送的呢?数据的传输方式有两种:推模式(Push mode)和拉模式(Pull mode)。

(一)推模式

推模式在DShow中主要用于实时源,如摄像头图像的采集。与它连接的下级Filter需要实现的Pin是IMemInputPin接口。它在数据传输时是主动的向下级Filter“推”数据。首先SourceFilter完成了数据的读取,然后会主动的调用下级Filter上IMemInputPin接口的IMemInputPin::Receive或IMemInputPin::ReceiveMultiple方法来传递数据。

(二)拉模式

拉模式在DShow中主要用于文件源,如一个视频文件的回放。要使用拉模式需要实现IAsyncReader接口。它的数据传输的时间和推模式相反。它是被动的提供数据。在需要数据时,由它上级的Filter的输入Pin主动来请求数据,调用IAsyncReader接口上的方法,它则被动的去执行。IAsyncReader数据处理方法有三个:IAsyncReader::Request、IAsyncReader::SyncReadAligned、IAsyncReader::SyncRead。第一个是异步的,后二个是同步的。

当然,推/拉模式只是数据传输的两种方式,开发过程中你可以任意的选择。但是还是推荐按照DShow建议:推模式在实时源时使用,拉模式在文件源时使用。因为这样有时会减少许多不必要的工作。

 

最后,当一个SourceFilter结束发送数据流时,它调用和它连接的filter的输入pin的IPin::EndOfStream,然后下游的filter再依次通知与之相连的filter。当EndOfStream方法一直调用到renderer filter的时候,最后的一个filter就给filter图表管理器发送一个EC_COMPLETE事件通知。如果renderer有多个输入pin,当所有的输入pin都接收到end of stream通知的时候,它才会给filter图表管理器发送一个EC_COMPLETE事件通知。在一些情况下,下游的filter可能比SourceFilter更早的发现数据流的结束。在这种情况下,下游filter发送 结束stream的通知,同时, IMemInputPin::Receive函数返回S_FALSE直到图表管理器停止。这个返回值提示源filter停止发送数据。缺省的情况下,filter图表管理器并不将EC_COMPLETE事件通知发送给应用程序,当所有的数据流都发送了EC_COMPLETE事件通知后,它才给应用程序发送一个EC_COMPLETE事件通知。所以,应用程序只有在所有的数据流停止的时候才能接收到这个通知。

 

  • SourceFilter

简单来说SourceFilter就是一个提供数据的Filter。这个数据可以是本地音视频流、图片数据、实时采集数据等。

这里将分别给出推模式和拉模式的例子。DShow提供了许多类供我们开发Filter,我们可以从这些基类中派生出我们的新类,从而简化开发流程。

 

  • 推模式例子(BMP图片)

虽然说推模式主要用于实时源,但这里给的例子是用拉模式推送文件源。这个Filter的功能就是向下一级Filter主动的推送图片数据,最后由系统的RenderFilter显示出来。

 

  1. Filter注册。

不同Filter的注册都是大同小异的。创建至少一个GUID,然后在CPP写上下面代码(红色部分按需修改):

const AMOVIESETUP_MEDIATYPE sudPinTypes =

{

&MEDIATYPE_NULL,            // Major type

&MEDIASUBTYPE_NULL          // Minor type

};

const AMOVIESETUP_PIN sudPins =

{

L"Output",                   // Pin string name(Pin的名字)

FALSE,                      // Is it rendered()

TRUE,                       // Is it an output(输出还是输入Pin?)

FALSE,                      // Allowed none

FALSE,                      // Likewise many

&CLSID_NULL,                // Connects to filter

L"Input",                  // Connects to pin

1,                          // Number of types

&sudPinTypes                // Pin information

};

 

const AMOVIESETUP_FILTER sudFilter =

{

&CLSID_SyncSourceFilter,       // Filter CLSID

L"SyncSourceFilter",           // String name

MERIT_DO_NOT_USE,           // Filter merit

1,                          // Number pins

&sudPins                    // Pin details

};

 

CFactoryTemplate g_Templates[]= {

L"SyncSourceFilter", &CLSID_SyncSourceFilter, CFileSource::CreateInstance, NULL, &sudFilter

};

int g_cTemplates = 1;//个数

 

2.首先为SourceFilter选择一个基类,这里选择CSource。SourceFilter包含一个或多个输出Pin(这里为1个)。这个输出Pin派生于CSourceStream(这里为CFileStream)。

要实现SourceFilter上的输出Pin,需要:

  1. CSourceStream派生出一个新类
  2. 重写CSourceStream::GetMediaType方法(可在这里面定义我们的数据的类型)和     CSourceStream::CheckMediaType(可选)来验证Pin上的媒体类型。
  3. 实现CBaseOutputPin::DecideBufferSize来决定Buffer的大小。
  4. 实现CSourceStream::FillBuffer来为Buffer填充数据

后面具体说明。

3.在SourceFilter类中实现

static CUnknown * WINAPI CreateInstance(IUnknown *pUnk, HRESULT *phr)

{

CFileSource *pNewFilter = new CFileSource(pUnk, phr );

    if (phr)

    {

        if (pNewFilter == NULL)

            *phr = E_OUTOFMEMORY;

        else

            *phr = S_OK;

    }

    return pNewFilter;

}

 

4. 在SourceFilter类中定义一个CSourceStream的指针成员变量m_pPin,并在SourceFilter类的构造函数中创建输出Pin的对象。并构造基类。

CFileSource::CFileSource(IUnknown *pUnk, HRESULT *phr)

           : CSource(NAME("SyncFileSource"), pUnk, CLSID_SyncSourceFilter)

{

    m_pPin = new CFileStream(phr, this);

    if (phr)

    {

        if (m_pPin == NULL)

            *phr = E_OUTOFMEMORY;

        else

            *phr = S_OK;

    }  

}

SourceFilter类到这里就完成了,下面来看CFileStream类。

1.在CFileStream的头文件中添加成员变量:

BITMAPINFO *m_pBitInfo;  //指向BMP信息结构体指针

DWORD       m_cbBitmapInfo; //BMP信息结构体大小

HANDLE m_hFile;

BYTE * m_pFile; //指向文件数据的开头

BYTE * m_pImageData;  //指向文件中图片数据(不包含BMP头)的开头 

int m_iFrameNumber;//帧数

const REFERENCE_TIME m_rtFrameLength;//每秒帧数

CCritSec m_csPin;//临界区

2.在CFileStream的构造函数中初始化:

CFileStream::CFileStream(HRESULT *phr, CSource *pFilter)

      : CSourceStream(NAME("SyncFileSourceOutPin"), phr, pFilter, L"Out"),//基类构造

        m_pBitInfo(0),

        m_cbBitmapInfo(0),

        m_hFile(INVALID_HANDLE_VALUE),

        m_pFile(NULL),

        m_pImageData(NULL),

        m_iFrameNumber(0),

        m_rtFrameLength(10000000/5)

{

    m_hFile = CreateFile(L"d:\\1.bmp", GENERIC_READ, 0, NULL, OPEN_EXISTING,

                         FILE_ATTRIBUTE_NORMAL, NULL);

    if (m_hFile == INVALID_HANDLE_VALUE)

    {       

            *phr =S_FALSE;

            return;      

    }

    DWORD dwFileSize = GetFileSize(m_hFile, NULL);

    if (dwFileSize == INVALID_FILE_SIZE)

    {    

       *phr =S_FALSE;

        return;

    }

    m_pFile = new BYTE[dwFileSize];

    if(!m_pFile)

    {       

        *phr = E_OUTOFMEMORY;

        return;

    }

    DWORD nBytesRead = 0;

    if(!ReadFile(m_hFile, m_pFile, dwFileSize, &nBytesRead, NULL))

    { *phr =S_FALSE;

        return;

    }

    int cbFileHeader = sizeof(BITMAPFILEHEADER);

    BITMAPFILEHEADER *pBm = (BITMAPFILEHEADER*)m_pFile;

    m_cbBitmapInfo = pBm->bfOffBits - cbFileHeader;

    m_pBitInfo = (BITMAPINFO*)(m_pFile + cbFileHeader);//取得BMP图片信息起始地址

    m_pImageData = m_pFile + cbFileHeader + m_cbBitmapInfo;//取得BMP图片的数据

    CloseHandle(m_hFile);

    m_hFile = INVALID_HANDLE_VALUE;

}

3.实现 CFileStream::GetMediaType方法:

因为我们写的是SourceFilter,因此我们需要在这里为我们推送的数据设置好它的数据类型。需要说明的是在2个Pin连接的时候,双方都会调用GetMediaType和CheckMediaType方法来检测双方的媒体类型,当双方都对某种媒体类型达成一致时,那么这个媒体类型协商的过程就完成了。

在DShow中,媒体类型是用来描述一定格式的数据流。它是一个结构体:

typedef struct  _MediaType {

    GUID      majortype;

    GUID      subtype;

    BOOL      bFixedSizeSamples;

    BOOL      bTemporalCompression;

    ULONG     lSampleSize;

    GUID      formattype;

    IUnknown  *pUnk;

    ULONG     cbFormat;

    [size_is(cbFormat)] BYTE *pbFormat;

} AM_MEDIA_TYPE;

从上可以看出媒体类型主要分3类:majortype:主类型

subtype:辅助格式说明

formattype:格式细节说明

 实现 CFileStream::GetMediaType:

HRESULT CFileStream::GetMediaType(CMediaType *pMediaType)

{

    CAutoLock cAutoLock(m_pFilter->pStateLock());

    CheckPointer(pMediaType, E_POINTER);  

    if (!m_pImageData)

        return E_FAIL;   

    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER*)pMediaType->AllocFormatBuffer(SIZE_PREHEADER + m_cbBitmapInfo);//这里为pMediaType分配格式数据块,如果数据块是存在的那么就释放。

    if (pvi == 0)

        return(E_OUTOFMEMORY);

    ZeroMemory(pvi, pMediaType->cbFormat);   

    pvi->AvgTimePerFrame = m_rtFrameLength;  //每秒帧

    memcpy(&(pvi->bmiHeader), m_pBitInfo, m_cbBitmapInfo);   

    pvi->bmiHeader.biSizeImage  = GetBitmapSize(&pvi->bmiHeader);   

    SetRectEmpty(&(pvi->rcSource));

    SetRectEmpty(&(pvi->rcTarget));

    pMediaType->SetType(&MEDIATYPE_Video);//设置主类型

    pMediaType->SetFormatType(&FORMAT_VideoInfo);

    pMediaType->SetTemporalCompression(FALSE);

    const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);

    pMediaType->SetSubtype(&SubTypeGUID);

    pMediaType->SetSampleSize(pvi->bmiHeader.biSizeImage);

    return S_OK;

}

 

4. CFileStream::DecideBufferSize实现

从函数名字就可看出这个函数要做什么事:决定一个Sample的大小

HRESULT CFileStream::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest)

{

    HRESULT hr;

    CAutoLock cAutoLock(m_pFilter->pStateLock());

    CheckPointer(pAlloc, E_POINTER);

    CheckPointer(pRequest, E_POINTER);

    if (!m_pImageData)

        return E_FAIL;

    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER*) m_mt.Format();  

    if (pRequest->cBuffers == 0)

    {

        pRequest->cBuffers = 2;

    }

    pRequest->cbBuffer = pvi->bmiHeader.biSizeImage;//这里就是Buffer的大小,这里取的是BMP图片大小

    ALLOCATOR_PROPERTIES Actual;

    hr = pAlloc->SetProperties(pRequest, &Actual);

    if (FAILED(hr))

    {

        return hr;

    }

    if (Actual.cbBuffer < pRequest->cbBuffer)

    {

        return E_FAIL;

    }

    return S_OK;

}

5.实现CFileStream::FillBuffer (IMediaSample *pSample)

在这个函数中,我们就要为pSample填充数据,使用pSample->GetPointer可以得到内存指针,使用pSample->GetSize可以得到大小。

HRESULT CFileStream::FillBuffer(IMediaSample *pSample)

{

    BYTE *pData;

    long cbData;

    CheckPointer(pSample, E_POINTER);

    if (!m_pImageData)

        return E_FAIL;

    CAutoLock cAutoLockShared(&m_csPin);  

    pSample->GetPointer(&pData);

    cbData = pSample->GetSize();  

    ASSERT(m_mt.formattype == FORMAT_VideoInfo);

    VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)m_mt.pbFormat;  

    memcpy(pData, m_pImageData, min(pVih->bmiHeader.biSizeImage, (DWORD) cbData));   

    REFERENCE_TIME rtStart = m_iFrameNumber * m_rtFrameLength;

    REFERENCE_TIME rtStop  = rtStart + m_rtFrameLength;

    pSample->SetTime(&rtStart, &rtStop);

    m_iFrameNumber++;  

    pSample->SetSyncPoint(TRUE);

    return S_OK;

}

到这里,一个推模式的SourceFilter就完了。纵观整体,不难发现实现这个Filter我们所要完成的事仅仅是重写几个函数: CSourceStream::GetMediaTypeCBaseOutputPin::DecideBufferSizeCSourceStream::FillBuffer 还是非常简单的。当然,这几个方法是最少也是必须重写的,还有一些方法还是可以重写,具体的方法可参见MSDN。

 

(二)拉模式的SourceFilter(视频播放)

拉模式的SourceFilter要比推模式的SourceFilter要复杂一些,但是通过基类来完成也并不是很困难。在拉模式中,Filter推荐使用CBaseFilter,Pin推荐使用CBasePin,当然IAsyncReader接口是必须要实现的。

  • CBaseFilter是一个抽象类,要实现这个类,至少需要以下步骤:

1.从CBaseFilter派生一个新类

2.为这个新类添加一个Pin的成员变量,这个Pin必须是继承CBasePin而来。

3.重写纯虚函数CBaseFilter::GetPin来返回这个Filter上的Pin。

4.重写纯虚函数CBaseFilter::GetPinCount,返回这个Filter上的Pin的个数。

  • 要实现CBasePin,仍然需要派生出一个新类,并且必须至少重写下面的方法

1. CBasePin::CheckMediaType ,在Pin连接时检查媒体类型时调用

2. CBasePin::GetMediaType ,在Pin连接时取得媒体类型时调用,你可以在这里修改媒体类型。

3.IPin::BeginFlush开始清除数据。这个方法我们不需要手动调用,由其他Filter来调用以清除graph中的数据。

4. IPin::EndFlush结束清除数据。同BeginFlush类似。

  • 重写IAsyncReader的所有方法,因为它们都是抽象的

1.IAsyncReader::BeginFlush,IAsyncReader::EndFlush,类似上面的。

2. IAsyncReader::Length,返回数据流的总长度以及可用长度。

3. IAsyncReader::RequestAllocator,在Pin连接的时候会调用它,当Pin的媒体类型协商成功之后,为Pin之间分配和管理内存的。

4. IAsyncReader::Request,异步请求数据,需要字节对齐。

5. IAsyncReader::SyncReadAligned,同步对齐请求数据,需要字节对齐。

6. IAsyncReader::SyncRead,同步请求数据,不需要字节对齐。

7. IAsyncReader::WaitForNext,等待请求的执行完成

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值