Directshow的中filter (滤镜) 其实可以看作是一个功能模块,它规定了标准的接口,用于不同的滤镜直接传递媒体数据。各个滤镜也不用知道其他滤镜具体是怎么样工作的,他只要知道传过来的 是什么样格式的媒体数据就行了,然后他自己又由什么样的格式传出去给下个滤镜。
一个典型的在播放器中播放mp3文件的filter出来大概是这样子到;
文件源滤镜(File source filter)-》 mp3 解码filter -》音频渲染filter
上面这个是加上dsp 音效处理的。
最开始的叫做 “源滤镜” ,最后的叫做“renderer filter” ,每个播放流程,这两种类型的滤镜都是一定存在的,中间 到按照功能等分类,还可以分为“transfer filter”(变换滤镜)、“分离滤镜(把音视频数据分离)”等等各种类型。有的还能够一个接受接口,输出多个接口。
滤镜和滤镜的连接还可以分为推模式(push filter)和拉模式(pull filter)两种。
推模式的特点就是数据由上游的滤镜决定,就是上游的滤镜一有数据了可以推给下游的去处理。
拉模式的呢就是下游什么时候处理数据完了,才通过接口像上游滤镜要数据。
两种的不同就是数据的传递方法不同,所以中间的缓存数据策略也不一样,具体可以去查看msdn。
总的来说拉模式的适合用来播放文件,而推模式适合用来网络传输实时媒体。因为对实时性要求比较高的,我在自己的源滤镜中收到数据,就马上要求播放这 个数据了呀,推模式呢就可以马上在这个源滤镜中把数据传给下游滤镜,要求播放。如果是拉模式就不一样咯,下游滤镜决定了播放的话语权,我什么时候播放完 了,我再什么时候找你源滤镜要数据,这样有可能慢吞吞的在放,而源滤镜那有可能已经累积了很多数据在那了来不及播放,这样实时性就不好。
我在写语音聊天的时候,也是试了这两种滤镜的,采用push filter到时候,实时性确实要好些的。
关于学习,可以参考directshow sdk自带的两个例子
memfile(pull filter到)
ball (push filter的)
仔细看一些,稍稍修改就可以工作了。
=================
pull filter的
下游需要数据时会调用这个函数向上游filter要数据
HRESULT Read(PBYTE pbBuffer,
DWORD dwBytesToRead,
BOOL bAlign, LPDWORD pdwBytesRead)
在这个函数可以设置我们推荐的缓存的大小,可以在这里改一下把数据大小改一下,缓存越小延时就,不过这里是有要求的,不能乱改,乱改了,决定权在下 游filter,他就会拒接连接,网上那些人说是要是32768 乘以6的大小,我测试的时候,播放mp3文件时这里最小可以调到4096 *48 。
// we need to return an addrefed allocator, even if it is the preferred
// one, since he doesn't know whether it is the preferred one or not.
STDMETHODIMP
CAsyncOutputPin::RequestAllocator(
IMemAllocator* pPreferred,
ALLOCATOR_PROPERTIES* pProps,
IMemAllocator ** ppActual)
{
CheckPointer(pPreferred,E_POINTER);
CheckPointer(pProps,E_POINTER);
CheckPointer(ppActual,E_POINTER);
ASSERT(m_pIo);
// we care about alignment but nothing else
if(!pProps->cbAlign || !m_pIo->IsAligned(pProps->cbAlign))
{
m_pIo->Alignment(&pProps->cbAlign);
}
ALLOCATOR_PROPERTIES Actual;
HRESULT hr;
/// 设置IMediaSample的长度更小一点,可以使语音的延时小一点?
///默认值为 32768 ,可能延时太大了
pProps->cbBuffer = 4096 ; /4096 *48 可以成功
pProps->cBuffers = 48;
if(pPreferred)
{
hr = pPreferred->SetProperties(pProps, &Actual);
if(SUCCEEDED(hr) && m_pIo->IsAligned(Actual.cbAlign))
{
pPreferred->AddRef();
*ppActual = pPreferred;
return S_OK;
}
}
// create our own allocator
IMemAllocator* pAlloc;
hr = InitAllocator(&pAlloc);
if(FAILED(hr))
{
return hr;
}
//...and see if we can make it suitable
hr = pAlloc->SetProperties(pProps, &Actual);
if(SUCCEEDED(hr) && m_pIo->IsAligned(Actual.cbAlign))
{
// we need to release our refcount on pAlloc, and addref
// it to pass a refcount to the caller - this is a net nothing.
*ppActual = pAlloc;
return S_OK;
}
// failed to find a suitable allocator
pAlloc->Release();
// if we failed because of the IsAligned test, the error code will
// not be failure
if(SUCCEEDED(hr))
{
hr = VFW_E_BADALIGN;
}
return hr;
}
--------------
push模式时,
他会在后台启动一个进程不停调用FillBuffer函数,我们只要在源滤镜中有数据来时,把数据传下去就可以了。在这个函数里面我们可以用锁来等 待数据的到来,一来了马上push就行了。
===================
写filter的时候,还需要注意设置filter的可以接受的和输出的 媒体类型,CMediaType 这个类的设置很关键 。设置好了,各个filter才能连的起来,虽然directshow是会进行一些智能连接尝试的。