網頁播監視器畫面有那麼難?RTSP、HLS 與 WebRTC 串流原理解密

如果你是一個 Web 開發者,某天老闆走到你位子上說:「欸,那個後台儀表板,幫我把廠區監視器的即時畫面接上去吧,網址是 rtsp://192.168...。」
你心想:「簡單啦,不就加個 HTML 標籤的事?」於是你寫下了
<video src="rtsp://..."></video>
然後打開瀏覽器一看——畫面全黑,Console 噴出一堆 Error。
恭喜你,正式踏入了 Web 影音串流的無底深淵(笑)。今天我們就來聊聊,為什麼傳統監視器的畫面沒辦法直接塞進網頁裡?而我們又是怎麼像變魔術一樣,把影像順利傳遞到使用者眼前的。
🚫 為什麼瀏覽器聽不懂 RTSP?
市面上 99% 的 IP Camera(網路攝影機)預設輸出的都是 RTSP (Real Time Streaming Protocol) 格式。這種協定非常古老且穩定,專門為持續不斷的即時影像設計,VLC 播放器或是傳統的監控主機都能輕鬆解碼。
但問題來了:瀏覽器不吃這一套。 現代的瀏覽器(Chrome, Edge, Safari)基於安全性與架構設計,基本上只講 HTTP(S) 協議(還有 WebSocket 跟 WebRTC)。當你硬要把一條底層走 TCP/UDP 持續連線的 RTSP 塞給瀏覽器時,它就像是聽到外星語一樣,完全無法解析。
所以,我們需要一個「翻譯蒟蒻」——也就是串流轉碼伺服器 (Streaming Server),而最常見的作法,就是把它轉成 HLS。
🔪 HLS (HTTP Live Streaming):把大象切塊放進冰箱
HLS 是 Apple 提出來的串流解決方案,它的核心精神超級暴力又聰明:既然瀏覽器只懂 HTTP,那我就把無窮無盡的直播影片,切成無數個極短的普通檔案,讓瀏覽器用 HTTP 一個一個下載來看!
要做到這件事,工程師最好的朋友就是 FFmpeg。它會在伺服器端當苦力,執行以下動作:
- 攔截與轉檔:FFmpeg 接住攝影機的 RTSP 串流,轉換成瀏覽器看得懂的 H.264 影像編碼。
- 無情切片:把連續的影片,每隔幾秒鐘(例如 3 秒)就切斷,存成一個個微小的
.ts(Transport Stream) 檔案(例如segment_001.ts,segment_002.ts)。 - 建立動態菜單:同時生成一個
.m3u8的純文字清單檔,裡面記錄著「目前最新的.ts切片有哪些」。
瀏覽器端的播放器(像是 video.js 或是 hls.js)只要負責去要這份 .m3u8 菜單,然後照著清單瘋狂地用 GET 請求把 .ts 檔載下來連續播放,你看起來就像是在看直播了。因為全都是走標準的 HTTP Port 80/443,防火牆根本不會擋,相容性極高!
⚔️ 實戰中的架構挑戰:不只是「播出來」而已
實務上,處理這些串流還要搭配複雜的業務邏輯。把單一畫面轉成 HLS 很簡單,但如果你的系統要同時吃幾十支攝影機呢?
舉個我實際遇過的專案情境:在一個多鏡頭監控系統中,大部分的廠區攝影機只需要維持標準的即時串流(Live Streaming)就好。但針對特定區域的鏡頭(像是 P001 到 P008),系統的需求是:網頁一載入時,不能播即時畫面,而是要自動抓取並回放這幾支鏡頭「過去 60 分鐘」的最新歷史錄影。
這時候光靠前端是沒救的,你必須在後端寫一套排程腳本,動態去調度 FFmpeg:
- 對於普通鏡頭:維持持續切片覆寫的 Live HLS 模式。
- 對於 P001-P008:要去讀取錄影檔伺服器,用 FFmpeg 快速抽取出最近一小時的片段,轉譯成點播 (VOD) 模式的
.m3u8清單,再交給前端。
這種針對不同型號與場景的「串流調度控制」,才是影像處理系統中最有價值的地方。
⚡ 題外話:那 WebRTC 是什麼?
聰明的你可能會發現,HLS 有個致命傷:延遲 (Latency)。 因為必須等切片切完(假設切 3 秒)、加上網路傳輸跟播放器緩衝,你看到的畫面通常已經是 5 到 10 秒前發生的事了。如果廠區失火,10 秒的延遲可是會出人命的。
如果你追求「毫秒級」的延遲(例如視訊會議或是遠端遙控機台),那就要捨棄 HTTP 切片,改用點對點的 WebRTC 協定。但 WebRTC 的後端 Signaling Server(信令伺服器)架設又是一門極其複雜的學問,這坑太深,我們留著下次再聊。
🍻 總結
下次再遇到要把攝影機接到網頁上的需求,別再傻傻地直接寫 <video src="rtsp..."> 了。
先在腦海中畫出 IP Camera (RTSP) ➡️ FFmpeg 切片 ➡️ HLS (.m3u8 + .ts) ➡️ 前端 HTML5 播放器 的架構圖,你就能優雅且專業地搞定這個需求。影像串流的水很深,但只要掌握了基本原理,其實也是挺好玩的!
