使用常見的視頻直播服務器,比如Nginx-RTMP做一個簡單的Demo環境。我們用OBS往Nginx-RTMP上推流,随便找一個播放器播放。一般情況下,我們會(huì)看到一個黑屏,然後(hòu)過(guò)一會(huì)之後(hòu),它才會(huì)出現圖像。我們對(duì)比一下,如果我們看手機直播,像陌陌直播,映客,我們點一下播放按紐之後(hòu),一秒鍾之内視頻直接就出來了。今天我們要講的内容就是這(zhè)個差别是怎麼(me)形成(chéng)的,背後(hòu)的原理是什麼(me)。出現黑屏的原因很簡單,就是解碼器沒(méi)有得到能(néng)解碼出視頻圖像的數據,那麼(me)導緻這(zhè)個問題的原因就很容易定位了:
比較搞笑一點的說法就是你家的網絡比較慢;
視頻直播服務器沒(méi)有打開(kāi)關鍵幀緩沖。
那麼(me)就這(zhè)些了嗎?我想對(duì)于這(zhè)個問題來說确實是這(zhè)樣(yàng)。爲了探究其中的原委,接下來我們需要往下去挖掘一下視頻到底是怎麼(me)播放的。
視頻是如何播放的
視頻要播放它肯定是有視頻數據,把視頻數據放到編碼器,然後(hòu)編碼器把這(zhè)個視頻數據解碼出來,解成(chéng)圖片,然後(hòu)播放到顯示器上,這(zhè)是一個基本的播放流程。一般來講,大家現在主流的用H.264編碼。對(duì)于H.264編碼來說,我們會(huì)有三個不同的幀,所謂幀是什麼(me)呢?就是你看到的每一個圖像。我們看到動态的視頻,大家知道(dào)電影最開(kāi)始用膠片拍的時候,每秒是25幀,是每秒25個圖片在切換。對(duì)于H.264來講,我們常見的有I幀,P幀,和B幀。
1.I幀,I-Frame也有人會(huì)叫(jiào)Inter Frame,那麼(me)它的意義是什麼(me)?
(1)它是一個自描述幀,你可以理解爲它就類似一個jpg圖片,它裡(lǐ)頭所有的數據,你解出來之後(hòu),它就是一整張圖片。
(2)無其他幀引用,它不需要去做前置和後(hòu)置的引用。
(3)它壓縮比是最小的,因爲它要包括整個圖片所有的數據在裡(lǐ)頭。
2.P幀,P-Frame也就是說預測幀,它的預測幀是怎麼(me)回事(shì)呢?大家有沒(méi)有用過(guò)版本管理軟件,比如git或SVN,這(zhè)樣(yàng)可能(néng)大家會(huì)比較好(hǎo)理解,P幀就是保留變的部分,不變的部分你去上一個或者幾個幀裡(lǐ)面(miàn)找就行。P幀隻是負責向(xiàng)前引用,也就是任何一個P幀,它隻看它往前的這(zhè)些幀的數據。P幀的好(hǎo)處是什麼(me)呢?因爲它隻存一些變化信息,所以它大概的壓縮比是I幀的50%。這(zhè)個數據哪來的?大家可以去翻一下維基百科,那裡(lǐ)會(huì)有一些介紹。
3.B幀,B-Frame,前後(hòu)雙向(xiàng)引用預測。
B幀比較特别,它要引用前面(miàn)P幀某一部分的圖像數據同時B幀後(hòu)面(miàn)的數據也會(huì)引用,這(zhè)個是B幀的特點,它要引用前面(miàn)的數據,也要引用後(hòu)面(miàn)的數據。那麼(me)它的優勢就是壓縮比比P幀還(hái)大,大概是I幀的25%,也就是我們B幀用的特别多的話,它會(huì)把視頻的大小降的比較低,因爲它的壓縮比更大一些。
I幀,B幀,P幀它是怎麼(me)組成(chéng)一個視頻流呢?我們管這(zhè)個東西叫(jiào)Group Of Picture,簡稱叫(jiào)GoP。
視頻解碼器,看到GoP它是怎麼(me)放呢?那很簡單,編碼器會(huì)有一個緩沖,然後(hòu)它會(huì)保留從I幀開(kāi)始,當然現在說是I幀,其實這(zhè)個I幀還(hái)有個特殊的類型。從I幀開(kāi)始,他會(huì)把數據緩存到解碼器的Buffer裡(lǐ),當他遇到下一個P幀,或者再下一個B幀的時候,它會(huì)從它Buffer裡(lǐ)找到它之前引用的那個幀,然後(hòu)把這(zhè)個數據解出來,最終播放到顯示器上。那麼(me)緩沖區開(kāi)始的第一個幀肯定是I幀,這(zhè)個毋庸置疑。另外還(hái)有一個比較特别的點,B幀和P幀并不會(huì)隻引用當前GoP裡(lǐ)的幀,他可能(néng)會(huì)往前去引用上一個GoP中的幀。既然它有這(zhè)麼(me)一個特性的話,我們什麼(me)時候去清空這(zhè)個緩沖區呢?這(zhè)裡(lǐ)就要介紹一個新的概念,叫(jiào)IDR幀。
IDR幀
IDR幀是I幀,但I幀并不一定是IDR幀,所謂IDR幀是什麼(me)?它就是拿到這(zhè)個幀之後(hòu),播放器可以直接從這(zhè)個幀開(kāi)始往後(hòu)播放,它保證後(hòu)面(miàn)的P幀和B幀的引用不會(huì)跨越這(zhè)個IDR幀,那麼(me)看到IDR幀,編碼器就可以把當前的Buffer清空,從當前這(zhè)IDR幀開(kāi)始解碼往Buffer裡(lǐ)邊放,後(hòu)續幀就可以從Buffer裡(lǐ)的數據引用,然後(hòu)解碼,也就是說編碼器可以從任何一個IDR幀開(kāi)始解碼。大家可以聯想到,當我播放一個視頻文件的時候,我可以拖動,但是我拖動的任何一個點,它肯定是一個IDR幀,當然它也是I幀,但是并不一定說每一個I幀我都(dōu)能(néng)讓它作爲一個拖動的點。
IDR幀有時也有它不太學(xué)術的叫(jiào)法:關鍵幀。在做編解碼程序的時候,我們可能(néng)會(huì)看到FFmpeg的數據結構裡(lǐ)會(huì)标著(zhe)PTS和DTS,那麼(me)PTS和DTS是什麼(me)呢?
PTS和DTS是什麼(me)
PTS,Presentation Time Stamp也就說這(zhè)個幀什麼(me)時候會(huì)放在顯示器上;DTS就是Decode Time Stamp,就是說這(zhè)個幀什麼(me)時候被(bèi)放在編碼器去解。那麼(me)如果全是I幀和P幀,PTS和DTS都(dōu)是單調遞增的,那麼(me)如果我們有B幀,會(huì)出現什麼(me)情況?因爲大家都(dōu)知道(dào),對(duì)于B幀來講,它會(huì)引用前面(miàn)的幀和後(hòu)面(miàn)的幀。
我們看這(zhè)個例子,就是當B幀進(jìn)來的時候,因爲它要引用後(hòu)面(miàn)的P幀,也要引用前面(miàn)的I幀,可以看到DTS的順序,一三四二,然後(hòu)PTS順序,一二三四。B幀它會(huì)根據它編碼時候的特性,它會(huì)自動的把它的DTS時間戳往後(hòu)挪,把它引用的幀先放到前面(miàn)去,等它引用的幀解完了,數據解完了之後(hòu),才會(huì)把B幀去解,否則的話,我先把B幀放進(jìn)去,它引用後(hòu)面(miàn)的P幀,P幀的數據還(hái)沒(méi)有,B幀解不出來。所以說這(zhè)點上給大家講一下,B幀,P幀,I幀它們整個放在一個視頻流裡(lǐ)面(miàn),它的解碼順序和編碼順序,當然後(hòu)續的話,我們可能(néng)會(huì)根據這(zhè)個有一些開(kāi)放性的思考。
爲什麼(me)直播會(huì)等待
對(duì)于直播來講,它是一個流,它不像點播,大家都(dōu)從0秒開(kāi)始,任何一個視頻文件,0秒第一個幀肯定都(dōu)是關鍵幀。那麼(me)對(duì)于直播來講,我是一個随機的時間點接到這(zhè)個視頻流進(jìn)行播放,那麼(me)我接入的這(zhè)個時間點的幀有可能(néng)拿到的第一個幀的數據是I幀,也有可能(néng)是B幀,也有可能(néng)是P幀。這(zhè)是一個随機的。在這(zhè)種(zhǒng)情況下,我們大概率會(huì)出現一個黑屏的狀态。因爲我拿到的是個P幀,對(duì)于P幀來講,解碼器面(miàn)那個Buffer是空的,它不知道(dào)這(zhè)個P幀如何進(jìn)行解碼,所以它隻能(néng)丢棄這(zhè)個幀。
對(duì)于直播來講,我一秒鍾的幀數是固定的,隻能(néng)等到我下一個關鍵幀到來的時候,我才能(néng)開(kāi)始去播放。當然正好(hǎo)趕巧了的話,接入那瞬間得到的數據正好(hǎo)是個I幀。就可以達到秒開(kāi)的效果。
關鍵幀緩沖如何工作
其實是在cache服務器上,它會(huì)去預先解一下這(zhè)個幀,然後(hòu)去看它到底是個I幀,還(hái)是個B幀,還(hái)是個P幀,當它發(fā)現是I幀的時候,它會(huì)放在它的程序的内存裡(lǐ)頭,當你每一次打開(kāi)這(zhè)個視頻流的時候,cache服務器會(huì)把内存中的I幀發(fā)送給客戶端比如當前播放到了P幀,那我把P幀前面(miàn)的I幀和P幀全波放到cache的内存裡(lǐ),然後(hòu)當客戶端接入之後(hòu)先把内存裡(lǐ)的數據發(fā)送給客戶端解碼器,然後(hòu)再從這(zhè)個B幀往後(hòu)給。對(duì)于這(zhè)個解碼器來講,它很舒服,它接到第一個數據流的第一個包肯定是I幀,那麼(me)它就可以直接播放了。
如何去做到秒開(kāi)?
沒(méi)有什麼(me)好(hǎo)的方法,任何人做這(zhè)種(zhǒng)事(shì)時候都(dōu)是一個笨的方法,就是去看文檔,沒(méi)有什麼(me)捷徑。大家可以翻閱FLV視頻文件格式文檔,它會(huì)告訴你package裡(lǐ)頭,它任何一個Video裡(lǐ)的package有一個Video TagHeader,對(duì)它是有一個Frame Type,Frame Type如果把它解出來,它是1的時候,它管這(zhè)個叫(jiào)key Frame,咱們看後(hòu)面(miàn)這(zhè)個AVC,a seekable Frame你可以理解爲它是個IDR幀,它并不一定就是I幀。
幾個問題
1.開(kāi)放性的解決問題,GoP Cache是從當前的這(zhè)個GoP幀開(kāi)還(hái)是從上一個GoP開(kāi)始
這(zhè)個問題比較有意思的是我從上一個GoP放的話,我拿過(guò)來的肯定是直接可以放了。因爲有時我也預見過(guò)一些比較特殊的編碼,會(huì)導緻我從當前這(zhè)個GoP的第一個I幀拿播放器放出來,我這(zhè)樣(yàng)可能(néng)會(huì)提高編碼器的兼容性,但是它會(huì)有一個問題,就是如果GoP開(kāi)的特别大的話,那麼(me)我的延時自然而然就會(huì)上去。因爲我上一個GoP如果是十秒,等于說我拿的是十秒之前的數據,也就是你看到的是10秒之前他說的話,他做得表情,他做的動作。那麼(me)我從當前這(zhè)個GoP開(kāi)始,它肯定是有一定的延遲,但是不會(huì)大到超過(guò)你整個GoP。對(duì)于視頻直播來講,我們GoP size多少合适,換一句話講如果GoP size設成(chéng)0,所有都(dōu)是I幀,不存在任何GoP cache問題,但是碼率來講會(huì)很高,因爲所有的都(dōu)是I幀,我們知道(dào)它壓縮比會(huì)比較低,那麼(me)反過(guò)來,就是我需要設一個GoP的Size是多少呢?
2.視頻直播的GoP Size設置成(chéng)多少合适
一般來講,對(duì)于手機直播,一到兩(liǎng)秒可能(néng)是比較合适的,因爲它本身的GoP時間也不會(huì)很長(cháng),我這(zhè)邊緩沖,一旦出現問題大概一到兩(liǎng)秒這(zhè)個視頻也能(néng)出來。有一個不太好(hǎo)的地方就是它碼率會(huì)稍微高一些,也就說同樣(yàng)的東西,如果我把GoP改成(chéng)十秒,我可能(néng)是500K,但是我改成(chéng)一秒,有可能(néng)變成(chéng)一個六七百K的樣(yàng)子,這(zhè)個還(hái)是跟編碼有關系,具體的比例是多少,可能(néng)跟實際相關。
另外如果是點播的話,不關心首屏打開(kāi)時間,隻要是客戶端下來速度快,CDN給力,那麼(me)我可能(néng)要求更小的範圍。告訴大家一個實踐過(guò)程中得出的結果,大家用過(guò)OBS?比如說做主播的話,大家用OBS會(huì)比較多,OBS它有一個問題就是它默認的話,如果你不調它的特性,GoP就是10秒,10秒的意思就是說GoP size。如果比較點背的話,看的是10秒之前的,如果是比較大的話,它的碼率,碼流的大小會(huì)小點,但是延遲會(huì)稍微高一些,CDN開(kāi)了幾個cache,有些情況下,我們也可以做些轉碼,強行把它的GoP size壓小,整個CDN層面(miàn)上加一個轉碼的話,它可能(néng)會(huì)增高這(zhè)個延遲,這(zhè)塊一個開(kāi)放性問題,大家可以根據自己的場景去思考,這(zhè)個GoP Size配成(chéng)多大比較合适。
3.你在視頻直播中到底用不用B幀
我有時候在搜相關的資料,也有做直播的不用B幀,所以這(zhè)一塊我并沒(méi)有什麼(me)結論,就是說給大家一個點,讓大家去想一想。鑒于B幀之前看的DTS和PTS的PPT,也知道(dào)B幀在解碼的時候,它是要打亂每一個幀傳入解碼器的順序,如果丢包或者一些特殊情況,它可能(néng)會(huì)影響解碼器的運行的特點。
一個數學(xué)故事(shì)
我不知道(dào)有多少人聽說過(guò)這(zhè)個事(shì),就是1943年以前,二戰的時候,因爲英國(guó)被(bèi)德國(guó)打的都(dōu)已經(jīng)找不著(zhe)北了,他希望美國(guó)在後(hòu)面(miàn)支持英國(guó),然後(hòu)去做一些物資上的支持,那麼(me)就會(huì)有很多的商船,運輸船把它的物資源源不斷的從美國(guó)送到英國(guó),德國(guó)人反制方式是什麼(me)?它的狼群潛艇在大西洋裡(lǐ)逛。對(duì)于軍事(shì)學(xué)家來講,他們可能(néng)想不出來一些什麼(me)好(hǎo)的辦法,他們就請來一些數學(xué)家問我這(zhè)個商船怎麼(me)開(kāi)比較合适,能(néng)保證一個是我的損失最小,另外一個我物資運送最大,碰到狼群的次數越低。這(zhè)些數學(xué)家根據概率統計的概率論,然後(hòu)得出一個結論,就是一定數量的船隊編隊規模越小,編次流越多與敵人相遇的概率就越大,換句話來講,就是我這(zhè)個船隊肯定是攢足了,比如說我原來有一百艘船的貨運量,那麼(me)我每一次發(fā)一艘船過(guò)去,那麼(me)我可能(néng)遇到狼群的概率會(huì)大一些,但是我把這(zhè)一百艘船變成(chéng)一個編隊,然後(hòu)我把我的護航編隊做到足夠好(hǎo),我把我這(zhè)一群送過(guò)去,我碰到的狼群概率很低的情況下,我可能(néng)效率更高一些。
數據包與網絡抖動之間的鬥争
我們從這(zhè)個數學(xué)故事(shì)發(fā)散去考慮一個問題,我們把視頻的數據保管好(hǎo),運維的數據保管好(hǎo),當成(chéng)我們的運輸艦,把網絡走動,丢包,我們想成(chéng)是一個狼群,就是德國(guó)的潛艇在整個大西洋上跑。我們就可以根據這(zhè)個數學(xué)結論去考慮一個特殊的點,我們的發(fā)包節奏是什麼(me)樣(yàng)的。
因爲我發(fā)一個包,比如說我發(fā)出一個數據包過(guò)去,你可以理解爲,我從美國(guó)發(fā)了一批貨運船,帶了一行護行艦隊去往英國(guó),那麼(me)我們到底是有數據就往外發(fā),還(hái)是我們攢一波數據之後(hòu)往外發(fā)?這(zhè)個就我剛才說的,你發(fā)包頻率應該是有講究的,看怎麼(me)去發(fā)比較合适。
另外,可不可以換一種(zhǒng)思路,使用類似TCP慢起(qǐ)動這(zhè)樣(yàng)的算法,我最開(kāi)始爲了保證首屏時間,我以最快的發(fā)包速率往上發(fā),等我發(fā)到一定的程度的時候,換成(chéng)慢包率發(fā)送,把時間拉長(cháng)一點,拉倒一個可以接受的範圍,然後(hòu)逐漸的去調整這(zhè)個東西,當然這(zhè)可能(néng)是比較傳統的,樸素的方式。最終的效果呢大家還(hái)得自己去根據這(zhè)個特性自己想,這(zhè)也是一個開(kāi)放性的問題。
發(fā)表評論 取消回複