spring boot集成javacv + websocket实现实时视频推流回放(延时1-2秒)
                                                            生活随笔
收集整理的這篇文章主要介紹了
                                spring boot集成javacv + websocket实现实时视频推流回放(延时1-2秒)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.                        
                                最近項目需要實時直播和回放,集成海康威視攝像頭:(適合少量用戶,或者內部系統使用)
<!-- 視頻處理庫 --><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/%s01replay-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*/@Asyncpublic 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*/@Asyncpublic 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*/@Asyncpublic 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 portString 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;@PostConstructprivate 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
總結
以上是生活随笔為你收集整理的spring boot集成javacv + websocket实现实时视频推流回放(延时1-2秒)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Cadence Allegro设置默认打
- 下一篇: 记一次Mysql数据恢复
