rtsp协议转换m3u8
目前项目中使用海康的摄像头,但需要提供实时预览。目前通过转换协议实现预览。同时能够尽量减少服务器的压力,比如生成的ts文件个数。
思路
通过ffmpeg 将rtsp协议转换成hls协议
具体步骤
1、java调用FFmpeg 命令进行协议转换
2、解决java调用runtime时,无法自主结束子进程问题
3、解决videojs中播放m3u8时出现 (CODE:3 MEDIA_ERR_DECODE) The media playback was aborted due to a corruption problem or because the media used features your browser did not support)
一、java部分
1 package com.hxq.device.rtsp;
2
3 import com.hxq.common.config.RuoYiConfig;
4 import com.hxq.common.utils.StringUtils;
5 import com.hxq.common.utils.SystemCmdHelper;
6 import com.hxq.common.utils.http.HttpUtils;
7 import lombok.extern.slf4j.Slf4j;
8 import org.apache.commons.compress.utils.IOUtils;
9 import org.apache.commons.io.FileUtils;
10 import org.apache.http.client.utils.DateUtils;
11 import org.springframework.scheduling.annotation.EnableScheduling;
12 import org.springframework.scheduling.annotation.Scheduled;
13 import org.springframework.stereotype.Service;
14 import org.springframework.transaction.annotation.Transactional;
15
16 import javax.annotation.PreDestroy;
17 import java.io.BufferedReader;
18 import java.io.File;
19 import java.io.IOException;
20 import java.io.InputStreamReader;
21 import java.util.Date;
22 import java.util.concurrent.ConcurrentHashMap;
23
24 /**
25 * rtsp 转 hlv协议
26 *
27 * @author kzw
28 */
29 @Service
30 @Slf4j
31 @EnableScheduling
32 public class RtspConvert {
33 //转换map
34 private static ConcurrentHashMap<String, CoverThread> coverMap = new ConcurrentHashMap<>();
35 //每次转换3小时,3小时之后自动停止
36 private static final String ffmpegCmd = "ffmpeg -timeout 3000000 -i \"%s\" -c copy -t 03:00:00 -f hls -hls_time 5.0 -hls_list_size 5 -hls_flags 2 %s";
37
38 @PreDestroy
39 public void closeProcess() {
40 log.info("关闭ffmpeg转换进程,java程序不一定关闭process进程");
41 for (String ip : coverMap.keySet()) {
42 try {
43 log.error("开始停止{}", ip);
44 coverMap.get(ip).stopTask();
45 } catch (Exception e) {
46 e.printStackTrace();
47 }
48 }
49 }
50
51 /**
52 * ffmpeg -i "rtsp://admin:xxx@192.168.0.251:554/Streaming/Channels/101" -c copy -f hls -hls_time 5.0 -hls_list_size 5 -hls_flags 2 F:\resources\hls\2000\live.m3u8
53 */
54 /**
55 * 检查设备ip是否能正常访问
56 */
57 private boolean checkDeviceOnline(String ip) {
58 String res = HttpUtils.sendGet("http://" + ip);
59 if (StringUtils.isNotBlank(res)) {
60 return true;
61 }
62 return false;
63 }
64
65 /**
66 * 转换rtsp并获取hls文件路径
67 */
68 public String rtsp2Hls(String ip, String userName, String pwd) {
69 if (coverMap.containsKey(ip)) {
70 CoverThread thread = coverMap.get(ip);
71 if (thread == null || thread.getTaskState() != CoverThread.running) {
72 } else {
73 return StringUtils.replace(thread.getM3U8File(), RuoYiConfig.getProfile(), "");
74 }
75 }
76 String rtspUrl = "rtsp://" + userName + ":" + pwd + "@" + ip + ":554/Streaming/Channels/101";
77 String m3u8File = RuoYiConfig.getProfile() + File.separator + "hls"
78 + File.separator + ip.replaceAll("\\.", "_") + File.separator + DateUtils.formatDate(new Date(), "yyyyMMddHHmm") + "live.m3u8";
79 startTransform(ip, rtspUrl, m3u8File, userName, pwd);
80 CoverThread thread = coverMap.get(ip);
81 if (thread != null) {
82 return StringUtils.replace(thread.getM3U8File(), RuoYiConfig.getProfile(), "");
83 }
84 return null;
85 }
86
87 /**
88 * 开启转换
89 */
90 private void startTransform(String ip, String rtspUrl, String m3u8Path, String userName, String pwd) {
91 log.info("转换rtsp, {},{},{}", ip, rtspUrl, m3u8Path);
92 String memKey = "startLive" + ip;
93 synchronized (memKey.intern()) {
94 if (coverMap.containsKey(ip)) {
95 stopTransform(ip);
96 }
97 CoverThread thread = new CoverThread(ip, rtspUrl, m3u8Path, userName, pwd);
98 coverMap.put(ip, thread);
99 thread.start();
100 }
101 }
102
103 /**
104 * 停止转换
105 */
106 public void stopTransform(String ip) {
107 String memKey = "startLive" + ip;
108 synchronized (memKey.intern()) {
109 if (coverMap.containsKey(ip)) {
110 CoverThread thread = coverMap.get(ip);
111 if (thread != null && thread.getTaskState() != CoverThread.fail) {
112 thread.stopTask();
113 }
114 }
115 }
116 }
117
118 /**
119 * 监控所有的转换线程
120 */
121 @Scheduled(cron = "0 0/8 * * * ?")
122 public synchronized void monitorThreads() {
123 for (String ip : coverMap.keySet()) {
124 CoverThread thread = coverMap.get(ip);
125 if (thread != null && thread.getTaskState() != CoverThread.running) {
126 //线程出现异常
127 rtsp2Hls(ip, thread.getUserName(), thread.getPwd());
128 }
129 }
130 }
131
132 /**
133 * 执行命令线程
134 */
135 private class CoverThread extends Thread {
136 private String ip;
137 private String rtspUrl;
138 private String m3u8File;
139 private String userName;
140 private String pwd;
141 private int taskState = 0; //运行状态 0未开始 1进行中 -1失败
142 private static final int notStart = 0;
143 private static final int running = 1;
144 private static final int fail = -1;
145 private Process process = null;
146
147 CoverThread(String ip, String rtspUrl, String m3u8File, String userName, String pwd) {
148 this.ip = ip;
149 this.rtspUrl = rtspUrl;
150 this.m3u8File = m3u8File;
151 this.userName = userName;
152 this.pwd = pwd;
153 setName("m3u8-" + ip);
154 this.taskState = notStart;
155 }
156
157 @Override
158 public void run() {
159 try {
160 FileUtils.forceMkdir(new File(m3u8File).getParentFile());
161 if (!checkDeviceOnline(ip)) {
162 log.warn("设备{},离线", ip);
163 this.taskState = fail;
164 return;
165 }
166 String command = String.format(ffmpegCmd, rtspUrl, m3u8File);
167 this.taskState = running;
168
169 //判断是操作系统是linux还是windows
170 String[] comds;
171 if (SystemCmdHelper.isWin()) {
172 comds = new String[]{"cmd", "/c", command};
173 // comds = new String[]{"cmd", "/c", "start", "/b", "cmd.exe", "/k", command};
174 } else {
175 comds = new String[]{"/bin/sh", "-c", command};
176 }
177
178 // 开始执行命令
179 log.info("执行命令:" + command);
180 process = Runtime.getRuntime().exec(comds);
181
182 //开启线程监听(此处解决 waitFor() 阻塞/锁死 问题)
183 new SystemCmdHelper.RunThread(process.getInputStream(), "INFO").start();
184 new SystemCmdHelper.RunThread(process.getErrorStream(), "ERROR").start();
185 int flag = process.waitFor();
186 log.info("结束{}", ip);
187 } catch (Exception e) {
188 log.error("出现异常" + e.getMessage(), e);
189 this.taskState = fail;
190 } finally {
191 if (process != null) {
192 try {
193 process.exitValue();
194 } catch (Exception e) {
195 }
196 try {
197 process.destroyForcibly();
198 } catch (Exception e) {
199 }
200 }
201 }
202 }
203
204 /**
205 * 获取任务执行状态
206 */
207 public int getTaskState() {
208 return taskState;
209 }
210
211 /**
212 * 立即停止任务
213 */
214 public void stopTask() {
215 if (process != null) {
216 try {
217 process.destroy();
218 } catch (Exception e) {
219 e.printStackTrace();
220 }
221 }
222 }
223
224 public String getM3U8File() {
225 return this.m3u8File;
226 }
227
228 public String getUserName() {
229 return userName;
230 }
231
232 public String getPwd() {
233 return pwd;
234 }
235 }
236
237 public static void main(String[] args) throws Exception {
238 RtspConvert convert = new RtspConvert();
239 String ip = "192.168.0.251";
240 String userName = "xxx";
241 String pwd = "xxx";
242 String m3u8 = convert.rtsp2Hls(ip, userName, pwd);
243 System.out.println("***********************************" + m3u8);
244 Thread.sleep(10 * 1000);
245 convert.stopTransform(ip);
246 System.out.println("************************************结束**************");
247 }
248 }
二、前端vue部分
<template>
<div class="app-container"> <!-- 视频播放 -->
<el-dialog title="实时播放" :visible.sync="openPlay" width="800px" :before-close="closePlayDialog" append-to-body>
<el-row>
<video-player v-if="hlsUrl != null" class="video-player vjs-custom-skin" ref="videoPlayer"
:playsinline="true" :options="playerOptions" @error="playError"></video-player>
</el-row>
</el-dialog>
</div>
</template> <script>
import { listDevice, getDevice, delDevice, addDevice, updateDevice,startTransform } from "@/api/biz/device";
import { queryAllClassRoom } from '@/api/biz/classRoom';
import { skipAiCamera, skipHikCamera } from '@/utils/ruoyi';
import 'videojs-contrib-hls' export default {
name: "Device",
dicts: ['device_type', 'device_states'],
data() {
return { playerOptions: {
// playbackRates: [0.5, 1.0, 1.5, 2.0], //可选择的播放速度
autoplay: false, //如果true,浏览器准备好时开始回放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 视频一结束就重新开始。
preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [{
type: "application/x-mpegURL",
src: ''//url地址
}],
hls: true,
poster: "", //你的封面地址
// width: document.documentElement.clientWidth,
notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: false,//当前时间和持续时间的分隔符
durationDisplay: false,//显示持续时间
remainingTimeDisplay: false,//是否显示剩余时间功能
fullscreenToggle: true //全屏按钮
}
},
hlsUrl: '',
openPlay: false,
};
},
created() { },
methods: { //播放监控画面
handlePlay(row) {
startTransform(row.id).then(res => {
let url = process.env.VUE_APP_BASE_API + res.msg.replaceAll("\\", "/");
// console.log("播放url",url);
this.openPlay = true;
this.playerOptions.sources[0].src = url;
// this.playerOptions.sources[0].src = "http://192.168.0.249:10000/hls/192_168_0_251/live.m3u8";
this.hlsUrl = url;
})
},
//关闭播放弹窗
closePlayDialog(done) {
try {
this.$refs.videoPlayer.player.pause();
this.$refs.videoPlayer.reset();
} catch(e) {
}
done();
},
playError(e) {
console.log("播放异常,自动重启播放器", e)
if (e.error_ && e.error_.code == 4) { } else { //当出现m3u8文件加载错误时,自动重新播放
this.$refs.videoPlayer.player.src(this.hlsUrl);
this.$refs.videoPlayer.player.load(this.hlsUrl);
this.$refs.videoPlayer.player.play();
}
}
}
};
</script>
三、效果

rtsp协议转换m3u8的更多相关文章
- RTSP协议转换RTMP直播协议
RTSP协议转换RTMP直播协议 RTSP协议也是广泛使用的直播/点播流媒体协议,最近实现了一个RTSP协议转换RTMP直播协议的程序,为的是可以接收远端设备或服务器的多路RTSP直播数据,实时转换为 ...
- 直播源格式转换教程——rtmp/rtsp/http/m3u8!!
之前寻找直播源,发现好多rtmp开头的,或者是rtsp开头的,但是ATV里面的个人链接是支持m3u8格式的.怎么办?小编发现了几个规律,网友可作参考.现在流行的直播地址差不多就这几种需要说明的是并不是 ...
- 园 首页 新随笔 联系 管理 订阅 订阅 RTSP协议转换RTMP直播协议
RTSP协议转换RTMP直播协议 RTSP协议也是广泛使用的直播/点播流媒体协议,最近实现了一个RTSP协议转换RTMP直播协议的程序,为的是可以接收远端设备或服务器的多路RTSP直播数据,实时转换为 ...
- rtmp转m3u8
不是所有的地址改成这样都能播 需要自己测试 先说一下rtmp的其中rtmp的常见的差不多是3种 1.一种是wowza服务器的 比如这个地址rtmp://116.55.245.135:8096/live ...
- iNeuOS工业互联平台,WEB组态(iNeuView)集成rtmp和websocket视频元件,支持海康、大华等摄像头实时显示视频
目 录 1. 概述... 1 2. 平台演示... 2 3. 硬件摄像头... 2 4. 视频流协议转换管理... 2 5. 组态视频元件 ...
- 构建AR视频空间大数据平台(物联网及工业互联网、视频、AI场景识别)
目 录 1. 应用背景... 2 2. 系统框架... 2 3. AI场景识别算法和硬件... 3 4. AR视频空间管理系统... 5 5. ...
- EasyHLS实现将IPCamera摄像机的RTSP流转成HLS(ts+m3u8)直播输出
本文转自:http://www.cnblogs.com/babosa/p/6033039.html EasyHLS EasyHLS是EasyDarwin开源流媒体团队开发的一款HLS打包库,接口非常简 ...
- 记录:通过ffmpeg rtsp转 http m3u8
环境 Windows 10 大华rtsp直播 转 http请求m3u8 ffmpeg -rtsp_transport tcp -i "rtsp://账号:密码@IP:端口/cam/realm ...
- EasyNVR RTSP转HLS(m3u8+ts)流媒体服务器前端构建之:bootstrap-datepicker日历插件的实时动态展现
EasyNVR中有对录像进行检索回放的功能,且先抛开录像的回放,为了更好的用户体验过.让用户方便快捷的找到对应通道对应日期的录像视频,是必须的功能. 基于上述的需求,为前端添加一个日历插件,在日历上展 ...
- EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器软件实现的多码率视频点播功能说明
关于EasyDSS EasyDSS(http://www.easydss.com)流媒体解决方案采用业界优秀的流媒体框架模式设计,服务运行轻量.高效.稳定.可靠.易维护,支持RTMP直播.RTMP推送 ...
随机推荐
- Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to 解决办法
main.js 配置如下 import Router from 'vue-router'; //路由导航冗余报错(路由重复) const originalPush = Router.prototype ...
- .Net 和 .Net Core 集成Swagger 以及配合JWT身份验证
Swagger介绍 简单来说swagger是一款WebAPI的接口管理帮助文档,并且可以直接进行接口测试 我们来看一下官网介绍 https://swagger.io Swagger is a powe ...
- Python绘制神经网络模型图
本文介绍基于Python语言,对神经网络模型的结构进行可视化绘图的方法. 最近需要进行神经网络结构模型的可视化绘图工作.查阅多种方法后,看到很多方法都比较麻烦,例如单纯利用graphviz模块 ...
- angr初探
前言 在搞fuzz的时候发现了一个比较难以解决的问题.例如if(*buf == "\xde\xad\xbe\xef"),我们如果想通过纯fuzz去进入这个if的分支,那么概率极其微 ...
- 李超树学习笔记 & JZOJ 5039. 【NOI2017模拟4.2】查询题解
李超树 它本质上是线段树的拓展运用 解决的问题:平面直角坐标系中,支持插入线段,问 \(x = x_0\) 这条直线上最大的 \(y\) 值 它维护的东西很奇特:优势线段 何为"优势线段&q ...
- 题解 [SCOI2005]王室联邦
之前树分块也只是听说,今天亲手学了一下(?)( 首先你会发现这个 \(B\) 和 \(3B\) 的约束就很迷(我也不知道为什么搞这种奇怪的约束(悲)),学了才知道... 所以这题的分块方法好像叫&qu ...
- nodejs npm错误Error:UNKNOWN:unknown error,mkdir 'D:\Develop\nodejs\node_global'at Error(可行)
错误原因 在设置npm的cache和predix时,因为使用参考其他教程照抄,而没有修改为本机对应路径,本机上并没有这个地址,因此造成了错误. npm config set cache "D ...
- centos7 开机自启动脚本
两种实现方式 方法1:(rc.local) 1.因为在centos中/etc/rc.d/rc.local的权限被降低了,所以需要赋予其可执行权 chmod +x /etc/rc.d/rc.local ...
- adb+monkey常用命令记录升级版
为什么要叫升级版呢,本来打算自己写一下adb常用命令的,但是在网上看到一个比较好的,就没有自己从头开始写,但是该博主写的有点小瑕疵就是缺少日志过滤和关于monkey的命令,笔者就加了关于日志和monk ...
- el-input 使用 回车键会刷新页面的问题
使用el-input的时候,光标聚焦在输入框,按下回车,会刷新页面.这是因为当el-form表单中只有一个input时,按下回车建会自动触发页面的提交功能, 产生刷新页面的行为 解决办法 法一: fo ...