使用JavaCV实现海康rtsp转rtmp实现无插件web端直播(无需转码,低资源消耗)

  • 目录结构
  • 添加依赖,编写配置文件
  • 创建Bean
  • 创建缓存Cache
  • 修改启动类
  • 拉流、推流、转封装
  • 定时任务Timer
  • 线程池管理
  • 编写controller
    • 1.开启视频流接口(POST)
    • 2.关闭视频流接口(DELETE)
    • 3.获取视频流(GET)
    • 4.视频流保活(PUT)
    • 5.获取服务信息(GET)
    • 6.video.js

项目码云(Gitee)地址:https://gitee.com/banmajio/RTSPtoRTMP
项目github地址:https://github.com/banmajio/RTSPtoRTMP
个人博客:banmajio’s blog

浏览器不支持flash插件之后,h5播放rtmp直播流的解决方案

参考:javaCV开发详解之8:转封装在rtsp转rtmp流中的应用(无须转码,更低的资源消耗)

【注意】该项目只能用来实现直播的rtsp转rtmp,无法满足回放需求。对于海康设备来说,rtsp指令带参数进行回放,会发生报:带宽不足的问题。所以需要回放功能的请对海康sdk进行二次开发,手动捕获码流数据转封装为rtmp流。实现思路请参考:海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP) 海康sdk二次开发推rtmp流的项目已经开发优化完成,但暂不考虑开源,有需要的请联系q:1402325991 有偿!! 介意勿扰!!

用到的技术:FFmpeg、JavaCV、ngingx
项目背景:将海康摄像头的rtsp流转为rtmp流,配合video.js实现web端播放。
[注]:该项目中的一些处理是为了满足公司项目需求添加完善的,如果需要改造扩展只需要在原来的基础上进行扩充或者剥离即可。最基本的核心操作在CameraPush.java这个类中,或者参考上述链接原作者的代码。

该项目需要搭配使用的nginx服务器下载地址:http://cdn.banmajio.com/nginx.rar
下载后解压该文件,点击nginx.exe(闪退是正常的,可以通过任务管理器查看是否存在nginx进程,存在则说明启动成功了)启动nginx服务。
nginx的配置文件存放在conf目录下的nginx.conf,根据需要修改。项目中的rtmp地址就是根据这个配置文件来的。

上述bug优化1:JavaCV中FFmpegFrameGrabber调用start()方法时出现阻塞的解决办法

项目github地址:https://github.com/banmajio/RTSPtoRTMP
个人博客:banmajio’s blog

目录结构

1.com.junction包里的类为SpringBoot项目启动类。
2.com.junction.cache包里的类为保存推流信息的缓存类。
3.com.junction.controller包里的类为项目controller API接口。
4.com.junction.pojo包里的类为相机信息和配置文件映射的bean。
5.com,junction.thread包里的类为线程池管理类。
6.com.junction.util包里的类为拉流推流业务处理类和定时任务Timer类。
7.application.yml为项目配置文件。

添加依赖,编写配置文件

1.添加依赖,引入javacpp和ffmpeg的jar包。

     <!-- javacv1.5.1 --><dependency><groupId>org.bytedeco</groupId><artifactId>javacv</artifactId><version>1.5.1</version></dependency><dependency><groupId>org.bytedeco</groupId><artifactId>ffmpeg-platform</artifactId><version>4.1.3-1.5.1</version></dependency><!-- 支持 @ConfigurationProperties 注解 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency>

2.pom中引入的spring-boot-configuration-processor是为了将配置文件映射为bean,方便项目中使用配置文件中的值

server:port: 8082servlet:context-path: /camera  config:
#直播流保活时间(分钟)keepalive: 5
#nginx推送地址push_ip: 127.0.0.1
#nginx推送端口push_port: 1935

创建Bean

1.CameraPojo(相机信息)

 private String username;// 摄像头账号private String password;// 摄像头密码private String ip;// 摄像头ipprivate String channel;// 摄像头通道号private String stream;// 摄像头码流(main为主码流、sub为子码流)private String rtsp;// rtsp地址private String rtmp;// rtmp地址private String startTime;// 回放开始时间private String endTime;// 回放结束时间private String openTime;// 打开时间private int count = 0;// 使用人数private String token;//唯一标识token

2.Config(读取配置文件的bean)

package com.junction.pojo;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** @Title ConfigPojo.java* @description 读取配置文件的bean* @time 2019年12月25日 下午5:11:21* @author wuguodong**/
@Component
//读取application.yml中config层级下的配置项
@ConfigurationProperties(prefix = "config")
public class Config {private String keepalive;//保活时长(分钟)private String push_ip;//推送地址private String push_port;//推送端口public String getKeepalive() {return keepalive;}public void setKeepalive(String keepalive) {this.keepalive = keepalive;}public String getPush_ip() {return push_ip;}public void setPush_ip(String push_ip) {this.push_ip = push_ip;}public String getPush_port() {return push_port;}public void setPush_port(String push_port) {this.push_port = push_port;}@Overridepublic String toString() {return "Config [keepalive=" + keepalive + ", push_ip=" + push_ip + ", push_port=" + push_port + "]";}
}

创建缓存Cache

保存推流信息,与服务启动的时间。

/*** @Title CacheUtil.java* @description 推流缓存信息* @time 2019年12月17日 下午3:12:45* @author wuguodong**/
public final class CacheUtil {/** 保存已经开始推的流*/public static Map<String, CameraPojo> STREAMMAP = new ConcurrentHashMap<String, CameraPojo>();/** 保存服务启动时间*/public static long STARTTIME;
}

修改启动类

项目启动时,将启动时间存入缓存中;项目结束时,销毁线程池和定时器,释放资源。

package com.junction;import java.util.Date;import javax.annotation.PreDestroy;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;import com.junction.cache.CacheUtil;
import com.junction.thread.CameraThread;
import com.junction.util.TimerUtil;@SpringBootApplication
public class CameraServerApplication {public static void main(String[] args) {//将服务启动时间存入缓存CacheUtil.STARTTIME = new Date().getTime();SpringApplication.run(CameraServerApplication.class, args);}@PreDestroypublic void destory() {System.err.println("释放空间...");// 关闭线程池CameraThread.MyRunnable.es.shutdownNow();// 销毁定时器TimerUtil.timer.cancel();}
}

拉流、推流、转封装

1.两个重要构造器FFmpegFrameGrabberFFmpegFrameRecorder
2.转封装不涉及转码,所以资源占用很低。

什么是转封装?为什么转封装比转码消耗更少?为什么转封装无法改动视频尺寸?
先举个栗子:假设视频格式(mp4,flv,avi等)是盒子,里面的视频编码数据(h264,hevc)是苹果,我们把这个苹果从盒子里取出来放到另一个盒子里,盒子是变了,苹果是没有变动的,因此视频相关的尺寸数据是没有改动的,这个就是转封装的概念。
有了上面这个例子,我们可以把“转码”理解为:把这个盒子里的苹果(hevc)拿出来削皮切块后再加工成樱桃(h264)后再装到另一个盒子里,多了一步对苹果(hevc)转换为樱桃(h264)的操作,自然比直接把苹果拿到另一个盒子(转封装)要消耗更多机器性能。


import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;import org.bytedeco.ffmpeg.avcodec.AVPacket;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;import com.junction.pojo.CameraPojo;/*** @Title CameraPush.java* @description 拉流推流* @time 2019年12月16日 上午9:34:41* @author wuguodong**/
public class CameraPush {protected FFmpegFrameGrabber grabber = null;// 解码器protected FFmpegFrameRecorder record = null;// 编码器int width;// 视频像素宽int height;// 视频像素高// 视频参数protected int audiocodecid;protected int codecid;protected double framerate;// 帧率protected int bitrate;// 比特率// 音频参数// 想要录制音频,这三个参数必须有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0private int audioChannels;private int audioBitrate;private int sampleRate;// 设备信息private CameraPojo cameraPojo;public CameraPush(CameraPojo cameraPojo) {this.cameraPojo = cameraPojo;}/*** 选择视频源* * @author wuguodong* @throws Exception*/public CameraPush from() throws Exception {// 采集/抓取器System.out.println(cameraPojo.getRtsp());grabber = new FFmpegFrameGrabber(cameraPojo.getRtsp());if (cameraPojo.getRtsp().indexOf("rtsp") >= 0) {grabber.setOption("rtsp_transport", "tcp");// tcp用于解决丢包问题}// 设置采集器构造超时时间grabber.setOption("stimeout", "2000000");grabber.start();// 开始之后ffmpeg会采集视频信息,之后就可以获取音视频信息width = grabber.getImageWidth();height = grabber.getImageHeight();// 若视频像素值为0,说明采集器构造超时,程序结束if (width == 0 && height == 0) {System.err.println("[ERROR]   拉流超时...");return null;}// 视频参数audiocodecid = grabber.getAudioCodec();System.err.println("音频编码:" + audiocodecid);codecid = grabber.getVideoCodec();framerate = grabber.getVideoFrameRate();// 帧率bitrate = grabber.getVideoBitrate();// 比特率// 音频参数// 想要录制音频,这三个参数必须有:audioChannels > 0 && audioBitrate > 0 && sampleRate > 0audioChannels = grabber.getAudioChannels();audioBitrate = grabber.getAudioBitrate();if (audioBitrate < 1) {audioBitrate = 128 * 1000;// 默认音频比特率}return this;}/*** 选择输出* * @author wuguodong* @throws Exception*/public CameraPush to() throws Exception {// 录制/推流器record = new FFmpegFrameRecorder(cameraPojo.getRtmp(), width, height);record.setVideoOption("crf", "28");// 画面质量参数,0~51;18~28是一个合理范围record.setGopSize(2);record.setFrameRate(framerate);record.setVideoBitrate(bitrate);record.setAudioChannels(audioChannels);record.setAudioBitrate(audioBitrate);record.setSampleRate(sampleRate);AVFormatContext fc = null;if (cameraPojo.getRtmp().indexOf("rtmp") >= 0 || cameraPojo.getRtmp().indexOf("flv") > 0) {// 封装格式flvrecord.setFormat("flv");record.setAudioCodecName("aac");record.setVideoCodec(codecid);fc = grabber.getFormatContext();}record.start(fc);return this;}/*** 转封装* * @author wuguodong* @throws org.bytedeco.javacv.FrameGrabber.Exception* @throws org.bytedeco.javacv.FrameRecorder.Exception* @throws InterruptedException*/public CameraPush go(Thread nowThread)throws org.bytedeco.javacv.FrameGrabber.Exception, org.bytedeco.javacv.FrameRecorder.Exception {long err_index = 0;// 采集或推流导致的错误次数// 连续五次没有采集到帧则认为视频采集结束,程序错误次数超过5次即中断程序//将探测时留下的数据帧释放掉,以免因为dts,pts的问题对推流造成影响grabber.flush();for (int no_frame_index = 0; no_frame_index < 5 || err_index < 5;) {try {// 用于中断线程时,结束该循环nowThread.sleep(1);AVPacket pkt = null;// 获取没有解码的音视频帧pkt = grabber.grabPacket();if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {// 空包记录次数跳过no_frame_index++;err_index++;continue;}// 不需要编码直接把音视频帧推出去err_index += (record.recordPacket(pkt) ? 0 : 1);av_packet_unref(pkt);} catch (InterruptedException e) {// 当需要结束推流时,调用线程中断方法,中断推流的线程。当前线程for循环执行到// nowThread.sleep(1);这行代码时,因为线程已经不存在了,所以会捕获异常,结束for循环// 销毁构造器grabber.close();record.close();System.err.println("设备中断推流成功...");break;} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {err_index++;} catch (org.bytedeco.javacv.FrameRecorder.Exception e) {err_index++;}}// 程序正常结束销毁构造器grabber.close();record.close();System.err.println("设备推流完毕...");return this;}
}

定时任务Timer

定时任务用来执行两部分操作:
1.定时检查正在推流的通道使用人数,如果该通道当前使用人数为0,则中断线程,结束该路视频推流并清除缓存。
2.定时检查正在推流的通道最后打开请求时间,如果与当前时间超过配置的保活时间时,则结束推流,并清除缓存。
当前设置的定时任务执行间隔为1分钟,可自行修改。

package com.junction.util;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;import com.junction.cache.CacheUtil;
import com.junction.controller.CameraController;
import com.junction.pojo.Config;/*** @Title TimerUtil.java* @description 定时任务* @time 2019年12月16日 下午3:10:08* @author wuguodong**/
@Component
public class TimerUtil implements CommandLineRunner {@Autowiredprivate Config config;// 配置文件beanpublic static Timer timer;@Overridepublic void run(String... args) throws Exception {// 超过5分钟,结束推流timer = new Timer("timeTimer");timer.schedule(new TimerTask() {@Overridepublic void run() {System.err.println("开始执行定时任务...");// 管理缓存if (null != CacheUtil.STREAMMAP && 0 != CacheUtil.STREAMMAP.size()) {Set<String> keys = CacheUtil.STREAMMAP.keySet();for (String key : keys) {try {// 最后打开时间long openTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(CacheUtil.STREAMMAP.get(key).getOpenTime()).getTime();// 当前系统时间long newTime = new Date().getTime();// 如果通道使用人数为0,则关闭推流if (CacheUtil.STREAMMAP.get(key).getCount() == 0) {// 结束线程CameraController.jobMap.get(key).setInterrupted();// 清除缓存CacheUtil.STREAMMAP.remove(key);CameraController.jobMap.remove(key);} else if ((newTime - openTime) / 1000 / 60 > Integer.valueOf(config.getKeepalive())) {CameraController.jobMap.get(key).setInterrupted();CameraController.jobMap.remove(key);CacheUtil.STREAMMAP.remove(key);System.err.println("[定时任务]  关闭" + key + "摄像头...");}} catch (ParseException e) {e.printStackTrace();}}}System.err.println("定时任务执行完毕...");}}, 1, 1000 * 60);}
}

线程池管理

package com.junction.thread;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import com.junction.cache.CacheUtil;
import com.junction.controller.CameraController;
import com.junction.pojo.CameraPojo;
import com.junction.util.CameraPush;/*** @Title CameraThread.java* @description TODO* @time 2019年12月16日 上午9:32:43* @author wuguodong**/
public class CameraThread {public static class MyRunnable implements Runnable {// 创建线程池public static ExecutorService es = Executors.newCachedThreadPool();private CameraPojo cameraPojo;private Thread nowThread;public MyRunnable(CameraPojo cameraPojo) {this.cameraPojo = cameraPojo;}// 中断线程public void setInterrupted() {nowThread.interrupt();}@Overridepublic void run() {// 直播流try {// 获取当前线程存入缓存nowThread = Thread.currentThread();CacheUtil.STREAMMAP.put(cameraPojo.getToken(), cameraPojo);// 执行转流推流任务CameraPush push = new CameraPush(cameraPojo).from();if (push != null) {push.to().go(nowThread);}// 清除缓存CacheUtil.STREAMMAP.remove(cameraPojo.getToken());CameraController.jobMap.remove(cameraPojo.getToken());} catch (Exception e) {System.err.println("当前线程:" + Thread.currentThread().getName() + " 当前任务:" + cameraPojo.getRtsp() + "停止...");CacheUtil.STREAMMAP.remove(cameraPojo.getToken());CameraController.jobMap.remove(cameraPojo.getToken());e.printStackTrace();}}}
}

编写controller

controller提供了五个接口,使用RESTful风格,故使用postman等软件测试时,选择相应的类型。
1.获取视频服务配置信息及服务运行时间
api: http://127.0.0.1:8082/camera/status (GET)
2.获取正在推送的所有视频流信息
api: http://127.0.0.1:8082/camera/cameras (GET)
3.开启视频流(直播or回放)
api: http://127.0.0.1:8082/camera/cameras (POST)
params: ip;username;password;channel;stream;starttime;endtime
4.关闭视频流
api: http://127.0.0.1:8082/camera/cameras/:tokens (DELETE)
5.视频流保活
api: http://127.0.0.1:8082/camera/cameras/:tokens (PUT)

1.开启视频流接口(POST)

先校验参数,然后判断缓存是否为空(如果为空说明目前没有推流任务,否则遍历缓存,通过参数判断当前通道是否在推流。如果找到,则该路视频的bean内人数count+1,反之调用openStream()方法进行推流)。

openStream()方法内先判断是否存在starttime参数,如果有则说明该流为历史流;在判断是否存在endtime,若无endtime则使用starttime前后各加一分钟作为历史流的开始时间和结束时间。若无starttime则视为该流为直播流。ffmpeg在拉取rtsp直播流和历史流时的命令不相同,所以需要上述判断!!

通过openStream()组装rtsp命令和rtmp命令以及UUID生成的token和其他参数,set进cameraPojo中。提交当前任务到线程池,并将当前任务线程存入jobMap(存放推流线程任务的缓存)中。

     // 执行任务CameraThread.MyRunnable job = new CameraThread.MyRunnable(cameraPojo);CameraThread.MyRunnable.es.execute(job);jobMap.put(token, job);

ffmpeg直播流与历史流命令格式:
1.ffmpeg -rtsp_transport tcp -i rtsp://admin:abc12345@192.168.1.8:554/h264/ch1/main/av_stream -vcodec h264 -f flv -an rtmp://localhost:1935/live/room
2.ffmpeg -rtsp_transport tcp -i rtsp://admin:abc12345@192.168.1.222:554/Streaming/tracks/101?starttime=20191227t084400z’&'endtime=20191227t084600z -vcodec copy -acodec copy -f flv rtmp://localhost:1935/history/room

/*** @Title: openCamera* @Description: 开启视频流* @param ip* @param username* @param password* @param channel   通道* @param stream    码流* @param starttime* @param endtime* @return Map<String,String>**/@RequestMapping(value = "/cameras", method = RequestMethod.POST)public Map<String, String> openCamera(String ip, String username, String password, String channel, String stream,String starttime, String endtime) {// 返回结果Map<String, String> map = new HashMap<String, String>();// 校验参数if (null != ip && "" != ip && null != username && "" != username && null != password && "" != password&& null != channel && "" != channel) {CameraPojo cameraPojo = new CameraPojo();// 获取当前时间String openTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date().getTime());Set<String> keys = CacheUtil.STREAMMAP.keySet();// 缓存是否为空if (0 == keys.size()) {// 开始推流cameraPojo = openStream(ip, username, password, channel, stream, starttime, endtime, openTime);map.put("token", cameraPojo.getToken());map.put("url", cameraPojo.getRtmp());} else {// 是否存在的标志;0:不存在;1:存在int sign = 0;for (String key : keys) {// 是否已经在推流if (ip.equals(CacheUtil.STREAMMAP.get(key).getIp())&& channel.equals(CacheUtil.STREAMMAP.get(key).getChannel())) {cameraPojo = CacheUtil.STREAMMAP.get(key);sign = 1;break;}}if (sign == 1) {cameraPojo.setCount(cameraPojo.getCount() + 1);cameraPojo.setOpenTime(openTime);} else {// 开始推流cameraPojo = openStream(ip, username, password, channel, stream, starttime, endtime, openTime);}map.put("token", cameraPojo.getToken());map.put("url", cameraPojo.getRtmp());}}return map;}/*** @Title: openStream* @Description: 推流器* @param ip* @param username* @param password* @param channel* @param stream* @param starttime* @param endtime* @param openTime* @return* @return CameraPojo**/private CameraPojo openStream(String ip, String username, String password, String channel, String stream,String starttime, String endtime, String openTime) {CameraPojo cameraPojo = new CameraPojo();// 生成tokenString token = UUID.randomUUID().toString();String rtsp = "";String rtmp = "";// 历史流if (null != starttime && "" != starttime) {if (null != endtime && "" != endtime) {rtsp = "rtsp://" + username + ":" + password + "@" + ip + ":554/Streaming/tracks/" + channel+ "01?starttime=" + starttime.substring(0, 8) + "t" + starttime.substring(8) + "z'&'endtime="+ endtime.substring(0, 8) + "t" + endtime.substring(8) + "z";} else {try {SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");String startTime = df.format(df.parse(starttime).getTime() - 60 * 1000);String endTime = df.format(df.parse(starttime).getTime() + 60 * 1000);rtsp = "rtsp://" + username + ":" + password + "@" + ip + ":554/Streaming/tracks/" + channel+ "01?starttime=" + startTime.substring(0, 8) + "t" + startTime.substring(8)+ "z'&'endtime=" + endTime.substring(0, 8) + "t" + endTime.substring(8) + "z";} catch (ParseException e) {e.printStackTrace();}}rtmp = "rtmp://" + config.getPush_ip() + ":" + config.getPush_port() + "/history/" + token;} else {// 直播流rtsp = "rtsp://" + username + ":" + password + "@" + ip + ":554/h264/ch" + channel + "/" + stream+ "/av_stream";rtmp = "rtmp://" + config.getPush_ip() + ":" + config.getPush_port() + "/live/" + token;}cameraPojo.setUsername(username);cameraPojo.setPassword(password);cameraPojo.setIp(ip);cameraPojo.setChannel(channel);cameraPojo.setStream(stream);cameraPojo.setRtsp(rtsp);cameraPojo.setRtmp(rtmp);cameraPojo.setOpenTime(openTime);cameraPojo.setCount(1);cameraPojo.setToken(token);// 执行任务CameraThread.MyRunnable job = new CameraThread.MyRunnable(cameraPojo);CameraThread.MyRunnable.es.execute(job);jobMap.put(token, job);return cameraPojo;}

2.关闭视频流接口(DELETE)

传入参数为tokens,通过,分隔,可以同时关闭多路视频。通过token查找缓存判断是否存在,如果存在,则人数count-1。不直接调用结束线程的方法是为了满足如果多个客户端同时观看该路视频,一人关闭会影响其他人使用。故调用该接口只是使该路视频的使用人数-1,最终结束线程的操作交由定时任务处理,如果定时器查询到视频使用人数的count为0,则结束该路视频的推流操作,并清除缓存。

/*** @Title: closeCamera* @Description:关闭视频流* @param tokens* @return void**/@RequestMapping(value = "/cameras/{tokens}", method = RequestMethod.DELETE)public void closeCamera(@PathVariable("tokens") String tokens) {if (null != tokens && "" != tokens) {String[] tokenArr = tokens.split(",");for (String token : tokenArr) {if (jobMap.containsKey(token) && CacheUtil.STREAMMAP.containsKey(token)) {if (0 < CacheUtil.STREAMMAP.get(token).getCount()) {// 人数-1CacheUtil.STREAMMAP.get(token).setCount(CacheUtil.STREAMMAP.get(token).getCount() - 1);}}}}}

3.获取视频流(GET)

获取当前进行的推流任务。

/*** @Title: getCameras* @Description:获取视频流* @return Map<String, CameraPojo>**/@RequestMapping(value = "/cameras", method = RequestMethod.GET)public Map<String, CameraPojo> getCameras() {return CacheUtil.STREAMMAP;}

4.视频流保活(PUT)

视频流保活的作用是为了应付以下场景:
如果客户端比如浏览器直接关闭掉,并不会通知服务客户已经不再观看视频了,这是服务还在进行推流。所以添加保活机制,如果客户端没有触发保活机制,定时任务执行时,如果该路视频的最后打开时间距当前时间超过配置的保活时间时,关闭该路视频的推流任务。如果客户端触发保活机制时,更新该路视频的最后打开时间(opentime)为当前系统时间。

/*** @Title: keepAlive* @Description:视频流保活* @param tokens* @return void**/@RequestMapping(value = "/cameras/{tokens}", method = RequestMethod.PUT)public void keepAlive(@PathVariable("tokens") String tokens) {// 校验参数if (null != tokens && "" != tokens) {String[] tokenArr = tokens.split(",");for (String token : tokenArr) {CameraPojo cameraPojo = new CameraPojo();// 直播流tokenif (null != CacheUtil.STREAMMAP.get(token)) {cameraPojo = CacheUtil.STREAMMAP.get(token);// 更新当前系统时间cameraPojo.setOpenTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date().getTime()));}}}}

5.获取服务信息(GET)

通过该接口获取服务运行时间,以及配置文件的配置

 /*** @Title: getConfig* @Description: 获取服务信息* @return Map<String, Object>**/@RequestMapping(value = "/status", method = RequestMethod.GET)public Map<String, Object> getConfig() {// 获取当前时间long nowTime = new Date().getTime();String upTime = (nowTime - CacheUtil.STARTTIME) / (1000 * 60 * 60) + "时"+ (nowTime - CacheUtil.STARTTIME) % (1000 * 60 * 60) / (1000 * 60) + "分";Map<String, Object> status = new HashMap<String, Object>();status.put("config", config);status.put("uptime", upTime);return status;}

6.video.js

测试需要的video.js。video.js用来播放rtmp的视频。注意chrome需要先允许加载flash插件(百度一下很简单的)。使用以下代码,在src处添加推流成功的rtmp地址。

<!DOCTYPE html>
<html lang="en">
<head>
<title>Video.js | HTML5 Video Player</title>
<link href="http://vjs.zencdn.net/5.20.1/video-js.css" rel="stylesheet">
<script src="http://vjs.zencdn.net/5.20.1/videojs-ie8.min.js"></script>
</head>
<body width="640px" height="360px"><video id="example_video_1" class="video-js vjs-default-skin" controlspreload="auto" width="640px" height="360px" data-setup="{}"style="float: left"><source src="此处填入rtmp地址" type="rtmp/flv"><p class="vjs-no-js">To view this video please enable JavaScript, and consider upgradingto a web browser that <ahref="http://videojs.com/html5-video-support/" target="_blank">supportsHTML5 video</a></p></video>
</body>
</html>

使用JavaCV实现海康rtsp转rtmp实现无插件web端直播(无需转码,低资源消耗)相关推荐

  1. 海康RTSP流转RTMP并推送至Web端展示

    最近帮着老师做项目的时候碰到一个难题,将海康摄像头的影像实时传输到前端页面进行展示.Google查了两天,终于有些眉目,记录一下经验. 大致需要经过以下几个步骤: 获取海康摄像头的视频流(基于RTSP ...

  2. EasyStreamClient对接海康流媒体V4.X实现无插件播放流程记录

    之前有一位朋友找到我们寻求帮助,需要解决对接海康流媒体的问题,当时对接的是海康流媒体V4.X,因为平台只支持SDK方式对接,无法满足无插件播放的需求,后来经过与海康官方SDK联系后,取得了开发版SDK ...

  3. 音视频开发(16)---海康IPC+NVR+EasyDarwin+EasyPusher+VLC实现Web实时播放RTSP视频

    海康IPC+NVR+EasyDarwin+EasyPusher+VLC实现Web实时播放RTSP视频 用ffmpeg+nginx实现web播放rtsp视频,原理是将rtsp转码成rtmp格式,再用fl ...

  4. SkeyeVSS综合安防Onvif、RTSP、GB/T28181无插件直播点播解决方案之报警中心管理

    SkeyeVSS综合安防Onvif/RTSP/GB28181视频云无插件直播点播解决方案之报警查询报警配置报警集中管理 Skeye支持对接入综合安防视频云系统中的服务器.设备(平台).监控点(摄像机) ...

  5. 大华、海康rtsp视频流格式

    一.海康威视热眼警戒摄像机DS-2TD1217-3/PA 型号 1号与2号摄像头(Camera 01.Camera 02) 方法一.只能读取1号摄像头 rtsp://admin:密码@192.168. ...

  6. 用vlc插件在页面上播放海康rtsp推流监控

    原文链接:https://blog.csdn.net/weixin_43948724/article/details/104422825 vlc插件目前只能在360.搜狗等可以开启兼容模式下的浏览器使 ...

  7. 完美对接海康、大华、华为等等设备的Onvif/RTSP流媒体服务全终端无插件直播-本地安装启动...

    LiveNVR Onvif/RTSP流媒体服务,支持RTSP稳定拉流接入,支持Onvif协议接入,支持RTMP/HLS/HTTP-FLV分发,将传统安防监控设备互联化,无插件直播等. 下载安装 下载L ...

  8. 大华海康摄像头人家自己是怎么在web上播放视频的

    最近处理安防视频,怎么把摄像头视频在web上展示费了很大功夫,当然这一篇不是讲解我是怎么显示的,而是回答当时领导问我的一个问题,人家大华自己是怎么显示的? 我们知道大华海康大部分摄像头只对外提供rts ...

  9. 海康、大华视频监控在浏览器端无插件低延时播放解决方案

    海康.大华视频监控无插件低延时播放解决方案 第一章 应用简介 第二章 方案的实现方式 2.1 方案的技术架构 2.2 功能模块构成 第三章 平台的安装和部署 3.1 视频转码工作站的搭建 3.2 流媒 ...

最新文章

  1. 中科院遗传发育所发表“重组菌群体系在根系微生物组研究中应用”的重要综述...
  2. Postfix+Amavisd-new+Spamassassin+ClamAV整合安装
  3. 正则表达式从基础到深入实战
  4. 智能推荐算法在直播场景中的应用
  5. 【模板】KMP算法、fail树
  6. ctfshow-萌新-web12( 利用命令执行函数获取网站敏感信息)
  7. 解决 PL/SQL Oracle错误:ORA-01033
  8. Spring Cloud (断路器) Hystrix(三)
  9. 【信息系统项目管理师】第2章-信息系统项目管理基础 知识点详细整理
  10. 新勒索软件在受害者阅读两篇勒索软件文章后解密
  11. 数据分析 时间序列分析 ARMA模型
  12. wi7计算机桌面删除,如何删除win7系统桌面IE图标|win7删除桌面IE图标的方法
  13. python和c++实现 不改变长宽比缩放图片
  14. 网络安全形势严峻:国内黑灰产业产值达千亿
  15. USB Type A/B/C的区别和基本知识
  16. SLAM相关学习资料:综述/激光/视觉/数据集/常用库
  17. Maven命令行窗口指定settings.xml
  18. 利用插件修改wordpress文件上传限制
  19. sql时间格式化,解决小于日23:59:59
  20. OpenCV图像二值化,Python

热门文章

  1. 基于python的对比度增强(线性变换、直方图正规化、直方图均衡化、CLAHE)
  2. 修改Element-ui表格样式
  3. 用什么软件可以修改PDF文件,软件的操作方法
  4. ue4白天夜晚切换_白天/夜晚编码的美好时光...多年来最佳
  5. 【计算机体系结构】非线性流水线调度算法 C++ Python
  6. 北大青鸟消防控制器组网_北大青鸟JBF-11SF-AC801显示控制盘
  7. 超通俗易懂科普:什么是光通信?
  8. Python 文件IO操作
  9. HarmonyOS的定义是什么?
  10. 流氓软件自动安装恶意插件导致浏览器闪退问题