工具系列 | 视频监控RTSP转HLS解决方案
视频监控RTSP转HLS解决方案
介绍
视频监控RTSP转RTMP转HLS解决方案
由于公司业务,需要实现基于WEB访问监控摄像头实时流的预览,经过各种百度,补充了不少相关知识,了解到了很多大神的实现方法,也因为很多过时的帖子,而踩了不少的坑。
尝试过nginx+ffmpeg的方案,虽然可行,但是实现单摄像头还行,想不明白如何实现多摄像头预览,尝试过写脚本,同时处理多个摄像头,但结果是服务器卡死。
后来尝试通过代码,动态根据当前要访问的设备,来调用ffmpeg命令处理该设备,最终因效果不好,而且各种无法控制而告终。
最终无意间浏览到一大神写的使用javacv实现通过调用ffmpeg库的实现方法,于是就尝试用此方法推流给nginx,由nginx负责将流切片保存,并配置nginx自动删除旧的切片,以节省硬盘空间。
运行流程
使用说明
- 参考根目录下的nginx.conf来配置自己的web代理nginx
- 解压nginx-rtmp-server.zip,这是作为rtmp流服务器用的nginx版本,可自行修改conf/nginx.conf配置
- 导入monitor-rtsp-hls至eclipse,右键Main.java运行即可,生产环境可打成jar包来运行也可导出为war包部署tomcat运行
说明:
本服务负责将各个监控设备的实时视频rtsp协议流,转换为rtmp协议流,推送给nginx的rtmp服务, 然后由nginx的rtmp模块将流切片,转换为m3u8+ts格式片断,然后由nginx对外提供web访问m3u8和ts片段的服务,供页面呈现
运行流程
1.用户访问nginx路由:http://127.0.0.1:8888/hls/{{设备编码}}/index.m3u8 2.nginx匹配到该路由,将请求转发至本服务,nginx路由规则:
location ~ /hls/([0-9]+)/index.m3u8 {
proxy_pass http://127.0.0.1:8083/$1;
}
3.本服务的ProcessController的index接收到请求,从getPara(0)获取到设备编码,然后根据设备编码从配置文件中找到设备的rtsp协议地址,基于javacv将数据流推送给nginx的rtmp模块。
我做的是使用Redis存储设备编码,通过Lua脚本从Redis中获取设备号
rtmp地址如:
rtmp://127.0.0.1:1935/hls/#(code)
推流线程中设置了转换持续最大时间,因为页面播放器会不断的请求m3u8文件,也就是会不断的请求本服务的转换请求,如果在有效时间内请求,只是重新激活线程,重新开始计时,如果有效期到了,则线程会自动结束,防止无用的浪费CPU资源。 4.nginx的rtmp模块接收推送流,进行切片生成ts片段,并生成m3u8格式媒体列表
rtmp{
server{
listen 1935;
application hls{
live on;
hls on;
hls_path 'E:/Server/html/hls'; #hls存放路径
hls_fragment 5s;
hls_playlist_length 60s;
hls_continuous on; #连续模式
hls_cleanup on; #对多余的切片进行删除
hls_nested on; #嵌套模式
}
}
}
5.由nginx提供对外访问ts文件的服务
server{
listen 8888;
#匹配m3u8请求,转发给后端rtmp推流服务
location ~ /hls/([0-9]+)/index.m3u8 {
proxy_pass http://127.0.0.1:8083/$1;
}
#ts片段文件匹配
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
add_header Cache-Control no-cache;
#后端配置支持HTTP1.1,必须配
proxy_http_version 1.1;
proxy_set_header Connection "";
alias 'E:/Server/html/hls'; #ts片段存放路径
autoindex on;
expires 24h;
}
#rtmp状态查看
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root 'E:/Server/nginx 1.7.12.1 Lizard with rtmp/nginx-rtmp-module/';
}
}
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 基于腾讯云的 Rust 和 WebAssembly 函数即服务
- 谷歌开源NLP模型可视化工具LIT,模型训练不再「黑箱」
- Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方案
- 社区开源框架预制件相关模块:CollectManager详解
- Kettle构建Hadoop ETL实践(三):Kettle对Hadoop的支持
- 3种 Springboot 全局时间格式化方式,别再写重复代码了
- 一文搞懂CDN加速原理
- 3分钟短文:Laravel Carbon自定义日期时间格式
- (30)字符截取命令sed
- 2. Bean Validation声明式校验方法的参数、返回值
- 如何识别和阻止基于电报的僵尸网络
- 由String,String Builder,String Buffer 引起的面试惨案
- MySQL案例:各类临时文件的存放位置
- 关于加@Transactional注解的方法之间调用,事务是否生效的问题
- 基于Vue实现一个有点意思的拼拼乐小游戏