【前五篇】系列文章傳送門:html
你們都會關注「在瀏覽器輸入一個地址,而後回車,會發生什麼」這樣一個問題,可是有沒有想過這樣一個問題:主播開始直播,用戶打開客戶端觀看,這個過程發生了什麼?算法
隨着技術的發展,直播技術對人們生活的滲透日益加深。從最開始的遊戲直播,到前幾天爆出來的教育直播,甚至如今都有直播招聘。編程
而咱們喜歡的這些直播,他們用到的傳輸協議有一個通用名-流媒體傳輸協議。瀏覽器
要認識流媒體協議,就離不開下面的三大系列名詞。緩存
在解釋上面的三大系列名詞以前,我們先來了解下,視頻到底是什麼?服務器
博主記得小時候,常常會玩一種叫動感畫冊的東西。一本很小的畫冊上,每一頁都畫了一幅圖,用手快速的翻過每一頁,就能看到一個很短的「動畫片」。網絡
沒錯,我們看到的視頻,本質上就是一連串快速播放的圖片。負載均衡
每一張圖片,咱們稱爲一幀。只要每秒鐘的數據足夠多,也就是播放速度足夠快,人眼就看不出是一張張獨立的圖片。對於人眼而言,這個播放臨界速度是每秒 30 幀,而這裏的 30 也就是咱們常說的幀率(FPS)。ide
每一張圖片,都是由像素組成,而每一個像素又是由 RGB 組成,每一個 8 位,共 24 位。函數
咱們假設一個視頻中的全部圖片的像素都是 1024*768,能夠大概估算下視頻的大小:
每秒鐘大小 = 30 幀 x 1024 x 768 x 24 = 566,231,010 Bits = 70,778,880 Bytes
按咱們上面的估算,一分鐘的視頻大小就是 4,,246,732,800 Bytes,這裏已經有 4 個 G 了。
是否是和咱們平常接觸到的視頻大小明顯不符?這是由於咱們在傳輸的過程當中,將視頻壓縮了。
爲何要壓縮視頻?按咱們上面的估算,一個一小時的視頻,就有 240G,這個數據量根本沒辦法存儲和傳輸。所以,人們利用編碼技術,給視頻「瘦身」,用盡可能少的 Bit 數保持視頻,同時要保證播放的時候,畫面仍然很清晰。實際上,編碼就是壓縮的過程。
咱們之因此可以對視頻流中的圖片進行壓縮,由於視頻和圖片有下列這些特色:
從上面這些特色中能夠看出,用於編碼的算法很是複雜,並且多種多樣。雖然算法多種,但編碼過程其實是相似的,以下圖:
視頻編碼的算法這麼多,能不能造成必定的標準呢?固然能,這裏我們就來認識下視頻編碼的兩大流派。
視頻通過編碼以後,生動活潑的一幀幀圖像就變成了一串串讓人看不懂的二進制。這個二進制能夠放在一個文件裏,而後按照必定的格式保存起來,這裏的保存格式,就是系列名詞一。
編碼後的二進制文件就能夠經過某種網絡協議進行封裝,放在互聯網上傳輸,這個時候就能夠進行網絡直播了。
網絡協議將編碼好的視頻流,從主播端推送到服務器,在服務器上有個運行了一樣協議的服務端來接收這些網絡數據包,從而獲得裏面的視頻流,這個過程稱爲接流。
服務端接到視頻流以後,能夠滴視頻流進行必定的處理,好比轉碼,也就是從一個編碼格式轉成另外一種格式,這樣才能適應各個觀衆使用的客戶端,保證他們都能看到直播。
流處理完畢後,就能夠等待觀衆的客戶端來請求這些視頻流。觀衆的客戶端請求視頻流的過程稱爲拉流。
若是有很是多的觀衆同時看一個視頻直播,都從一個服務器上拉流,壓力就很是大,所以須要一個視頻的分發網絡,將視頻預先加載到就近的邊緣節點,這樣大部分觀衆就能經過邊緣節點拉取視頻,下降服務器的壓力。
當觀衆將視頻流拉下來後,就須要進行解碼,也就是經過上述過程的逆過程,將一串串看不懂的二進制轉變成一幀幀生動的圖片,在客戶端播放出來。
整個直播過程,能夠用下圖來描述:
接下來,咱們依次來看一下每一個過程:
雖然咱們說視頻是一張張圖片的序列,但若是每張圖片都完整,就太大了,於是會將視頻序列分紅三種幀:
能夠看出,I 幀最完整,B 幀壓縮率最高,而壓縮後幀的序列,應該是 IBBP 間隔出現。這就是經過時序進行編碼。
在一幀中,分紅多個片,每一個片中分紅多個宏塊,每一個宏塊分紅多個子塊,這樣將一張大圖分解成一個個小塊,能夠方便進行空間上的編碼。以下圖:
儘管時空很是立體的組成了一個序列,但總歸仍是要壓縮成一個二進制流。這個流是有結構的,是一個個的網絡提取層單元(NALU,Network Abstraction Layer Unit)。變成這種格式就是爲了傳輸,由於網絡上的傳輸,默認的是一個個的包,於是這裏也就分紅了一個個的單元。
如上圖,每一個 NALU 首先是一個起始標識符,用於標識 NALU 之間的間隔。而後是 NALU 的頭,裏面主要配置了 NALU 的類型。最後的 Payload 裏面是 NALU 承載的數據。
在 NALU 頭裏面,主要的內容是類型 NAL Type,其中:
在傳輸視頻流以前,剝削要傳輸者兩類參數,否則就沒法解碼。爲了保證容錯性,每個 I 幀以前,都會傳一遍這兩個參數集合。
若是 NALU Header 裏面的表示類型是 SPS 或 PPS,則 Payload 中就是真正的參數集的內容。
若是類型是幀,則 Payload 中是真正的視頻數據。固然也是一幀幀保存的。前面說了,一幀的內容仍是挺多的,於是每個 NALU 裏面保存的是一片。對於每一片,究竟是 I 幀,仍是 P 幀,亦或是 B 幀,在片結構裏面也有 Header,這裏面有個類型用來標識幀的類型,而後是片的內容。
這樣,整個格式就出來了。一個視頻,能夠拆分紅一系列的幀,每一幀拆分紅一系列的片,每一片都放在一個 NALU 裏面,NALU 之間都是經過特殊的起始標識符分隔,在每個 I 幀的第一片前面,要插入單獨保存 SPS 和 PPS 的 NALU,最終造成一個長長的 NALU 序列。
造成 NALU 序列後,還須要將這個二進制的流打包成網絡包進行發送。這裏咱們以 RTMP 協議爲例,進入第二個過程,推流。
RTMP 是基於 TCP 的,於是也須要雙方創建一個 TCP 鏈接。在有 TCP 的鏈接的基礎上,還須要創建一個 RTMP 鏈接,也就是在程序裏面,咱們調用 RTMP 類庫的 Connet 函數,顯式建立一個鏈接。
RTMP 爲何須要創建一個單獨的鏈接呢?
由於通訊雙方須要商量一些事情,保證後續的傳輸能正常進行。其實主要就是兩個事情:
溝通這些事情,須要發送 6 條消息:
首先,客戶端發送 C0 表示本身的版本號,沒必要等對方回覆,而後發送 C1 表示本身的時間戳。
服務器只有在收到 C0 的時候,纔會返回 S0,代表本身的版本號,若是版本不匹配,能夠斷開鏈接。
服務器發送完 S0 後,也不用等待,就直接發送本身的時間戳 S1。
客戶端收到 S1 時,發一個知道了最煩時間戳的 ACK C2。同理,服務器收到 C1 的時候,發一個知道了對方時間戳的 ACK S2。
因而,握手完成。
握手以後,雙方須要互相傳遞一些控制信息,例如 Chunk 塊的大小、窗口大小等。
真正傳輸數據的時候,仍是須要建立一個流 Stream,而後經過這個 Stream 來推流。
推流的過程,就是講 NALU 放在 Message 裏面發送,這個也稱爲 RTMP Packet 包。其中,Message 的格式就像下圖所示:
發送的時候,去掉 NALU 的起始標識符。由於這部分對於 RTMP 協議來說沒有用。接下來,將 SPS 和 PPS 參數集封裝成一個 RTMP 包發送,而後發送一個個片的 NALU。
RTMP 在收發數據的時候並非以 Message 爲單位的,而是把 Message 拆分紅 Chunk 發送,並且必須在一個 Chunk 發送完成以後,才能開始發送下一個 Chunk。每一個 Chunk 中都帶有 Message ID,表示屬於哪一個 Message,接收端也會按照這個 ID 將 Chunk 組裝成 Message。
前面鏈接的時候,設置 Chunk 塊大小就是指這個 Chunk。將大的消息變爲小的塊再發送,能夠在低帶寬的狀況下,減小網絡擁塞。
下面用一個分塊的示例,來了解下 RTMP 是如何分塊的。
假設一個視頻的消息長度是 307,而 Chunk 大小約定爲 128,那麼消息就會被拆分爲 3 個 Chunk。
第一個 Chunk 的 Type = 0,表示 Chunk 頭是完整的。頭裏面 Timestamp 爲 1000,總長度 Length 爲 307,類型爲 9,是個視頻,Stream ID 爲 12346,正文部分承擔 128 個字節的 Data。
第二個 Chunk 也要發送 128 個字節,可是因爲 Chunk 頭和第一個同樣,所以它的 Chunk Type = 3,表示頭和第一個 Chunk 同樣。
第三個 Chunk 要發送的 Data 的長度爲 51 個字節,Chunk Type 仍是用的 3。
就這樣,數據源源不斷的到達流媒體服務器,整個過程就像下圖:
這個時候,大量觀看直播的觀衆就能夠經過 RTMP 協議從流媒體服務器上拉取。爲了減輕服務器壓力,咱們會使用分發網絡。
分發網絡分爲中心和邊緣兩層。邊緣層服務器部署在全國各地及橫跨各大運營商裏,和用戶距離很近。而中心層是流媒體服務集羣,負責內容的轉發。
智能負載均衡系統,根據用戶的地理位置信息,就近選擇邊緣服務器,爲用戶提供推/拉流服務。中心層也負責轉碼服務。例如,將 RTMP 協議的碼流轉換成 HLS 碼流。
接下來,咱們再來看看觀衆經過 RTMP 拉流的過程。
先讀到的是 H.264 的解碼參數,例如 SPS 和 PPS,而後對收到的 NALU 組成一個個幀,進行解碼,交給播放器播放,這樣客戶端就能看到直播視頻了。
參考: