赞
踩
最近项目需要实时直播和回放,集成海康威视摄像头:(适合少量用户,或者内部系统使用)
- <!-- 视频处理库 -->
- <dependency>
- <groupId>org.bytedeco</groupId>
- <artifactId>javacv-platform</artifactId>
- <version>1.5.1</version>
- </dependency>
这里是利用javacv抓取rtsp地址的视频流,通过转换成图片使用websocket实时推送
首先了解rtsp地址,这里举例就用海康威视的规则
- 通道号下面详细介绍
- # 单播
- rtsp://{用户名}:{密码}@{IP地址}:{端口号}/Streaming/Channels/{通道号}?transportmode=unicast
- # 多播
- rtsp://{用户名}:{密码}@{IP地址}:{端口号}/Streaming/Channels/{通道号}?transportmode=multicast
- # 分时获取
- rtsp://{用户名}:{密码}@{IP地址}:{端口号}/Streaming/tracks/{通道号}?starttime=20191008t063812z&endtime=20191008t064816z
-
- 上面地址获取不到时可以试试下面的其他版本地址:
- rtsp://{用户名}:{密码}@{IP地址}:{端口号}/Streaming/Unicast/channels/{通道号}
- rtsp://{用户名}:{密码}@{IP地址}:{端口号}/h264/{通道号}/main/av_stream
这里通道号分类是IP通道号和模拟通道号,如果是12年以前的老机器,地址建议使用最后一个获取实时视频
可以使用官方提供的java demo 来查看,测试是否能获取视频
这里有打包好的jar包和环境可以直接运行jar包使用,解压后将里面内容放置在System32下,cmd执行jar
地址 百度网盘 请输入提取码 密码 67v1
下图就是IP通道号 若只是Camera20 则为模拟通道号
通道号可以参考这里最新海康摄像机、NVR、流媒体服务器、回放取流RTSP地址规则说明 - github.com/starRTC - 博客园
接下来就是java代码:
首先yml配置动态RTSP地址
- myconfig:
- rtsp: rtsp://%s:%s@%s:%s/Streaming/Channels/%s01
- replay-rtsp: rtsp://%s:%s@%s:%s/Streaming/tracks/%s01?starttime=%s&endtime=%s
媒体工具类:
- @Slf4j
- @Component
- public class MediaUtils {
- /**
- * 直播摄像机id集合,避免重复拉流
- */
- private static Set<Long> liveSet = new ConcurrentSet<>();
- /**
- * 用于构造回放rtsp地址
- */
- @Value("${myconfig.replay-rtsp}")
- private String rtspReplayPattern;
- /**
- * 视频帧率
- */
- public static int frameRate = 15;
- /**
- * 视频宽度
- */
- public static int frameWidth = 480;
- /**
- * 视频高度
- */
- public static int frameHeight = 270;
-
- /**
- * 摄像机直播
- * @param rtsp 摄像机直播地址
- * @param cameraName 摄像机名称
- * @param cameraId 摄像机id
- * @throws Exception e
- */
- @Async
- public void live(String rtsp, String cameraName, Long cameraId) throws Exception {
- if (liveSet.contains(cameraId)) {
- return;
- }
- liveSet.add(cameraId);
- FFmpegFrameGrabber grabber = createGrabber(rtsp);
- startCameraPush(grabber, cameraName, cameraId);
- }
-
-
- /**
- * 构造视频抓取器
- * @param rtsp 拉流地址
- * @return
- */
- public FFmpegFrameGrabber createGrabber(String rtsp) {
- // 获取视频源
- FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(rtsp);
- grabber.setOption("rtsp_transport","tcp");
- //设置帧率
- grabber.setFrameRate(frameRate);
- //设置获取的视频宽度
- grabber.setImageWidth(frameWidth);
- //设置获取的视频高度
- grabber.setImageHeight(frameHeight);
- //设置视频bit率
- grabber.setVideoBitrate(2000000);
- return grabber;
- }
-
- /**
- * 推送图片(摄像机直播)
- * @param grabber
- * @throws Exception
- */
- @Async
- public void startCameraPush(FFmpegFrameGrabber grabber, String cameraName, Long cameraId) throws Exception {
- Java2DFrameConverter java2DFrameConverter = new Java2DFrameConverter();
- try {
- grabber.start();
- int i = 1;
- while (liveSet.contains(cameraId)) {
- Frame frame = grabber.grabImage();
- if (null == frame) {
- continue;
- }
- BufferedImage bufferedImage = java2DFrameConverter.getBufferedImage(frame);
-
- byte[] bytes = imageToBytes(bufferedImage, "jpg");
-
- //使用websocket发送图片数据
- LiveWebsocket.sendImage(ByteBuffer.wrap(bytes), cameraId);
- }
- } finally {
- if (grabber != null) {
- grabber.stop();
- }
- }
- }
-
- /**
- * 图片转字节数组
- * @param bImage 图片数据
- * @param format 格式
- * @return 图片字节码
- */
- private byte[] imageToBytes(BufferedImage bImage, String format) {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- ImageIO.write(bImage, format, out);
- } catch (IOException e) {
- e.printStackTrace();
- }
- return out.toByteArray();
- }
-
- /**
- * 回放视频播放超期检查
- * @param userId 用户id
- * @return
- */
- private boolean replayOverTime(Integer userId) {
- if (replayMap.containsKey(userId)) {
- Long updateTime = replayMap.get(userId);
- if (updateTime != null) {
- if (System.currentTimeMillis() - updateTime < 10000) {
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * 构造监控回放查询字段
- * @param date 时间
- * @param start
- * @return
- */
- private String formatPullTime(Date date, boolean start) {
- Calendar calendar = Calendar.getInstance();
- if (date != null) {
- calendar.setTime(date);
- }
- if (start) {
- calendar.add(Calendar.SECOND, -10);
- } else {
- calendar.add(Calendar.SECOND, 10);
- }
- //海康威视取回放的时间格式
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd#HHmmss$");
- String ret = sdf.format(calendar.getTime());
- ret = ret.replace("#", "t");
- ret = ret.replace("$", "z");
- return ret;
- }
-
- /**
- * 回放视频播放超期检查
- * @param userId 用户id
- * @return
- */
- private boolean replayOverTime(Integer userId) {
- if (replayMap.containsKey(userId)) {
- Long updateTime = replayMap.get(userId);
- if (updateTime != null) {
- if (System.currentTimeMillis() - updateTime < 10000) {
- return false;
- }
- }
- }
- return true;
- }
- /**
- * 监控回放
- * @param userId 用户id websocket 用户编号,作为心跳标识检测心跳
- * @param startDate 起始时间
- * @param endDate 结束时间
- * @param channel 通道号
- * @param username 用户名
- * @param password 密码
- * @param ip
- * @param port
- * @throws Exception e
- */
- @Async
- public void replayVideo(Integer userId, Date startDate, Date endDate,
- Integer channel, String username, String password, String ip, Integer port) throws Exception {
- Java2DFrameConverter java2DFrameConverter = new Java2DFrameConverter();
- FFmpegFrameGrabber grabber = null;
- try {
- if (grabber != null) {
- grabber.stop();
- }
- if (channel != null) {
- String st = formatPullTime(startDate, true);
- String et = formatPullTime(endDate, false);
- //构造rtsp回放流地址 username password ip port
- String rtsp = String.format(
- rtspReplayPattern,
- username,
- password,
- ip,
- port,
- channel,
- st,
- et
- );
- if (grabber != null) {
- grabber.stop();
- }
- grabber = createGrabber(rtsp);
- grabber.setTimeout(10000);
- grabber.start();
- //心跳消失停止推流
- while (!replayOverTime(userId)) {
- Frame frame = grabber.grabImage();
- if (null == frame) {
- continue;
- }
-
- BufferedImage bufferedImage = java2DFrameConverter.getBufferedImage(frame);
-
- byte[] bytes = imageToBytes(bufferedImage, "jpg");
- ByteBuffer buffer = ByteBuffer.wrap(bytes);
-
- //使用websocket发送图片数据
- ReplayWebsocke.sendImage(buffer, userId);
- }
- }
- } catch (Exception e){
- e.printStackTrace();
- } finally {
- if (grabber != null) {
- grabber.stop();
- }
- }
- }
- }
视频水印添加:
- @Component
- public class ImgMarker {
-
- /**
- * 视频水印图片
- */
- BufferedImage logoImg;
-
- private Font font;
- private Font font2;
- private FontDesignMetrics metrics;
- private FontDesignMetrics metrics2;
-
- @PostConstruct
- private void init() {
- // 加水印图片
- try {
- ImageIO.read(new File("图片地址"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- font = new Font("黑体", Font.BOLD, 16);
- font2 = new Font("黑体", Font.BOLD, 24);
- metrics = FontDesignMetrics.getMetrics(font);
- metrics2 = FontDesignMetrics.getMetrics(font2);
- }
-
- /**
- * 加水印
- * @param bufImg 视频帧
- */
- public void mark(BufferedImage bufImg) {
- if (bufImg == null || logoImg == null) {
- return;
- }
- int width = bufImg.getWidth();
- int height = bufImg.getHeight();
- Graphics2D graphics = bufImg.createGraphics();
- graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
- graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
- //设置图片背景
- graphics.drawImage(bufImg, 0, 0, width, height, null);
- //添加右上角水印
- graphics.drawImage(logoImg, width - 130, 8, 121, 64, null);
- }
-
- /**
- *
- * @param bufImg 视频帧
- */
- public void markTag(BufferedImage bufImg, String msg, int videoWidth) {
- int width = bufImg.getWidth();
- int height = bufImg.getHeight();
- Graphics2D graphics = bufImg.createGraphics();
- graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
- graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
- //设置图片背景
- graphics.drawImage(bufImg, 0, 0, width, height, null);
- //设置由上方标签号
- graphics.setColor(Color.orange);
- if (videoWidth <= 400) {
- graphics.setFont(font2);
- graphics.drawString(msg, width - metrics2.stringWidth(tagId) - 24, metrics2.getAscent());
- } else {
- graphics.setFont(font);
- graphics.drawString(msg, width - metrics.stringWidth(msg) - 12, metrics.getAscent());
- }
- graphics.dispose();
- }
-
- }
至此可以调用了(还有其他方式是直接调用SDK和推送视频流媒体服务器,后续文章更新)
websocket 涉及到高并发阻塞情况 后续更新,建议使用netty
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。