目前项目中使用海康的摄像头,但需要提供实时预览。目前通过转换协议实现预览。同时能够尽量减少服务器的压力,比如生成的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的更多相关文章

  1. RTSP协议转换RTMP直播协议

    RTSP协议转换RTMP直播协议 RTSP协议也是广泛使用的直播/点播流媒体协议,最近实现了一个RTSP协议转换RTMP直播协议的程序,为的是可以接收远端设备或服务器的多路RTSP直播数据,实时转换为 ...

  2. 直播源格式转换教程——rtmp/rtsp/http/m3u8!!

    之前寻找直播源,发现好多rtmp开头的,或者是rtsp开头的,但是ATV里面的个人链接是支持m3u8格式的.怎么办?小编发现了几个规律,网友可作参考.现在流行的直播地址差不多就这几种需要说明的是并不是 ...

  3. 园 首页 新随笔 联系 管理 订阅 订阅 RTSP协议转换RTMP直播协议

    RTSP协议转换RTMP直播协议 RTSP协议也是广泛使用的直播/点播流媒体协议,最近实现了一个RTSP协议转换RTMP直播协议的程序,为的是可以接收远端设备或服务器的多路RTSP直播数据,实时转换为 ...

  4. rtmp转m3u8

    不是所有的地址改成这样都能播 需要自己测试 先说一下rtmp的其中rtmp的常见的差不多是3种 1.一种是wowza服务器的 比如这个地址rtmp://116.55.245.135:8096/live ...

  5. iNeuOS工业互联平台,WEB组态(iNeuView)集成rtmp和websocket视频元件,支持海康、大华等摄像头实时显示视频

    目       录 1.      概述... 1 2.      平台演示... 2 3.      硬件摄像头... 2 4.      视频流协议转换管理... 2 5.      组态视频元件 ...

  6. 构建AR视频空间大数据平台(物联网及工业互联网、视频、AI场景识别)

    目       录 1.      应用背景... 2 2.      系统框架... 2 3.      AI场景识别算法和硬件... 3 4.      AR视频空间管理系统... 5 5.    ...

  7. EasyHLS实现将IPCamera摄像机的RTSP流转成HLS(ts+m3u8)直播输出

    本文转自:http://www.cnblogs.com/babosa/p/6033039.html EasyHLS EasyHLS是EasyDarwin开源流媒体团队开发的一款HLS打包库,接口非常简 ...

  8. 记录:通过ffmpeg rtsp转 http m3u8

    环境 Windows 10 大华rtsp直播 转 http请求m3u8 ffmpeg -rtsp_transport tcp -i "rtsp://账号:密码@IP:端口/cam/realm ...

  9. EasyNVR RTSP转HLS(m3u8+ts)流媒体服务器前端构建之:bootstrap-datepicker日历插件的实时动态展现

    EasyNVR中有对录像进行检索回放的功能,且先抛开录像的回放,为了更好的用户体验过.让用户方便快捷的找到对应通道对应日期的录像视频,是必须的功能. 基于上述的需求,为前端添加一个日历插件,在日历上展 ...

  10. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器软件实现的多码率视频点播功能说明

    关于EasyDSS EasyDSS(http://www.easydss.com)流媒体解决方案采用业界优秀的流媒体框架模式设计,服务运行轻量.高效.稳定.可靠.易维护,支持RTMP直播.RTMP推送 ...

随机推荐

  1. C#零基础小白快速入门

    前言 本文写给想学C#的朋友,目的是以尽快的速度入门 C#好学吗? 对于这个问题,我以前的回答是:好学!但仔细想想,不是这么回事,对于新手来说,C#没有那么好学. 反而学Java还要容易一些,学Jav ...

  2. C++ 地球人口承载力

    题目描述 假设地球上的新生资源按恒定速度增长.照此测算,地球上现有资源加上新生资源可供 xx 亿人生活 aa 年,或供 yy 亿人生活 bb 年. 为了能够实现可持续发展,避免资源枯竭,地球最多能够养 ...

  3. unity 实现自定义class深度拷贝 deep copy 深度复制 引用类型复制

    气死我了,搜半天没有,全让序列化再反序列化,又不方便又不美观.结果自己试着一写就通,两行完事. 首先先安装Newtonsoft.Json 包,这个很常用也很简单,随便搜一下安上就行,早晚得学. 然后两 ...

  4. Vue13 样式动态绑定

    1 class样式的动态绑定 1.1 说明 通过命令v-bind:class设置一个对象,动态切换class.可以简写为:class. class=""可以和:class=&quo ...

  5. 解决 Vue3 中路由切换到其他页面再切换回来时 Echarts 图表不显示的问题

    问题复现: 正常状态下: 切换到其他页面再切换回来: 问题解决: 其实这个问题的解决方式官网写得清清楚楚,我们看看官网怎么解决的: 接下来我用代码解释下这句话(正确的做法是,在图表容器被销毁之后,调用 ...

  6. Windows 串口代码

    #pragma once #include <Windows.h> #define DEFAULT_THREAD_TERMINATED_TIME 2000 class CAutoThrea ...

  7. 多线程并发(二):聊聊AQS中的共享锁实现原理

    在上一篇文章多线程并发(一)中我们通过acquire()详细地分析了AQS中的独占锁的获取流程,提到独占锁,自然少不了共享锁,所以我们这边文章就以AQS中的acquireShared()方法为例,来分 ...

  8. 消息传递(news)题解

    代码 #include<cstdio> #include<algorithm> using namespace std; const int N = 200000; int f ...

  9. ABP微服务系列学习-搭建自己的微服务结构(三)

    上一篇我们基础服务初步搭建完毕,接下来我们整一下认证和网关. 搭建认证服务 认证服务的话,ABP CLI生成的所有模板都包括了一个AuthServer.我们直接生成模板然后微调一下就可以直接用了. a ...

  10. python路径含空格导致打开文件报错

    2023.02.14更新: python中使用os.listdir不行,但是可以用os.walk,可以绕过空格的问题. 在windows中总有奇怪的问题,比如路径或文件名带空格,打开文件就会报错. 一 ...