使用Gin+WebSocket在HTML中无插件播放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.cnblogs.com/haoxi/p/12066634.html

时间: 2024-11-05 03:52:38

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

GB28181实现摄像头网页无插件直播、回放过程中设备状态分析

关于LiveGBS GB28181流媒体服务器的部署详见 https://www.liveqing.com/docs/download/LiveGBS.html.通过LiveGBS流媒体服务可以实现GB28181接入摄像头.硬盘录像机.其他支持GB28181的视频平台,实现网页无插件直播. 如下是GB28181注册成功后设备状态查询的信令分析: 1. 发送设备状态查询命令(服务>>设备) MESSAGE sip:[email protected] SIP/2.0 Via: SIP/2.0/UD

LiveNVR实现安防摄像头RTSP WEB无插件直播中ONVIF预制位接口的使用说明

ONVIF发现接入摄像机 通过ONVIF探测发现可以将摄像接入LiveNVR,并提供互联网无插件直播,具体介绍可以参考 https://www.liveqing.com/docs/products/LiveNVR.html ONVIF相关接口 ONVIF探测 云台控制 删除预制位 获取预制位列表 设置预制位 跳转到预制位 原文地址:https://www.cnblogs.com/kumukim/p/11535435.html

第一章 1.5 无插件范式

过去,很多功能只能通过插件或者复杂的 hack(本地绘图 API.本地 socket 等)来实现,但在 HTML5 中提供了对这些功能的原生支持.插件的方式存在很多问题: 插件安装可能失败: 插件可以被禁用或屏蔽(例如 Apple 的 iPad 就不支持 Flash 插件): 插件自身会成为被攻击的对象: 插件不容易与 HTML 文档的其他部分集成(因为插件边界.剪裁和透明度问题). 虽然一些插件的安装率很高,但在控制严格的公司内部网络环境中经常会被封锁.此外,由于插件经常还会给用户带来烦人的广

无插件启动谷歌浏览器

2017-02-13 有时候想暂时不需要插件,又不想去设置面板一个一个关闭,怎么办?毕竟插件会嵌入一些js什么的. …… start "" "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-plugins pauseexit命令一: "D:\xwapp\googlechrome_dev\googlechrome_dev\chrome.exe" --dis

通过MIME标准实现无插件极速生成多Sheet Excel文件

注意声明:文件中代码复制/下载自动保存为xls文件用Excel打开即可看到效果,注意红色部分内容 一.单Sheet的Excel 含样式合并 MIME-Version: 1.0 X-Document-Type: Workbook Content-Type: multipart/related; boundary="----BOUNDARY_9527----" ------BOUNDARY_9527---- Content-Location: file:///C:/0E8D990C/Mim

Knockout.js 数据验证之插件版和无插件版

本文我们将介绍使用 Knockout.js 实现一些基本的数据验证.就如我们在标题里提到的,我们会使用两种方法来创建数据验证方法. 使用自定义方法,不需要任何插件 最简单的方法是使用已有的插件 如果你刚刚接触 Knockout.js,强烈建议你阅读我之前的 文章,那篇文章中我分享了一些关于 Knockout.js 的基本知识.本文我们使用 Visual Studio 进行开发,希望你能喜欢.现在开始. 源码下载 Knockout.js Validations 第 1 段(可获 1.23 积分)

IntelliJ IDEA中Maven插件无法更新索引之解决办法

IT草根 WangXu's 代码馆 BLOG Home Archives Resume Links About IntelliJ IDEA中Maven插件无法更新索引之解决办法 By Wang Xu 发表于 2015-12-09 文章目录 1. Maven的仓库.索引 2. IntelliJ IDEA利用索引实现自动补全 3. IntelliJ IDEA中Maven插件配置 4. IntelliJ14.1更新索引失败原因 5. 使用国内Maven仓库的镜像 6. 下载Maven仓库的索引 7. 

HTML5 CSS3 经典案例:无插件拖拽上传图片 (支持预览与批量) (二)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/31513065 上一篇已经实现了这个项目的整体的HTML和CSS: HTML5 CSS3 经典案例:无插件拖拽上传图片 (支持预览与批量) (一) 这篇博客直接在上篇的基础上完成,最终效果: 效果图1: 效果图2: 好了,请允许我把图片贴了两遍,方便大家看效果了~ 可以看出我们的图片的li的html其实还是挺复杂的,于是我把html文档做了一些修改: <span style=&quo

无插件的大模型浏览器Autodesk Viewer开发培训-武汉-2014年8月28日 9:00 &ndash; 12:00

武汉附近的同学们有福了,这是全球第一次关于Autodesk viewer的教室培训. :) 你可能已经在各种场合听过或看过Autodesk最新推出的大模型浏览器,这是无需插件的浏览器模型,支持几十种数据格式.目前该产品还没有正式发布,但如果你感兴趣,座位紧张,赶紧报名:   http://www.autodesk.com.cn/adsk/servlet/item?siteID=1170359&id=23581540  (这是系列培训中的一部分)   通用无插件大模型浏览器–Autodesk Vi