回 帖 发 新 帖 刷新版面

主题:_____DShow____开发笔记(二)

.5构建一个Filter Graph图
1.6数据流在Filter Graph里的流动(Data Flow)
1 directshow数据流动概述
数据总是存在内存块中的字节集合,每个buffer都被封装在一个叫做media sample的com组件,它引出了IMediaSample接口。这个sample一般都有一个叫做内存分配器(alloctor)的com对象来创建,这个对象具有IMemAllocator接口。每一个pin之间的连接都要指定一个allocator,有时也有几个连接同用一个allocator。
 
   每一个allocator都要创建一个media sample池,并且给每一个sample分配一个内存buffer。每当一个Filter需要一个buffer来填充数据,它就通过allocator的函数IMemAllocator::GetBuffer.来获得一个sample。如果分配器allocator正好有空闲的sample,GetBuffer立即返回一个指向该sample的指针。如果没有空闲的sample,该方法就阻塞,直到有一个sample可用为止。当该函数返回一个sample时,Filter就将数据填充到buffer里,设置好标识,然后就将sample传递给下一个Filter。
当一个renderfilter接收到一个sample时,它就检查该sample的时间戳,直到Fliter Graph的参考时钟表明该数据可用播放,Filter就开始播放该数据。当数据播放完毕,Filter释放sample,直到所有的Filter都释放对该sample的引用,该sample的引用计数为0时,这个sample才返回到sample池。
 
  有时也许数据流的上游对buffer的填充比播放要快,即使这样,render Filter也要按照时间戳播放数据,这样sample池中的sample数量就少,从而填充的速度减慢。
上面描述了在流中只有一个allocator的情景,实际上,在每条数据流中总是有好几个allocator,当一个sample被释放的时候,也许此时有好几个allocator都在等着该sample,这就有新的问题了,也许有的alloctor永远都不能被分配sample,陷入互锁状态。下面的图就演示了这种情形,Decoder有数据需要压缩,因此它在等待Renderer释放sample,但是,Parser也在请求sample,它在等待decoder释放sample。
 
具体参加help
2 传输(Transports)
为了在过滤器图表中传送媒体数据,DirectShow过滤器需要支持一些协议,称之为传输协议(transport)。相连的过滤器必须支持同样的传输协议,否则不能交换媒体数据。
大多数的DirectShow过滤器把媒体数据保存在主存储器中,并通过引脚把数据提交给其它的过滤器,这种传输称为局部存储器传输(local memory transport)。虽然局部存储器传输在DirectShow中最常用,但并不是所有的过滤器都使用它。例如,有些过滤器通过硬件传送媒体数据,引脚只是用来提交控制信息,如IOverlay接口。
DirectShow为局部存储器传输定义了两种机制:推模式(push model)和拉模式(pull model)。在推模式中,源过滤器生成数据并提交给下一级过滤器。下一级过滤器被动的接收数据,完成处理后再传送给再下一级过滤器。在拉模式中,源过滤器与一个分析过滤器相连。分析过滤器向源过滤器请求数据后,源过滤器才传送数据以响应请求。推模式使用的是IMemInputPin接口,拉模式使用IAsyncReader接口,推模式比拉模式要更常用。
3 Samples和Allocators
当一个pin向另一个pin传递数据的时候,它并不是直接将内存块的指针传递下一个pin,实际上,它将传递一个管理内存的com对象的指针给下一个pin。这个com对象称为media sample。暴露了IMediaSample接口。接收pin通过调用IMediaSample的方法来对内存进行操作,比如方法IMediaSample::GetSize, IMediaSample::GetActualDataLength以及IMediaSample::GetPointer。
   Sample一般都是从源filter开始,通过输出pin传递到下一个filter的输入pin,一路传递下去一直到render filter。在拉模式中,输出pin通过调用输入pin上的IMemInputPin::Receive方法传递sample,输入pin或者在Receive函数同步处理数据,或者另外采用一个工作线程异步出来的方式。如果在Recive方法中需要等待资源的话,也可以阻塞。
另外一个com对象,叫做allocator,用来创建和管理sample的。暴露了IMemAllocator接口。当一个filter需要一个空的buffer的时候,就可以调用IMemAllocator::GetBuffer,该方法返回一个指向sample的指针。每一个pin连接都共享一个allocator,当两个pin连接的时候,他们会决定由哪个filter来提供allocator,通过pin还可以设置allocator的属性,例如,buffer的数量和大小。
下面的图表显示了allocator,sample和filter之间的关系。
 
Media sample引用计数
Allocator创建了一个sample池。因此 ,当某个Filter调用GetBuffer函数时,一些sample被使用,其他空闲的sample可以响应。Allocator通过引用计数来跟踪samples。Filter调用Getbuffer返回的sample的引用计数是1。当sample的引用计数为0时,sample就返回内存池,成为空闲的sample,可以再次响应Getbuffer的调用。如果所有的sample都处于繁忙状态,Getbuffer就会阻塞,直到有一个sample空闲。
例如,假设一个输入pin接到一个sample,如果它在Receive方法里同步的处理这个sample,没有增加该sample的引用计数,等到Receive返回后,输出pin就释放这个sample,引用计数为0,sample就返回到内存池中。 如果输入pin的线程还要处理该sample,引用计数增加1,成为2,输出pin返回,释放,计数成1。
当一个输入pin接收一个sample时,它可以将数据复制到另一个sample中,也可以将这个sample传递到下一个Filter。一个sample可以流遍整个filter graph。不过引用计数要保持大于0。 当一个输出pin调用了Release以后,就不应该再次使用该sample,因为也许下游还有filter正在使用该sample。输出pin必须调用GetBuffer获取新的sample。
这种机制减少 了内存分配的,因为buffer可以重用。也防止了数据没有被处理的sample被重新写入。
当一个Filter创建一个allocator的时候,allocator还没有保留任何的内存,如果这个时候有人Getbuffer,就会失败。只有当数据流开始的时候,输出pin调用IMemAllocator::Commit,提交allocator,现在才能分配内存。
当数据流停止的时候,pin就调用IMemAllocator::Decommit,来销毁allocator。在allocator再次committ之前,所有调用GetBuffer方法都会失败。当然,如果有一个GetBuffer阻塞调用在等待sample的时候,遇到Decommit方法,会立即返回一个错误码。
4 Filter的状态
Filter有三种状态,停止,暂停,运行。
 过滤器图表管理器 控制着Filter的所有状态的转换。当应用程序调用IMediaControl::Run, IMediaControl::Pause, or IMediaControl::Stop时, 过滤器图表管理器就调用Filter相应的IMediaFilter方法。停止,运行状态的切换总是要经过暂停,因此,当一个应用程序对一个停止的Graph 调用RUN命令时,过滤器图表管理器  在run之前首先要暂停。
对于大多数的filter来说,running和paused状态是一样的。看下面的Graph
Source > Transform > Renderer

当一个Filter停止时,它拒绝发送给它的任何samples,源filter关闭他们的stream线程,其他filter也关闭他们创建的其他线程,pin  decommit他们的内存分配器。
过滤器图表管理器按照逆流的方向来切换Filter的状态,从Renderer Filter到源filter,这种方式可以防止死锁。最关键的状态切换是暂停和停止之间。
从停止到暂停,当filter暂停时,它就做好了接收sample的准备,源filter是最后一个切换到暂停的。它开始创建streaming线程,发送sample,因为下游的filter的状态都已经切换到暂停了,所以,所有的filter都可以接收sample。只有当所有的flter都接收到sample,过滤器图表管理器才算完成了状态的切换
从暂停到停止。当一个filter停止时,它要释放它拥有的所有的samples。当图表管理器试图停掉上游的一个filter时,这个filter不会阻塞在Getbuffer和receive方法里,它会立即响应stop命令。上游的filter也许在执行stop命令前还会讲少量的sample传递下去,但是下游的filter会拒绝的,因为他们已经停止了。
5拉PULL模式
在IMemInputPin接口中,上游的flter决定了发送什么样的数据,然后将数据推给下游的filter。但在另外的场合,拉模式更适合。下游的filter向上游的filter请求数据,数据依然是从上游到下游,从输出pin到输入pin,但是下游的filter主导着数据的流动。这种类型的连接采用的是IAsyncReader接口
   拉模式的典型应用的文件的回放,例如在一个AVI文件的回放graph中,Async File Source
Filter就担负着从文件中读取数据,然后将数据以字节流的方式发送给下面的filter。
1.7事件通知机制(Event Notification)
1概述
当某个事件发生时,比如数据流结束,产生一个错误等,Filter就给Filter图表管理器发送一个事件通知。Filter图表管理器处理其中的一部分事件,另一部分交给应用程序处理。如果图表管理器没有处理一个filter事件,它就把事件通知放入到一个队列中,图表管理器也可以将自己的事件通知放进队列中。
应用程序可以自己处理队列中的事件,dshow中的事件通知就和windows的消息机制差不多,filter,图表管理器和应用程序通过这种机制就可以互相通信。

回复列表 (共13个回复)

11 楼


天热,贴冷,只能顶,,,,,

12 楼


好长时间没有来了,有些事没有做好,只做的虎头蛇尾,,
\

我会努力发完整把这个帖子,,

同道们,,,,为VC++


再加点油吧~~~~

13 楼

加油吧 !

我来回复

您尚未登录,请登录后再回复。点此登录或注册