本文記錄我在攝像頭 RTSP 流視頻多路實時監控項目裏,落地的一套「多路 RTSP 低延遲播放」方案的全過程:從選型、編碼、到Web/桌面端播放與硬解優化。
一、需求現狀
現場有一個遠程監控端,需要同時監控多台車載設備的攝像頭畫面,每台設備約 6 路攝像頭,攝像頭輸出 RTSP(視頻 H.264;部分攝像頭型號還有音頻),由於是車載實時攝像頭,關鍵的不是能播,而是多路、低延遲(由於在現場操作需要實時反饋,所以需要 1 秒以內)、低 CPU 佔用,因此核心需求可以總結成四點:
- 多路併發:同屏 6+ 路播放,最多一個監控端同時播放 12 路視頻;
- 低延遲:操作鏈路希望接近實時(目標 < 500ms 級);
- 桌面端:需要有編碼控制能力,最好 Qt 桌面程序;
- 性能與穩定性:客户端需要穩定走 GPU 硬解,否則多路全走 CPU 軟解解碼會被拖死;
- 部署/運維複雜度:比如要更換視頻流接入的時候要足夠方便,最好能配置後一鍵部署;
二、技術選型
經過一番調研,主要有下面幾個方案:
HLS
HLS 的核心思路是把連續視頻切成一段段 TS 分片(segment),服務器創建並動態更新一個 .m3u8 播放列表文件,這個文件裏記錄了所有 .ts 視頻切片的文件名、順序、時長等信息,客户端按 m3u8 索引列表去拉分片播放。它的優點是兼容性很好,所有現代瀏覽器和操作系統都原生支持,適合普通直播、點播和 CDN 分發。
在一個 TS 分片沒有播完並同步到 m3u8 之前,客户端是無法看到最新畫面的。這天生決定了 HLS 的實時性不高,一般在 3 秒以上,加上各個鏈路的延遲和緩衝,總延遲輕鬆到達 10 秒左右,這對於實時監控是完全不可接受的。
HTTP-FLV
HTTP-FLV 通常是服務器將音視頻數據用 FLV(Flash Video)格式進行封裝,通過客户端和服務端建立的 HTTP 長連接流式傳輸到播放器上,延遲在 1-3s,可以滿足一定的實時性需求。缺點是需要前端引入 flv.js 之類的 JS 庫在 JS 層對 FLV 流解封裝成 H.264、音頻等數據,尤其在多路併發時,CPU 佔用會比較高,且瀏覽器對 FLV 的支持不如 HLS/WebRTC 原生。
海康 WebSDK 的 WebSocket
海康的 WebSDK 提供了基於 WebSocket 的視頻流傳輸能力,延遲可以做到 1 秒以內,適合海康設備的接入,但我看有些老一些的海康設備可能不支持 WebSocket,而且 WebSDK 要求 Chrome 版本不低於 91,如果你的攝像頭都支持 WebSocket 且能保證瀏覽器的 chromium 內核版本在 91+,那麼這個方式也是可行的。
WebRTC
WebRTC(Web Real-Time Communication)是目前實時音視頻通信的主流技術,現代瀏覽器(Chrome, Firefox, Safari 等)都原生支持 WebRTC 協議棧,無需安裝插件,實時性通常能做到 500ms 左右,非常適合對實時性要求高的監控場景。WebRTC 的設計目標就是實時通話與互動,瀏覽器對其實現非常成熟。對於 H.264 等常見編碼格式,瀏覽器能直接調用 GPU 進行硬件加速解碼,顯著降低 CPU 佔用。
三、核心實踐:通過 SRS 將 RTSP + H.264 視頻流轉封裝為 WebRTC + H.264
我最終使用 SRS(Simple Realtime Server)開源流媒體服務器,一直在穩定維護,也有不少用到生產級環境的案例,文檔有中文,部署也比較簡單,直接 docker 拉一個鏡像然後一行命令就能啓動,另外它自帶視頻流錄製功能,後期做錄製也方便。
在遠程監控服務器上用 Docker 部署 SRS,把攝像頭 RTSP 拉到本機後,再以 WebRTC 的方式提供給前端播放。
這裏要強調一個關鍵點:儘量做轉封裝(Remux),避免轉碼(Transcode),攝像頭已經輸出 RTSP 包着的 H.264 服務器只需要轉協議成 WebRTC 包着 H.264 即可,不需要重新編碼,轉碼會帶來很大的 CPU 負擔和延遲。
整體思路:攝像頭推流 RTSP(H.264) ,經過 SRS ingest 拉流並通過 rtmp_to_rtc 轉封裝為 WebRTC(H.264) ,前端瀏覽器用 <video> 播放。
3.1 SRS 核心配置解析
SRS 的配置文件 srs.conf 是核心。下面用一個最小配置片段:
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank file;
srs_log_file /usr/local/srs/objs/logs/srs.log;
rtc_server {
enabled on; # 啓用 RTC 服務器
listen 8000; # WebRTC UDP 端口
candidate $CANDIDATE; # 自動獲取服務器 IP
}
http_server {
enabled on; # 啓用 HTTP 服務器
listen 8080;
dir ./objs/nginx/html; # 默認打開是控制枱,也可以改為自己的前端頁面
crossdomain on;
}
vhost __defaultVhost__ {
rtc {
enabled on; # 啓用 RTC 功能
rtmp_to_rtc on; # 開啓 RTMP 到 RTC 的轉換
nack on; # 開啓丟包重傳
twcc on; # 開啓擁塞控制
}
ingest camera_RIG001_0 { # 定義 Ingest 拉流配置,主動拉取攝像頭 RTSP 流
enabled on;
input {
type stream;
url rtsp://admin:password@192.168.1.60:554/Streaming/Channels/101; # 你攝像頭的rtsp地址
}
ffmpeg ./objs/ffmpeg/bin/ffmpeg; # 使用 SRS 內置的 FFmpeg
engine {
enabled on;
perfile {
rtsp_transport tcp;
fflags nobuffer;
flags low_delay;
probesize 32;
analyzeduration 0;
max_delay 0;
}
vcodec copy; # 視頻流直接複製,不轉碼
acodec copy; # 音頻流直接複製,我將攝像頭的音頻輸出格式配為 AAC
output rtmp://127.0.0.1:[port]/live/camera_RIG001_0?vhost=[vhost];
}
}
}
3.2 Docker 一鍵配置
由於現場設備的 IP 和攝像頭數量可能會變化,手動修改 srs.conf 比較繁瑣且容易出錯。我做了一套自動化配置流程:
- 配置文件:提供一個 JSON 文件,維護用户攝像頭的 IP/Port 列表。
- 生成腳本:讀取攝像頭列表,生成
srs.conf,並在上一部配置變更時更新文件。 - 自動重啓:檢測到配置變更後,執行
docker restart srs讓配置生效。
Docker 命令:
# 拉取 SRS 鏡像並打標籤
docker pull registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5
docker tag registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5 ossrs/srs:5
# 運行 SRS 容器
docker run -d --name srs \
--restart=always \
-p 1935:1935 \
-p 1985:1985 \
-p 8080:8080 \
-p 8000:8000/udp \
-e CANDIDATE="127.0.0.1" \
-v ~/hello/config/srs_latest.conf:/usr/local/srs/conf/srs.conf \
ossrs/srs:5 \
./objs/srs -c conf/srs.conf
其中:
1935/tcp:RTMP(SRS 內部迴環推流也會用到)1985/tcp:HTTP API8080/tcp:HTTP Server(SRS 自帶的控制枱頁面)8000/udp:WebRTC(RTC Server)
我實際工程裏會把 改攝像頭配置 -> 生成 srs.conf -> 重啓容器 這套動作做成一鍵腳本,避免手工改配置帶來的維護成本,規範一點可以弄個網頁讓用户維護攝像頭配置。
四、前端落地
1. Web 端播放
由於瀏覽器對 WebRTC 原生支持比較好,標準 HTML5 <video> 可以直接接住 WebRTC 流播放,我在前端頁面裏直接用 <video> 標籤播放 SRS 輸出的 WebRTC 流,讓瀏覽器原生解碼器(通常能自動走 H.264 硬解)解碼,前端不需要引入其他第三方庫,CPU 佔用也更可控。
前端的靜態資源服務器可以是 Nginx,也可以是其它輕量方案,比如 SRS 自帶的 HTTP Server 就可以直接用來託管前端頁面,或者 nodejs、python 都行。
2. 桌面化(Qt / QWebEngine)
為了集成到現有的 Qt@6.8.3 桌面應用中,我用 QWebEngineView 直接嵌入前端頁面,這樣 UI 與 Web 端可以最大化複用。
這裏有一個必踩的坑:
QT 官方包裏帶的 QWebEngine 因為專利原因是不包含 H.264 編解碼能力的,需要下載源碼自行編譯並增加 -webengine-proprietary-codecs 編譯指令啓用專有編解碼器支持 ,才能讓 QWebEngineView 的 Chromium 內核支持 H.264 編碼。
另外注意讓內置瀏覽器的視頻解碼儘可能走 VA-API 等硬解路徑,可以在 QWebEngineView 裏打開 chrome://gpu 看一下最下面的 Video Acceleration Information 有沒有 H.264 硬解支持。我增加的 QT 環境變量如下:
// 開啓 H.264 和 WebRTC 支持
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--enable-features=VaapiVideoDecoder,VaapiVideoEncoder,VaapiIgnoreDriverChecks,VaapiVideoDecodeLinuxGL --ignore-gpu-blocklist --enable-gpu-rasterization --in-process-gpu --disable-features=UseChromeOSDirectVideoDecoder --limit-fps=20 --num-raster-threads=4 ");
優化項:SmartH264
注意在攝像頭的配置中,將攝像頭的 H.264 編碼參數調優為 SmartH264,可以讓攝像頭在畫面靜止時降低碼率(在畫面不怎麼動的情況下可以最大降低 90% 碼流),減少網絡帶寬佔用,同時在畫面有變化時提升碼率和幀率,保證畫面質量。這樣在多路併發時,可以顯著降低整體的網絡和解碼壓力。
優化項:硬解驗證與驅動選擇
在使用 intel 集顯的 i7-7700 測試時,通過指定集顯使用特定的 VA-API 硬件加速驅動 export LIBVA_DRIVER_NAME=iHD,強制開啓 VA-API 硬解:
# 通過下面這個命令查看當前 GPU 使用情況
sudo intel_gpu_top
如果 intel_gpu_top 命令 Engine 的 Video 不為 0,則説明硬解成功,事實證明如果可以成功開啓 GPU 硬解,那麼即使使用集顯,CPU 負載也能保持較低水平。如果使用 AMD/NVIDIA 顯卡,需要參考對應的 VA-API 驅動文檔,確保硬解被正確啓用。
五、總結
通過 SRS 將 RTSP + H.264 視頻流轉封裝為 WebRTC + H.264,並在前端瀏覽器和 Qt 桌面應用中播放,成功實現了多路低延遲的實時視頻監控需求。我這邊在實際測試中,i7-7700 的 CPU 上使用集顯 GPU 播放 6 路 1080p H.264 流,CPU 總佔用保持在 20% 左右,延遲控制在 300-500ms 之間。
網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出,如果本文幫助到了你,別忘了點贊支持一下,你的點贊是我更新的最大動力!~
- SRS Getting Started
- Qt WebEngine H.264 Feature
PS:本文同步更新於在下的博客 Github - SHERlocked93/blog
系列文章中,歡迎大家關注我的公眾號 CPP下午茶,直接搜索即可添加,持續為大家推送 CPP 以及 CPP 周邊相關優質技術文,共同進步,一起加油~