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

时间:2022-10-25 14:50:58

项目地址: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:admin@192.168.1.11: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:password@192.168.3.10: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:password@192.168.3.10: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,在浏览器中打开查看视频。

显示效果

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

总结

得益于JSMpeg项目的强大,实现一个WebSocket的在网页上播放RTSP视频流还是很简单的了。随着golang语言日渐成熟,基于现成的库也可以方便的在Gin中添加WebSocket功能。需要注意主要是并发时,对FFMPEG子进程,WebSocket客户端的增删问题,好在golang天生对并发有良好的支持,gouroutine,channel,sync库这些golang核心知识掌握好了便可很好的应对这些问题。

首发自个人博客 某中二的黑科技研究中心 ,欢迎访问交流。

使用Gin+WebSocket在HTML中无插件播放RTSP的更多相关文章

  1. Web下无插件播放rtsp视频流的方案及各家优秀内容资源整理

    Web下无插件播放rtsp视频流的方案及各家优秀内容资源整理 方案一:服务器端用 websocket 接受 rtsp ,然后,推送至客户端 实现步骤: 方案二:使用 ffmpeg + nginx 把 ...

  2. &lbrack;转&rsqb; web无插件播放RTSP摄像机方案,拒绝插件,拥抱H5!

    需求 问题:有没有flash播放RTSP的播放器?H5能不能支持RTSP播放? 答案:没见过,以后估计也不会有: 问题:可以自己做浏览器插件播放RTSP吗? 答案:可以的,chrome做ppapi插件 ...

  3. web无插件播放RTSP摄像机方案,拒绝插件,拥抱H5!

    本文转自:http://www.cnblogs.com/babosa/p/7355468.html 需求 问题:有没有flash播放RTSP的播放器?H5能不能支持RTSP播放? 答案:没见过,以后估 ...

  4. EasyNVR网页H5无插件播放摄像机视频功能二次开发之直播通道接口保活示例代码

    背景需求 随着雪亮工程.明厨亮灶.手机看店.智慧幼儿园监控等行业开始将传统的安防摄像头进行互联网.微信直播,我们知道摄像头直播的春天了.将安防摄像头或NVR上的视频流转成互联网直播常用的RTMP.HT ...

  5. EasyNVR无插件播放HLS&sol;RTMP网页直播方案前端完善:监听表单变动

    在上一篇博客中我们表述完了防止提交成功后多余操作提交的一个过程:其中的精髓在于ajax的触发事件的使用. 而这篇博客主要想说明一下如何实时的判断出表单是否发生变化. 问题表述: 在网页前端的开发过程中 ...

  6. EasyNVR、EasyDSS二次开发之:RTMP、HLS流在web页面进行无插件播放示例Demo代码

    不管是基于EasyNVR还是EasyDSS,都是支持无插件直播,这也是未来视频直播的一个趋势.对于传统的浏览器插件播放谁用谁知道: 以上是软件自带播放展示 背景需求 对于EasyNVR和EasyDSS ...

  7. EasyNVR实现海康、大华NVR硬盘录像机Web无插件播放方案(支持取特定时间段视频流)

    本文转自:https://blog.csdn.net/black_3717/article/details/79872725 背景说明: 由于视频自身的直观性和便利性,对于传统安防行业,摄像机的直播和 ...

  8. 通过Onvif设备探索获取EasyNVR网页无插件播放所需要的摄像机硬盘录像机NVR的RTSP地址

    想实现网络监控摄像头进行视频直播的朋友门应该知道,方法其实非常简单,你不需要使用支持直播的网络摄像机,只需要经过一套流媒体服务器将监控摄像头的RTSP视频流转为RTMP\HLS\HTTP-FLV视频流 ...

  9. EasyNVR网页无插件播放摄像机RTSP流是如何调取接口在Web页实现多窗口同时直播的

    背景需求 在互联网飞速发展的时代,开发者常会说的一个词就是"跨平台".自从移动端的用户需求越来越大,H5逐渐发展,跨平台似乎已经成为了软件开发不可或缺的技术.EasyNVR互联网直 ...

随机推荐

  1. launch文件

    launch在ROS应用中,每个节点通常有许多参数需要设置,为了方便高效操作多个节点,可以编写launch文件,然后用roslaunch命令运行roslaunch: roslaunch [option ...

  2. 基于 unity ngui 上的滚动加载&lowbar;&lowbar;UiVirtual

    在游戏里面经常会有背包,好友,对话,这样的列表.当列表的内容多了,如果一打开界面就对所有内容进行实例化,会消耗大量的性能,且会造成UI上的卡顿. 于是便需要,在列表里面只实例化屏幕上可见的item.屏 ...

  3. C&num;操作Excel基本操作

    /// using Microsoft.Office.Core; using Microsoft.Office.Interop.Excel; using System.IO; using System ...

  4. HDU1257:最少拦截系统&lpar;LIS&rpar;

    Problem Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高 ...

  5. Robot Framework自动化测试---元素定位

    不要误认为Robot framework 只是个web UI测试工具,更正确的理解Robot framework是个测试框架,之所以可以拿来做web UI层的自动化是国为我们加入了selenium2的 ...

  6. &bsol;classes&bsol;META-INF&bsol;MANIFEST&period;MF &lpar;系统找不到指定的路径。&rpar;

    开发项目重启eclipse 发现了如下错误! 解决方法 选择出现错误的项目 点击clean 即可~

  7. 解决springboot jar包冲突

    直接导入springboot父项依赖,其它相关springboot依赖version不用写,由spring自动依赖. <parent> <groupId>org.springf ...

  8. SAP HANA S4 FI TABLE表结构

    一.统一日记账的表 1)一个行项目表,存储所有应用的全部明细–迅速获得洞察力和扩展能力; 2)次级成本要素也变成了总账科目,统一入口维护和管理; 3)数据只需存储一次在一张表,不需要再做月末对账,如A ...

  9. 求 pi 的近似值题型汇总

    (注:暂时先记录这些问题,后期会持续更新) 一.用格雷戈里公式计算π的近似值,精度要求:最后一项的绝对值小于0.00001 1,用while循环实现 int denominator,flag; dou ...

  10. Mysql数据库一个表字段中存了id&comma;并以逗号分隔&comma;id对应的详细信息在另一个表中

    有两张表, 一张为爱好表b表 一张为用户表 u表 u表 id   名称   爱好Id 1    张三     1,2,3,4 2    李四      2,5 b表 id  名称 1    打乒乓 2 ...