您的位置:首页 > 其它

directshow之filters 例程BALL的详细解析

2015-01-10 20:57 351 查看
directshow之filters 例程BALL的详细解析

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

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



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

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

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

给m_paStreams变量,m_paStreams是CBouncingBall的父类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开始运行后会调用SourceFIlterpin的Active函数,CBallStream没有

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

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

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

CSourceStream的Active函数:

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();

}

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

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

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;

}

CSourceStream的ThreadProc函数:
ThreadProc函数有do…while循环来源源不断解析各个命名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;

}

CSourceStream的DoBufferProcessingLoop函数:该函数的do…while结构来源源不断的产生、传输数据。在产生数据之前要先分配空间来保存数据,那么要分配多大的空间才算合适呢,这个由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放出响应的命令码,发送命令码时调用CAMThread的CallWorker传给CAMThread,在CSourceStream的线程函数中通过CAMThread的GetRequest或者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,类CBasePin的Connect方法)。连接的大致过程为,枚举欲连接的输入Pin上所有的媒体类型,逐一用这些媒体类型与输出Pin进行连接,如果输出Pin也接受这种媒体类型,则Pin之间的连接宣告成功;如果所有输入Pin上枚举的媒体类型输出Pin都不支持,则枚举输出Pin上的所有媒体类型,并逐一用这些媒体类型与输入Pin进行连接。如果输入Pin接受其中的一种媒体类型,则Pin之间的连接到此也宣告成功;如果输出Pin上的所有媒体类型,输入Pin都不支持,则这两个Pin之间的连接过程宣告失败。

CBallStream类中的GetMediaType()、CheckMediaType()就是在pin媒体协商用用到的关键函数。GetMediaType()用来得到CBallStream(一个输出pin)支持的媒体类型,CheckMediaType()用来接触传入的媒体类型CBallStream(一个输出pin)是否支持。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: