視頻要播放它確定是有視頻數據,把視頻數據放到編碼器,而後編碼器把這個視頻數據解碼出來,解成圖片,而後播放到顯示器上,這是一個基本的播放流程。通常來說,你們如今主流的用H.264編碼。對於H.264編碼來講,咱們會有三個不一樣的幀,所謂幀是什麼呢?就是你看到的每個圖像。咱們看到動態的視頻,你們知道電影最開始用膠片拍的時候,每秒是25幀,是每秒25個圖片在切換。對於H.264來說,咱們常見的有I幀,P幀,和B幀。git
1.I幀,I-Frame也有人會叫Inter Frame,那麼它的意義是什麼?算法
它是一個自描述幀,你能夠理解爲它就相似一個jpg圖片,它裏頭全部的數據,你解出來以後,它就是一整張圖片。緩存
無其餘幀引用,它不須要去作前置和後置的引用。服務器
它壓縮比是最小的,由於它要包括整個圖片全部的數據在裏頭。網絡
2.P幀,P-Frame也就是說預測幀,它的預測幀是怎麼回事呢?你們有沒有用過版本管理軟件,好比git或SVN,這樣可能你們會比較好理解,P幀就是保留變的部分,不變的部分你去上一個或者幾個幀裏面找就行。P幀只是負責向前引用,也就是任何一個P幀,它只看它往前的這些幀的數據。P幀的好處是什麼呢?由於它只存一些變化信息,因此它大概的壓縮比是I幀的50%。這個數據哪來的?你們能夠去翻一下維基百科,那裏會有一些介紹。數據結構
3.B幀,B-Frame,先後雙向引用預測。運維
B幀比較特別,它要引用前面P幀某一部分的圖像數據同時B幀後面的數據也會引用,這個是B幀的特色,它要引用前面的數據,也要引用後面的數據。那麼它的優點就是壓縮比比P幀還大,大概是I幀的25%,也就是咱們B幀用的特別多的話,它會把視頻的大小降的比較低,由於它的壓縮比更大一些。ide
I幀,B幀,P幀它是怎麼組成一個視頻流呢?咱們管這個東西叫Group Of Picture,簡稱叫GoP。編碼
視頻解碼器,看到GoP它是怎麼放呢?那很簡單,編碼器會有一個緩衝,而後它會保留從I幀開始,固然如今說是I幀,其實這個I幀還有個特殊的類型。從I幀開始,他會把數據緩存到解碼器的Buffer裏,當他遇到下一個P幀,或者再下一個B幀的時候,它會從它Buffer裏找到它以前引用的那個幀,而後把這個數據解出來,最終播放到顯示器上。那麼緩衝區開始的第一個幀確定是I幀,這個毋庸置疑。另外還有一個比較特別的點,B幀和P幀並不會只引用當前GoP裏的幀,他可能會往前去引用上一個GoP中的幀。既然它有這麼一個特性的話,咱們何時去清空這個緩衝區呢?這裏就要介紹一個新的概念,叫IDR幀。spa
IDR幀是I幀,但I幀並不必定是IDR幀,所謂IDR幀是什麼?它就是拿到這個幀以後,播放器能夠直接從這個幀開始日後播放,它保證後面的P幀和B幀的引用不會跨越這個IDR幀,那麼看到IDR幀,編碼器就能夠把當前的Buffer清空,從當前這IDR幀開始解碼往Buffer裏邊放,後續幀就能夠從Buffer裏的數據引用,而後解碼,也就是說編碼器能夠從任何一個IDR幀開始解碼。你們能夠聯想到,當我播放一個視頻文件的時候,我能夠拖動,可是我拖動的任何一個點,它確定是一個IDR幀,固然它也是I幀,可是並不必定說每個I幀我都能讓它做爲一個拖動的點。
IDR幀有時也有它不太學術的叫法:關鍵幀。在作編解碼程序的時候,咱們可能會看到FFmpeg的數據結構裏會標着PTS和DTS,那麼PTS和DTS是什麼呢?
PTS,Presentation Time Stamp也就說這個幀何時會放在顯示器上;DTS就是Decode Time Stamp,就是說這個幀何時被放在編碼器去解。那麼若是全是I幀和P幀,PTS和DTS都是單調遞增的,那麼若是咱們有B幀,會出現什麼狀況?由於你們都知道,對於B幀來說,它會引用前面的幀和後面的幀。
咱們看這個例子,就是當B幀進來的時候,由於它要引用後面的P幀,也要引用前面的I幀,能夠看到DTS的順序,一三四二,而後PTS順序,一二三四。B幀它會根據它編碼時候的特性,它會自動的把它的DTS時間戳日後挪,把它引用的幀先放到前面去,等它引用的幀解完了,數據解完了以後,纔會把B幀去解,不然的話,我先把B幀放進去,它引用後面的P幀,P幀的數據尚未,B幀解不出來。因此說這點上給你們講一下,B幀,P幀,I幀它們整個放在一個視頻流裏面,它的解碼順序和編碼順序,固然後續的話,咱們可能會根據這個有一些開放性的思考。
對於直播來說,它是一個流,它不像點播,你們都從0秒開始,任何一個視頻文件,0秒第一個幀確定都是關鍵幀。那麼對於直播來說,我是一個隨機的時間點接到這個視頻流進行播放,那麼我接入的這個時間點的幀有可能拿到的第一個幀的數據是I幀,也有多是B幀,也有多是P幀。這是一個隨機的。在這種狀況下,咱們大機率會出現一個黑屏的狀態。由於我拿到的是個P幀,對於P幀來說,解碼器面那個Buffer是空的,它不知道這個P幀如何進行解碼,因此它只能丟棄這個幀。
對於直播來說,我一秒鐘的幀數是固定的,只能等到我下一個關鍵幀到來的時候,我才能開始去播放。固然正好趕巧了的話,接入那瞬間獲得的數據正好是個I幀。就能夠達到秒開的效果。
實際上是在cache服務器上,它會去預先解一下這個幀,而後去看它究竟是個I幀,仍是個B幀,仍是個P幀,當它發現是I幀的時候,它會放在它的程序的內存裏頭,當你每一次打開這個視頻流的時候,cache服務器會把內存中的I幀發送給客戶端好比當前播放到了P幀,那我把P幀前面的I幀和P幀全波放到cache的內存裏,而後當客戶端接入以後先把內存裏的數據發送給客戶端解碼器,而後再從這個B幀日後給。對於這個解碼器來說,它很舒服,它接到第一個數據流的第一個包確定是I幀,那麼它就能夠直接播放了。
沒有什麼好的方法,任何人作這種事時候都是一個笨的方法,就是去看文檔,沒有什麼捷徑。你們能夠翻閱FLV視頻文件格式文檔,它會告訴你package裏頭,它任何一個Video裏的package有一個Video TagHeader,對它是有一個Frame Type,Frame Type若是把它解出來,它是1的時候,它管這個叫key Frame,我們看後面這個AVC,a seekable Frame你能夠理解爲它是個IDR幀,它並不必定就是I幀。
開放性的解決問題,GoP Cache是從當前的這個GoP幀開仍是從上一個GoP開始?
這個問題比較有意思的是我從上一個GoP放的話,我拿過來的確定是直接能夠放了。由於有時我也預見過一些比較特殊的編碼,會致使我從當前這個GoP的第一個I幀拿播放器放出來,我這樣可能會提升編碼器的兼容性,可是它會有一個問題,就是若是GoP開的特別大的話,那麼個人延時天然而然就會上去。由於我上一個GoP若是是十秒,等於說我拿的是十秒以前的數據,也就是你看到的是10秒以前他說的話,他作得表情,他作的動做。那麼我從當前這個GoP開始,它確定是有必定的延遲,可是不會大到超過你整個GoP。對於視頻直播來說,咱們GoP size多少合適,換一句話講若是GoP size設成0,全部都是I幀,不存在任何GoP cache問題,可是碼率來說會很高,由於全部的都是I幀,咱們知道它壓縮比會比較低,那麼反過來,就是我須要設一個GoP的Size是多少呢?
通常來說,對於手機直播,一到兩秒多是比較合適的,由於它自己的GoP時間也不會很長,我這邊緩衝,一旦出現問題大概一到兩秒這個視頻也能出來。有一個不太好的地方就是它碼率會稍微高一些,也就說一樣的東西,若是我把GoP改爲十秒,我多是500K,可是我改爲一秒,有可能變成一個六七百K的樣子,這個仍是跟編碼有關係,具體的比例是多少,可能跟實際相關。
另外若是是點播的話,不關心首屏打開時間,只要是客戶端下來速度快,CDN給力,那麼我可能要求更小的範圍。告訴你們一個實踐過程當中得出的結果,你們用過OBS?好比說作主播的話,你們用OBS會比較多,OBS它有一個問題就是它默認的話,若是你不調它的特性,GoP就是10秒,10秒的意思就是說GoP size。若是比較點背的話,看的是10秒以前的,若是是比較大的話,它的碼率,碼流的大小會小點,可是延遲會稍微高一些,CDN開了幾個cache,有些狀況下,咱們也能夠作些轉碼,強行把它的GoP size壓小,整個CDN層面上加一個轉碼的話,它可能會增高這個延遲,這塊一個開放性問題,你們能夠根據本身的場景去思考,這個GoP Size配成多大比較合適。
我有時候在搜相關的資料,也有作直播的不用B幀,因此這一塊我並無什麼結論,就是說給你們一個點,讓你們去想想。鑑於B幀以前看的DTS和PTS的PPT,也知道B幀在解碼的時候,它是要打亂每個幀傳入解碼器的順序,若是丟包或者一些特殊狀況,它可能會影響解碼器的運行的特色
我不知道有多少人據說過這個事,就是1943年之前,二戰的時候,由於英國被德國打的都已經找不着北了,他但願美國在後面支持英國,而後去作一些物資上的支持,那麼就會有不少的商船,運輸船把它的物資源源不斷的從美國送到英國,德國人反制方式是什麼?它的狼羣潛艇在大西洋裏逛。對於軍事學家來說,他們可能想不出來一些什麼好的辦法,他們就請來一些數學家問我這個商船怎麼開比較合適,能保證一個是個人損失最小,另一個我物資運送最大,碰到狼羣的次數越低。這些數學家根據機率統計的機率論,而後得出一個結論,就是必定數量的船隊編隊規模越小,編次流越多與敵人相遇的機率就越大,換句話來說,就是我這個船隊確定是攢足了,好比說我原來有一百艘船的貨運量,那麼我每一次發一艘船過去,那麼我可能遇到狼羣的機率會大一些,可是我把這一百艘船變成一個編隊,而後我把個人護航編隊作到足夠好,我把我這一羣送過去,我碰到的狼羣機率很低的狀況下,我可能效率更高一些。
咱們從這個數學故事發散去考慮一個問題,咱們把視頻的數據保管好,運維的數據保管好,當成咱們的運輸艦,把網絡走動,丟包,咱們想成是一個狼羣,就是德國的潛艇在整個大西洋上跑。咱們就能夠根據這個數學結論去考慮一個特殊的點,咱們的發包節奏是什麼樣的。
由於我發一個包,好比說我發出一個數據包過去,你能夠理解爲,我從美國發了一批貨運船,帶了一行護行艦隊去往英國,那麼咱們究竟是有數據就往外發,仍是咱們攢一波數據以後往外發?這個就我剛纔說的,你發包頻率應該是有講究的,看怎麼去發比較合適。
另外,可不能夠換一種思路,使用相似TCP慢起動這樣的算法,我最開始爲了保證首屏時間,我以最快的發包速率往上發,等我發到必定的程度的時候,換成慢包率發送,把時間拉長一點,拉倒一個能夠接受的範圍,而後逐漸的去調整這個東西,固然這多是比較傳統的,樸素的方式。最終的效果呢你們還得本身去根據這個特性本身想,這也是一個開放性的問題。