最新要闻
- 当前快播:新农股份: 2022年度业绩快报
- 环球观天下!OPPO Find X6 Pro搭载三星E6屏:亮度高达2500nit、支持Pro XDR显示
- 焦点滚动:用到安卓17没问题!OPPO宣布Find X系列将支持4次大版本更新
- 449元-6999元!OPPO Find X6发布会四大新品一图看懂
- 全球速递!报告:2022年中国每四辆新车就有一辆电动车 比亚迪无敌
- 环球观点:杜绝虚标!哈趣投影率先启用中国CVIA亮度标准:成单片式LCD领头羊
- OPPO Find N2系列赢麻了!连续三个月折叠屏销量第一
- 天天速看:自动洗烘拖布 石头自清洁扫拖机器人P10图赏
- 道奇纯燃油谢幕之作!挑战者SRT恶魔170发布:V8机械增压马力超千匹
- 环球简讯:爵士力克国王将湖人挤出附加赛区 迷失盐湖城小萨准三双数据难掩低迷状态
- 入睡妙招!研究表明穿袜子睡觉更助眠
- 全球热消息:AMD Zen4霸气!移动版12核心解锁130W 直追170W桌面12核心
- "周杰伦演唱会门票"登顶微博热搜 14万张秒售罄
- 全球热推荐:今天春分白昼长了!全国春日地图出炉 看看春天到哪了
- 天天热议:汽车界“海底捞服务”!蔚来2023无忧服务发布:11600元/年
- 世界聚焦:重庆不再实行旧车置换:直接给予新车补贴 总计达3000万
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
全球微动态丨记录监控摄像头的接入过程及web端播放
1.rtsp视频流网页播放概述
需求:当我们通过ONVIF协议,获取到了摄像头的rtsp流地址(长这样:rtsp://admin:123456789@192.168.9.16:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif)后,通过vlc播放器,我们可以查看监控视频内容,可是,我们应该如何在网页上查看视频内容呢?因为现在的浏览器都不支持rtsp流(详见:https://blog.csdn.net/SY__CSDN/article/details/129255690?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-129255690-blog-113454774.pc_relevant_landingrelevant&spm=1001.2101.3001.4242.1&utm_relevant_index=3),因此我所选用的解决方案便是推流 + 转码
(资料图片)
(1)转码推流工具ffmpeg(安装教程详见:https://www.cnblogs.com/h2285409/p/16982120.html),安装好之后,便可使用命令 ffmpeg -re -rtsp_transport tcp -i rtsp://admin:123456789@192.168.9.16:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif -c:v copy -c:a copy -f flv rtmp://127.0.0.1/live/16 将我们的rtsp视频流转码并推至流媒体服务器上,在这个命令中含有两个URL,前面的是我们的rtsp流地址,而后面的URL便是我们流媒体服务器的地址,以及一个-f参数,指定了我们视频流转码后的格式为flv
(2)流媒体服务器,主要调研了2款,一是整合了Rtmp模块的Nginx,二是SRS视频服务器,而我所选用的是SRS(官方文档:http://ossrs.net/lts/zh-cn/),在使用ffmpeg推流上SRS后,便可直接从SRS获得HTTP-FLV视频流地址(如本例:http://127.0.0.1/live/16.flv ),然后,前端通过flv.js组件库便可直接在页面上播放该视频流
SRS与ffmpeg参考:https://blog.csdn.net/diyangxia/article/details/120172920
ffmpeg进阶参考:https://segmentfault.com/a/1190000039782685
flv.js参考:http://www.kaotop.com/it/446261.html
2.rtsp推流转码相关代码实现
//ffmpeg安装路径@Value("${ffmpegPath}")private String ffmpegPathPrefix;//srs视频服务器地址@Value("${srsAddress}")private String srsAddress;//srs端口,默认为8080@Value("${srsPort}")private String srsPort;//srs-http-api端口,默认为1985@Value("${srsHttpApiPort}")private String srsHttpApiPort;@Resourceprivate MonitorMapper monitorMapper;@Resourceprivate RedisTemplate redisTemplate;@Resourceprivate RestTemplate restTemplate;@Resourceprivate ThreadPoolTaskExecutor threadPoolTaskExecutor;private ConcurrentHashMap id2transcodeModelMap = new ConcurrentHashMap<>();/*** 进行推流转码* @param id ipc的主键id* @return 转码推流后的http-flv地址,前端可通过flv.js直接播放*/public String transcodeAndPushStream(String id) { Ipc ipc = monitorMapper.getIpcInfoById(id); try { //先给这个流加锁,防止其他用户请求该流信息 while(!redisTemplate.opsForValue().setIfAbsent(id, 1, Duration.ofSeconds(60))) { Thread.sleep(200); } //避免重复对某一个流的推流工作 if(!id2transcodeModelMap.containsKey(id)) { String command = String.format("%sffmpeg -re -rtsp_transport tcp -i %s -c:v copy -c:a copy -f flv %s",this.ffmpegPathPrefix, ipc.getRtspUrl(), "rtmp://" + this.srsAddress + "/live/" + id); //通过命令行执行推流转码 System.out.println("启动推流转码, 其命令为: " + command); Process process = Runtime.getRuntime().exec(command); //可选,开启异步线程,观察推流进程所打印的日志 Future processOutputHandler = threadPoolTaskExecutor.submit(() -> { BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream())); String msg = null; try { while ((msg = br.readLine()) != null) { if (Thread.currentThread().isInterrupted()) { System.out.println("关闭推流进程的日志输出线程: " + Thread.currentThread().getName()); break; } if (msg.contains("fail") || msg.contains("miss")) { System.err.println(ipc.getId() + " 在推流过程中发生故障或丢包: " + msg); } } } catch (IOException e) { System.err.println(ipc.getId() + " 在推流转码过程中发生异常错误,原因: " + e.getMessage()); } finally { if (Thread.currentThread().isAlive()) { Thread.currentThread().interrupt(); } } return null; }); id2transcodeModelMap.put(id, new TranscodeModel(id, command, process, processOutputHandler)); } } catch (Exception e) { this.closeTranscode(id); throw new RuntimeException("启动对 " + id + " 的推流转码失败,原因: " + e.getMessage()); } finally { redisTemplate.delete(id); } //返回转码后的flv流地址 return "http://" + this.srsAddress + ":" + this.srsPort + "/live/" + id + ".flv";}/*** 关闭推流进程*/private void closeTranscode(String id) { TranscodeModel transcodeModel = null; if((transcodeModel = id2transcodeModelMap.get(id)) != null) { Future outputHandler = transcodeModel.getOutputHandler(); //关闭输出线程 if(outputHandler != null && !outputHandler.isDone()) { outputHandler.cancel(true); } //停止推流转码进程 if (transcodeModel.getProcess() != null) { transcodeModel.getProcess().destroy(); } id2transcodeModelMap.remove(id); System.out.println("关闭对 " + id + " 的推流转码"); }}/*** 客户端结束播放流后,srs可配置触发一个on_stop回调,通过该回调,我们就可以知道哪些流可能没人看了,然后结束对该流进行的推流转码工作* @param data srs触发回调时所携带的参数*/public void stopPlay(CallbackOnStopPlay data) { String clientId = data.getClient_id(); JSONObject srsClient = this.requestSrsClientById(clientId); if(!srsClient.isEmpty()) { String streamId = srsClient.getString("stream"); if (!StringUtils.hasText(streamId)) { System.err.println("获取client " + clientId +" 的流失败, 未关联流"); return; } //在请求这个流的信息之前,先给这个流加锁,防止其他用户预览该流 try { while(!redisTemplate.opsForValue().setIfAbsent(data.getStream(), 1, Duration.ofSeconds(60))) { Thread.sleep(200); } JSONObject vidiconStream = this.requestSrsStreamById(streamId); if(!vidiconStream.isEmpty()) { Integer clients = vidiconStream.getInteger("clients"); //当前观看该流的人数 <= 2时,说明没人看了可以停止推流,至于为什么是2,可以自己观察打印日志看看 if(clients <= 2) { this.closeTranscode(vidiconStream.getString("name")); } } } catch (Exception e) { System.err.println("关闭视频流 " + streamId + " 失败, 原因: " + e.getMessage()); } finally { redisTemplate.delete(data.getStream()); } }}/** * 根据clientId获取某个client信息 */private JSONObject requestSrsClientById(String clientId) { if(!StringUtils.hasText(clientId)) { return new JSONObject(); } String url = "http://" + this.srsAddress + ":" + this.srsHttpApiPort + "/api/v1/clients/" + clientId; ResponseEntity exchange = null; try { exchange = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, new HttpHeaders()), JSONObject.class); } catch (Exception e) { System.err.println("请求srs的client " + clientId + " 失败,原因: " + e.getMessage()); return new JSONObject(); } if (exchange == null || exchange.getBody() == null || exchange.getBody().getInteger("code") != 0) { System.err.println("请求srs中client " + clientId + " 失败"); return new JSONObject(); } System.out.println("请求到client " + clientId + " 的信息为: " + exchange.getBody()); return exchange.getBody().getJSONObject("client");}/** * 根据流的id获取某个流 */private JSONObject requestSrsStreamById(String streamId) { if(!StringUtils.hasText(streamId)) { return new JSONObject(); } String url = "http://" + this.srsAddress + ":" + this.srsHttpApiPort + "/api/v1/streams/" + streamId; ResponseEntity exchange = null; try { exchange = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, new HttpHeaders()), JSONObject.class); } catch (Exception e) { System.err.println("请求srs中的流 " + streamId + " 失败,原因: " + e.getMessage()); return new JSONObject(); } if (exchange == null || exchange.getBody() == null || exchange.getBody().getInteger("code") != 0) { System.err.println("请求srs中的流 " + streamId + " 失败, 由于服务器重启或其他原因,该流已失效"); return new JSONObject(); } System.out.println("请求到流 " + streamId + " 的信息为: " + exchange.getBody()); return exchange.getBody().getJSONObject("stream");}public class TranscodeModel { private String id; private String command; private Process process; //推流过程中的输出线程 private Future outputHandler;}//客户端关闭流时触发的回调所传递的参数public class CallbackOnStopPlay { private String server_id; private String action; private String client_id; private String ip; private String vhost; private String app; private String stream; private String param;}//ipc类public class Ipc { //ipc的主键id private String id; //ipc的rtsp流地址 private String rtspUrl;}
对推流进程的关闭,可以选择定时任务轮询srs中流的信息,然后对那些没人看的流进行关闭,也可以选择配置srs客户端关闭流时的回调,来进行关闭,至于回调如何配置使用,可以详看官方文档中开放接口相关内容和这篇文章:https://blog.csdn.net/weixin_44341110/article/details/120829847
3.通过海康,大华NVR来接入IPC
未完待续...
关键词:
-
每日观点:为什么Tomcat架构要这么设计?这篇文章告诉你答案!
Tomcat体系架构Tomcat项目结构bin目录bin目录主要是用来存放tomcat的命令,主要有两大类,一类是以 sh...
来源: 每日观点:为什么Tomcat架构要这么设计?这篇文章告诉你答案!
全球微动态丨记录监控摄像头的接入过程及web端播放
全球快资讯丨narak靶机
当前快播:新农股份: 2022年度业绩快报
环球观天下!OPPO Find X6 Pro搭载三星E6屏:亮度高达2500nit、支持Pro XDR显示
焦点滚动:用到安卓17没问题!OPPO宣布Find X系列将支持4次大版本更新
449元-6999元!OPPO Find X6发布会四大新品一图看懂
全球速递!报告:2022年中国每四辆新车就有一辆电动车 比亚迪无敌
全球焦点!为什么不建议用redis做消息队列
世界今亮点!【数位DP】计数问题
前端设计模式——状态模式
每日讯息!关于 Vue 列表渲染 key 绑定 index 的性能问题
每日观察!黄金时间·千海金:避险情绪推升金价 但本周的美联储议息会议依然关键
环球观点:杜绝虚标!哈趣投影率先启用中国CVIA亮度标准:成单片式LCD领头羊
OPPO Find N2系列赢麻了!连续三个月折叠屏销量第一
天天速看:自动洗烘拖布 石头自清洁扫拖机器人P10图赏
道奇纯燃油谢幕之作!挑战者SRT恶魔170发布:V8机械增压马力超千匹
简单讲透Mac环境下多版本python的环境变量设置,仅对小白生效
视点!"error LNK2019: 无法解析的外部符号"原因分析
今头条!IDEA Rebuild项目错误:Information:java: java.lang.AssertionError: Value of x -1
对斗破苍穹进行python文本分析
实时焦点:VsCode 常用好用插件/配置+开发Vue 必装的插件
环球简讯:爵士力克国王将湖人挤出附加赛区 迷失盐湖城小萨准三双数据难掩低迷状态
入睡妙招!研究表明穿袜子睡觉更助眠
全球热消息:AMD Zen4霸气!移动版12核心解锁130W 直追170W桌面12核心
"周杰伦演唱会门票"登顶微博热搜 14万张秒售罄
dnf机械牛和悲鸣图在哪里?DNF机械牛和悲鸣的门票分别是什么?
雨过天晴一键还原怎么用?怎么删除雨过天晴一键还原?
OA对话框打不开是怎么回事?OA对话框怎么变成普通对话框?
今日最新!脚本编写的一个通用框架
天天速讯:编写高质量c#代码的20个建议
面试常考:C#用两个线程交替打印1-100的五种方法
全球新资讯:Paramiko_Linux
【全球独家】跟着字节AB工具DataTester,5步开启一个实验
英雄联盟自动关闭是什么意思?英雄联盟自动关闭怎么解决?
冒险岛的时空裂缝是什么?冒险岛怎么提升面板?
全球热推荐:今天春分白昼长了!全国春日地图出炉 看看春天到哪了
天天热议:汽车界“海底捞服务”!蔚来2023无忧服务发布:11600元/年
世界聚焦:重庆不再实行旧车置换:直接给予新车补贴 总计达3000万
世界报道:跨界做智能手表 比亚迪回应:消息属实 4月上新
对标《原神》!二次元开放世界游戏《鸣潮》开启测试招募
热点!如果设备不支持vulkan,就用swiftshader,否则就加载系统的vulkan的正确姿势(让程序能够智能的在vulkan-1.dll和libvk_s
【全球快播报】springboot使用easyExcel导出Excel表格以及LocalDateTime时间类型转换问题
《前端serverless 面向全栈的无服务器架构实战》读书笔记
每日视点!海关总署:2月下旬以来我国出口用箱量持续增长
国产纯电跑车前途K50美国秽土转生:换了名称、LOGO还没变
全球最新:40万级领先行业两代 赵长江:腾势N7月销量将轻松破万 抢夺BBA用户
【全球速看料】沙县小吃旗舰店包间最低消费300元 网友:吃的完吗?
全球头条:英国小镇被巨型老鼠入侵:像猫一样大 悬崖都要被挖塌了
OpenGL 图像 lookup 色彩调整
天天时讯:剑指 Offer 07. 重建二叉树(java解题)
为什么Redis不直接使用C语言的字符串?看完直接吊打面试官!
天天观天下!广东人睡觉时间全国最晚:“打工人”平均睡眠时长7.5小时
一根USB线就能偷走韩系车!现代、起亚已开始免费送车主方向盘锁
今日关注:再不发力就晚了!新一代奥迪Q5效果图曝光:内外大变革
当前时讯:沙尘暴黄色预警:北方超10省将迎来大范围沙尘天气
环球热点评!昔日巨头彻底退场!爱普生宣布所有相机明年终止官方服务
环球关注:论文解读TCPN
西部证券:3月20日融资买入1459.71万元,融资融券余额12.72亿元
环球速讯:中国罐头在海外多国热销:成为香饽饽
天天讯息:大反转!南京大学团队推翻美室温超导技术 复刻实验没发现超导现象
天天速看:又一致命真菌爆发:已遍布美国一半以上的州
天天微动态丨OPPO Find X6系列外观公布:拼接设计、后摄巨大
比亚迪汉唐冠军版发布会高能金句感受下 合资燃油车瑟瑟发抖
MAUI Blazor 加载本地图片的解决方案
每日热点:朴素系统优化思维的实践
焦点热文:债券通北向通2月成交规模环比增超三成 政金债跃升为最活跃券种
今日热讯:LCD荣光犹在!iQOO Z7开启预售:1599元起
全球观察:漫威后期制作总裁离职
当前通讯!2022年度个税汇算今起不用预约:多退少补你能退多少
移除雷达传感器后 特斯拉车祸数量上升:车主反映莫名刹车故障
快消息!读C#代码整洁之道笔记02_类、对象和数据结构及编写整洁函数
鲁抗医药:3月20日融资买入477.87万元,融资融券余额2.49亿元
全球速读:今年以来险企“补血”超340亿元 数百亿元补充资本“在路上”
世界观点:国际金融市场早知道:3月21日
铁矿石价格“非理性”上涨 监管层频频发声剑指价格炒作
天天最新:春分迎接春天:昼夜时间等长
2TB硬盘开车价400多 SSD便宜到没朋友:5大巨头流泪数钱
每日速递:读Java性能权威指南(第2版)笔记23_ 性能分析工具
焦点速讯:影响人类文明的“小方块” USB接口进化史
当前资讯!快来!我们发现了藏在新风空调里的“秘密”
当前速看:C++温故补缺(一):引用类型
要闻速递:【Visual Leak Detector】简介
Spotify 畅听全网高品质音乐
焦点热门:GPT-4外逃计划曝光!教授发现它正引诱人类帮助 网友:灭绝之门
天天快看点丨因禽流感爆发 阿根廷已扑杀70余万只禽类!
焦点!27款进口游戏版号获批!《赛马娘》《蔚蓝档案》等改名引热议
你相信吗?每天都有10多万人 学习流浪汉的生存技巧
环球今亮点!15年后 官方发布北京奥运福娃全新手办:五个一套440元
26种死法知乎_26种死法怎么样
天天速讯:织金县鸡场乡:防范电信诈骗,拉响反诈警报
Go 并发编程(一):协程 gorotine、channel、锁
vue和xml复习
Vue——vue2错误处理收集【七】
环球关注:iPhone开始在俄罗斯遭禁用了:不安全!苹果此前已在该国停售
焦点快看:填补空白!中国将首次开启海上二氧化碳封存
C# 探秘如何优雅的终止线程
行人车辆检测与计数系统(Python+YOLOv5深度学习模型+清新界面)
焦点播报:Spring IOC官方文档学习笔记(十四)之ApplicationContext的其他功能
如何上传一个npm包