directshow之filters 例程BALL的详细解析

directshow之filters 例程BALL的详细解析

 

BALL是一个推模式的source filter,其产生一个不断撞击窗口边界的小球。编译BALL

工程,将生成ball.ax文件,用regsvr32注册该文件,打开graphedit.exe插入Bouncing BallColor Space ConverterVideo Renderer三个filter,其中Bouncing Ballball.ax。将插入的三个filters连接起来,运行就可以看到下图所示的界面。

BALL工程中有三个类CBallCBallStreamCBouncingBall,其中CBall封装了小球的行为即产生每个图像时小球应在图像中的位置。CBouncingBall继承自CSourceCSource是一个基类在BaseClasses工程中实现,CSource一般作为source filters的基类使用,CSource继承于CBaseFilter,旨在简化source Filters组件的生成,支持连续数据源的生成,因此CBouncingBall为该工程的主filterCBallStream继承自CSourceStream,而CSourceStream又继承自CAMThreadCBaseOutputPin,其中CAMThread用于工作线程的生成,CBaseOutputPin使CBallStream类具有输出pin的特性。

1、 CBouncingBall CBallStream是怎么关联起来的?

CBouncingBall的构造函数中构造了一个CBallStream实例,并将该实例的指针赋值

m_paStreams变量,m_paStreamsCBouncingBall的父类CSource的变量,该变量用来保存SourceFilter拥有的pin。代码如下:

CBouncingBall::CBouncingBall(LPUNKNOWN lpunk, HRESULT *phr) :

   CSource(NAME("Bouncingball"),

           lpunk,

           CLSID_BouncingBall)

{

   CAutoLock cAutoLock(&m_cStateLock);

 

    m_paStreams    = (CSourceStream **) new CBallStream*[1];

    if (m_paStreams == NULL) {

        *phr = E_OUTOFMEMORY;

    return;

   }

 

    m_paStreams[0] = newCBallStream(phr, this, L"A Bouncing Ball!");

    if (m_paStreams[0] == NULL) {

        *phr = E_OUTOFMEMORY;

    return;

    }

 

} // (Constructor

2、原始数据是在什么位置产生的?

该工程的原始数据是CBallStream负责产生的,在该类的FillBuffer方法中,将产生的图像填充到MediaSample中。

HRESULT CBallStream::FillBuffer(IMediaSample *pms)

{

    BYTE *pData;

    long lDataLen;

   

    pms->GetPointer(&pData);//得到MediaSample中存储数据buffer的地址

    lDataLen = pms->GetSize();//得到存储数据buffer的大小

 

    // If true then we clear theoutput buffer and don't attempt to

    // erase a previous drawing ofthe ball - this will be the case

    // when we start running as thebuffer will be full of rubbish

    if( m_bZeroMemory ) {

        ZeroMemory( pData, lDataLen);

    }

 

    {

        CAutoLockcAutoLockShared(&m_cSharedState);

 

        // If we haven't justcleared the buffer delete the old

        // ball and move the ball on

 

        if( !m_bZeroMemory ){

            BYTE aZeroes[ 4 ] = { 0,0, 0, 0 };

           m_Ball->PlotBall(pData, aZeroes, m_iPixelSize);//画球

           m_Ball->MoveBall(m_rtSampleTime - (LONG) m_iRepeatTime);//移动球

        }

 

        m_Ball->PlotBall(pData,m_BallPixel, m_iPixelSize);//画球

 

        // The current time is thesample's start

        CRefTime rtStart =m_rtSampleTime;

 

        // Increment to find thefinish time

        m_rtSampleTime+= (LONG)m_iRepeatTime;//增加参考时间

 

       pms->SetTime((REFERENCE_TIME *) &rtStart,(REFERENCE_TIME *)&m_rtSampleTime);

    }

 

    m_bZeroMemory = FALSE;

    pms->SetSyncPoint(TRUE);

    return NOERROR;

} // FillBuffer

3、 产生的数据是怎么源源不断的传输给下游filter的?

Graph开始运行后会调用SourceFIlterpinActive函数,CBallStream没有

CSourceStreamActive方法,所以CSourceStreamActive方法会被调用,在Active方法中调用Create函数(CAMThread类的方法)来创建一个工作线程,线程函数为CAMThread::InitialThreadPoc,在InitialThreadPoc函数中调用CAMThread子类(CSourceStream及其子类)的threadpoc方法进行graph需求命令的解析和执行。由于CBallStream没有实现threadpoc方法,所以调用的是CSourceStreamthreadpoc方法。

   threadpoc方法中GetRequest,并根据GetRequest的返回值做出相应动作,当返回值为CMD_PAUSE时调用DoBufferProcessingLoop来生成和传输数据。DoBufferProcessingLoop函数中先调用GetDeliveryBuffer生成一个MediaSample,然后调用FillBufferCBallStream重载了该函数)填充该MediaSample,最后调用Deliver函数将填充后的MediaSample向下游filter传输。

 Deliver函数是CBallStream父类CSourceStream父类CBaseOutputPIn的方法,即调用filter的输出pinDeliver方法,Deliver方法中调用与该输出pin连接的输入pinReceive方法将MediaSample传送给下一个Filter。而输出pin是怎么知道和它连接的输入pin的呢?CBaseOutputPin基类中定义了一个m_pInputPin的变量用来保存和它连接的输入pin,在pin连接成功时将与其连接的pin保存到m_pInputPin的变量中,这样在MediaSample传输中直接调用m_pInputPin的接收方法,就可以将数据向下传输了。这也是pinpin直接沟通的方法,输出pin中有变量来保存与其相连的输入pin,通样的输入pin中也有变量来保存与其相连的输出pin的变量。

CSourceStreamActive函数:

HRESULT CSourceStream::Active(void) {

    CAutoLocklock(m_pFilter->pStateLock());

    HRESULT hr;

    if (m_pFilter->IsActive()) {

    return S_FALSE; // succeeded, but did not allocate resources(they already exist...)

    }

    // do nothing if not connected -its ok not to connect to

    // all pins of a source filter

    if (!IsConnected()) {

        return NOERROR;

    }

    hr = CBaseOutputPin::Active();

    if (FAILED(hr)) {

        return hr;

    }

    ASSERT(!ThreadExists());

// start the thread

// Create()函数来创建工作线程,该函数是CAMThread的方法,由于CAMThread//CSourceStream的一个父类,用来管理工作线程。

    if (!Create()) {

        return E_FAIL;

    }

 

    // Tell thread to initialize. IfOnThreadCreate Fails, so does this.

    hr = Init();

    if (FAILED(hr))

    return hr;

    returnPause();

}

CAMThreadCreate()方法:在CreateThread函数中用this作为参数传给线程函数InitialThreadProc,当在CSourceStream调用该函数时,this指向CSourceStream实例。

InitialThreadProc函数调用了this指向实例的ThreadProc函数,即CSourceStreamThreadProc函数

BOOL

CAMThread::Create()

{

    DWORD threadid;

 

    CAutoLocklock(&m_AccessLock);

 

    if (ThreadExists()) {

    return FALSE;

    }

  //创建线程,并以this作为线程函数的指针。

    m_hThread = CreateThread(

            NULL,

            0,

            CAMThread::InitialThreadProc,

            this,

            0,

            &threadid);

 

    if (!m_hThread) {

    return FALSE;

    }

    return TRUE;

}

CSourceStreamThreadProc函数: ThreadProc函数有dowhile循环来源源不断解析各个命名DWORD CSourceStream::ThreadProc(void) {

    HRESULT hr;  // the return code from calls

Command com;

//处理线程线程初始化

    do {

    com = GetRequest();

    if (com != CMD_INIT) {

        DbgLog((LOG_ERROR, 1, TEXT("Threadexpected init command")));

        Reply((DWORD) E_UNEXPECTED);

    }

    } while (com != CMD_INIT);

 

    DbgLog((LOG_TRACE, 1,TEXT("CSourceStream worker thread initializing")));

   // CSourceStream定义的函数,在该工程中没有做任何动作

   hr = OnThreadCreate(); // perform set up tasks

    if (FAILED(hr)) {

        DbgLog((LOG_ERROR, 1,TEXT("CSourceStream::OnThreadCreate failed. Aborting thread.")));

        OnThreadDestroy();

    Reply(hr);  // send failed return code from OnThreadCreate

        return 1;

    }

 

    // Initialisation suceeded

    Reply(NOERROR);

    //处理执行各种命令

    Command cmd;

    do {

    cmd = GetRequest();

    switch (cmd) {

    case CMD_EXIT:

        Reply(NOERROR);

        break;

    case CMD_RUN:

        DbgLog((LOG_ERROR, 1, TEXT("CMD_RUNreceived before a CMD_PAUSE???")));

        // !!! fall through???

    //DoBufferProcessingLoop()函数中调用FillBuffer Deliver函数产生传输数据。

    case CMD_PAUSE:

        Reply(NOERROR);

        DoBufferProcessingLoop();

        break;

    case CMD_STOP:

        Reply(NOERROR);

        break;

    default:

        DbgLog((LOG_ERROR, 1, TEXT("Unknowncommand %d received!"), cmd));

        Reply((DWORD) E_NOTIMPL);

        break;

    }

    } while (cmd != CMD_EXIT);

   

    hr = OnThreadDestroy();  // tidy up.

    if (FAILED(hr)) {

        DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadDestroyfailed. Exiting thread.")));

        return 1;

    }

    DbgLog((LOG_TRACE, 1,TEXT("CSourceStream worker thread exiting")));

    return 0;

}

CSourceStreamDoBufferProcessingLoop函数:该函数的dowhile结构来源源不断的产生、传输数据。在产生数据之前要先分配空间来保存数据,那么要分配多大的空间才算合适呢,这个由CBallStream类的DecideBufferSize(IMemAllocator*pAlloc,ALLOCATOR_PROPERTIES *pProperties)函数来告知内存分配对象Allocator。

HRESULT CSourceStream::DoBufferProcessingLoop(void) {

Command com;

// CSourceStream定义的函数,在该工程中没有做任何动作

OnThreadStartPlay();

//

do {

//检查com命令码是否有效

    while (!CheckRequest(&com)) {

    //分配MediaSample空间用于保存产生的样本数据

        IMediaSample *pSample;

        HRESULT hr =GetDeliveryBuffer(&pSample,NULL,NULL,0);

        if (FAILED(hr)) {

                Sleep(1);

        continue;   // go round again. Perhaps the error will goaway

                // or the allocator is decommited & wewill be asked to

                // exit soon.

        }

        // Virtual function user will override.

        //生成、填充数据

        hr = FillBuffer(pSample);

        if (hr == S_OK) {

        hr = Deliver(pSample);//传输数据

               pSample->Release();

                // downstream filterreturns S_FALSE if it wants us to

                // stop or an errorif it's reporting an error.

                if(hr != S_OK)

                {

                  DbgLog((LOG_TRACE,2, TEXT("Deliver() returned %08x; stopping"), hr));

                  return S_OK;

                }

        } else if (hr == S_FALSE) {

                // derived classwants us to stop pushing data

        pSample->Release();

        DeliverEndOfStream();

        return S_OK;

        } else {

                // derived classencountered an error

               pSample->Release();

        DbgLog((LOG_ERROR, 1,TEXT("Error %08lX from FillBuffer!!!"), hr));

               DeliverEndOfStream();

                m_pFilter->NotifyEvent(EC_ERRORABORT,hr, 0);

                return hr;

        }

            // all paths release thesample

    }

        // For all commands sent tous there must be a Reply call!

    if (com == CMD_RUN || com ==CMD_PAUSE) {

        Reply(NOERROR);

    } else if (com != CMD_STOP) {

        Reply((DWORD) E_UNEXPECTED);

        DbgLog((LOG_ERROR, 1, TEXT("Unexpectedcommand!!!")));

    }

    } while (com != CMD_STOP);

    return S_FALSE;

}

 

4、  CSourceStream类中线程函数中的命令码是怎么传输的?

CSourceStream类中线程函数中的命令码CMD_STOP等,并不是DrectShow通用的,只有CSourceStream类才实现了这样了机制(PULLPIN也实现了类似的机制TM_STOP等)。CMD_INIT这样的命令码定义在CSourceStream的头文件中。在程序指向过程中由CSourceStream发出,有CSourceStream的线程函数进行处理。在Craph开始、暂停、运行、停止时,CSourceStream放出响应的命令码,发送命令码时调用CAMThreadCallWorker传给CAMThread,在CSourceStream的线程函数中通过CAMThreadGetRequest或者CheckRequest函数获得命令码来进行响应的处理。

CSourceStream类中命令码的定义和发送

enum Command {CMD_INIT,CMD_PAUSE, CMD_RUN, CMD_STOP, CMD_EXIT};

    HRESULT Init(void) { returnCallWorker(CMD_INIT); }

    HRESULT Exit(void) { returnCallWorker(CMD_EXIT); }

    HRESULT Run(void) { returnCallWorker(CMD_RUN); }

    HRESULT Pause(void) { returnCallWorker(CMD_PAUSE); }

HRESULT Stop(void) { returnCallWorker(CMD_STOP); }

 

5、 pin连接构成中的媒体协商怎么实现的?

所谓的Filter Pin之间的连接,实际上是Pin之间Media Type(媒体类型)的一个协商过程。连接总是从输出Pin指向输入Pin的。要想深入了解具体的连接过程,就必须认真研读

SDK的基类源代码(位DXSDK\samples\Multimedia\DirectShow\BaseClasses\amfilter.cpp,类CBasePinConnect方法)。连接的大致过程为,枚举欲连接的输入Pin上所有的媒体类型,逐一用这些媒体类型与输出Pin进行连接,如果输出Pin也接受这种媒体类型,则Pin之间的连接宣告成功;如果所有输入Pin上枚举的媒体类型输出Pin都不支持,则枚举输出Pin上的所有媒体类型,并逐一用这些媒体类型与输入Pin进行连接。如果输入Pin接受其中的一种媒体类型,则Pin之间的连接到此也宣告成功;如果输出Pin上的所有媒体类型,输入Pin都不支持,则这两个Pin之间的连接过程宣告失败。

   CBallStream类中的GetMediaType()CheckMediaType()就是在pin媒体协商用用到的关键函数。GetMediaType()用来得到CBallStream(一个输出pin)支持的媒体类型,CheckMediaType()用来接触传入的媒体类型CBallStream(一个输出pin)是否支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值