009 DirectShow一些流程

Post date: 2015/4/9 上午 01:24:30

以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即可. 不過這種方式細節考慮的比較多, 不推薦使用.