最近要用到一个TransformFilter 做一些转换,就看了些Directshow的东西,一直困惑于Filter之间是怎么进行连接协商的,那些必须要重写的CTransformFilter方法有什么用,又是什么时候被调用的也感到迷惑,现在就记录一下自己的探索过程。
Filter连接的本质就是Filter的Pin与Pin之间的连接,首先上游Filter的OutputPin的CBasePin::Connect( IPin * pReceivePin,__in_opt const AM_MEDIA_TYPE *pmt):
STDMETHODIMP CBasePin::Connect( IPin * pReceivePin,__in_opt const AM_MEDIA_TYPE *pmt) {
// pReceivePin 表示将要和自己连接的下游Filter的InputPin
// pmt 商讨的类型
// ...省略一些其他的判断
/* 是否已经处于连接状态 */
if (m_Connected) {
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Already connected")));
return VFW_E_ALREADY_CONNECTED;
}
// ...
// 转到AgreeMediaType()..
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() ) );
return hr;
}
return NOERROR;
}
可以看到,Connect()方法对自身的状态做了一些判断后,就将连接的协商交给AgreeMediaType()来处理了,来看看AgreeMediaType做了什么:
HRESULT CBasePin::AgreeMediaType( IPin *pReceivePin, const CMediaType *pmt)
{
ASSERT(pReceivePin);
// 类型的枚举器
IEnumMediaTypes *pEnumMediaTypes = NULL;
// 如果pmt是一个完全类型 ,直接尝试连接
if ( (pmt != NULL) && (!pmt->IsPartiallySpecified())) {
// pmt 非NULL ,且 pmt是一个完全指定的类型,
// 那么就用pmt进行连接,连接成功则成功,失败则就是败了,
// 尝试连接
return AttemptConnection(pReceivePin, pmt);
}
/* 尝试其他类型 */
HRESULT hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;
for (int i = 0; i < 2; i++) {
HRESULT hr;
// m_bTryMyTypesFirst 默认为 0
if (i == (int)m_bTryMyTypesFirst) {
// 获取下游Filter 的InputPin的类型枚举
hr = pReceivePin->EnumMediaTypes(&pEnumMediaTypes);
} else {
// 获取本Filter 的OutputPin的类型枚举
hr = EnumMediaTypes(&pEnumMediaTypes);
}
if (SUCCEEDED(hr)) {
ASSERT(pEnumMediaTypes);
//尝试 pEnumMediaTypes 中的类型, 看看双方同不同意连接
hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes);
pEnumMediaTypes->Release();
if (SUCCEEDED(hr)) {
// 连接成功
return NOERROR;
} else {
// 记录错误 ...略
}
}
}
return hrFailure;
}
看看 TryMediaTypes():
HRESULT CBasePin::TryMediaTypes(IPin *pReceivePin, __in_opt const CMediaType *pmt,
IEnumMediaTypes *pEnum)
{
CMediaType *pMediaType = NULL;
ULONG ulMediaCount = 0;
// attempt to remember a specific error code if there is one
HRESULT hrFailure = S_OK;
for (;;) {
/* 一个一个的枚举出pEnum里的类型 */
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);
// 尝试使用pMediaType 进行连接
if (pMediaType &&
((pmt == NULL) ||
pMediaType->MatchesPartial(pmt))) {
// 尝试连接
hr = AttemptConnection(pReceivePin, pMediaType);
// 记录错误.. 略
} else {
hr = VFW_E_NO_ACCEPTABLE_TYPES;
}
if(pMediaType) {
DeleteMediaType(pMediaType);
pMediaType = NULL;
}
if (S_OK == hr) {
//连接成功
return hr;
}
}
}
TryMediaTypes()不断从枚举器里取出类型来尝试连接,直到AttemptConnection() 返回S_OK(连接成功)或者类型被枚举完毕返回,若果此时使用的是下游Filter InputPin的类型枚举器, 接下来会尝试使用本Filter OutpinPin的类型枚举器来尝试连接(可以参考上文AgreeMediaType() 里的 for () 循环 )。
HRESULT CBasePin::AttemptConnection(
IPin* pReceivePin, // 连接到这个Pin
const CMediaType* pmt
)
{
// 一些例常的状态检查 ... 略
//本Filter的OutputPin 检查此类型是否满足自己的要求
hr = CheckMediaType(pmt);
if (hr == NOERROR) {
/*
如果满足要求 :
记录对方的Pin
*/
m_Connected = pReceivePin;
m_Connected->AddRef();
// 记录自己同意的 Type , SetMediaType() 里也可以有一些类型检查
// 如 CheckTransform(pmt1, pmt2) ...
hr = SetMediaType(pmt);
if (SUCCEEDED(hr)) {
// 本 Filter 已经完全认同这个 pmt 了 , 那么对方的Filter ( 或者说下游Filter 的 InputPin)
// 是否也同意这个 pmt ?
// 对方同意吗 ?
// 将此 OutputPin 传给对方问个话。 如果同意连接,对方要保存这个OutputPin 。
// ReceiveConnection() 里会调用:
// CheckMediaType(pmt) 、SetMediaType(pcmt) 对 pmt进行判断,
// 若同意 ,则会调用 : CompleteConnect() 。
hr = pReceivePin->ReceiveConnection((IPin *)this, pmt);
if (SUCCEEDED(hr)) {
/*
对方InputPin 也同意了, 那就 CompleteConnect()吧,
完成连接的协商。
*/
hr = CompleteConnect(pReceivePin);
if (SUCCEEDED(hr)) {
return hr;
} else {
DbgLog((LOG_TRACE,
CONNECT_TRACE_LEVEL,
TEXT("Failed to complete connection")));
pReceivePin->Disconnect();
}
}
}
} else {
// 记录错误... 略
}
// 其他...略
return hr;
}
这样,上游Filter 和 下游Filter 就算连接完毕了。但是,Pin上的类型是怎被枚举的 ?继承 CTransformFilter 写自己的TransformFilter 时为什么必须要重写像GetMediaType() 、DecideBufferSize()等函数? 下篇文章再分析。