多媒體處理,不可避免地要解決音視頻的同步問題。DirectShow是怎么來實(shí)現(xiàn)的呢?我們一起來學(xué)習(xí)一下。
大家知道,DirectShow結(jié)構(gòu)最核心的部分是Filter Graph Manager:向下控制Graph中的所有Filter,向上對τ貿(mào)絳蛺峁┍喑探涌?。其中,F(xiàn)ilter Graph Manager實(shí)現(xiàn)的很重要一個功能,就是同步音視頻的處理。簡單地說,就是選一個公共的參考時鐘,并且要求給每個Sample都打上時間戳,Video Renderer或Audio Renderer根據(jù)Sample的時間戳來控制播放。如果到達(dá)Renderer的Sample晚了,則加快Sample的播放;如果早了,則Renderer等待,一直到Sample時間戳的開始時間再開始播放。這個控制過程還引入一個叫Quality Control的反饋機(jī)制。
下面,我們來看一下參考時鐘(Reference Clock)。所有Filter都參照于同一個時鐘,才能統(tǒng)一步調(diào)。DirectShow引入了兩種時鐘時間:Reference time和Stream time。前者是從參考時鐘返回的絕對時間(IReferenceClock::GetTime),數(shù)值本身的意義取決于參考時鐘的內(nèi)部實(shí)現(xiàn),利用價(jià)值不大;后者是兩次從參考時鐘讀取的數(shù)值的差值,實(shí)際應(yīng)用于Filter Graph內(nèi)部的同步。Stream time在Filter Graph不同狀態(tài)的取值為:
1. Filter Graph運(yùn)行時,取值為當(dāng)前參考時鐘時間減去Filter Graph啟動時的時間(啟動時間是通過調(diào)用Filter上的IMediaFilter::Run來設(shè)置的);
2. Filter Graph暫停時,保持為暫停那一刻的Stream time;
3. 執(zhí)行完一次Seek操作后,復(fù)位至零;
4. Filter Graph停止時,取值不確定。
那么,參考時鐘究竟是什么東西呢?其實(shí),它只是一個實(shí)現(xiàn)了IReferenceClock接口的對象。也就是說,任何一個實(shí)現(xiàn)了IReferenceClock接口的對象都可以成為參考時鐘。在Filter Graph中,這個對象一般就是一個Filter。(在GraphEdit中,實(shí)現(xiàn)了參考時鐘的Filter上會顯示一個時鐘的圖標(biāo);如果同一個Graph中有多個Fiter實(shí)現(xiàn)了參考時鐘,當(dāng)前被Filter Graph Manager使用的那個會高亮度顯示。)而且大多數(shù)情況下,參考時鐘是由Audio Renderer這個Filter提供的,因?yàn)槁暱ㄉ媳旧韼в辛擞布〞r器資源。接下來的問題是,如果Filter Graph中有多個對象實(shí)現(xiàn)了IReferenceClock接口,F(xiàn)ilter Graph Manager是如何做出選擇的呢?默認(rèn)的算法如下:
1. 如果應(yīng)用程序設(shè)置了一個參考時鐘,則直接使用這個參考時鐘。(應(yīng)用程序通過IMediaFilter:: SetSyncSource設(shè)置參考時鐘,參數(shù)即為參考時鐘;如果參數(shù)值為NULL,表示Filter Graph不使用參考時鐘,以最快的速度處理Sample;可以調(diào)用IFilterGraph:: SetDefaultSyncSource來恢復(fù)Filter Graph Manager默認(rèn)的參考時鐘。值得注意的是,這時候的IMediaFilter接口應(yīng)該從Filter Graph Manager上獲得,而不是枚舉Graph中所有的Filter并分別調(diào)用Filter上的這個接口方法。)
2. 如果Graph中有支持IReferenceClock接口的Live Source,則選擇這個Live Source。
3. 如果Graph中沒有Live Source,則從Renderer依次往上選擇一個實(shí)現(xiàn)IReferenceClock接口的Filter。如果連接著的Filter都不能提供參考時鐘,則再從沒有連接的Filter中選擇。這一步算法中還有一個優(yōu)先情況,就是如果Filter Graph中含有一個Audio Render的鏈路,則直接選擇Audio Renderer這個Filter(原因上面已經(jīng)提及)。
4. 如果以上方法都找不到一個適合的Filter,則選取系統(tǒng)參考時鐘。(System Reference Clock,通過CoCreateInstance創(chuàng)建,CLSID為CLSID_SystemClock。)
我們再來看一下Sample的時間戳(Time Stamp)。需要注意的是,每個Sample上可以設(shè)置兩種時間戳:IMediaSample::SetTime和IMediaSample::SetMediaTime。我們通常講到時間戳,一般是指前者,它又叫Presentation time,Renderer正是根據(jù)這個時間戳來控制播放;而后者對于Filter來說不是必須的,Media time有沒有用取決于你的實(shí)現(xiàn),比如你給每個發(fā)出去的Sample依次打上遞增的序號,在后面的Filter接收時就可以判斷傳輸?shù)倪^程中是否有Sample丟失。我們再看一下IMediaSample::SetTime的參數(shù),兩個參數(shù)類型都是REFERENCE_TIME,千萬不要誤解這里的時間是Reference time,其實(shí)它們用的是Stream time。還有一點(diǎn),就是并不是所有的Sample都要求打上時間戳。對于一些壓縮數(shù)據(jù),時間戳是很難打的,而且意義也不是很大(不過壓縮數(shù)據(jù)經(jīng)過Decoder出來之后到達(dá)Renderer之前,一般都會打好時間戳了)。時間戳包括兩個時間,開始時間和結(jié)束時間。當(dāng)Renderer接收到一個Sample時,一般會將Sample的開始時間和當(dāng)前的Stream time作比較,如果Sample來晚了或者沒有時間戳,則馬上播放這個Sample;如果Sample來得早了,則通過調(diào)用參考時鐘的IReferenceClock::AdviseTime等待Sample的開始時間到達(dá)后再將這個Sample播放。Sample上的時間戳一般由Source Filter或Parser Filter來設(shè)置,設(shè)置的方法有如下幾種情況:
1. 文件回放(File playback):第一個Sample的時間戳從0開始打起,后面Sample的時間戳根據(jù)Sample有效數(shù)據(jù)的長度和回放速率來定。
2. 音視頻捕捉(Video and audio capture):原則上,采集到的每一個Sample的開始時間都打上采集時刻的Stream time。對于視頻幀,Preview pin出來的Sample是個例外,因?yàn)槿绻瓷鲜龇椒ù驎r間戳的話,每個Sample通過Filter鏈路傳輸,最后到達(dá)Video Renderer的時候都將是遲到的;Video Renderer通過Quality Control反饋給Source Filter,會導(dǎo)致Source Filter丟幀。所以,Preview pin出來的Sample都不打時間戳。對于音頻采集,需要注意的是,Audio Capture Filter與聲卡驅(qū)動程序兩者各自使用了不同的緩存,采集的數(shù)據(jù)是定時從驅(qū)動程序緩存拷貝到Filter的緩存的,這里面有一定時間的消耗。
3. 合成(Mux Filters):取決于Mux后輸出的數(shù)據(jù)類型,可以打時間戳,也可以不打時間戳。
大家可以看到,Sample的時間戳對于保證音視頻同步是很重要的。Video Renderer和Audio Renderer作為音視頻同步的最終執(zhí)行者,需要做很多工作。我們或許要開發(fā)其它各種類型的Filter,但一般這兩個Filter是不用再開發(fā)的。一是因?yàn)镽enderer Filter本身的復(fù)雜性,二是因?yàn)槲④洉@兩個Filter不斷升級,集成DirectX中其它模塊的最新技術(shù)(如DirectSound、DirectDraw、Direct3D等)。
最后,我們再來仔細(xì)看一下Live Source的情況。Live Source又叫Push source,包括Video /Audio Capture Filter、網(wǎng)絡(luò)廣播接收器等。Filter Graph Manager是如何知道一個Filter是Live Source的呢?通過如下任何一個條件判斷:
1. 調(diào)用Filter上的IAMFilterMiscFlags::GetMiscFlags返回有AM_FILTER_MISC_FLAGS_IS_SOURCE標(biāo)記,并且至少有一個Output pin實(shí)現(xiàn)了IAMPushSource接口。
2. Filter實(shí)現(xiàn)了IKsPropertySet接口,并且有一個Capture output pin(Pin的類型為PIN_CATEGORY_CAPTURE)。
Live Source對于音視頻同步的影響主要是以下兩個方面:Latency和Rate Matching。Latency是指Filter處理一個Sample花費(fèi)的時間,對于Live Source來說,主要取決于使用緩存的大小,比如采集30fps的視頻一般采集完一幀后才將數(shù)據(jù)以一個Sample發(fā)送出去,則這個Filter的Latency為33ms,而Audio一般緩存500ms后才發(fā)送一個Sample,則它的Latency就為500ms。這樣的話,Audio與Video到達(dá)Renderer就會偏差470ms,造成音視頻的不同步。默認(rèn)情況下,F(xiàn)ilter Graph Manager是不會對這種情況進(jìn)行調(diào)整的。當(dāng)然,應(yīng)用程序可以通過IAMPushSource接口來進(jìn)行Latency的補(bǔ)償,方法是調(diào)用IAMGraphStreams::SyncUsingStreamOffset函數(shù)。Filter Graph Manager的實(shí)現(xiàn)如下:對所有實(shí)現(xiàn)IAMPushSource接口的Filter調(diào)用IAMLatency::GetLatency得到各個Source的Latency值,記下所有Latency值中的最大值,然后調(diào)用IAMPushSource::SetStreamOffset對各個Source設(shè)置偏移值。
這樣,在Source Filter產(chǎn)生Sample時,打的時間戳就會加上這個偏移量。Rate Matching問題的引入,主要是由于Renderer Filter和Source Filter使用的是不同的參考時鐘。這種情況下,Renderer對數(shù)據(jù)的播放要么太快,要么太慢。另外,一般Live Source不能控制輸出數(shù)據(jù)的速率,所以必須在Renderer上進(jìn)行播放速率的匹配。因?yàn)槿说穆犛X敏感度要大于視覺敏感度,所以微軟目前只在Audio Renderer上實(shí)現(xiàn)了Rate Matching。實(shí)現(xiàn)Rate Matching的算法是比較復(fù)雜的,這里就不再贅述。
看到這里,大家應(yīng)該對DirectShow是如何解決音視頻同步問題的方案有一點(diǎn)眉目了吧。深層次的研究,還需要更多的測試、Base class源碼閱讀,以及DirectShow相關(guān)控制機(jī)制的理解,比如Quality Control Management等。
海峽廣播電視設(shè)備工程有限公司地址:福建省福州市鼓樓區(qū)軟件大道89號福州軟件園A區(qū)28號樓五層
Copyright ? 1999-2024All Rights Reserved閩ICP備12023208號