通过海康接口返回的rtsp视频接口,转换成.m3u8格式文件,逻辑如下
1、采用ffmpeg实时转化rtsp链接视频,转化为m3u8,存放服务器固定地址
2、采用nginx代理视频出.m3u8视频链接地址
3、采用token+redis方式处理视频播放和删除过程,开启视频录像,并将token或者自定义文件夹存入redis,将用户token解析部分(我解析的是jwt的token最后一个点后面内容,作为当前用户的开始视频存放的文件夹A),视频摄像头唯一编码作为下面一个子文件夹B,A+B作为ffmpeg开启的key
4、停止某个视频,通过A+B停止ffmpeg视频转化,并删除B下面所有资源,包含B所有文件夹
5、退出登录,停止并删除A下面的所有视频资源转化,并删除A文件夹
6、redis中的token过期,回调方法返回过期的key,对key解析,拿到token最后一个点后面内容,也是就是文件A,对第五步进行操作

1、nginx转码配置及ffmpeg转化,我参考的下面博客
https://www.freesion.com/article/5775913700/
, 注意,java的ffmpeg部分,我自定义了一个文件夹ffmpeg,


1、我的调用ffmpeg的start方法开始转视频流,注意转流的文件路径要先创建,fileExistTWo.mkdirs();
@Autowired CommandManager manager;public String toHls(String fileName, String code, String url) {//.m3u8文件路径 String basePath = rootPath + fileName + File.separator + code + File.separator + code + File.separator; //文件夹路径 String basePathTWo = rootPath + fileName + File.separator + code + File.separator; File fileExist = new File(basePath); File fileExistTWo = new File(basePathTWo); System.err.println(fileExist); // 文件夹不存在,则新建 if (!fileExistTWo.exists()) { fileExistTWo.mkdirs(); } // 省略查询路径部分 实体-> result String codeId = fileName + ":" + code; manager.stop(codeId); // 先停止视频 manager.start(codeId, CommandBuidlerFactory.createBuidler() .add("ffmpeg") .add("-rtsp_transport", "tcp") .add("-i", url) // 取videoUrl .add("-c", "copy") .add("-f", "hls") .add("-hls_time", "2.0") .add("-hls_list_size", "2") .add("-hls_flags", "2") .add(fileExist + ".m3u8")); TaskEntity info= (TaskEntity) manager.query(codeId); System.out.println(info); int suspensionState = info.getSuspensionState(); System.out.println(suspensionState); String urlHead = "http://"; // 返回路径根据ffmpeg存放视频路径+nginx代理灵活配置 String urlTwo = urlHead + CommonUtil.getIpv4IP().trim() + ":" + 1011 + "/videoCache/" + fileName + "/" + code + "/" + code + ".m3u8"; return urlTwo;}
获取公网IP方法
public static String getIpv4IP() {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try {
URL realUrl = new URL("https://www.taobao.com/help/getip.php");
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
// log.error("获取ipv4公网地址异常");
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
String str = result.toString().replace("ipCallback({ip:", "");
String ipStr = str.replace("})", "");
return ipStr.replace('"', ' ');
}
2、停止视频方法
token值如下
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJpc3N1c2VyIiwiYXVkIjoiYXVkaWVuY2UiLCJ0ZW5hbnRfaWQiOiIwMDAwMDAiLCJyb2xlX25hbWUiOiJhZG1pbmlzdHJhdG9yIiwicG9zdF9pZCI6IjExMjM1OTg4MTc3Mzg2NzUyMDEiLCJ1c2VyX2lkIjoiMTEyMzU5ODgyMTczODY3NTIwMSIsInJvbGVfaWQiOiIxMTIzNTk4ODE2NzM4Njc1MjAxIiwidXNlcl9uYW1lIjoiYWRtaW4iLCJuaWNrX25hbWUiOiLnrqHnkIblkZgiLCJkZXRhaWwiOnsidHlwZSI6IndlYiIsInByb2plY3RJZCI6MTU4MjAwNTQ3Mzk1NTEzMTM5MywiVXNlckNhdGVnb3J5IjoiMCIsIldzSWQiOjE0MzgzMzIwNjQ1NjExNDM4MDl9LCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwiZGVwdF9pZCI6IjExMjM1OTg4MTM3Mzg2NzUyMDEiLCJhY2NvdW50IjoiYWRtaW4iLCJjbGllbnRfaWQiOiJzYWJlciIsImV4cCI6MTY3MDU2OTAyMSwibmJmIjoxNjcwNTY1NDIxfQ.NHZiaWqrCIRukfvAqChkDNAAH34Pffm_PvQIEfqAU0SdKkS9ZNhxnB354demmkAJ2l8m3OXWIkeSkeHHGNuzEg
停止的方法如下
public R stopBackVideo(String code, String token) throws IOException {
//解析jwt的token值,拿到最后面一截,这个也是不会重复
String fileName = StringUtils.split(token, ".")[2];
String basePath = rootPath + fileName + File.separator + code + File.separator;
String basePathAll = rootPath + fileName + File.separator;
List<String> fileNamesList = getFileNamesList(basePathAll);
fileNamesList.forEach(x -> {
System.err.println("文件名称:" + x);
});
System.err.println(basePathAll);
File fileExist = new File(basePath);
String codeId = fileName + ":" + code;
//停止ffmpeg转码
manager.stop(codeId);
manager.start(codeId, CommandBuidlerFactory.createBuidler()
.add("rm -rf", fileExist.toString()));
//对文件夹进行删除操作
if (fileExist.exists()) {
deleteDir(basePath);
}
return R.data("删除成功");
}
删除文件夹下面所有文件
public void deleteDir(String basePath) throws IOException { Path path = Paths.get(basePath); Files.walkFileTree(path, new SimpleFileVisitor<Path>() { // 先去遍历删除文件 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); System.out.printf("文件被删除 : %s%n", file); return FileVisitResult.CONTINUE; } // 再去遍历删除目录 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); System.out.printf("文件夹被删除: %s%n", dir); return FileVisitResult.CONTINUE; } } ); }
3、停止并删除当前用户的所有视频,及记录
public void removeAllVideoByToken(String token1) throws IOException { String fileName = StringUtils.split(token1, ".")[2]; String basePathAll = rootPath + fileName + File.separator; File fileExist = new File(basePathAll); // 文件夹文件夹存在,则停止后删除 if (fileExist.exists()) { List<String> fileNamesList = getFileNamesList(basePathAll); if (CollectionUtils.isNotEmpty(fileNamesList)) { fileNamesList.forEach(x -> { try { stopBackVideo(x, token1); } catch (IOException e) { e.printStackTrace(); } }); if (fileExist.exists()) { deleteDir(basePathAll); } } } }
拿取到所有该文件目录,下面所有的文件夹名称 集合
private List<String> getFileNamesList(String path) { File file = new File(path); if (!file.exists()) { return null; } List<String> fileNames = new ArrayList<>(); return getFileNames(file, fileNames); } /** * 得到文件名称 * * @param file 文件 * @param fileNames 文件名 * @return {@link List}<{@link String}> */ private List<String> getFileNames(File file, List<String> fileNames) { File[] files = file.listFiles(); for (File f : files) { if (f.isDirectory()) { fileNames.add(f.getName()); } } //所有文件 // if (f.isDirectory()) { // getFileNames(f, fileNames); // } else { // fileNames.add(f.getName()); // } // } return fileNames; }
4、监听redis中token失效回调方法,并停止用户的没有关闭的视频流,删除文件,减少资源占用
redis.conf将 notify-keyspace-events修改 成Ex
notify-keyspace-events Ex
RedisMessageListenerContainer 加入容器
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
我是放在application下面

监听key变化,key过期则对视频流进行清理操作
@Component @Slf4j public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } @Autowired CameraSecretInfoService cameraSecretInfoService; /** * key过期触发的事件 */ @SneakyThrows @Override public void onMessage(Message message, byte[] pattern) { String channel = new String(message.getChannel(), StandardCharsets.UTF_8); String key = new String(message.getBody(), StandardCharsets.UTF_8); boolean contains = key.contains("bladeFile:fileName:"); if (contains) { log.info("redis key 过期:pattern={},channel={},key={}", new String(pattern), channel, key); String token = key.substring(19); cameraSecretInfoService.removeAllVideoByToken(token); } } }
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
动漫制作技巧是很多新人想了解的问题,今天小编就来解答与大家分享一下动漫制作流程,为了帮助有兴趣的同学理解,大多数人会选择动漫培训机构,那么今天小编就带大家来看看动漫制作要掌握哪些技巧?一、动漫作品首先完成草图设计和原型制作。设计草图要有目的、有对象、有步骤、要形象、要简单、符合实际。设计图要一致性,以保证制作的顺利进行。二、原型制作是根据设计图纸和制作材料,可以是手绘也可以是3d软件创建。在此步骤中,要注意的问题是色彩和平面布局。三、动漫制作制作完成后,加工成型。完成不同的表现形式后,就要对设计稿进行加工处理,使加工的难易度降低,并得到一些基本准确的概念,以便于后续的大样、准确的尺寸制定。四、
2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p
Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图
目前我正在使用这个正则表达式从YoutubeURL中提取视频ID:url.match(/v=([^&]*)/)[1]我怎样才能改变它,以便它也可以从这个没有v参数的YoutubeURL获取视频ID:http://www.youtube.com/user/SHAYTARDS#p/u/9/Xc81AajGUMU感谢阅读。编辑:我正在使用ruby1.8.7 最佳答案 对于Ruby1.8.7,这就可以了。url_1='http://www.youtube.com/watch?v=8WVTOUh53QY&feature=feedf'url
我经常使用嵌套数据结构,很多时候我必须从控制台手动分析它们。问题是它们全部打印在一行中。是否有一种简单的方法可以根据{,[,],}和逗号重新构造数据结构的显示,使其看起来像Ruby的pretty_print输出? 最佳答案 :%s/\([{,]\)/\1\r/gggVG=:setft=ruby呜呜呜 关于ruby-如何将Vim中的"expand"文本转换成一种易于阅读的方式?,我们在StackOverflow上找到一个类似的问题: https://stacko
我一直在为使用acts_as_list的模型实现一些不错的交互界面,这些界面可以对我的mRails应用程序中的列表进行排序。我有一个排序函数,在每次拖放之后使用sortable_elementscript.aculo.us函数调用并设置每条记录的位置。这是在拖放完成后处理排序的Controller操作示例:defsortparams[:documents].each_with_indexdo|id,index|Document.update_all(['position=?',index+1],['id=?',id])endend现在我正在尝试对嵌套集模型(acts_as_nested
最近在工作中,看到一些新手测试同学,对接口测试存在很多疑问,甚至包括一些从事软件测试3,5年的同学,在聊到接口时,也是一知半解;今天借着这个机会,对接口测试做个实战教学,顺便总结一下经验,分享给大家。计划拆分成4个模块跟大家做一个分享,(接口测试、接口基础知识、接口自动化、接口进阶)感兴趣的小伙伴记得关注,希望对你的日常工作和求职面试,带来一些帮助。注:文章较长有5000多字,希望小伙伴们认真看完,当然有些内容对小白同学不是太友好,如果你需要详细了解其中的一些概念或者名词,请在文章之后留言,后续我将针对大家的疑问,整理输出一些大家感兴趣的文章。随着开发模式的迭代更新,前后端分离已不是新的概念,
一、什么是web项目ui自动化测试?通过测试工具模拟人为操控浏览器,使软件按照测试人员的预定计划自动执行测试的一种方式,可以完成许多手工测试无法完成或者不易实现的繁琐工作。正确使用自动化测试,可以更全面的对软件进行测试,从而提高软件质量进而缩短迭代周期。二、构建测试用例的“九部曲”(一)创建流程包划分功能模块日常测试活动中,都会根据功能模块进行拆分,所以在设计器中我们可以通过创建流程包的方式来拆分需要测试的功能模块,如下图中操作创建一个电脑流程包并且取名为对应的功能模块名称,如果有多个功能模块就创建多个对应的流程包,实在RPA设计器有易用的图形可视化界面,方便管理较多的功能模块。(二)在流程包
目录需求基于JavaCV跨平台执行ffmpeg命令[^1]坑一内存不足坑二多个ffmpeg进程并行导致IO负载大,进而导致ioerror?坑三使用Java操作ffmpeg时,有时会卡死坑四Process的waitFor死锁问题及解决办法需求给透明背景的视频自动叠加一张背景图片基于JavaCV跨平台执行ffmpeg命令1我测试发现的本需求的最小依赖:dependency>groupId>org.bytedecogroupId>artifactId>ffmpeg-platform-gplartifactId>version>5.0-1.5.7version>dependency>核心代码:Stri