spring boot 开发篇六 之JAVACV + WEBSOCKET实现实时视频推流回放

1、依赖文件

<!-- 视频处理库 -->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.1</version>
        </dependency>

2、yml配置文件

myconfig:
  rtsp: rtsp://%s:%s@%s:%s/Streaming/Channels/%s01
  replay-rtsp: rtsp://%s:%s@%s:%s/Streaming/tracks/%s01?starttime=%s&endtime=%s

3、实现方法

@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();
                }
            }
    }
}

4、添加水印

@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();
    }
 
}

未完待续…
技术讨论qq群: 545432289

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大海中一粒沙子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值