directshow原理分析之filter到filter的连接
Filter是Directshow中最基本的概念。Directshow使用filter graph来管理filter。filter graph是filter的容器。Filter一般由一个或者几个Pin组成。filter之间通过Pin来连接,组成一条链。PIN也是一种COM组件,每一个PIN都实现了IPin接口。试图链接的两个Pin必须在一个filter graph中。连接过
Filter是Directshow中最基本的概念。Directshow使用filter graph来管理filter。filter graph是filter的容器。
Filter一般由一个或者几个Pin组成。filter之间通过Pin来连接,组成一条链。
PIN也是一种COM组件,每一个PIN都实现了IPin接口。
试图链接的两个Pin必须在一个filter graph中。
连接过程如下:
1.Filter Graph Manager在输出pin上调用IPin::Connect
2.如果输出Pin接受连接,则调用IPin::ReceiveConnection
3.如果输入Pin也接受此次连接,则双方连接成功
首先来分析基类函数CBasePin的Connect实现:
CBasePin继承了IPin。
<pre name="code" class="cpp">HRESULT __stdcall CBasePin::Connect(IPin * pReceivePin, __in_opt const AM_MEDIA_TYPE *pmt // optional media type)
{
CheckPointer(pReceivePin,E_POINTER);
ValidateReadPtr(pReceivePin,sizeof(IPin));
CAutoLock cObjectLock(m_pLock);
DisplayPinInfo(pReceivePin);
/* 检查该Pin是否早已连接 */
if (m_Connected) {
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Already connected")));
return VFW_E_ALREADY_CONNECTED;
}
/*一般Filter只在停止状态下接受连接*/
if (!IsStopped() && !m_bCanReconnectWhenActive) {
return VFW_E_NOT_STOPPED;
}
/*开始媒体类型检查,找出一种双方均支持的类型*/
const CMediaType * ptype = (CMediaType*)pmt;
HRESULT hr = AgreeMediaType(pReceivePin, ptype);
if (FAILED(hr)) {
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to agree type")));
// Since the procedure is already returning an error code, there
// is nothing else this function can do to report the error.
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );
#ifdef DXMPERF
PERFLOG_CONNECT( (IPin *) this, pReceivePin, hr, pmt );
#endif // DXMPERF
return hr;
}
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Connection succeeded")));
#ifdef DXMPERF
PERFLOG_CONNECT( (IPin *) this, pReceivePin, NOERROR, pmt );
#endif // DXMPERF
return NOERROR;
}
事实上,以上函数并没有进行真正的连接,只是进行了类型检查和状态检查。并且这个函数是从输出Pin进入的。
Connect两个参数分别代表:输出Pin试图链接的输入Pin和指定连接用的媒体类型。
真正的链接是CBase::AgreeMediaType来实现的。
<pre name="code" class="cpp">HRESULT CBasePin::AgreeMediaType(IPin *pReceivePin, const CMediaType *pmt)
{
ASSERT(pReceivePin);
IEnumMediaTypes *pEnumMediaTypes = NULL;
// 判断pmt是不是一个完全指定的媒体类型
if ( (pmt != NULL) && (!pmt->IsPartiallySpecified())) {
//用这个指定的媒体类型去做连接,如果失败不做任何处理。
return AttemptConnection(pReceivePin, pmt);
}
/* 进行pin上支持的媒体类型的枚举 ,开始媒体类型的协商过程*/
HRESULT hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;
for (int i = 0; i < 2; i++) {
HRESULT hr;
if (i == (int)m_bTryMyTypesFirst) {
hr = pReceivePin->EnumMediaTypes(&pEnumMediaTypes);
} else {
hr = EnumMediaTypes(&pEnumMediaTypes);
}
if (SUCCEEDED(hr)) {
ASSERT(pEnumMediaTypes);
hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes);
pEnumMediaTypes->Release();
if (SUCCEEDED(hr)) {
return NOERROR;
} else {
// try to remember specific error codes if there are any
if ((hr != E_FAIL) &&
(hr != E_INVALIDARG) &&
(hr != VFW_E_TYPE_NOT_ACCEPTED)) {
hrFailure = hr;
}
}
}
}
return hrFailure;
}
从AgreeMediaType看到,该函数首先判断媒体类型的有效性,如果pmt是一种完全指定的媒体类型,那么就以这种媒体类型调用内部
函数AttempConnection进行连接。
但是如果pmt是一个空指针或者不是完全指定的媒体类型,那么真正的协商过程也就开始了。注意,for循环的次数为2,输出Pin上的成员变量
m_bTryMyTypesFirst初始值为false,也就是说,连接过程进行到这里,会首先得到输入pin上的媒体类型枚举器的试连接,如果连接不成功,再去得到
输出Pin上的媒体类型枚举器的试连接。
下面看一下TryMediaTypes函数的实现:
HRESULT CBasePin::TryMediaTypes(IPin *pReceivePin, __in_opt const CMediaType *pmt, IEnumMediaTypes *pEnum)
{
/* 复位枚举器内部状态 */
HRESULT hr = pEnum->Reset();
if (FAILED(hr)) {
return hr;
}
CMediaType *pMediaType = NULL;
ULONG ulMediaCount = 0;
// attempt to remember a specific error code if there is one
HRESULT hrFailure = S_OK;
for (;;) {
/* Retrieve the next media type NOTE each time round the loop the
enumerator interface will allocate another AM_MEDIA_TYPE structure
If we are successful then we copy it into our output object, if
not then we must delete the memory allocated before returning */
hr = pEnum->Next(1, (AM_MEDIA_TYPE**)&pMediaType,&ulMediaCount);
if (hr != S_OK) {
if (S_OK == hrFailure) {
hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;
}
return hrFailure;
}
ASSERT(ulMediaCount == 1);
ASSERT(pMediaType);
// 检查当前枚举得到的类型是否与不完全指定的类型参数匹配
if (pMediaType &&
((pmt == NULL) ||
pMediaType->MatchesPartial(pmt))) {
<span style="white-space:pre"> </span> //进行试连接
hr = AttemptConnection(pReceivePin, pMediaType);
// attempt to remember a specific error code
if (FAILED(hr) &&
SUCCEEDED(hrFailure) &&
(hr != E_FAIL) &&
(hr != E_INVALIDARG) &&
(hr != VFW_E_TYPE_NOT_ACCEPTED)) {
hrFailure = hr;
}
} else {
hr = VFW_E_NO_ACCEPTABLE_TYPES;
}
if(pMediaType) {
DeleteMediaType(pMediaType);
pMediaType = NULL;
}
if (S_OK == hr) {
return hr;
}
}
}
当连接进程进入TryMediaTypes函数后,会使用媒体类型枚举器枚举Pin上提供的所有的媒体类型,然后一种一种的进行试连接。如果有一种成功,则整个Pin连接成功。
最后需要看一下AttempConnection函数的跟Pin连接相关的虚函数的调用的顺序,对与自己实现filter有指导意义:
<pre name="code" class="cpp">HRESULT CBasePin::AttemptConnection(IPin* pReceivePin, // connect to this pin
const CMediaType* pmt // using this type
)
{
// The caller should hold the filter lock becasue this function
// uses m_Connected. The caller should also hold the filter lock
// because this function calls SetMediaType(), IsStopped() and
// CompleteConnect().
ASSERT(CritCheckIn(m_pLock));
// Check that the connection is valid -- need to do this for every
// connect attempt since BreakConnect will undo it.
HRESULT hr = CheckConnect(pReceivePin);
if (FAILED(hr)) {
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("CheckConnect failed")));
// Since the procedure is already returning an error code, there
// is nothing else this function can do to report the error.
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );
return hr;
}
//可以显示媒体类型,有时候挺有用
DisplayTypeInfo(pReceivePin, pmt);
/* Pin上的媒体类型检查 */
hr = CheckMediaType(pmt);
if (hr == NOERROR) {
/* Make ourselves look connected otherwise ReceiveConnection
may not be able to complete the connection
*/
m_Connected = pReceivePin;
m_Connected->AddRef();
hr = SetMediaType(pmt);//在Pin上保存媒体类型
if (SUCCEEDED(hr)) {
/* 询问连接对方Pin是否能接受当前的媒体类型 */
hr = pReceivePin->ReceiveConnection((IPin *)this, pmt);
//连接成功
if (SUCCEEDED(hr)) {
/* Complete the connection */
hr = CompleteConnect(pReceivePin);
if (SUCCEEDED(hr)) {
return hr;
} else {
DbgLog((LOG_TRACE,
CONNECT_TRACE_LEVEL,
TEXT("Failed to complete connection")));
pReceivePin->Disconnect();
}
}
}
} else {
// we cannot use this media type
// return a specific media type error if there is one
// or map a general failure code to something more helpful
// (in particular S_FALSE gets changed to an error code)
if (SUCCEEDED(hr) ||
(hr == E_FAIL) ||
(hr == E_INVALIDARG)) {
hr = VFW_E_TYPE_NOT_ACCEPTED;
}
}
// BreakConnect and release any connection here in case CheckMediaType
// failed, or if we set anything up during a call back during
// ReceiveConnection.
// Since the procedure is already returning an error code, there
// is nothing else this function can do to report the error.
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );
/* If failed then undo our state */
if (m_Connected) {
m_Connected->Release();
m_Connected = NULL;
}
return hr;
}
至此:连接双方Pin上的使用的媒体类型协商完了,但是并不能真正的传输数据。因为内存还没分配呗。
这些均是在输出Pin上的CompleteConnect中完成的。
CBaseOutPin继承自CBasePin
HRESULT CBaseOutputPin::CompleteConnect(IPin *pReceivePin)
{
UNREFERENCED_PARAMETER(pReceivePin);
return DecideAllocator(m_pInputPin, &m_pAllocator);
}
DecideAllocator就是Pin之间数据传送所使用的内存分配器的协商。在dshow中数据传输的单元叫做Sample(也是一种COM组件,管理内存用的)。而Sample是由分配器Allocator(也是COM组件)来管理的。连接双方必须使用同一个分配器,该分配器由谁来创建也需要协商。
<pre name="code" class="cpp">HRESULT CBaseOutputPin::DecideAllocator(IMemInputPin *pPin, __deref_out IMemAllocator **ppAlloc)
{
HRESULT hr = NOERROR;
*ppAlloc = NULL;
// get downstream prop request
// the derived class may modify this in DecideBufferSize, but
// we assume that he will consistently modify it the same way,
// so we only get it once
ALLOCATOR_PROPERTIES prop;
ZeroMemory(&prop, sizeof(prop));
// 询问输入Pin对于分配器的需求
pPin->GetAllocatorRequirements(&prop);
// if he doesn't care about alignment, then set it to 1
if (prop.cbAlign == 0) {
prop.cbAlign = 1;
}
/* 询问输入Pin是否提供一个分配器 */
hr = pPin->GetAllocator(ppAlloc);
if (SUCCEEDED(hr)) {
//决定Sample使用的内存的大小,以及分配器管理的Sample的数量
hr = DecideBufferSize(*ppAlloc, &prop);
if (SUCCEEDED(hr)) {
//通知输入Pin最终使用的分配器的对象
hr = pPin->NotifyAllocator(*ppAlloc, FALSE);
if (SUCCEEDED(hr)) {
return NOERROR;
}
}
}
/* 如果输入Pin上不提供分配器,则必须在输出Pin上提供 */
if (*ppAlloc) {
(*ppAlloc)->Release();
*ppAlloc = NULL;
}
/* 创建一个输出Pin上的分配器 */
hr = InitAllocator(ppAlloc);
if (SUCCEEDED(hr)) {
// note - the properties passed here are in the same
// structure as above and may have been modified by
// the previous call to DecideBufferSize
hr = DecideBufferSize(*ppAlloc, &prop);
if (SUCCEEDED(hr)) {
hr = pPin->NotifyAllocator(*ppAlloc, FALSE);
if (SUCCEEDED(hr)) {
return NOERROR;
}
}
}
/* Likewise we may not have an interface to release */
if (*ppAlloc) {
(*ppAlloc)->Release();
*ppAlloc = NULL;
}
return hr;
}
当Pin上的数据传送内存分配器协商成功后,并没有马上分配内存,实际上是等Filter Graph运行后,调用输出Pin上的Active函数时进行的。
HRESULT CBaseOutputPin::Active(void)
{
if (m_pAllocator == NULL) {
return VFW_E_NO_ALLOCATOR;
}
return m_pAllocator->Commit();
}
至此整个Pin的链接才算真正完成。
更多推荐
所有评论(0)