当前位置:首页 > Web开发 > 正文

使用Gin+WebSocket在HTML中无插件播放RTSP

2024-03-31 Web开发

在后台的开发中遇到了对接显示摄像头视频流的需求。目前获取海康及大华等主流的摄像头的视频流使用的基本都是RTSP协议。不过HTML页面并不能直接播放RTSP协议的视频流,查询了一番各种网页播放RTSP的资料,有如下的一些方案:

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地址散列化保证返回的地址相同。

processCh := uuid.NewV3(uuid.NamespaceURL, splitList[1]).String() playURL := fmt.Sprintf("/stream/live/%s", processCh)

FFMPEG转换的视频数据也会通过HTTP协议传回服务端,每帧byte数据会以‘\n‘结束,在go语言中可以通过bufio模块来读出这样的数据。

bodyReader := bufio.NewReader(c.Request.Body) for { data, err := bodyReader.ReadBytes('\n') if err != nil { break } } FFMPEG 视频转换

视频转换模块会在收到需要转换的RTSP流地址后,启动一个FFMPEG子进程来转换RTSP视频流,这里是使用exec.Command来完成:

params := []string{ "-rtsp_transport", "tcp", "-re", "-i", rtsp, "-q", "5", "-f", "mpegts", "-fflags", "nobuffer", "-c:v", "mpeg1video", "-an", "-s", "960x540", fmt.Sprintf(":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可以很方便的实现这个功能。

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,可以很好的解决这个问题。具体实现在 ,代码比较长就不贴了。

测试

温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/web/40010.html