做爲一個從未接觸過實時流(直播流)的人,我以前對實時視頻一直沒有概念,而最近參與的項目恰好有視頻監控的需求,在參與技術選型以前,我對前端實時流的展現進行了一下摸底。
javascript
視頻有一個流的概念,因此稱流媒體。實時視頻的流很好理解,由於視頻是實時的,須要有一個地方不停地輸出視頻出來,因此整個視頻能夠用流來稱呼。那麼視頻能否直接輸出到前端頁面上呢?惋惜,若是能夠的話,就沒有我這篇文章了。如今攝像頭的實時視頻流廣泛採用的是 RTSP 協議,而前端並不能直接播放 RTSP 的視頻流。css
那麼咱們就須要一層中間層,來將 RTSP 流轉成前端能夠支持的協議,這也引伸出了目前實時流技術的幾種方向:html
RTMP(Real Time Messaging Protocol)是屬於 Adobe 的一套視頻協議,這套方案須要專門的 RTMP 流媒體,而且若是想要在瀏覽器上播放,沒法使用 HTML5 的 video 標籤,只能使用 Flash 播放器。(經過使用 video.js@5.x 如下的版本能夠作到用 video 標籤進行播放,但仍然須要加載 Flash)。它的實時性在幾種方案中是最好的,可是因爲只能使用 Flash 的方案,因此在移動端就直接 GG 了,在 PC 端也是明日黃花。
因爲下面的兩種方法也須要用到 RTMP,因此這裏就展現一下 RTSP 流如何轉換成 RTMP ,咱們使用 ffmpeg+Nginx+nginx-rtmp-module 來作這件事:前端
# 在 http 同一層配置 rtmp 協議的相關字段 rtmp { server { # 端口 listen 1935; # 路徑 application test { # 開啓實時流模式 live on; record off; } } }
# bash 上執行 ffmpeg 把 rtsp 轉成 rtmp,並推到 1935 這個端口上 ffmpeg -i "rtsp://xxx.xxx.xxx:xxx/1" -vcodec copy -acodec copy -f flv "rtmp://127.0.0.1:1935/live/"
這樣咱們就獲得了一個 RTMP 的流,咱們能夠直接用 VLC 或者 IINA 來播放這個流。
java
HLS(HTTP Live Streaming)是蘋果公司提出的基於 HTTP 協議的的流媒體網絡傳輸協議,它的工做原理是把整個流分紅一個個小的基於 HTTP 的文件來下載,每次只下載一些。HLS 具備跨平臺性,支持 iOS/Android/瀏覽器,通用性強。可是它的實時性差:蘋果官方建議是請求到3個片以後纔開始播放。因此通常不多用 HLS 作爲互聯網直播的傳輸協議。假設列表裏面的包含5個 ts 文件,每一個 TS 文件包含5秒的視頻內容,那麼總體的延遲就是25秒。蘋果官方推薦的小文件時長是 10s,因此這樣就會有30s(n x 10)的延遲。
下面是 HLS 實時流的整個鏈路:
從圖中能夠看出來咱們須要一個服務端做爲編碼器和流分割器,接受流並不斷輸出成流片斷(stream),而後前端再經過一個索引文件,去訪問這些流片斷。那麼咱們一樣可使用 nginx+ffmpeg 來作這件事情。nginx
# 在 rtmp 的 server 下開啓 hls # 做爲上圖中的 Server,負責流的處理 application hls{ live on; hls on; hls_path xxx/; #保存 hls 文件的文件夾 hls_fragment 10s; }
# 在 http 的 server 中添加 HLS 的配置: # 做爲上圖中的 Distribution,負責分片文件和索引文件的輸出 location /hls { # 提供 HLS 片斷,聲明類型 types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /Users/mark/Desktop/hls; #訪問切片文件保存的文件夾 # Cache-Controll no-cache; expires -1; }
而後一樣使用 ffmpeg 推流到 hls 路徑上:git
ffmpeg -i "rtsp://xxx.xxx.xxx:xxx/1" -vcodec copy -acodec copy -f flv rtmp://127.0.0.1:1935/hls
這個時候能夠看到文件夾裏已經有許多流文件存在,且不停地更新:
而後咱們可使用 video.js+video.js-contrib-hls 來播放這個視頻:github
<html> <head> <title>video</title> <!-- 引入css --> <link href="https://unpkg.com/video.js/dist/video-js.min.css" rel="stylesheet"> </head> <body> <div class="videoBox"> <video id="video" class="video-js vjs-default-skin" controls> <source src="http://localhost:8080/hls/test.m3u8" type="application/x-mpegURL"> </video> </div> </body> </html> <script src="https://unpkg.com/video.js/dist/video.min.js"></script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js"></script> <script> videojs.options.flash.swf = "./videojs/video-js.swf" videojs('video', {"autoplay":true}).play(); </script>
在個人測試下,HLS 的延遲在10-20秒左右,咱們能夠經過調整切片的大小來減小延遲,可是因爲架構的限制,延遲是一個不可忽視的問題。
ajax
接下來就是重頭戲 HTTP-FLV 了,它集合了 HLS 的通用性和 RTMP 的實時性,能夠作到在瀏覽器上用 HTML5 的 video 標籤,以較低的延時播放實時流。HTTP-FLV 依靠 MIME 的特性,根據協議中的 Content-Type 來選擇相應的程序去處理相應的內容,使得流媒體能夠經過 HTTP 傳輸。除此以外,它能夠經過 HTTP 302 跳轉靈活調度/負載均衡,支持使用 HTTPS 加密傳輸,也可以兼容支持 Android,iOS 等移動端。HTTP-FLV 本質上是將流轉成 HTTP 協議下的 flv 文件,在 Nginx 上咱們可使用 nginx-http-flv-module 來將 RTMP 流轉成 HTTP 流。瀏覽器
其實 flv 格式依然是 Adobe 家的格式,原生 Video 標籤沒法直接播放,可是好在咱們有 bilibili 家的 flv.js,它能夠將 FLV 文件流轉碼複用成 ISO BMFF(MP4 碎片)片斷,而後經過 Media Source Extensions 將 MP4 片斷喂進瀏覽器。
在支持瀏覽器的協議裏,延遲排序是這樣的:RTMP = HTTP-FLV = WebSocket-FLV < HLS
而性能排序是這樣的:RTMP > HTTP-FLV = WebSocket-FLV > HLS
說了這麼多,不如直接上手看看吧:
# rtmp server application myvideo { live on; gop_cache: on; #減小首屏等待時間 } # http server location /live { flv_live on; }
// 前端使用 flv.js,開啓實時模式,而後訪問這個 nginx 地址下的路徑便可 import flvJs from 'flv.js'; export function playVideo(elementId, src) { const videoElement = document.getElementById(elementId); const flvPlayer = flvJs.createPlayer({ isLive: true, type: 'flv', url: src, }); flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); } playVideo('#video', 'http://localhost:8080/live?port=1985&app=myvideo&stream=streamname')
能夠看到 flv.js 使用了 video/x-flv
這個 MIME 返回數據。
若是對延遲有更高的要求,能夠嘗試下面的操做:
enableStashBuffer
字段,它是 flv.js 用於控制緩存 buffer 的開關,關閉了以後能夠作到最小延遲,但因爲沒有緩存,可能會看到網絡抖動帶來的視頻卡頓。gop_cache
。gop_cache
又稱關鍵幀緩存,其意義是控制視頻的關鍵幀之間的緩存是否開啓。這裏引入了一個關鍵幀的概念:咱們使用最普遍的 H.264
視頻壓縮格式,它採用了諸如幀內預測壓縮/幀間預測壓縮等壓縮方案,最後獲得了 BPI 三種幀:
帶有 I 幀、B 幀和 P 幀的典型視頻序列。P 幀只須要參考前面的 I 幀或 P 幀,而 B 幀則須要同時參考前面和後面的 I 幀或 P 幀。因爲 P/B 幀對於 I 幀都有直接或者間接的依賴關係,因此播放器要解碼一個視頻幀序列,並進行播放,必須首先解碼出 I 幀。假設 GOP(就是視頻流中兩個I幀的時間距離) 是 10 秒,也就是每隔 10 秒纔有關鍵幀,若是用戶在第 5 秒時開始播放,就沒法拿到當前的關鍵幀了。這個時候 gop_cache 就起做用了:gop_cache 能夠控制是否緩存最近的一個關鍵幀。開啓 gop_cache 可讓客戶端開始播放時,當即收到一個關鍵幀,顯示出畫面,固然,因爲增長了對上一個幀的緩存,因此延時天然就變大了。若是對延時有更高的要求,而對於首屏時間/播放流暢度的要求沒那麼高的話,那麼能夠嘗試關閉 gop_cache,來達到低延時的效果。
實時視頻的延時與卡頓是視頻質量中最重要的兩項指標。 然而,這兩項指標從理論上來講,是一對矛盾的關係——須要更低的延時,則代表服務器端和播放端的緩衝區都必須更短,來自網絡的異常抖動容易引發卡頓;業務能夠接受較高的延時時,服務端和播放端均可以有較長的緩衝區,以應對來自網絡的抖動,提供更流暢的體驗。
如今各個直播平臺基本上都放棄了以上這些比較傳統的方式,使用了雲服務商提供的 CDN,但仍是離不開前文所說的幾種協議與方式。以下圖是阿里雲的直播服務圖。能夠看到其流程大概分爲這幾步:
PS:若是你已經看到這兒了,以爲我寫得還行的話,麻煩給個贊,謝謝!🙏