DirectShow一些流程

 以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即可.不过这种方式细节考虑的比较多,不推荐使用.
原文地址:https://www.cnblogs.com/qq78292959/p/2077036.html