以dsnetwork为例,Directshow协商过程:
1.BuildGraph维护着链表,有各个filter的链接信息.
首先对输入filter和输入filter
1. ConnectFilter中协商类型:
我们实现的ConnectFilter方法:
1). 枚举输入pin的每个媒体类型: EnumPins由basefilter创建一个IEnumPin接口,basefilter已经实现,
IEnumPin的next方法中会调用basefilter::GetPinCount(),和basefilter::GetPin(index)获取的每一个IOutputPin
首先通过调用UpPin->ConnectedTo()获取是否已经inputPin内部创建或者已经链接了一个DownPin,如果没有
枚举down(input)的Pin,对downPin和上面的流程一样,也是先获取IEnumPin接口,而且开始先DownPin->ConnectTo()
如果up和downPin都无法ConnectTo,则调用 BuildGraph->Connect
2). BuildGraph->Connect的实现流程:
在首先对的IOutPin:IBasePin->Connect(pReceivePin),在IBasePin:Connect中
首先调用AgreeMediaType(pReceivePin,pmt)
1.2.1 在AgreeMediaType中,如果pmt类型完全指定,则直接返回AttemptConnection(pReceivePin, MediaType).
1.2.2 for(x=0;x<2;x++)依据IOutPin变量m_bTryMyTypesFirst(bool,只可能为0或者1)判断是先看recvpin的类型还是看自己的类型。
先假设为0,则先看pReceivePin的媒体类型(通过EnumMediaTypes返回type的枚举).
1.2.3 接着调用TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes).在TryMediaTypes中,枚举传入的pEnumMediaTypes调用next,对每个MediaType,调用AttemptConnection(pReceivePin, pMediaType)
1.2.4 在AttemptConnection中,先CheckConnect看自己的内存分配器,接着调用自己的IOutPin->CheckMediaType检查自己是否接受系统的这个的媒体类型,如果不接受,跳至1.2.3继续系统的下一个媒体类型
1.2.5 如果自己IOutPin支持此媒体类型,接着调用自己的SetMediaType媒体类型设置当前的媒体类型,保存完之后,对该pReceivePin->ReceiveConnection(this<outputpin>, MediaType)
1.2.5.1 在pReceivePin->ReceiveConnection(MediaType)中,主要工作是和AttemptConnection()差不多,只不过是判断调用者已经是recvPin的.先CheckConnect():判断内存分配器;
再CheckMediaType(recvPin);检查此类型,如果成功,则调用CompleteConnect(). 在outputPinBase中的CompleteConnect中,会调用DecideAllocator(),这里是input的,所以直接返回NOERROR.到1.2.7步
1.2.5.2 如果pReceivePin->ReceiveConnection(MediaType)成功,则IOutPin会调用自己的IOutput::CompleteConnect
1.2.6 IOutPinBase:DecideAllocator(m_InputMemPin)协商内存分配器. 内存分配器只能使用一个,但具体使用哪个需要协商, 而且从OutPin发起这次协商
(m_InputMemPin是OutPinBase中的方法CheckConnect从inputPin中查询出来的接口)
1.2.6.1 先调用InputPin->GetAllocatorRequirements,返回Input的内存需求的属性,inpro
1.2.6.2 接着调用InputPin->GetAllocator获取input的内存分配器器inalloc,失败则跳到1.2.6.5
1.2.6.3 调用自己的DecideBufferSize(inalloc,inpro),,失败则跳到1.2.6.5
1.2.6.4 调用InputPin->NotifyAllocator(inalloc,bReadOnly),通知给InputPin使用此Alloc.失败则跳到1.2.6.5
1.2.6.5 如果InputPin的Alloc不可用,则先IOutPinBaser自己InitAllocator()创建alloc,接着,流程和1.2.6.3,1.2.6.4一样了.
1.2.3 如果AgreeMediaType成功,则成功,否则失败.
2. 智能链接:
智能链接主要是RenderFile(LPCWSTR lpwstrFile, LPCWSTR lpwstrPlayList)工作的过程,
2.1 智能协商的过程中首先要加入(AddSourceFilter)一个最初的source filters.(必须除了IBaseFilter之外,需要额外实现IFileSourceFilter接口:Load和GetCurFile()2个接口)
2.1.1 首先看lpwstrFile中协议:是HTTP,FILE,还是mms,RTSP等(:之前的字符),没有协议的话则是文件.比如rtsp://192.168.1.3/test.avi则是一个rtsp协议的文件.
2.1.2 如果有协议(FILE不算)
查找HKEY_CLASS_ROOT/协议名字(比如rtsp),接着查找它的SubKey,有无"Source Filter"和"Extensions":
HKEY_CLASSES_ROOT
<protocol(比如rtsp)>
Source Filter = <Source filter CLSID>
Extensions
<.ext1> = <Source filter CLSID>
<.ext2> = <Source filter CLSID>
它先查找Extensions下,如果有对应的Extensions匹配,比如上面的ext1为.avi,则把右边的<Source filter CLSID>则是source filters
如果Extensions没有对应的,则把Source Filter右边<Source filter CLSID>认为是最终的source filters
如果都没有,则认为系统提供的File Source (URL) 为这次的source filters
2.1.3 如果是文件(或者FILE://....)
2.1.3.1 查找:HKEY_CLASSES_ROOT\Media Type\Extensions\.ext,比如.mp3,查找该健值中的Source Filter,如果有,则此Source Filter就是本次的Source Filter
2.1.3.2 另外,有时,有的HKEY_CLASSES_ROOT\Media Type\Extensions\.ext的还有另外2个值(Media Type和SubType),这2个键值都指向了一个GUID,
2.1.3.3 在健HKEY_CLASSES_ROOT\Media Type 查找这2个GUID,
比如.mp3 Media Type={E436EB83-524F-11CE-9F53-0020AF0BA770},Subtype={E436EB87-524F-11CE-9F53-0020AF0BA770}
则在HKEY_CLASSES_ROOT\Media Type\{E436EB83-524F-11CE-9F53-0020AF0BA770}\{E436EB87-524F-11CE-9F53-0020AF0BA770}存在键就是配置了文件是否真正是mp3文件
和.mp3对应的source filter.
比如:.midi
{e436eb83-524f-11ce-9f53-0020af0ba770}
{7364696D-0000-0010-8000-00AA00389B71}
0 "0,4,,52494646,8,4,,524D4944"
1 "0,4,,4D546864"
Source Filter "{E436EBB5-524F-11CE-9F53-0020AF0BA770}"
怎么判断是否是mp3文件呢?在此健下有一些数字值(1,2,...),每个数字的值有offset,cb,mask,val这4个值的多个组合值,
如果满足了某一个数字中任意:read(buf, cb, 1, fp+offset) & mask == val,则是此格式文件,详细介绍见dirext文档
比如:0, 4, , ABCD1234, -4, 4, , ABAB00AB,表示前个值必须是ABCD1234,而最后4个值是ABAB00AB.(-4是倒数第4个字节)
2.1.4 如果一个Source Filter都没有找到,则使用Async File Source filter,并且它的类型是Media Type=MEDIATYPE_Stream, SubType=MEDIASUBTYPE_None
2.2 RenderFile()内部接着加载其它的Filter:从SourceFilter的输出IPin开始,从这里开始一条智能链接,本质上链接的过程就是上面connect的协商过程,而智能的含义则是尽快找到
一个合适的链路,这些合适的filter从哪来呢?这就是智能的算法所要解决的问题。
2.3.1 如果Source Filter支持IStreamBuilder方法,则直接转交给IStreamBuilder::Render(SourceFilter, BuildGragh);这种方式主要是相当于取消智能链接而采用自己的方法.
2.3.2 buildGraph使用在内存缓冲的filter进行链接,内存缓冲的filter是指此buildgraph曾经成功链接过的filter.
2.3.3 buildGraph使用加入了buildGraph中(addFilter),但未用的filter进行链接
2.3.4 如果还没有找到,则使用IFilterMapper2::EnumMatchingFilters来找到每个Merit不为MERIT_DO_NOT_USE的filter,一个一个进行链接.
2.3.5 都没有找到,则失败
2.3 智能链接成功之后,从Source Filter中查询到IFileSourceFilter接口,调用它的Load方法,值得注意的是,此Load方法第一次调用最好返回失败,实际上此方法作用没有弄明白.
typedef struct tagAM_SAMPLE2_PROPERTIES
{
DWORD cbData;
DWORD dwTypeSpecificFlags;
DWORD dwSampleFlags;
LONG lActual;
REFERENCE_TIME tStart;
REFERENCE_TIME tStop;
DWORD dwStreamId;
AM_MEDIA_TYPE *pMediaType;
BYTE *pbBuffer;
LONG cbBuffer;
} AM_SAMPLE2_PROPERTIES;
3. 动态改变运行时刻的MediaType和SubType:
3.1 从上往下改变,也就是OutputPin通知InputPin改变(假设A->B,A导出OutputPin,B导出InputPin)
通常如果类型被改变之后,需要整个buildGraph重新构建.但有时确实需要动态改变媒体类型.首先OutputPin调用InputPin的QueryAccept检查类型,接着再进行CBaseInputPin::Receive;
3.1.1 BuildGraph传递Sample,通过调用InputPin->Receive(ISample),首先检查CheckStreaming():检查是否inputPin被链接,且非停止状态,Flush状态,m_bRunTimeError==TRUE.
3.1.2 先查询ISample是否支持IID_IMediaSample2接口,如果支持,从此接口中获取pSample2->GetProperties(&m_SampleProps),跳至3.1.4
3.1.3 如果不支持IID_IMediaSample2接口,则分别调用pISample->IsDiscontinuity,pSample->IsPreroll(),pSample->IsSyncPoint,pSample->GetTime,pSample->GetMediaType()
现在关键来了,如果pSample->GetMediaType()获取成功,则表示类型改变!每个pISample获取的接口,都会修改m_SampleProps里面的值.
补充: 接着是获取Sample中的内存信息:pSample->GetPointer(&m_SampleProps.pbBuffer), pSample->GetActualDataLength(),pSample->GetSize().
3.1.4 检查m_SampleProps中的类型是否改变,如果没有改变则直接返回NOERROR.
3.1.5 如果类型改变了,则InputPin->CheckMediaType() Sample传入的新的类型.如果检查通过,则返回NOERROR
3.1.6 如果不支持新的类型,则把m_bRunTimeError置为TRUE,接着调用EndOfStream(),最后调用此InputPin的Filter->NotifyEvent消息(EC_ERRORABORT,VFW_E_TYPE_NOT_ACCEPTED,0)
3.2 从下往上改变,也就是InputPin通知OutputPin改变.这种情况需要B的内存分配器由B创建的才可以上下往上改变类型.
3.2.1 B调用A的 QueryAccept检查类型,如果无问题.
3.2.2 B设置自己的内存缓冲的空Sample设置类型(所以需要B管理内存分配器才可以上下往上该变),A这样调用GetBuffer()时,获取的Sample就可以知道类型被改变.
3.3 上面2种如果sample的也有改变,则需要这样做:
3.3.1 调用下一级的A filter::ReceiveConnection,(见类型协商章节,里面会重新协商内存分配器,DecideAllocator),如果成功,则调用A的OutputPin的IMemAllocator::SetProperties.
3.3.2 调用输入pin的IMemAllocator::NotifyAllocator通知使用新的Sample
3.3.3 调用这些之前确保以前的Sample类型已经发送完毕
4.动态增删Filter
4.1
5.数据传送:
传送的数据是个ISample接口,并不做实际的内存拷贝.
5.1 推模式:比如网络流,数字电视等实时数据
OupputPin和InputPin等都保存了一个IInputMem(在OutputPin的CheckConnect时,查询InputPin获取的此接口).
5.1.1 OutPutPin获取数据之后,OutputPin.Deliver()数据,Deliver(pSample)就是调用的是InputPin的Receive(pSample)
5.1.2 InputPin在Receive(pSample)内部的处理流程见3.1.1~3.1.6
5.1.3 一直到Rendder,Rendder获取之后,给硬件输出。
5.1.4 pSample如果不用,则注意Release,在你的Sample实现的接口中,Release()方法需要DeleteMediaType(),以及内存回收.
5.2 推模式:比如从文件中读取,文件source只有等待别人来拉数据,同步等信息由后面的filter来拉。
从另外 一个 角度上讲文件Source和后面的filter一起可以看作是一个推模式的Filter.
文件sourcefilter的outputPin,也实现了一个IAsyncReader接口。
5.3 IMemAllocator, IMediaSample, IMediaSample2这几个接口的关系
IMemAllocator是管理内存的地方
IMediaSample是设置内存属性,包括时间戳,获取内存缓冲地址, 获取和设置数据的真正长度, 获取和设置媒体类型(用于动态改变,见3章节)
IMediaSample2是继承IMediaSample,简化了IMediaSample的操作,一次性可以把IMediaSample的所有属性全部获取过来.(GetProperties,SetProperties)
6.状态跳转:Run,Stop,Pause.
GraphBuilder为了降低死锁的概率,逆序filter,一个一个地状态跳转.
6.1 Stop->Run
Stop->Run和Stop->Pause一样,唯一的区别在于RenndefFilter是否会阻塞数据传送线程.
6.2 Stop->Pause
6.2.1 逆序filter,从render开始对filter的每个Pin调用Activate(一般实现了内存分配m_pAllocator->Commit()),如果是SourceFilter则还会开启数据发送线程
6.2.2 到了Source Filter之后,Source Filter开起一个线程开始数据发送,一直发送到render.
6.2.3 Render收到此第一个Sample之后,阻塞数据发送线程.
6.3 Pause->Stop
遗留问题: 线程同步问题:数据发送线程和主控制线程的事件谁创建的?GetBuffer()为什么也需要阻塞?Recieve()也会阻塞?
对数据流程的发送和控制需要进一步了解.
GetBuffer是从有Buffer的地方获取(相当于源),缓冲满了会阻塞。
Recieve()会阻塞是发送到render的地方还没有处理完,或者下一级的pin的Outputpin缓冲满了而阻塞了.
分析ZmNetWork.
7.媒体定位:IMediaSeek
由Graphbuilder发起,先是Rendder,然后->transform->Source Filter, SourceFilter或者拉模式的Parsefilter和SpliterFilter是实现IMediaSeek的真正地方
7.1 定位:另外一般遵守这个规则:
7.1.1 Rendder调用上一级TransformerFilter的输出pin进行IMediaSeek
7.1.2 TransformerFilter对输出Pin中的此操作又对上一级的Filterpin进行IMediaSeek,直到SourceFilter
7.1.3 如果TransFormerFilter有多个InputPin,则要选择合适的InputPin对应的outputPin进行IMediaSeek
7.1.4 如果是Source Filter,如果有多个outputpin,则只要实现一个OutPin支持IMediaSeek就可以了
7.1.5 无论是哪个定位,如果当前状态是Run状态,则GraphBuild会先进行Pause.
7.2 传送速率:SetRate
7.2.1 GraphBuilder会先对IMediaSeek获取当前位置:IMediaSeek->GetCurPosition
7.2.2 GraphBuilder如果在运行或者暂停,则停止
7.2.3 GraphBuilder重新设置以下位置:IMediaSeek->SetCurPosition
7.2.4 调用IMediaSeek->SetRate()
7.2.5 恢复原有的状态.
原则:
比如原来正常速度播放时候,sample的时间戳是这样的
0 - 2
2 - 4
4 - 6
如果你设置setRate为2倍速,那么source filter送出去的sample上的时间戳应该是
0 - 1
1 - 2
2 - 3
注:上面的时间戳0/1/2/3/4...仅为方便说明,不是真实的时间戳.
其实就是欺骗video/audio Render,让它们以为当前的时间是真实时间的1/2,相当于加快了播放了.
8.质量控制:由render发出,主要目的是控制sample(Source Filter或者拉模式的splitter)的速率.
质量控制的接口是:IQualityControl::Notify(filter, Q),IQualityControl::SetSink(IQualityControl)
1. render发起
2. Transformer,首先调用transformerFilter的AlterQuality(Q).如果返回S_FALSE,则调用TransformerFilter::m_inputPin的PassNotify(Q)
3. 一直到最终的source filter
4.第二种方法,另外写个组件,实现了IQualityControl,然后再IQualityControl::SetSink给renderFilter即可.不过这种方式细节考虑的比较多,不推荐使用.