大家都會關注“在瀏覽器輸入一個地址,然后回車,會發生什么”這樣一個問題,但是有沒有想過這樣一個問題:主播開始直播,用戶打開客戶端觀看,這個過程發生了什么?
隨著技術的發展,直播技術對人們生活的滲透日益加深。從最開始的游戲直播,到前幾天爆出來的教育直播,甚至現在都有直播招聘。
而我們喜歡的這些直播,他們用到的傳輸協議有一個通用名-流媒體傳輸協議。
要認識流媒體協議,就離不開下面的三大系列名詞。
三大系列名詞
系列一:AVI、MPEG、RMVB、MP4、MOV、FLV、WebM、WMV、ASF、MKV。是不是就 MP4 看著熟悉?
系列二:H.261、H.262、H.263、H.264、H.265。能認出來幾個?別著急,重點關注 H.264。
系列三:MPEG-1、MPEG-2、MPEG-4、MPEG-7。是不是更懵逼了?
在解釋上面的三大系列名詞之前,咱們先來了解下,視頻究竟是什么?
博主記得小時候,經常會玩一種叫動感畫冊的東西。一本很小的畫冊上,每一頁都畫了一幅圖,用手快速的翻過每一頁,就能看到一個很短的“動畫片”。
沒錯,咱們看到的視頻,本質上就是一連串快速播放的圖片。
每一張圖片,我們稱為一幀。只要每秒鐘的數據足夠多,也就是播放速度足夠快,人眼就看不出是一張張獨立的圖片。對于人眼而言,這個播放臨界速度是每秒 30 幀,而這里的 30 也就是我們常說的幀率(FPS)。
每一張圖片,都是由像素組成,而每個像素又是由 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 數保持視頻,同時要保證播放的時候,畫面仍然很清晰。實際上,編碼就是壓縮的過程。
視頻和圖片的壓縮特點
我們之所以能夠對視頻流中的圖片進行壓縮,因為視頻和圖片有下列這些特點:
空間冗余:圖像的相鄰像素之間有較強的相關性,一張圖片相鄰像素往往是漸變的,而不是突變的,沒必要每個像素都完整的保存,可以隔幾個保存一個,中間的用算法計算出來。
時間冗余:視頻序列的相鄰圖像之間內容相似。一個視頻中連續出現的圖片也不是突變的,可以根據已有的圖片進行預測和推斷。
視覺冗余:人的視覺系統對某些細節不敏感,因此不會注意到每一個細節,可以允許丟失一些數據。
編碼冗余:不同像素值出現的概率不同,概率高的用的字節少,概率低的用的字節多,類似霍夫曼編碼的思路。
從上面這些特點中可以看出,用于編碼的算法非常復雜,而且多種多樣。雖然算法多種,但編碼過程實際上是類似的,如下圖:
視頻編碼的兩大流派
視頻編碼的算法這么多,能不能形成一定的標準呢?當然能,這里咱們就來認識下視頻編碼的兩大流派。
流派一: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 為什么需要建立一個單獨的連接呢?
因為通信雙方需要商量一些事情,保證后續的傳輸能正常進行。其實主要就是兩個事情:
確定版本號。如果客戶端、服務端的版本號不一致,就不能正常工作;
確定時間戳。視頻播放中,時間是很重要的一個元素,后面的數據流互通的時候,經常要帶上時間戳的差值,因而一開始雙方就要知道對方的時間戳。
溝通這些事情,需要發送 6 條消息:
客戶端發送 C0、C1、C2
服務端發送 S0、S1、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,解碼成視頻格式進行播放。
編輯:hfy
-
流媒體
+關注
關注
1文章
194瀏覽量
16659 -
編碼技術
+關注
關注
1文章
35瀏覽量
11047
發布評論請先 登錄
相關推薦
評論