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