前面有两篇文章讲解了PIN连接过程中需要做2件事情:
- 媒体类型的协商
- 分配器的协商
推模式和拉模式关于媒体类型的协商调用的接口都一样,主要的区别还是在分配器的协商。
前面讲解的分配器的协商过程是推模式的,今天我们来看看拉模式的分配器协商过程。
如果我们要实现拉模式的PIN连接,则输出PIN和输入PIN不能从CBaseOuputPin和CBaseInputPin继承。因为这两个类是为推模式连接设计的:
- CBaseInputPin实现了IMemInputPin接口。(IMemInputPin接口专门针对推模式设计的)
- CBaseOuputPin没有实现IAsyncReader接口。(IAsyncReader接口专门针对拉模式设计的)
那要实现拉模式的PIN连接,输入和输出PIN需要如何实现呢?
由于拉模式的数据读取线程是由输入PIN创建的,所以输出PIN比较简单,只要继承自CBasePin类和IAsyncReader接口就行。由于没有找到相关PIN的实现代码,这里就只是简单介绍一下。我们重点看一下拉模式输入PIN的实现方式:
为了方便大家实现拉模式的输入PIN,DirectShow为大家创建了一个帮助类:CPullPin。这个类没有从CBasePin继承,也没有实现IPin接口。它在内部通过调用上游连接的输出PIN的IAsyncReader接口获取数据。CPullPin内部有很多方法是和IPin同名的,因此它是你实现的输入Pin的一个帮助类。
接下来我大概讲解一下这个类的使用方法:
- 创建一个新类CMyPullPin继承自CPullPin类,创建另外一个新类CMyBasePin继承自CBasePin类。声明一个CMyPullPin类的实例作为CMyBasePin类的成员变量。
- 重新实现CBasePin::CheckConnect 方法,并在此方法中调用 CPullPin::Connect方法。CPullPin::Connect方法会负责分配器的协商和连接输出PIN的IAsyncReader接口的查询。
- 重新实现CBasePin::BreakConnect方法,并在此方法中调用 CPullPin::Disconnect方法。
- 重新实现CBasePin::Active方法,并在此方法中调用CPullPin::Active方法。CPullPin::Active方法会创建一个线程用来从上游的FIlter中获取数据。当PIN连接成功,你可以指定读取数据的方式为同步模式还是异步模式。
- 重新实现CBasePin::Inactive方法,并在此方法中调用CPullPin::Inactive方法。CPullPin::Inactive方法会关闭数据读取线程。
- 在CMyPullPin类中实现纯虚CPullPin::Receive方法,并在实现中处理接收到的数据以及将处理完的数据交付给下游Filter.
- 设置开始、停止的位置或者Seek数据流,调用CPullPin::Seek方法。这个方法会暂停数据线程并且清空调用缓存数据。
- 在CMyPullPin类中实现纯虚函数CPullPin::EndOfStream, CPullPin::BeginFlush, CPullPin::EndFlush,CPullPin::OnError。
CPullPin类的声明:
class CPullPin : public CAMThread
{
IAsyncReader* m_pReader;
REFERENCE_TIME m_tStart;
REFERENCE_TIME m_tStop;
REFERENCE_TIME m_tDuration;
BOOL m_bSync;
enum ThreadMsg {
TM_Pause, // stop pulling and wait for next message
TM_Start, // start pulling
TM_Exit, // stop and exit
};
ThreadMsg m_State;
// override pure thread proc from CAMThread
DWORD ThreadProc(void);
// running pull method (check m_bSync)
void Process(void);
// clean up any cancelled i/o after a flush
void CleanupCancelled(void);
// suspend thread from pulling, eg during seek
HRESULT PauseThread();
// start thread pulling - create thread if necy
HRESULT StartThread();
// stop and close thread
HRESULT StopThread();
// called from ProcessAsync to queue and collect requests
HRESULT QueueSample(
__inout REFERENCE_TIME& tCurrent,
REFERENCE_TIME tAlignStop,
BOOL bDiscontinuity);
HRESULT CollectAndDeliver(
REFERENCE_TIME tStart,
REFERENCE_TIME tStop);
HRESULT DeliverSample(
IMediaSample* pSample,
REFERENCE_TIME tStart,
REFERENCE_TIME tStop);
protected:
IMemAllocator * m_pAlloc;
public:
CPullPin();
virtual ~CPullPin();
// returns S_OK if successfully connected to an IAsyncReader interface
// from this object
// Optional allocator should be proposed as a preferred allocator if
// necessary
// bSync is TRUE if we are to use sync reads instead of the
// async methods.
HRESULT Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync);
// disconnect any connection made in Connect
HRESULT Disconnect();
// agree an allocator using RequestAllocator - optional
// props param specifies your requirements (non-zero fields).
// returns an error code if fail to match requirements.
// optional IMemAllocator interface is offered as a preferred allocator
// but no error occurs if it can't be met.
virtual HRESULT DecideAllocator(
IMemAllocator* pAlloc,
__inout_opt ALLOCATOR_PROPERTIES * pProps);
// set start and stop position. if active, will start immediately at
// the new position. Default is 0 to duration
HRESULT Seek(REFERENCE_TIME tStart, REFERENCE_TIME tStop);
// return the total duration
HRESULT Duration(__out REFERENCE_TIME* ptDuration);
// start pulling data
HRESULT Active(void);
// stop pulling data
HRESULT Inactive(void);
// helper functions
LONGLONG AlignDown(LONGLONG ll, LONG lAlign) {
// aligning downwards is just truncation
return ll & ~(lAlign-1);
};
LONGLONG AlignUp(LONGLONG ll, LONG lAlign) {
// align up: round up to next boundary
return (ll + (lAlign -1)) & ~(lAlign -1);
};
// GetReader returns the (addrefed) IAsyncReader interface
// for SyncRead etc
IAsyncReader* GetReader() {
m_pReader->AddRef();
return m_pReader;
};
// -- pure --
// override this to handle data arrival
// return value other than S_OK will stop data
virtual HRESULT Receive(IMediaSample*) PURE;
// override this to handle end-of-stream
virtual HRESULT EndOfStream(void) PURE;
// called on runtime errors that will have caused pulling
// to stop
// these errors are all returned from the upstream filter, who
// will have already reported any errors to the filtergraph.
virtual void OnError(HRESULT hr) PURE;
// flush this pin and all downstream
virtual HRESULT BeginFlush() PURE;
virtual HRESULT EndFlush() PURE;
};
接下来我们重点讲解一下这个类是如何协商分配器和获取数据的。
上面讲解CPullPin 类的使用时第二步中提到,CBasePin的CheckConnect 方法会调用CPullPin的Connect方法,此方法会负责分配器的协商以及IAsyncReader接口的获取。具体实现代码如下:
HRESULT
CPullPin::Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync)
{
CAutoLock lock(&m_AccessLock);
if (m_pReader) {
return VFW_E_ALREADY_CONNECTED;
}
HRESULT hr = pUnk->QueryInterface(IID_IAsyncReader, (void**)&m_pReader);
if (FAILED(hr)) {
return(hr);
}
hr = DecideAllocator(pAlloc, NULL);
if (FAILED(hr)) {
Disconnect();
return hr;
}
LONGLONG llTotal, llAvail;
hr = m_pReader->Length(&llTotal, &llAvail);
if (FAILED(hr)) {
Disconnect();
return hr;
}
// convert from file position to reference time
m_tDuration = llTotal * UNITS;
m_tStop = m_tDuration;
m_tStart = 0;
m_bSync = bSync;
return S_OK;
}
从 实现可知,首先获取了IAsyncReader接口,然后调用DecideAllocator接口来实现分配器的协商。我接下来看看DecideAllocator的源码实现:
HRESULT
CPullPin::DecideAllocator(
IMemAllocator * pAlloc,
__inout_opt ALLOCATOR_PROPERTIES * pProps)
{
ALLOCATOR_PROPERTIES *pRequest;
ALLOCATOR_PROPERTIES Request;
if (pProps == NULL) {
Request.cBuffers = 3;
Request.cbBuffer = 64*1024;
Request.cbAlign = 0;
Request.cbPrefix = 0;
pRequest = &Request;
} else {
pRequest = pProps;
}
HRESULT hr = m_pReader->RequestAllocator(
pAlloc,
pRequest,
&m_pAlloc);
return hr;
}
从实现可知,最终分配器的协商交由连接的输出PIN的IAsyncReader接口RequestAllocator方法。
我们接下来看看拉模式数据的读取方式,CPullPin拉数据线程的源码如下:
DWORD
CPullPin::ThreadProc(void)
{
for (;;) {
DWORD cmd = GetRequest();
switch(cmd) {
case TM_Exit:
Reply(S_OK);
return 0;
case TM_Pause:
// we are paused already
Reply(S_OK);
break;
case TM_Start:
Reply(S_OK);
Process();
break;
}
// at this point, there should be no outstanding requests on the
// upstream filter.
// We should force begin/endflush to ensure that this is true.
// !!!Note that we may currently be inside a BeginFlush/EndFlush pair
// on another thread, but the premature EndFlush will do no harm now
// that we are idle.
m_pReader->BeginFlush();
CleanupCancelled();
m_pReader->EndFlush();
}
}
从实现可知,此方法调用的主要方法为Process();
Process方法源码如下:
void
CPullPin::Process(void)
{
IMediaSample* pSample;
hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0);
LONGLONG tStopThis = tCurrent + (pSample->GetSize() * UNITS);
pSample->SetTime(&tCurrent, &tStopThis);
tCurrent = tStopThis;
if (bDiscontinuity) {
pSample->SetDiscontinuity(TRUE);
bDiscontinuity = FALSE;
}
hr = m_pReader->SyncReadAligned(pSample);
hr = DeliverSample(pSample, tStart, tStop);
return;
}
此方法代码有点长,所以我就只留下比较重要的几行,可以大概了解下整个数据获取的过程。
- hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0);//从分配器获取到一个Sampler对象。
- hr = m_pReader->SyncReadAligned(pSample);//从输出Pin读取数据到Sampler对象。
- hr = DeliverSample(pSample, tStart, tStop);//处理Sampler数据,并交付给下游Filter.
我们接下来看看DeliverSample的实现代码:
HRESULT
CPullPin::DeliverSample(
IMediaSample* pSample,
REFERENCE_TIME tStart,
REFERENCE_TIME tStop
)
{
// fix up sample if past actual stop (for sector alignment)
REFERENCE_TIME t1, t2;
if (S_OK == pSample->GetTime(&t1, &t2)) {
if (t2 > tStop) {
t2 = tStop;
}
// adjust times to be relative to (aligned) start time
t1 -= tStart;
t2 -= tStart;
HRESULT hr = pSample->SetTime(&t1, &t2);
if (FAILED(hr)) {
return hr;
}
}
HRESULT hr = Receive(pSample);
pSample->Release();
return hr;
}
由代码实现可知,此函数将Sampler对象交给Receive函数处理,然后就释放掉这个Sampler 对象了。
所以具体的Sampler处理还是由派生自CPullPin的CMyPullPin类(我们自己实现的类)决定。
总结:以上就是拉模式的分配器协商过程和数据处理过程。推模式的分配器协商过程在输出CBaseOutputPin的CompleteConnect函数中实现。拉模式的分配器协商过程在CBasePin的CheckConnect函数中实现,此函数会在ReceiveConnection函数中被调用。