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

Post date: 2015/4/10 上午 01:33:43

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的链接才算真正完成。