做直播,我們是認真的:Web音視訊串流與WebRTC

語言: CN / TW / HK

摘要

本文章主要對在瀏覽器環境中實現直播相關的技術介紹,其中包括了對音視訊的格式以及推送、播放技術的探索。分享過程中還會對目前自己已經實現的一套Web直播方案進行分析。最後還會簡單介紹現在比較熱門的WebRTC技術。

什麼是音視訊串流

音視訊串流從廣義角度來說,就是能夠實現裝置A將音視訊畫面同步傳輸給裝置B進行播放,例如電視投屏、會議投屏。而今天所介紹的Web音視訊串流主要是實現允許使用者在瀏覽器環境就能完成串流。

一個完整的Web媒體串流應當具備3種角色,推流客戶端(主播側),媒體伺服器(MediaServer)和拉流客戶端(觀眾側)。其中推流和拉流客戶端其實都是在網頁中進行處理的,而MediaServer位於服務端,並且要能夠完成同時接收來自不同推流客戶端的流資料,並對這些流進行區分,向拉流客戶端提供獲取對應流的渠道。

Untitled Diagram (8)

需要知道的音視訊概念

音視訊編碼格式

在封裝格式裡的視訊可以用不同的編碼格式,編碼格式簡單的理解就是用特定的壓縮技術把視訊做些處理。不過容器其實也可以做些壓縮處理。所以視訊是可以在編碼格式、容器格式中做兩次壓縮。

常見的編碼格式有:mpeg-2、mpeg-4、h.263、h.264、RV40

音視訊封裝格式

封裝格式就是把已經編碼封裝好的視訊、音訊按照一定的規範放到一起

同一種封裝格式中可以放不同編碼的視訊,不過一種視訊容器格式一般是隻支援某幾類編碼格式的視訊。我們能夠最直觀判斷封裝格式的方法就是檔案字尾。

常見的容器格式有: MP4、rmvb、rm、flv、AVI、mov、WMV、mk

瀏覽器支援的格式

對於video標籤來說,瀏覽器支援視訊播放的封裝格式有:MP4、WebM和Ogg。其中我們就MP4來說,必須使用h.264編碼的視訊和aac編碼的音訊,才能被瀏覽器正確解析,否則是沒有辦法播放的。

Untitled Diagram (2)

下面列出了另外兩種封裝格式下瀏覽器能直接解碼的音視訊編碼組合:

  • WebM :使用 VP8 視訊編解碼器和 Vorbis 音訊編解碼器
  • Ogg:使用 Theora 視訊編解碼器和 Vorbis音訊編解碼器

適合媒體串流中的封裝格式

由於在媒體串流的過程中,客戶端是需要源源不斷接受來自外界傳輸的音視訊,並且需要播放已經接受到的部分,後文中把這種特殊的媒體簡寫為流媒體

而不論是MP4還是WebM還是Ogg都是需要等待完整的資料傳遞完成後才能夠開始播放,並且不能將多個音視訊進行無縫連線播放,所以這三種格式統統無法沒流媒體利用,涼涼。

因此我們需要其他更加適合串流的媒體封裝格式,這裡主要介紹下面兩種,為後文作鋪墊。

FLV(Flash Video)

Untitled Diagram (5)

FLV格式的流中, 每一個音視訊資料都被封裝成了包含時間戳資訊頭的資料包。在傳輸時,只需要當播放器拿到這些資料包解包的時候能夠根據時間戳資訊把這些音視訊資料和之前到達的音視訊資料連續起來播放。

而MP4,MKV等等類似這種封裝,必須拿到完整的音視訊檔案才能播放,因為裡面的單個音視訊資料塊不帶有時間戳資訊,播放器不能將這些沒有時間戳資訊資料塊連續起來,所以就不能實時的解碼播放。

TS(Transport Stream)

就如TS(傳輸流)的命名,似乎天生就是為了流媒體而生的一種封裝格式,其特點是多個TS片段可以被播放器無縫拼接進行播放,無需等待重新載入。不過TS的實現相對FLV要複雜許多,在此不再說明,這裡我們簡單知道它的特點就可以了。

Web音視訊串流的協議

RTMP(Real Time Messaging Protocol 實時資訊控制協議)

RTMP是 Adobe Systems 公司為 Flash 播放器和伺服器之間音訊、視訊和資料傳輸開發的開放協議, 該協議在國內直播平臺中較為普及。

RTMP 是一種基於TCP進行實時流媒體通訊的網路協議,主要用來在 Flash 平臺和支援 RTMP 協議的流媒體伺服器之間進行音視訊和資料通訊。RTMP協議下可以用來拉流,也可以進行退流。在瀏覽器中並不支援RTMP協議,只能通過Flash外掛進行處理。RTMP傳輸是所支援的媒體格式為FLV。

主播 ==> MediaServer ==> 觀眾

HTTP-FLV

這種協議主要是為了讓原本只能在RTMP中進行傳輸的FLV音視訊流也能夠在HTTP下進行傳輸。主要是用於FLV能夠在瀏覽器頁面中進行播放,由於HTML的Video不直接支援Flv格式的音視訊,所以在早期需要在網頁中加入Flash外掛才能夠播放。目前大量的流媒體伺服器 Media Server都支援了將FLV格式流通過HTTP-FLV的形式對外界進行開放。

HLS

HLS(HTTP Living Stream) 是一個由蘋果公司提出的基於 HTTP 的流媒體網路傳輸協議。

HLS 的工作原理是把整個流分成一個個小的基於 HTTP 的檔案來下載,每次只下載一些。當媒體流正在播放時,客戶端可以選擇從許多不同的備用源中以不同的速率下載同樣的資源,允許流媒體會話適應不同的資料速率。在開始一個流媒體會話時,客戶端會下載一個包含元資料的 extended M3U (m3u8) playlist檔案,用於尋找可用的媒體流。

img

來解釋一下這張圖,從左到右講,左下方的inputs的視訊源是什麼格式都無所謂,他與server之間的通訊協議也可以任意(比如RTMP),總之只要把視訊資料傳輸到伺服器上即可。這個視訊在server伺服器上被轉換成HLS格式的視訊(既TS和m3u8檔案)檔案。細拆分來看server裡面的Media encoder的是一個轉碼模組負責將視訊源中的視訊資料轉碼到目標編碼格式(H264)的視訊資料。轉碼成H264視訊資料之後,在stream segmenter模組將視訊切片,切片的結果就是index file(m3u8)和ts檔案了。圖中的Distribution其實只是一個普通的HTTP檔案伺服器,然後客戶端只需要訪問一級m3u8檔案的路徑就會自動播放HLS視訊流了。

說說m3u8檔案

m3u8的命名來源是m3u檔案 + utf-8編碼而來,兩者的檔案內容是完全一樣的。下文中直接稱為m3u

m3u (移動影象專家組音訊層3統一資源定位器)

m3u實際上就是一個索引檔案,其中可以記錄TS檔案地址,客戶端會按照下載的順序進行連續播放。

image-20210418115656418

對於一個記錄了TS的檔案M3U的檔案內容如下

``` m3u8

EXTM3U // 宣告檔案為M3U,必須寫在第一行

EXT-X-PLAYLIST-TYPE:VOD // 當前播放型別為點播

EXT-X-TARGETDURATION:10 //每個視訊分段最大的時長(單位秒)

EXTINF:10, //下面ts切片的播放時長

2000kbps-00001.ts //ts檔案路徑

EXTINF:10,

2000kbps-00002.ts

ZEN-TOTAL-DURATION:20

ZEN-AVERAGE-BANDWIDTH:2190954

ZEN-MAXIMUM-BANDWIDTH:3536205

EXT-X-ENDLIST // m3u結束指令

```

不光如此,m3u還可以記錄二級m3u的檔案的地址。

1328846-d0df01e6b2dec3bb

下面是一個記錄了檔案地址的m3u8內容

```m3u8

EXTM3U

EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2128000

drawingSword(1080p).m3u8

EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1064000

drawingSword(720p).m3u8

EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=564000

drawingSword(480p).m3u8

EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=282000

drawingSword(360p).m3u8

EXT-X-ENDLIST

```

bandwidth指定視訊流的位元率,PROGRAM-ID表示資源的ID,每一個#EXT-X-STREAM-INF的下一行是二級index檔案的路徑,可以用相對路徑也可以用絕對路徑。例子中用的是相對路徑。這個檔案中記錄了不同位元率視訊流的二級index檔案路徑,客戶端可以當前環境的網路頻寬,來決定播放哪一個視訊流。也可以在網路頻寬變化的時候平滑切換到和頻寬匹配的視訊流。

HLS目前的不足

由於HLS需要將採集到的音視訊進行分片、客戶端也需要對接受到的分片後的音視訊進行合併處理,因此相對來時會存在比較大的延遲,大概會達到 10s左右。

HLS在瀏覽器中的相容情況

事實上,HLS 在 PC 端僅支援safari瀏覽器,而其他大部分PC瀏覽器使用HTML5 video標籤由於無法解析TS所以不能直接播放(需要通過hls.js)。移動端不論是安卓還是IOS統統都原生支援HLS。這點主要是由於HLS是由Apple公司推廣的,蘋果自家的瀏覽器上都是支援的,而安卓也進行了跟進。所以如果想要在PC瀏覽器上使用到HLS,仍然需要使用其他技術手段才能實現。

各協議總結:

| 協議 | http-flv | rtmp | hls | | --------- | -------- | ------ | ---------- | | 傳輸層 | http | tcp | http | | 視訊格式 | flv | flv | Ts檔案 | | 延時 | 低 | 低 | 很高 | | 資料分段 | 連續流 | 連續流 | 切片檔案 | | Html5播放 | 暫不支援 | 不支援 | 移動端支援 |

Web音視訊串流實踐方案(WebSocket + RTMP)

這裡簡單介紹一種在網頁中進行音視訊串流的方案: 把RTMP推流的工作放到中間服務層(此次以Node為例)去,而拉流通過流媒體伺服器開放的HTTP-FLV,並在播放前使用Flv.js將轉換後的流資料餵給Video去解析播放。

FFmpeg:一款處理音視訊非常有效的工具, 這款工具提供以命令列的方式去對視訊進行轉碼、轉封裝格式、增加水印等等功能其中還包括了RTMP推流的功能。同時FFmpeg也為Node提供了一些控制的Bridge,在後文中,我會在中間層使用Node來控制FFmpeg進行音視訊的推流。

loadimage

STEP1:推流客戶端 => Node中間處理層

這裡有包括兩種推流的音視訊源,一種是實時錄製自己的攝像頭進行推流。另外一種是使用本地的視訊檔案進行傳送。這裡我們分開來講

對於推流客戶端推送實時錄製的音視訊資料時,其核心操作就用過瀏覽器來呼叫攝像頭資料,並通過MediaStreamRecorder.js去捕獲音視訊二進位制資料(Blob)注意此處每個時間片返回一個blob,獲得當前時間片的Blob後通過WebSocket將blob資料傳遞給Node中間層。

而對於推流客戶端推送視訊格式的檔案時,需要做的就是將視訊檔案按照每段時間的視訊量進行分片。例如對於一個100s的視訊,如果按照每段blob需要傳送4s的資料量,那就需要分為25段blob,這些可以通過前端去進行分片,分片傳送的中間Node層。後面的操作就和攝像頭推流一致了。

STEP2:Node中間處理層 => 流媒體伺服器

在Blob資料抵達Node中間層後會被轉化為Buffer,在Node層我們需要做的這些分段的視訊格式轉化為ts格式的片段,再將ts片段(這裡就利用到了TS能夠無縫拼接進行播放的特性,能夠讓ffmpeg推送持續的流媒體)通過ffmpeg將持續不斷接受到的TS格式的流媒體轉化為Flv流並通過RTMP推流到流媒體伺服器上。

Untitled Diagram (12)

流媒體伺服器是在Node.js環境下通過node-media-server第三方包進行搭建。Node-media-server許多不同協議的推流和拉流的方法。其中它支援外界通過RTMP的方式進行推流拉流(埠1935),也支援將RTMP流轉換為HTTP-FLV協議的流對外開放(埠8000)。通過Node-media-server建立一個流媒體服務步驟也很簡單,只需要如下程式碼:

```javascript const NodeMediaServer = require('node-media-server');

const config = { rtmp: { port: 1935, chunk_size: 60000, gop_cache: true, ping: 30, ping_timeout: 60 }, http: { port: 8000, allow_origin: '*' } };

const nms = new NodeMediaServer(config) nms.run();

```

在Node-media-server中,提供了通過使用不用的URL路徑來對流進行區分。這裡貼上一個官方提供的URL格式 rtmp://hostname:port/appname/stream http://hostname:port/appname/stream.flv 例如對於音視訊流A,它的URL是這樣的:

rtmp://47.110.88.142:1935/live/root_14465

可以看出對於這個流,appName這裡被命名為了live, 而stream被命名為了root_14465。對應的,如果想通過HTTP-FLV進行拉這條流,可以通過下面這條URL:

http://47.110.88.142:8000/live/root_14465.flv

在完成流媒體伺服器的搭建後,不同使用者會生成對應不同的推流URL,只要為觀眾發放正確對應的拉流URL就會播放正確的流資料。實現了並行串流的目的。

本地演示:

  1. 起流媒體伺服器
  2. 通過ffmpeg推流
  3. 通過http-flv拉流

STEP3: 流媒體伺服器 => 拉流客戶端

通過Http-flv進行傳輸,瀏覽器的video標籤通過http獲取到flv流後,使用Flv.js【後文中介紹】進行轉換封包格式後為MP4後便能夠持續播放。

Flv.js

在實現過程中,我們用到了flv.js, 這是B站開源的一款媒體流轉碼外掛,該外掛能夠利用Js將接受到的HTTP-FLV實時轉碼為Video能夠接受的MP4格式。

img

得益於瀏覽器提供了Media Source Extensions API(MSE),使得flv.js能夠通過JavaScript來對流資料格式進行轉換,並分片為

這裡我們貼一段flv.js的簡單的使用程式碼

``` html

```

Flv.js不足

MSE目前在PC端瀏覽器上支援較好,但是一些較低版本的移動端流覽器就不是那麼理想了如圖:

image-20210418002005470

由於Flv.js是基於MSE製作的,所以一些低版本移動端流覽器中是無法正常播放的,因此在考慮相容性的情況下需要慎用。

關於WebRTC和音視訊會議

我們不難發現市場已經出現了一種新興的平臺 - Web視訊會議,使用者直接通過PC瀏覽器即可加入音視訊會議。列出一些目前市場上的web視訊會議產品:

Google Meet: https://meet.google.com

輕雀視訊會議https://www.qingque.cn/meet/

image-20210419160402279

我們知道移動端的瀏覽器由於種類繁多並不能較好的適配,因此不被推薦直接在移動端瀏覽器中使用web視訊會議

為此上面兩款產品中Google給出的方案是需要下載一個應用程式GoogleMeet,而輕雀給出的方案比較適合國內的場景,提供了微信小程式或者輕雀APP兩種方式供參會者加入。

音視訊會議的思考

那讓我們從音視訊串流的角度來考慮,視訊會議就是讓傳統的單向直播變化為了雙向直播,即每個參與者即需要推流,也需要拉流。例如下圖,3位參會者,伺服器就需要接受3條推流和6條拉流,顯然這樣對流媒體伺服器的壓力過於龐大,並且還有不可忽視的延時問題。

Untitled Diagram (9)

上面這個問題促進了Google對WebRTC的建設:

WebRTC (Web Real-Time Communications) 是一項由Google推行的實時通訊技術,它允許網路應用或者站點,在不借助中間媒介的情況下,建立瀏覽器之間點對點(Peer-to-Peer)的連線,實現視訊流和(或)音訊流或者其他任意資料的傳輸。

大部分主流瀏覽器為開發者開放了可以開箱即用WebRTC - API,可以通過執行API就能呼叫指定的Peer並建立連線。但需要注意使用WebRTC技術雖然音視訊資料不經過服務端傳輸,但是仍然需要服務端去交換一些連線的必要資訊。我們在開頭所說的兩款Web音視訊會議產品就是通過WebRTC實現的。

WebRTC技術的工作流

WebRTC更適合用來做Web視訊會議的原因就是它能夠實現瀏覽器和瀏覽器之間進行音視訊的傳輸。

Untitled Diagram (10)

這其中的傳輸過程如圖。這裡面的PeerN指的是連線的瀏覽器客戶端,在多個Peer連線之前,每個Peer需要和信令伺服器(Signaling Server)進行連線,這裡面提到的信令伺服器需要由開發者自行搭建。

可以看到Peer與 信令伺服器的連線是雙向的,這是因為連線的各方需要通過信令伺服器交換一些關鍵資訊,這些資訊都會以信令的資訊傳送和接受。

Peer之間主要需要交換下面3種類型的資訊

  • 初始化和關閉通訊,及報告錯誤;
  • 使用者WebRTC的的IP和埠號
  • 當前瀏覽器支援播放的音視訊編碼及封裝格式,以及能播放的最高解析度等資訊。

其中需要重點關注第二點,因為需要交換各個Peer之間的IP來進行Peer之間的連線,所以要保證Peer之間是要能夠正確訪問到對方ip的,如果所有的Peer都是在同一個區域網下沒有問題,但是如果參會的Peer來自不通的區域網,那麼他們交換的IP是無法訪問到的。

所以在這裡還需要增加一個藉助另一種伺服器(稱為STUN server)實現NAT/Firewall穿越。主要做的工作就是將使用者交換IP的過程中需要把根據區域網IP生成一個公網IP,這樣就實現了公網環境的傳輸。

Untitled Diagram (11)

最後

我們在文章中瞭解了Web傳統的音視訊串流、初窺了新興的WebRTC技術,這兩項技術能力其實也對應著兩種不同的應用場景。前者輔以CDN更符合面向觀眾的直播場景,後者適合中小型的音視訊會議的場景,短期來看WebRTC無法代替傳統音視訊串流。