專案地址:gin-rtsp
在后臺的開發中遇到了對接顯示攝像頭視頻流的需求,目前獲取海康及大華等主流的攝像頭的視頻流使用的基本都是RTSP協議,不過HTML頁面并不能直接播放RTSP協議的視頻流,查詢了一番各種網頁播放RTSP的資料,有如下的一些方案:
-
插件開發播放:使用ActiveX等瀏覽器插件的方式來播放,海康和大華的瀏覽器管理頁面便是通過安裝瀏覽器插件來播放視頻的,視頻播放穩定,延時短,但是對技術要求較高,對于chrome等現代瀏覽器也存在兼容性問題,并不想考慮,
-
RTSP 轉 HLS:使用FFMPEG將RTSP轉為HLS,推流到流服務器,如安裝了
nginx-rtmp-module模塊的nginx,用這個方案測驗了下,HLS協議在PC端和移動端的瀏覽器的播放都很穩,但是用HLS協議的直播流延時很大,至少有15秒左右,對于低延時視頻的需求只能PASS, -
RTSP 轉 RTMP:與上一方案類似,使用FFMPEG將RTSP轉為RTMP推到流服務器分發播放,相比HLS延時很低,本來已經準備使用這個方案了,但是前端使用的video.js庫總是會偶現無法加載視頻的問題,而且播放RTMP需要使用到Flash,在chrome等瀏覽器中已經默認禁止加載逐步淘汰,只能拋棄,
-
WebSocket:最終在萬能的
Github上翻到了一個JSMpeg專案,采用FFMPEG轉為MPEG1 Video通過WebSocket代理推送到前端直接解碼播放的方案,測驗了下,延遲低,無需插件,畫面質量也可以根據需要調整,效果很不錯,
JSMpeg專案示例的WebSocket代理使用的是JS,簡單實作了單個視頻源的播放功能,我們的后臺使用的是golang的Gin框架,會有多個網頁客戶端播放多個視頻流,好在看了下JS的代碼,這個WebSocket代理的原理并不難,在Gin中集成WebSocket也很方便,這里記錄下我的集成方案,
主要模塊
-
API 介面:接收FFMPEG的推流資料和客戶端的HTTP請求,將客戶端需要播放的RTSP地址轉換為一個對應的WebSocket地址,客戶端通過這個WebSocket地址便可以直接播放視頻,為了及時釋放不再觀看的視頻流,這里設計為客戶端播放時需要在每隔60秒的時間里回圈請求這個介面,超過指定時間沒有收到請求的話后臺便會關閉這個視頻流,
-
FFMPEG 視頻轉換:收到前端的請求后,啟動一個Goroutine呼叫系統的FFMPEG命令轉換指定的RTSP視頻流并推送到后臺對應的介面,自動結束已超時轉換任務,
-
WebSocket Manager:管理WebSocket客戶端,將請求同一WebSocket地址的客戶端添加到一個Group中,向各個Group廣播對應的RTSP視頻流,洗掉Group中已斷開連接的客戶端,釋放空閑的Group,
這里大致介紹下這三個主要模塊的實作要點,
API 介面
API接收客戶端發送的包含了需要播放RTSP流地址的Json資料,格式如:
{
"url":"rtsp://admin:[email protected]:554/cam/realmonitor?channel=1&subtype=0"
}
在有多個客戶端需要播放相同的RTSP流地址時,需要保證回傳對應的WebSocket地址相同,這里使用了UUID v3來將RTSP地址散列化保證回傳的地址相同,
service/rtsptrans.go
processCh := uuid.NewV3(uuid.NamespaceURL, splitList[1]).String()
playURL := fmt.Sprintf("/stream/live/%s", processCh)
FFMPEG轉換的視頻資料也會通過HTTP協議傳回服務端,每幀byte資料會以'\n'結束,在go語言中可以通過bufio模塊來讀出這樣的資料,
api/rtsp.go
bodyReader := bufio.NewReader(c.Request.Body)
for {
data, err := bodyReader.ReadBytes('\n')
if err != nil {
break
}
}
FFMPEG 視頻轉換
視頻轉換模塊會在收到需要轉換的RTSP流地址后,啟動一個FFMPEG子行程來轉換RTSP視頻流,這里是使用exec.Command來完成:
service/rtsptrans.go
params := []string{
"-rtsp_transport",
"tcp",
"-re",
"-i",
rtsp,
"-q",
"5",
"-f",
"mpegts",
"-fflags",
"nobuffer",
"-c:v",
"mpeg1video",
"-an",
"-s",
"960x540",
fmt.Sprintf("http://127.0.0.1:3000/stream/upload/%s", playCh),
}
cmd := exec.Command("ffmpeg", params...)
cmd.Stdout = nil
cmd.Stderr = nil
stdin, err := cmd.StdinPipe()
通過FFMPEG的 -q 和 -s 引數可以除錯視頻的質量和解析度,為了簡便,命令的stdout和stderr都賦值為了nil,實際專案中可以保存到日志中方便排查問題,為了及時釋放不再播放的資源,客戶端停止請求超過一定時間后,FFMPEG子行程會自動關閉,通過golang的select可以很方便的實作這個功能,
service/rtsptrans.go
for {
select {
case <-*ch:
util.Log().Info("reflush channel %s rtsp %v", playCh, rtsp)
case <-time.After(60 * time.Second):
stdin.Write([]byte("q"))
err = cmd.Wait()
if err != nil {
util.Log().Error("Run ffmpeg err:%v", err.Error)
}
return
}
}
這里的*ch channel通過一個map和每個子行程關聯,子行程關閉時需要從map中清除,需要考慮并發的問題,可以使用sync.Map來保證執行緒安全,
WebSocket Manager
WebSocket Manager 負責對頁面上請求視頻資料的 ws 客戶端進行管理,在Gin中,主要是使用github.com/gorilla/websocket這個庫來開發相關功能,JSMpeg庫連接WebSocket時使用到了Sec-WebSocket-Protocol這個header,需要對其處理:
upgrader := websocket.Upgrader{
// cross origin domain
CheckOrigin: func(r *http.Request) bool {
return true
},
// 處理 Sec-WebSocket-Protocol Header
Subprotocols: []string{ctx.GetHeader("Sec-WebSocket-Protocol")},
}
conn, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
ws 客戶端連接后,會分配一個唯一的UUID,放入到URL對應的Group中,相同Group下的客戶端會收到同一視頻流的資料,客戶端斷開連接后,需要從Group中洗掉,同時釋放掉已經為空的Group,這個程序同樣需要考慮到并發的問題,WebSocket Manager通過單獨啟動一個Goroutine監聽注冊,斷開連接,廣播的三個對應的golang的channel,來統一管理各個Group,可以很好的解決這個問題,具體實作在 service/wsservice.go#L75,代碼比較長就不貼了,
測驗
專案需要運行在安裝有FFMPEG程式的環境中,通過撰寫了一份Dockerfile已經封裝好了需要的環境,可以使用Docker build后,以Docker的方式運行,
$ docker build -t ginrtsp .
$ docker run -td -p 3000:3000 ginrtsp
使用內置的FFMPEG轉換
將需要播放的RTSP流地址提交到 /stream/play 介面,例如:
POST /stream/play
{
"url": "rtsp://admin:[email protected]:554/cam/realmonitor?channel=1&subtype=0"
}
后臺可以正常轉換此RTSP地址時便會回傳一個對應的地址,例如:
{
"code": 0,
"data": {
"path": "/stream/live/5b96bff4-bdb2-3edb-9d6e-f96eda03da56"
},
"msg": "success"
}
編輯html檔案夾下view-stream.html檔案,將script部分的url修改為此地址,在瀏覽器中打開,便可以看到視頻了,
手動運行FFMPEG
由于后臺轉換RTSP的行程在超過60秒沒有請求后便會停止,也可以通過手動運行ffmpeg命令,來更方便地在測驗狀態下查看視頻,
ffmpeg -rtsp_transport tcp -re -i 'rtsp://admin:[email protected]:554/cam/realmonitor?channel=1&subtype=0' -q 0 -f mpegts -c:v mpeg1video -an -s 960x540 http://127.0.0.1:3000/stream/upload/test
通過如上命令,運行之后在view-stream.html檔案的url中填入對應的地址為/stream/upload/test,在瀏覽器中打開查看視頻,
顯示效果

總結
得益于JSMpeg專案的強大,實作一個WebSocket的在網頁上播放RTSP視頻流還是很簡單的了,隨著golang語言日漸成熟,基于現成的庫也可以方便的在Gin中添加WebSocket功能,需要注意主要是并發時,對FFMPEG子行程,WebSocket客戶端的增刪問題,好在golang天生對并發有良好的支持,gouroutine,channel,sync庫這些golang核心知識掌握好了便可很好的應對這些問題,
首發自個人博客 某中二的黑科技研究中心 ,歡迎訪問交流,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/53020.html
標籤:Go
上一篇:kratos微服務框架學習筆記一(kratos-demo)
下一篇:洗掉單鏈表,你會嗎?
