目前项目中使用海康的摄像头,但需要提供实时预览。目前通过转换协议实现预览。同时能够尽量减少服务器的压力,比如生成的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. VS针对Linux远程调试步骤

    VS2019下对于远程Linux下C++代码的调试 VS2017后新增了对跨平台代码的编写,编译和调试的功能,2019后更是新增了多种插件,以下是针对C++版本的linux环境代码调试 准备工作 安装 ...

  2. docker命令之docker build

    docker命令之docker build 明天要讲docker file的公开课,正好借此机会,整理下docker 命令的专题 语法 docker build [OPTIONS] PATH | UR ...

  3. 通过Nacos配置刷新进行RabbitMQ消费者在线启停

    前提 公司在做一些金融相关业务,某些时候由于数据提供商定期维护或者特殊原因需要暂停某些服务的消费者.之前选用的消息队列技术栈是RabbitMQ,用于微服务之间的消息投递,对于这类需要暂停消费者的场景是 ...

  4. 关于Powerlink和EtherCAT的对比

    https://www.amobbs.com/thread-5679636-1-2.html 转发自:http://book.2cto.com/201508/55093.html 这个问题经常会被人问 ...

  5. JavaScript原型和原型链?有什么特点?

    一.原型 JavaScript 常被描述为一种基于原型的语言--每个对象拥有一个原型对象 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上 ...

  6. JZOJ 1494. 密码

    留个高精度的模板 \(Code\) #include<cstdio> #include<cstring> using namespace std; int n , a[5005 ...

  7. 依那西普减量维持过程中RA病人自报病情复发可能预示未来放射学进展[EULAR2015_SAT0147]

    依那西普减量维持过程中RA病人自报病情复发可能预示未来放射学进展   SAT0147 SELF-REPORTED FLARES PREDICT RADIOGRAPHIC PROGRESSION IN ...

  8. TikTok 推荐引擎强大的秘密

    作者:Heorhii Skovorodnikov 深入研究TikTok令人惊叹的实时推荐系统的内部工作原理,了解是什么使它成为该领域最好的产品之一. 为什么TikTok的feed如此让人上瘾?秘诀在于 ...

  9. Blob 和 ArrayBuffer

    Blob 和 ArrayBuffer Blob 对象表示的是二进制到文本的对象: ArrayBuffer 对象表示一段二进制数据,用来模拟内存里面的数据. Blob 关于 Blob 的详细内容:了解 ...

  10. 内容分发网络 CDN

    介绍 CDN 内容分发网络(英语:Content Delivery Network 或 Content Distribution Network,缩写:CDN)是建立并覆盖在承载网上,由不同区域的服务 ...