網絡協議 14 - 流媒體協議:要說愛你不容易

【前五篇】系列文章傳送門:html

  1. 網絡協議 9 - TCP協議(下):聰明反被聰明誤
  2. 網絡協議 10 - Socket 編程(上):實踐是檢驗真理的惟一標準
  3. 網絡協議 11 - Socket 編程(下):眼見爲實耳聽爲虛
  4. 網絡協議 12 - HTTP 協議:經常使用而不簡單
  5. 網絡協議 13 - HTTPS 協議:加密路上無盡頭

    你們都會關注「在瀏覽器輸入一個地址,而後回車,會發生什麼」這樣一個問題,可是有沒有想過這樣一個問題:主播開始直播,用戶打開客戶端觀看,這個過程發生了什麼?算法

    隨着技術的發展,直播技術對人們生活的滲透日益加深。從最開始的遊戲直播,到前幾天爆出來的教育直播,甚至如今都有直播招聘。編程

    而咱們喜歡的這些直播,他們用到的傳輸協議有一個通用名-流媒體傳輸協議瀏覽器

    要認識流媒體協議,就離不開下面的三大系列名詞。緩存

三大系列名詞

  • 系列一:AVI、MPEG、RMVB、MP四、MOV、FLV、WebM、WMV、ASF、MKV。是否是就 MP4 看着熟悉?
  • 系列二:H.26一、H.26二、H.26三、H.26四、H.265。能認出來幾個?彆着急,重點關注 H.264。
  • 系列三:MPEG-一、MPEG-二、MPEG-四、MPEG-7。是否是更懵逼了?

    在解釋上面的三大系列名詞以前,我們先來了解下,視頻到底是什麼?服務器

    博主記得小時候,常常會玩一種叫動感畫冊的東西。一本很小的畫冊上,每一頁都畫了一幅圖,用手快速的翻過每一頁,就能看到一個很短的「動畫片」。網絡

    沒錯,我們看到的視頻,本質上就是一連串快速播放的圖片負載均衡

    每一張圖片,咱們稱爲一幀。只要每秒鐘的數據足夠多,也就是播放速度足夠快,人眼就看不出是一張張獨立的圖片。對於人眼而言,這個播放臨界速度是每秒 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 數保持視頻,同時要保證播放的時候,畫面仍然很清晰。實際上,編碼就是壓縮的過程

視頻和圖片的壓縮特色

咱們之因此可以對視頻流中的圖片進行壓縮,由於視頻和圖片有下列這些特色:

  1. 空間冗餘:圖像的相鄰像素之間有較強的相關性,一張圖片相鄰像素每每是漸變的,而不是突變的,不必每一個像素都完整的保存,能夠隔幾個保存一個,中間的用算法計算出來。
  2. 時間冗餘:視頻序列的相鄰圖像之間內容類似。一個視頻中連續出現的圖片也不是突變的,能夠根據已有的圖片進行預測和推斷。
  3. 視覺冗餘:人的視覺系統對某些細節不敏感,所以不會注意到每個細節,能夠容許丟失一些數據。
  4. 編碼冗餘:不一樣像素值出現的機率不一樣,機率高的用的字節少,機率低的用的字節多,相似霍夫曼編碼的思路。

    從上面這些特色中能夠看出,用於編碼的算法很是複雜,並且多種多樣。雖然算法多種,但編碼過程其實是相似的,以下圖:

視頻編碼的兩大流派

    視頻編碼的算法這麼多,能不能造成必定的標準呢?固然能,這裏我們就來認識下視頻編碼的兩大流派。

  • 流派一:ITU(International tELECOMMUNICATIONS Union)的 VCEG(Video Coding Experts Group),這個稱爲國際電聯下的 VCEG。既然是電信,可想而知,他們最初是作視頻編碼,主要側重傳輸。咱們上面的系列名詞二,就是這個組織制定的標準。
  • 流派二:ISO(International Standards Organization)的 MPEG(Moving Picture Experts Group),這個是 ISO 旗下的 MPEG。原本是作視頻存儲的,就像我們場面常說的 VCD 和 DVD。後來也慢慢側重視頻傳輸了。系列名詞三就是這個組織制定的標準。
  • 後來,ITU-T(國際電信聯盟電信標準化部門)與 MPEG 聯合制定了 H.264/MPEG-4 AVC,這也是咱們重點關注的。

直播數據傳輸

    視頻通過編碼以後,生動活潑的一幀幀圖像就變成了一串串讓人看不懂的二進制。這個二進制能夠放在一個文件裏,而後按照必定的格式保存起來,這裏的保存格式,就是系列名詞一。

    編碼後的二進制文件就能夠經過某種網絡協議進行封裝,放在互聯網上傳輸,這個時候就能夠進行網絡直播了。

    網絡協議將編碼好的視頻流,從主播端推送到服務器,在服務器上有個運行了一樣協議的服務端來接收這些網絡數據包,從而獲得裏面的視頻流,這個過程稱爲接流

    服務端接到視頻流以後,能夠滴視頻流進行必定的處理,好比轉碼,也就是從一個編碼格式轉成另外一種格式,這樣才能適應各個觀衆使用的客戶端,保證他們都能看到直播。

    流處理完畢後,就能夠等待觀衆的客戶端來請求這些視頻流。觀衆的客戶端請求視頻流的過程稱爲拉流

    若是有很是多的觀衆同時看一個視頻直播,都從一個服務器上拉流,壓力就很是大,所以須要一個視頻的分發網絡,將視頻預先加載到就近的邊緣節點,這樣大部分觀衆就能經過邊緣節點拉取視頻,下降服務器的壓力。

    當觀衆將視頻流拉下來後,就須要進行解碼,也就是經過上述過程的逆過程,將一串串看不懂的二進制轉變成一幀幀生動的圖片,在客戶端播放出來。

    整個直播過程,能夠用下圖來描述:

    接下來,咱們依次來看一下每一個過程:

編碼:將豐富多彩的圖片變成二進制流

    雖然咱們說視頻是一張張圖片的序列,但若是每張圖片都完整,就太大了,於是會將視頻序列分紅三種幀:

  • I幀,也稱關鍵幀。裏面是完整的圖片,只須要本幀數據,就能夠完成解碼。
  • P幀,前向預測編碼幀。P 幀表示的是這一幀跟以前一個關鍵幀(或 P 幀)的差異,解碼時須要用以前緩存的畫面,疊加上和本幀定義的差異,生成最終畫面。
  • B幀,雙向預測內插編碼幀。B 幀記錄的是本幀與先後幀的差異。要解碼 B 幀,不只要取得以前的緩存畫面,還要解碼以後的畫面,經過先後畫面的數據與本幀數據的疊加,取得最終的畫面。

    能夠看出,I 幀最完整,B 幀壓縮率最高,而壓縮後幀的序列,應該是 IBBP 間隔出現。這就是經過時序進行編碼

    在一幀中,分紅多個片,每一個片中分紅多個宏塊,每一個宏塊分紅多個子塊,這樣將一張大圖分解成一個個小塊,能夠方便進行空間上的編碼。以下圖:

    儘管時空很是立體的組成了一個序列,但總歸仍是要壓縮成一個二進制流。這個流是有結構的,是一個個的網絡提取層單元(NALU,Network Abstraction Layer Unit)。變成這種格式就是爲了傳輸,由於網絡上的傳輸,默認的是一個個的包,於是這裏也就分紅了一個個的單元。

    如上圖,每一個 NALU 首先是一個起始標識符,用於標識 NALU 之間的間隔。而後是 NALU 的頭,裏面主要配置了 NALU 的類型。最後的 Payload 裏面是 NALU 承載的數據。

    在 NALU 頭裏面,主要的內容是類型 NAL Type,其中:

  • 0x07 表示 SPS,是序列參數集,包括一個圖像序列的全部信息,如圖像尺寸、視頻格式等。
  • 0x08 表示 PPS,是圖像參數集,包括一個圖像的全部分片的全部相關信息,包括圖像類型、序列號等。

    在傳輸視頻流以前,剝削要傳輸者兩類參數,否則就沒法解碼。爲了保證容錯性,每個 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 爲何須要創建一個單獨的鏈接呢?

    由於通訊雙方須要商量一些事情,保證後續的傳輸能正常進行。其實主要就是兩個事情:

  1. 肯定版本號。若是客戶端、服務端的版本號不一致,就不能正常工做;
  2. 肯定時間戳。視頻播放中,時間是很重要的一個元素,後面的數據流互通的時候,常常要帶上時間戳的差值,於是一開始雙方就要知道對方的時間戳。

    溝通這些事情,須要發送 6 條消息:

  • 客戶端發送 C0、C一、C2
  • 服務端發送 S0、S一、S2

    首先,客戶端發送 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 組成一個個幀,進行解碼,交給播放器播放,這樣客戶端就能看到直播視頻了。

小結

  • 視頻名詞比較多,編碼兩大流派達成了一致,都是經過時間、空間的各類算法來壓縮數據;
  • 壓縮好的數據,爲了傳輸而組成一系列的 NALU,按照幀和片依次排列;
  • 排列好的 NALU,在網絡傳輸的是,要按照 RTMP 包的格式進行包裝,RTMP 的包會拆分紅 Chunk 進行傳輸;
  • 推送到流媒體集羣的視頻流通過轉碼和分發,能夠被客戶端經過 RTMP 協議拉取,而後組合成 NALU,解碼成視頻格式進行播放。

參考:

  1. RTMP 協議規範;
  2. 劉超 - 趣談網絡協議系列課;
相關文章
相關標籤/搜索