directshow原理分析之filter到filter的连接

Filter是Directshow中最基本的概念。Directshow使用filter graph来管理filter。filter graph是filter的容器。

Filter一般由一个或者几个Pin组成。filter之间通过Pin来连接,组成一条链。

PIN也是一种COM组件,每一个PIN都实现了IPin接口。

试图链接的两个Pin必须在一个filter graph中。

directshow原理分析之filter到filter的连接

连接过程如下:

1.Filter Graph Manager在输出pin上调用IPin::Connect

2.如果输出Pin接受连接,则调用IPin::ReceiveConnection

3.如果输入Pin也接受此次连接,则双方连接成功

首先来分析基类函数CBasePin的Connect实现:

CBasePin继承了IPin。

[cpp] view plain copy
  1. <pre name="code" class="cpp">HRESULT __stdcall CBasePin::Connect(IPin * pReceivePin, __in_opt const AM_MEDIA_TYPE *pmt   // optional media type)  
  2. {  
  3.     CheckPointer(pReceivePin,E_POINTER);  
  4.     ValidateReadPtr(pReceivePin,sizeof(IPin));  
  5.     CAutoLock cObjectLock(m_pLock);  
  6.     DisplayPinInfo(pReceivePin);  
  7.   
  8.     /* 检查该Pin是否早已连接 */  
  9.     if (m_Connected) {  
  10.         DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Already connected")));  
  11.         return VFW_E_ALREADY_CONNECTED;  
  12.     }  
  13.   
  14.     /*一般Filter只在停止状态下接受连接*/  
  15.     if (!IsStopped() && !m_bCanReconnectWhenActive) {  
  16.         return VFW_E_NOT_STOPPED;  
  17.     }  
  18.   
  19.     /*开始媒体类型检查,找出一种双方均支持的类型*/  
  20.     const CMediaType * ptype = (CMediaType*)pmt;  
  21.     HRESULT hr = AgreeMediaType(pReceivePin, ptype);  
  22.     if (FAILED(hr)) {  
  23.         DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to agree type")));  
  24.   
  25.         // Since the procedure is already returning an error code, there  
  26.         // is nothing else this function can do to report the error.  
  27.         EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );  
  28.   
  29. #ifdef DXMPERF  
  30.         PERFLOG_CONNECT( (IPin *) this, pReceivePin, hr, pmt );  
  31. #endif // DXMPERF  
  32.   
  33.         return hr;  
  34.     }  
  35.   
  36.     DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Connection succeeded")));  
  37.   
  38. #ifdef DXMPERF  
  39.     PERFLOG_CONNECT( (IPin *) this, pReceivePin, NOERROR, pmt );  
  40. #endif // DXMPERF  
  41.   
  42.     return NOERROR;  
  43. }  



事实上,以上函数并没有进行真正的连接,只是进行了类型检查和状态检查。并且这个函数是从输出Pin进入的。

Connect两个参数分别代表:输出Pin试图链接的输入Pin和指定连接用的媒体类型。

真正的链接是CBase::AgreeMediaType来实现的。

[cpp] view plain copy
  1. <pre name="code" class="cpp">HRESULT CBasePin::AgreeMediaType(IPin *pReceivePin, const CMediaType *pmt)  
  2. {  
  3.     ASSERT(pReceivePin);  
  4.     IEnumMediaTypes *pEnumMediaTypes = NULL;  
  5.   
  6.     // 判断pmt是不是一个完全指定的媒体类型  
  7.     if ( (pmt != NULL) && (!pmt->IsPartiallySpecified())) {  
  8.     //用这个指定的媒体类型去做连接,如果失败不做任何处理。  
  9.         return AttemptConnection(pReceivePin, pmt);  
  10.     }  
  11.   
  12.   
  13.     /* 进行pin上支持的媒体类型的枚举 ,开始媒体类型的协商过程*/  
  14.     HRESULT hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;  
  15.   
  16.     for (int i = 0; i < 2; i++) {  
  17.         HRESULT hr;  
  18.         if (i == (int)m_bTryMyTypesFirst) {  
  19.             hr = pReceivePin->EnumMediaTypes(&pEnumMediaTypes);  
  20.         } else {  
  21.             hr = EnumMediaTypes(&pEnumMediaTypes);  
  22.         }  
  23.         if (SUCCEEDED(hr)) {  
  24.             ASSERT(pEnumMediaTypes);  
  25.             hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes);  
  26.             pEnumMediaTypes->Release();  
  27.             if (SUCCEEDED(hr)) {  
  28.                 return NOERROR;  
  29.             } else {  
  30.                 // try to remember specific error codes if there are any  
  31.                 if ((hr != E_FAIL) &&  
  32.                     (hr != E_INVALIDARG) &&  
  33.                     (hr != VFW_E_TYPE_NOT_ACCEPTED)) {  
  34.                     hrFailure = hr;  
  35.                 }  
  36.             }  
  37.         }  
  38.     }  
  39.   
  40.     return hrFailure;  
  41. }  

从AgreeMediaType看到,该函数首先判断媒体类型的有效性,如果pmt是一种完全指定的媒体类型,那么就以这种媒体类型调用内部

函数AttempConnection进行连接。

但是如果pmt是一个空指针或者不是完全指定的媒体类型,那么真正的协商过程也就开始了。注意,for循环的次数为2,输出Pin上的成员变量

m_bTryMyTypesFirst初始值为false,也就是说,连接过程进行到这里,会首先得到输入pin上的媒体类型枚举器的试连接,如果连接不成功,再去得到

输出Pin上的媒体类型枚举器的试连接。

下面看一下TryMediaTypes函数的实现:

[cpp] view plain copy
  1. HRESULT CBasePin::TryMediaTypes(IPin *pReceivePin, __in_opt const CMediaType *pmt, IEnumMediaTypes *pEnum)  
  2. {  
  3.     /* 复位枚举器内部状态 */  
  4.   
  5.     HRESULT hr = pEnum->Reset();  
  6.     if (FAILED(hr)) {  
  7.         return hr;  
  8.     }  
  9.   
  10.     CMediaType *pMediaType = NULL;  
  11.     ULONG ulMediaCount = 0;  
  12.   
  13.     // attempt to remember a specific error code if there is one  
  14.     HRESULT hrFailure = S_OK;  
  15.   
  16.     for (;;) {  
  17.   
  18.         /* Retrieve the next media type NOTE each time round the loop the 
  19.            enumerator interface will allocate another AM_MEDIA_TYPE structure 
  20.            If we are successful then we copy it into our output object, if 
  21.            not then we must delete the memory allocated before returning */  
  22.   
  23.         hr = pEnum->Next(1, (AM_MEDIA_TYPE**)&pMediaType,&ulMediaCount);  
  24.         if (hr != S_OK) {  
  25.             if (S_OK == hrFailure) {  
  26.                 hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;  
  27.             }  
  28.             return hrFailure;  
  29.         }  
  30.   
  31.   
  32.         ASSERT(ulMediaCount == 1);  
  33.         ASSERT(pMediaType);  
  34.   
  35.         // 检查当前枚举得到的类型是否与不完全指定的类型参数匹配  
  36.   
  37.         if (pMediaType &&  
  38.             ((pmt == NULL) ||  
  39.             pMediaType->MatchesPartial(pmt))) {  
  40. <span style="white-space:pre">    </span>    //进行试连接  
  41.             hr = AttemptConnection(pReceivePin, pMediaType);  
  42.   
  43.             // attempt to remember a specific error code  
  44.             if (FAILED(hr) &&  
  45.             SUCCEEDED(hrFailure) &&  
  46.             (hr != E_FAIL) &&  
  47.             (hr != E_INVALIDARG) &&  
  48.             (hr != VFW_E_TYPE_NOT_ACCEPTED)) {  
  49.                 hrFailure = hr;  
  50.             }  
  51.         } else {  
  52.             hr = VFW_E_NO_ACCEPTABLE_TYPES;  
  53.         }  
  54.   
  55.         if(pMediaType) {  
  56.             DeleteMediaType(pMediaType);  
  57.             pMediaType = NULL;  
  58.         }  
  59.   
  60.         if (S_OK == hr) {  
  61.             return hr;  
  62.         }  
  63.     }  
  64. }  

当连接进程进入TryMediaTypes函数后,会使用媒体类型枚举器枚举Pin上提供的所有的媒体类型,然后一种一种的进行试连接。如果有一种成功,则整个Pin连接成功。

最后需要看一下AttempConnection函数的跟Pin连接相关的虚函数的调用的顺序,对与自己实现filter有指导意义:

[cpp] view plain copy
  1. <pre name="code" class="cpp">HRESULT CBasePin::AttemptConnection(IPin* pReceivePin,      // connect to this pin  
  2.                     const CMediaType* pmt   // using this type  
  3. )  
  4. {  
  5.     // The caller should hold the filter lock becasue this function  
  6.     // uses m_Connected.  The caller should also hold the filter lock  
  7.     // because this function calls SetMediaType(), IsStopped() and  
  8.     // CompleteConnect().  
  9.     ASSERT(CritCheckIn(m_pLock));  
  10.   
  11.     // Check that the connection is valid  -- need to do this for every  
  12.     // connect attempt since BreakConnect will undo it.  
  13.     HRESULT hr = CheckConnect(pReceivePin);  
  14.     if (FAILED(hr)) {  
  15.         DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("CheckConnect failed")));  
  16.   
  17.         // Since the procedure is already returning an error code, there  
  18.         // is nothing else this function can do to report the error.  
  19.         EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );  
  20.   
  21.         return hr;  
  22.     }  
  23.     //可以显示媒体类型,有时候挺有用  
  24.     DisplayTypeInfo(pReceivePin, pmt);  
  25.   
  26.     /* Pin上的媒体类型检查 */  
  27.   
  28.     hr = CheckMediaType(pmt);  
  29.     if (hr == NOERROR) {  
  30.   
  31.         /*  Make ourselves look connected otherwise ReceiveConnection 
  32.             may not be able to complete the connection 
  33.         */  
  34.         m_Connected = pReceivePin;  
  35.         m_Connected->AddRef();  
  36.         hr = SetMediaType(pmt);//在Pin上保存媒体类型  
  37.         if (SUCCEEDED(hr)) {  
  38.             /* 询问连接对方Pin是否能接受当前的媒体类型 */  
  39.   
  40.             hr = pReceivePin->ReceiveConnection((IPin *)this, pmt);  
  41.         //连接成功  
  42.             if (SUCCEEDED(hr)) {  
  43.                 /* Complete the connection */  
  44.   
  45.                 hr = CompleteConnect(pReceivePin);  
  46.                 if (SUCCEEDED(hr)) {  
  47.                     return hr;  
  48.                 } else {  
  49.                     DbgLog((LOG_TRACE,  
  50.                             CONNECT_TRACE_LEVEL,  
  51.                             TEXT("Failed to complete connection")));  
  52.                     pReceivePin->Disconnect();  
  53.                 }  
  54.             }  
  55.         }  
  56.     } else {  
  57.         // we cannot use this media type  
  58.   
  59.         // return a specific media type error if there is one  
  60.         // or map a general failure code to something more helpful  
  61.         // (in particular S_FALSE gets changed to an error code)  
  62.         if (SUCCEEDED(hr) ||  
  63.             (hr == E_FAIL) ||  
  64.             (hr == E_INVALIDARG)) {  
  65.             hr = VFW_E_TYPE_NOT_ACCEPTED;  
  66.         }  
  67.     }  
  68.   
  69.     // BreakConnect and release any connection here in case CheckMediaType  
  70.     // failed, or if we set anything up during a call back during  
  71.     // ReceiveConnection.  
  72.   
  73.     // Since the procedure is already returning an error code, there  
  74.     // is nothing else this function can do to report the error.  
  75.     EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );  
  76.   
  77.     /*  If failed then undo our state */  
  78.     if (m_Connected) {  
  79.         m_Connected->Release();  
  80.         m_Connected = NULL;  
  81.     }  
  82.   
  83.     return hr;  
  84. }  


至此:连接双方Pin上的使用的媒体类型协商完了,但是并不能真正的传输数据。因为内存还没分配呗。

这些均是在输出Pin上的CompleteConnect中完成的。

CBaseOutPin继承自CBasePin

[cpp] view plain copy
  1. HRESULT CBaseOutputPin::CompleteConnect(IPin *pReceivePin)  
  2. {  
  3.     UNREFERENCED_PARAMETER(pReceivePin);  
  4.     return DecideAllocator(m_pInputPin, &m_pAllocator);  
  5. }  
DecideAllocator就是Pin之间数据传送所使用的内存分配器的协商。在dshow中数据传输的单元叫做Sample(也是一种COM组件,管理内存用的)。而Sample是由分配器Allocator(也是COM组件)来管理的。连接双方必须使用同一个分配器,该分配器由谁来创建也需要协商。

[cpp] view plain copy
  1. <pre name="code" class="cpp">HRESULT CBaseOutputPin::DecideAllocator(IMemInputPin *pPin, __deref_out IMemAllocator **ppAlloc)  
  2. {  
  3.     HRESULT hr = NOERROR;  
  4.     *ppAlloc = NULL;  
  5.   
  6.     // get downstream prop request  
  7.     // the derived class may modify this in DecideBufferSize, but  
  8.     // we assume that he will consistently modify it the same way,  
  9.     // so we only get it once  
  10.     ALLOCATOR_PROPERTIES prop;  
  11.     ZeroMemory(&prop, sizeof(prop));  
  12.   
  13.     // 询问输入Pin对于分配器的需求  
  14.     pPin->GetAllocatorRequirements(&prop);  
  15.   
  16.     // if he doesn't care about alignment, then set it to 1  
  17.     if (prop.cbAlign == 0) {  
  18.         prop.cbAlign = 1;  
  19.     }  
  20.   
  21.     /* 询问输入Pin是否提供一个分配器 */  
  22.   
  23.     hr = pPin->GetAllocator(ppAlloc);  
  24.     if (SUCCEEDED(hr)) {  
  25.        //决定Sample使用的内存的大小,以及分配器管理的Sample的数量  
  26.         hr = DecideBufferSize(*ppAlloc, &prop);  
  27.         if (SUCCEEDED(hr)) {  
  28.             //通知输入Pin最终使用的分配器的对象  
  29.             hr = pPin->NotifyAllocator(*ppAlloc, FALSE);  
  30.             if (SUCCEEDED(hr)) {  
  31.                 return NOERROR;  
  32.             }  
  33.         }  
  34.     }  
  35.   
  36.     /* 如果输入Pin上不提供分配器,则必须在输出Pin上提供 */  
  37.   
  38.     if (*ppAlloc) {  
  39.         (*ppAlloc)->Release();  
  40.         *ppAlloc = NULL;  
  41.     }  
  42.   
  43.     /* 创建一个输出Pin上的分配器 */  
  44.   
  45.     hr = InitAllocator(ppAlloc);  
  46.     if (SUCCEEDED(hr)) {  
  47.   
  48.         // note - the properties passed here are in the same  
  49.         // structure as above and may have been modified by  
  50.         // the previous call to DecideBufferSize  
  51.         hr = DecideBufferSize(*ppAlloc, &prop);  
  52.         if (SUCCEEDED(hr)) {  
  53.             hr = pPin->NotifyAllocator(*ppAlloc, FALSE);  
  54.             if (SUCCEEDED(hr)) {  
  55.                 return NOERROR;  
  56.             }  
  57.         }  
  58.     }  
  59.   
  60.     /* Likewise we may not have an interface to release */  
  61.   
  62.     if (*ppAlloc) {  
  63.         (*ppAlloc)->Release();  
  64.         *ppAlloc = NULL;  
  65.     }  
  66.     return hr;  
  67. }  

当Pin上的数据传送内存分配器协商成功后,并没有马上分配内存,实际上是等Filter Graph运行后,调用输出Pin上的Active函数时进行的。

[cpp] view plain copy
  1. HRESULT CBaseOutputPin::Active(void)  
  2. {  
  3.     if (m_pAllocator == NULL) {  
  4.         return VFW_E_NO_ALLOCATOR;  
  5.     }  
  6.     return m_pAllocator->Commit();  
  7. }  

至此整个Pin的链接才算真正完成。