細說RTSP實現前端直播流探索記「乾貨」

細說RTSP實現前端直播流探索記「乾貨」

作者:竹之同學

轉發連結:https://segmentfault。com/a/1190000022994032

前言

作為一個從未接觸過實時流(直播流)的人,我之前對實時影片一直沒有概念,而最近參與的專案剛好有影片監控的需求,在參與技術選型之前,我對前端實時流的展示進行了一下摸底。

概覽

影片有一個流的概念,所以稱流媒體。實時影片的流很好理解,因為影片是實時的,需要有一個地方不停地輸出影片出來,所以整個影片可以用流來稱呼。那麼影片可否直接輸出到前端頁面上呢?可惜,如果可以的話,就沒有我這篇文章了。現在攝像頭的實時影片流普遍採用的是 RTSP 協議,而前端並不能直接播放 RTSP 的影片流。

RTSP(Real-Time Stream Protocol),是 TCP/UDP 協議體系中的一個應用層協議,跟 HTTP 處在同一層。RTSP 在體系結構上位於 RTP 和RTCP 之上,它使用 TCP 或者 RTP 完成資料傳輸。RTSP 實時效果非常好,適合影片聊天、影片監控等方向。

那麼我們就需要一層中間層,來將 RTSP 流轉成前端可以支援的協議,這也引申出了目前實時流技術的幾種方向:

RTSP -> RTMP

RTSP -> HLS

RTSP -> RTMP -> HTTP-FLV

細說RTSP實現前端直播流探索記「乾貨」

RTMP

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 來播放這個流。

HLS

HLS(HTTP Live Streaming)是蘋果公司提出的基於 HTTP 協議的的流媒體網路傳輸協議,它的工作原理是把整個流分成一個個小的基於 HTTP 的檔案來下載,每次只下載一些。HLS 具有跨平臺性,支援 iOS/Android/瀏覽器,通用性強。但是它的實時性差:蘋果官方建議是請求到3個片之後才開始播放。所以一般很少用 HLS 作為網際網路直播的傳輸協議。假設列表裡面的包含5個 ts 檔案,每個 TS 檔案包含5秒的影片內容,那麼整體的延遲就是25秒。蘋果官方推薦的小檔案時長是 10s,所以這樣就會有30s(n x 10)的延遲。

下面是 HLS 實時流的整個鏈路:

細說RTSP實現前端直播流探索記「乾貨」

從圖中可以看出來我們需要一個服務端作為編碼器和流分割器,接受流並不斷輸出成流片段(stream),然後前端再透過一個索引檔案,去訪問這些流片段。那麼我們同樣可以使用 nginx+ffmpeg 來做這件事情。

# 在 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 路徑上:

ffmpeg -i “rtsp://xxx。xxx。xxx:xxx/1” -vcodec copy -acodec copy -f flv rtmp://127。0。0。1:1935/hls

這個時候可以看到資料夾裡已經有許多流檔案存在,且不停地更新:

細說RTSP實現前端直播流探索記「乾貨」

然後我們可以使用 video。js+video。js-contrib-hls 來播放這個影片:

video<!—— 引入css ——>

在我的測試下,HLS 的延遲在10-20秒左右,我們可以透過調整切片的大小來減少延遲,但是由於架構的限制,延遲是一個不可忽視的問題。

HTTP-FLV

接下來就是重頭戲 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

說了這麼多,不如直接上手看看吧:

首先我們需要一個新的 nginx 外掛:nginx-http-flv-module

在 nginx。conf 中進行一些新的配置:

# rtmp serverapplication myvideo { live on; gop_cache: on; #減少首屏等待時間}# http serverlocation /live { flv_live on;}

依然用 ffmpeg 來推流,使用上面 RTMP 的命令

前端 import flv。js,然後使用它來播放

// 前端使用 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 返回資料。

細說RTSP實現前端直播流探索記「乾貨」

如果對延遲有更高的要求,可以嘗試下面的操作:

可以配置 flv。js 的 enableStashBuffer 欄位,它是 flv。js 用於控制快取 buffer 的開關,關閉了之後可以做到最小延遲,但由於沒有快取,可能會看到網路抖動帶來的影片卡頓。

可以嘗試關閉 nginx 的 http 配置裡的 gop_cache 。gop_cache 又稱關鍵幀快取,其意義是控制影片的關鍵幀之間的快取是否開啟。

這裡引入了一個關鍵幀的概念:我們使用最廣泛的 H。264 影片壓縮格式,它採用了諸如幀內預測壓縮/幀間預測壓縮等壓縮方案,最後得到了 BPI 三種幀:

I 幀:關鍵幀,採用幀內壓縮技術。

P 幀:向前參考幀,在壓縮時,只參考前面已經處理的幀,表示的是當前幀畫面與前一幀(前一幀可能是 I 幀也可能是 P 幀)的差別。採用幀間壓縮技術。

B 幀:雙向參考幀,在壓縮時,它即參考前面的幀,又參考它後面的幀。B 幀記錄的是本幀與前後幀的差別。採用幀間壓縮技術。

帶有 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,但還是離不開前文所說的幾種協議與方式。如下圖是阿里雲的直播服務圖。可以看到其流程大概分為這幾步:

採集影片流(主播端使用 RTMP 進行推流)

推流到 CDN 節點(上傳流)

CDN 節點轉到直播中心,直播中心類似於強大的具有計算能力的中間源,可以提供額外服務諸如裸存(錄製/錄製到雲端儲存/點播),轉碼,稽核,多種協議的輸出等。

直播中間分發到 CDN 節點

播放(阿里雲支援 RTMP、FLV 及 HLS 三種播流協議)

細說RTSP實現前端直播流探索記「乾貨」

PS:如果你已經看到這兒了,覺得我寫得還行的話,麻煩給個贊,謝謝!

推薦JavaScript學習相關文章

《一文帶你搞懂前端登陸設計》

《使用 Node。js 將圖片中的蘋果變成橘子「實踐」》

《基於Canvas實現的高斯模糊(上)「JS篇」》

《基於Canvas實現的高斯模糊(下)「JS篇」》

《由淺入深,66條JavaScript面試知識點(一)》

《由淺入深,66條JavaScript面試知識點(二)》

《由淺入深,66條JavaScript面試知識點(三)》

《由淺入深,66條JavaScript面試知識點(四)》

《由淺入深,66條JavaScript面試知識點(五)》

《由淺入深,66條JavaScript面試知識點(六)》

《由淺入深,66條JavaScript面試知識點(七)》

《為什麼 setTimeout 有最小時延 4ms ?》

《如何處理 Node。js 中出現的未捕獲異常?》

《Angular v10。0。0 正式釋出,不再支援 IE9/10》

《基於 Docker 的 SSR 持續開發整合環境實踐》

《細聊圖解webpack 指南手冊》

《一文帶你徹底搞懂 NPM 知識點「進階篇」》

《細聊webpack效能最佳化面面觀》

《JS實現各種日期操作方法彙總》

《「實踐」細聊前端效能最佳化總結》

《「實踐」瀏覽器中的畫中畫(Picture-in-Picture)模式及其 API》

《「多圖」一文帶你徹底搞懂 Web Workers (上)》

《「多圖」一文帶你徹底搞懂 Web Workers (中)》

《深入細聊前端下載總結「乾貨」》

《細品西瓜播放器功能分析(上)「實踐」》

《細品西瓜播放器功能分析(下)「實踐」》

《細聊50道JavaScript基礎面試題「附答案」》

《webpack4主流程原始碼解說以及動手實現一個簡單的webpack(上)》

《webpack4主流程原始碼解說以及動手實現一個簡單的webpack(下)》

《細聊前端架構師的視野》

《細聊應用場景再談防抖和節流「進階篇」》

《前端埋點統一接入方案實踐》

《細聊微核心架構在前端的應用「乾貨」》

《一種高效能的Tree元件實現方案「乾貨」》

《進擊的JAMStack》

《前後端全部用 JS 開發是什麼體驗(Hybrid + Egg。js經驗分享)上》

《前後端全部用 JS 開發是什麼體驗(Hybrid + Egg。js經驗分享)中》

《前後端全部用 JS 開發是什麼體驗(Hybrid + Egg。js經驗分享)下》

《一文帶你搞懂 babel-plugin-import 外掛(上)「原始碼解析」》

《一文帶你搞懂 babel-plugin-import 外掛(下)「原始碼解析」》

《JavaScript常用API合集彙總「值得收藏」》

《推薦10個常用的圖片處理小幫手(上)「值得收藏」》

《推薦10個常用的圖片處理小幫手(下)「值得收藏」》

《JavaScript 中ES6代理的實際用例》

《12 個實用的前端開發技巧總結》

《一文帶你搞懂搭建企業級的 npm 私有倉庫》

《教你如何使用內聯框架元素 IFrames 的沙箱屬性提高安全性?》

《細說前端開發UI公共元件的新認識「實踐」》

《細說DOM API中append和appendChild的三個不同點》

《細品淘系大佬講前端新人如何上王者「乾貨」》

《一文帶你徹底解決背景跟隨彈窗滾動問題「乾貨」》

《推薦常用的5款程式碼比較工具「值得收藏」》

《Node。js實現將文字與圖片合成技巧》

《愛奇藝雲剪輯Web端的技術實現》

《我再也不敢說我會寫前端 Button元件「實踐」》

《NodeX Component - 滴滴集團 Node。js 生態元件體系「實踐」》

《Node Buffers 完整指南》

《推薦18個webpack精美外掛「乾貨」》

《前端開發需要了解常用7種JavaScript設計模式》

《淺談瀏覽器架構、單執行緒js、事件迴圈、訊息佇列、宏任務和微任務》

《了不起的 Webpack HMR 學習指南(上)「含原始碼講解」》

《了不起的 Webpack HMR 學習指南(下)「含原始碼講解」》

《10個打開了我新世界大門的 WebAPI(上)「實踐」》

《10個打開了我新世界大門的 WebAPI(中)「實踐」》

《10個打開了我新世界大門的 WebAPI(下)「實踐」》

《「圖文」ESLint 在中大型團隊的應用實踐》

《Deno是程式碼的瀏覽器,你認同嗎?》

《前端儲存除了 localStorage 還有啥?》

《Javascript 多執行緒程式設計​的前世今生》

《微前端方案 qiankun(實踐及總結)》

《「圖文」V8 垃圾回收原來這麼簡單?》

《Webpack 5模組聯邦引發微前端的革命?》

《基於 Web 端的人臉識別身份驗證「實踐」》

《「前端進階」高效能渲染十萬條資料(時間分片)》

《「前端進階」高效能渲染十萬條資料(虛擬列表)》

《圖解 Promise 實現原理(一):基礎實現》

《圖解 Promise 實現原理(二):Promise 鏈式呼叫》

《圖解 Promise 實現原理(三):Promise 原型方法實現》

《圖解 Promise 實現原理(四):Promise 靜態方法實現》

《實踐教你從零構建前端 Lint 工作流「乾貨」》

《高效能多級多選級聯元件開發「JS篇」》

《深入淺出講解Node。js CLI 工具最佳實戰》

《延遲載入影象以提高Web網站效能的五種方法「實踐」》

《比較 JavaScript 物件的四種方式「實踐」》

《使用Service Worker讓你的 Web 應用如虎添翼(上)「乾貨」》

《使用Service Worker讓你的 Web 應用如虎添翼(中)「乾貨」》

《使用Service Worker讓你的 Web 應用如虎添翼(下)「乾貨」》

《前端如何一次性處理10萬條資料「進階篇」》

《推薦三款正則視覺化工具「JS篇」》

《如何讓使用者選擇是否離開當前頁面?「JS篇」》

《JavaScript開發人員更喜歡Deno的五大原因》

《僅用18行JavaScript實現一個倒數計時器》

《圖文細說JavaScript 的執行機制》

《一個輕量級 JavaScript 全文搜尋庫,輕鬆實現站內離線搜尋》

《推薦Web程式設計師常用的15個原始碼編輯器》

《10個實用的JS技巧「值得收藏」》

《細品269個JavaScript小函式,讓你少加班熬夜(一)「值得收藏」》

《細品269個JavaScript小函式,讓你少加班熬夜(二)「值得收藏」》

《細品269個JavaScript小函式,讓你少加班熬夜(三)「值得收藏」》

《細品269個JavaScript小函式,讓你少加班熬夜(四)「值得收藏」》

《細品269個JavaScript小函式,讓你少加班熬夜(五)「值得收藏」》

《細品269個JavaScript小函式,讓你少加班熬夜(六)「值得收藏」》

《深入JavaScript教你記憶體洩漏如何防範》

《手把手教你7個有趣的JavaScript 專案-上「附原始碼」》

《手把手教你7個有趣的JavaScript 專案-下「附原始碼」》

《JavaScript 使用 mediaDevices API 訪問攝像頭自拍》

《手把手教你前端程式碼如何做錯誤上報「JS篇」》

《一文讓你徹底搞懂移動前端和Web 前端區別在哪裡》

《63個JavaScript 正則大禮包「值得收藏」》

《提高你的 JavaScript 技能10 個問答題》

《JavaScript圖表庫的5個首選》

《一文徹底搞懂JavaScript 中Object。freeze與Object。seal的用法》

《視覺化的 JS:動態圖演示 - 事件迴圈 Event Loop的過程》

《教你如何用動態規劃和貪心演算法實現前端瀑布流佈局「實踐」》

《視覺化的 js:動態圖演示 Promises & Async/Await 的過程》

《原生JS封裝拖動驗證滑塊你會嗎?「實踐」》

《如何實現高效能的線上 PDF 預覽》

《細說使用字型庫加密資料-仿58同城》

《Node。js要完了嗎?》

《Pug 3。0。0正式釋出,不再支援 Node。js 6/8》

《純JS手寫輪播圖(程式碼邏輯清晰,通俗易懂)》

《JavaScript 20 年 中文版之創立標準》

《值得收藏的前端常用60餘種工具方法「JS篇」》

《箭頭函式和常規函式之間的 5 個區別》

《透過釋出/訂閱的設計模式搞懂 Node。js 核心模組 Events》

《「前端篇」不再為正則煩惱》

《「速圍」Node。js V14。3。0 釋出支援頂級 Await 和 REPL 增強功能》

《深入細品瀏覽器原理「流程圖」》

《JavaScript 已進入第三個時代,未來將何去何從?》

《前端上傳前預覽檔案 image、text、json、video、audio「實踐」》

《深入細品 EventLoop 和瀏覽器渲染、幀動畫、空閒回撥的關係》

《推薦13個有用的JavaScript陣列技巧「值得收藏」》

《前端必備基礎知識:window。location 詳解》

《不要再依賴CommonJS了》

《犀牛書作者:最該忘記的JavaScript特性》

《36個工作中常用的JavaScript函式片段「值得收藏」》

《Node + H5 實現大檔案分片上傳、斷點續傳》

《一文了解檔案上傳全過程(1。8w字深度解析)「前端進階必備」》

《【實踐總結】關於小程式掙脫枷鎖實現批次上傳》

《手把手教你前端的各種檔案上傳攻略和大檔案斷點續傳》

《位元組跳動面試官:請你實現一個大檔案上傳和斷點續傳》

《談談前端關於檔案上傳下載那些事【實踐】》

《手把手教你如何編寫一個前端圖片壓縮、方向糾正、預覽、上傳外掛》

《最全的 JavaScript 模組化方案和工具》

《「前端進階」JS中的記憶體管理》

《JavaScript正則深入以及10個非常有意思的正則實戰》

《前端面試者經常忽視的一道JavaScript 面試題》

《一行JS程式碼實現一個簡單的模板字串替換「實踐」》

《JS程式碼是如何被壓縮的「前端高階進階」》

《前端開發規範:命名規範、html規範、css規範、js規範》

《【規範篇】前端團隊程式碼規範最佳實踐》

《100個原生JavaScript程式碼片段知識點詳細彙總【實踐】》

《關於前端174道 JavaScript知識點彙總(一)》

《關於前端174道 JavaScript知識點彙總(二)》

《關於前端174道 JavaScript知識點彙總(三)》

《幾個非常有意思的javascript知識點總結【實踐】》

《都2020年了,你還不會JavaScript 裝飾器?》

《JavaScript實現圖片合成下載》

《70個JavaScript知識點詳細總結(上)【實踐】》

《70個JavaScript知識點詳細總結(下)【實踐】》

《開源了一個 JavaScript 版敏感詞過濾庫》

《送你 43 道 JavaScript 面試題》

《3個很棒的小眾JavaScript庫,你值得擁有》

《手把手教你深入鞏固JavaScript知識體系【思維導圖】》

《推薦7個很棒的JavaScript產品步驟引導庫》

《Echa哥教你徹底弄懂 JavaScript 執行機制》

《一個合格的中級前端工程師需要掌握的 28 個 JavaScript 技巧》

《深入解析高頻專案中運用到的知識點彙總【JS篇】》

《JavaScript 工具函式大全【新】》

《從JavaScript中看設計模式(總結)》

《身份證號碼的正則表示式及驗證詳解(JavaScript,Regex)》

《瀏覽器中實現JavaScript計時器的4種創新方式》

《Three。js 動效方案》

《手把手教你常用的59個JS類方法》

《127個常用的JS程式碼片段,每段程式碼花30秒就能看懂-【上】》

《深入淺出講解 js 深複製 vs 淺複製》

《手把手教你JS開發H5遊戲【消滅星星】》

《深入淺出講解JS中this/apply/call/bind巧妙用法【實踐】》

《手把手教你全方位解讀JS中this真正含義【實踐】》

《書到用時方恨少,一大波JS開發工具函式來了》

《乾貨滿滿!如何優雅簡潔地實現時鐘翻牌器(支援JS/Vue/React)》

《手把手教你JS 非同步程式設計六種方案【實踐】》

《讓你減少加班的15條高效JS技巧知識點彙總【實踐】》

《手把手教你JS開發H5遊戲【黃金礦工】》

《手把手教你JS實現監控瀏覽器上下左右滾動》

《JS 經典例項知識點整理彙總【實踐】》

《2。6萬字JS乾貨分享,帶你領略前端魅力【基礎篇】》

《2。6萬字JS乾貨分享,帶你領略前端魅力【實踐篇】》

《簡單幾步讓你的 JS 寫得更漂亮》

《恭喜你獲得治療JS this的詳細藥方》

《談談前端關於檔案上傳下載那些事【實踐】》

《面試中教你繞過關於 JavaScript 作用域的 5 個坑》

《Jquery外掛(常用的外掛庫)》

《【JS】如何防止重複傳送ajax請求》

《JavaScript+Canvas實現自定義畫板》

《Continuation 在 JS 中的應用「前端篇」》

作者:竹之同學

轉發連結:https://segmentfault。com/a/1190000022994032