SPRING BOOT集成JAVACV + WEBSOCKET实现实时视频推流回放(延时1-2秒)

标签: spring-boot  视频直播  rtsp  javacv

最近项目需要实时直播和回放,集成海康威视摄像头:(适合少量用户,或者内部系统使用)

 
  1. <!-- 视频处理库 -->

  2. <dependency>

  3. <groupId>org.bytedeco</groupId>

  4. <artifactId>javacv-platform</artifactId>

  5. <version>1.5.1</version>

  6. </dependency>

这里是利用javacv抓取rtsp地址的视频流,通过转换成图片使用websocket实时推送

首先了解rtsp地址,这里举例就用海康威视的规则

 
  1. 通道号下面详细介绍

  2. # 单播

  3. rtsp://{用户名}:{密码}@{IP地址}:{端口号}/Streaming/Channels/{通道号}?transportmode=unicast

  4. # 多播

  5. rtsp://{用户名}:{密码}@{IP地址}:{端口号}/Streaming/Channels/{通道号}?transportmode=multicast

  6. # 分时获取

  7. rtsp://{用户名}:{密码}@{IP地址}:{端口号}/Streaming/tracks/{通道号}?starttime=20191008t063812z&endtime=20191008t064816z

  8.  
  9. 上面地址获取不到时可以试试下面的其他版本地址:

  10. rtsp://{用户名}:{密码}@{IP地址}:{端口号}/Streaming/Unicast/channels/{通道号}

  11. rtsp://{用户名}:{密码}@{IP地址}:{端口号}/h264/{通道号}/main/av_stream

这里通道号分类是IP通道号和模拟通道号,如果是12年以前的老机器,地址建议使用最后一个获取实时视频

可以使用官方提供的java demo 来查看,测试是否能获取视频

这里有打包好的jar包和环境可以直接运行jar包使用,解压后将里面内容放置在System32下,cmd执行jar

地址 https://pan.baidu.com/s/1QxELvKLmgqjeKu6NKUGP0A  密码 67v1

下图就是IP通道号 若只是Camera20 则为模拟通道号

 

通道号可以参考这里https://www.cnblogs.com/elesos/p/9881690.html

接下来就是java代码:

首先yml配置动态RTSP地址

 
  1. myconfig:

  2. rtsp: rtsp://%s:%s@%s:%s/Streaming/Channels/%s01

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

媒体工具类:

 
  1. @Slf4j

  2. @Component

  3. public class MediaUtils {

  4. /**

  5. * 直播摄像机id集合,避免重复拉流

  6. */

  7. private static Set<Long> liveSet = new ConcurrentSet<>();

  8. /**

  9. * 用于构造回放rtsp地址

  10. */

  11. @Value("${myconfig.replay-rtsp}")

  12. private String rtspReplayPattern;

  13. /**

  14. * 视频帧率

  15. */

  16. public static int frameRate = 15;

  17. /**

  18. * 视频宽度

  19. */

  20. public static int frameWidth = 480;

  21. /**

  22. * 视频高度

  23. */

  24. public static int frameHeight = 270;

  25.  
  26. /**

  27. * 摄像机直播

  28. * @param rtsp 摄像机直播地址

  29. * @param cameraName 摄像机名称

  30. * @param cameraId 摄像机id

  31. * @throws Exception e

  32. */

  33. @Async

  34. public void live(String rtsp, String cameraName, Long cameraId) throws Exception {

  35. if (liveSet.contains(cameraId)) {

  36. return;

  37. }

  38. liveSet.add(cameraId);

  39. FFmpegFrameGrabber grabber = createGrabber(rtsp);

  40. startCameraPush(grabber, cameraName, cameraId);

  41. }

  42.  
  43.  
  44. /**

  45. * 构造视频抓取器

  46. * @param rtsp 拉流地址

  47. * @return

  48. */

  49. public FFmpegFrameGrabber createGrabber(String rtsp) {

  50. // 获取视频源

  51. FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(rtsp);

  52. grabber.setOption("rtsp_transport","tcp");

  53. //设置帧率

  54. grabber.setFrameRate(frameRate);

  55. //设置获取的视频宽度

  56. grabber.setImageWidth(frameWidth);

  57. //设置获取的视频高度

  58. grabber.setImageHeight(frameHeight);

  59. //设置视频bit率

  60. grabber.setVideoBitrate(2000000);

  61. return grabber;

  62. }

  63.  
  64. /**

  65. * 推送图片(摄像机直播)

  66. * @param grabber

  67. * @throws Exception

  68. */

  69. @Async

  70. public void startCameraPush(FFmpegFrameGrabber grabber, String cameraName, Long cameraId) throws Exception {

  71. Java2DFrameConverter java2DFrameConverter = new Java2DFrameConverter();

  72. try {

  73. grabber.start();

  74. int i = 1;

  75. while (liveSet.contains(cameraId)) {

  76. Frame frame = grabber.grabImage();

  77. if (null == frame) {

  78. continue;

  79. }

  80. BufferedImage bufferedImage = java2DFrameConverter.getBufferedImage(frame);

  81.  
  82. byte[] bytes = imageToBytes(bufferedImage, "jpg");

  83.  
  84. //使用websocket发送图片数据

  85. LiveWebsocket.sendImage(ByteBuffer.wrap(bytes), cameraId);

  86. }

  87. } finally {

  88. if (grabber != null) {

  89. grabber.stop();

  90. }

  91. }

  92. }

  93.  
  94. /**

  95. * 图片转字节数组

  96. * @param bImage 图片数据

  97. * @param format 格式

  98. * @return 图片字节码

  99. */

  100. private byte[] imageToBytes(BufferedImage bImage, String format) {

  101. ByteArrayOutputStream out = new ByteArrayOutputStream();

  102. try {

  103. ImageIO.write(bImage, format, out);

  104. } catch (IOException e) {

  105. e.printStackTrace();

  106. }

  107. return out.toByteArray();

  108. }

  109.  
  110. /**

  111. * 回放视频播放超期检查

  112. * @param userId 用户id

  113. * @return

  114. */

  115. private boolean replayOverTime(Integer userId) {

  116. if (replayMap.containsKey(userId)) {

  117. Long updateTime = replayMap.get(userId);

  118. if (updateTime != null) {

  119. if (System.currentTimeMillis() - updateTime < 10000) {

  120. return false;

  121. }

  122. }

  123. }

  124. return true;

  125. }

  126.  
  127. /**

  128. * 构造监控回放查询字段

  129. * @param date 时间

  130. * @param start

  131. * @return

  132. */

  133. private String formatPullTime(Date date, boolean start) {

  134. Calendar calendar = Calendar.getInstance();

  135. if (date != null) {

  136. calendar.setTime(date);

  137. }

  138. if (start) {

  139. calendar.add(Calendar.SECOND, -10);

  140. } else {

  141. calendar.add(Calendar.SECOND, 10);

  142. }

  143. //海康威视取回放的时间格式

  144. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd#HHmmss$");

  145. String ret = sdf.format(calendar.getTime());

  146. ret = ret.replace("#", "t");

  147. ret = ret.replace("$", "z");

  148. return ret;

  149. }

  150.  
  151. /**

  152. * 回放视频播放超期检查

  153. * @param userId 用户id

  154. * @return

  155. */

  156. private boolean replayOverTime(Integer userId) {

  157. if (replayMap.containsKey(userId)) {

  158. Long updateTime = replayMap.get(userId);

  159. if (updateTime != null) {

  160. if (System.currentTimeMillis() - updateTime < 10000) {

  161. return false;

  162. }

  163. }

  164. }

  165. return true;

  166. }

  167. /**

  168. * 监控回放

  169. * @param userId 用户id websocket 用户编号,作为心跳标识检测心跳

  170. * @param startDate 起始时间

  171. * @param endDate 结束时间

  172. * @param channel 通道号

  173. * @param username 用户名

  174. * @param password 密码

  175. * @param ip

  176. * @param port

  177. * @throws Exception e

  178. */

  179. @Async

  180. public void replayVideo(Integer userId, Date startDate, Date endDate,

  181. Integer channel, String username, String password, String ip, Integer port) throws Exception {

  182. Java2DFrameConverter java2DFrameConverter = new Java2DFrameConverter();

  183. FFmpegFrameGrabber grabber = null;

  184. try {

  185. if (grabber != null) {

  186. grabber.stop();

  187. }

  188. if (channel != null) {

  189. String st = formatPullTime(startDate, true);

  190. String et = formatPullTime(endDate, false);

  191. //构造rtsp回放流地址 username password ip port

  192. String rtsp = String.format(

  193. rtspReplayPattern,

  194. username,

  195. password,

  196. ip,

  197. port,

  198. channel,

  199. st,

  200. et

  201. );

  202. if (grabber != null) {

  203. grabber.stop();

  204. }

  205. grabber = createGrabber(rtsp);

  206. grabber.setTimeout(10000);

  207. grabber.start();

  208. //心跳消失停止推流

  209. while (!replayOverTime(userId)) {

  210. Frame frame = grabber.grabImage();

  211. if (null == frame) {

  212. continue;

  213. }

  214.  
  215. BufferedImage bufferedImage = java2DFrameConverter.getBufferedImage(frame);

  216.  
  217. byte[] bytes = imageToBytes(bufferedImage, "jpg");

  218. ByteBuffer buffer = ByteBuffer.wrap(bytes);

  219.  
  220. //使用websocket发送图片数据

  221. ReplayWebsocke.sendImage(buffer, userId);

  222. }

  223. }

  224. } catch (Exception e){

  225. e.printStackTrace();

  226. } finally {

  227. if (grabber != null) {

  228. grabber.stop();

  229. }

  230. }

  231. }

  232. }

视频水印添加:

 
  1. @Component

  2. public class ImgMarker {

  3.  
  4. /**

  5. * 视频水印图片

  6. */

  7. BufferedImage logoImg;

  8.  
  9. private Font font;

  10. private Font font2;

  11. private FontDesignMetrics metrics;

  12. private FontDesignMetrics metrics2;

  13.  
  14. @PostConstruct

  15. private void init() {

  16. // 加水印图片

  17. try {

  18. ImageIO.read(new File("图片地址"));

  19. } catch (IOException e) {

  20. e.printStackTrace();

  21. }

  22. font = new Font("黑体", Font.BOLD, 16);

  23. font2 = new Font("黑体", Font.BOLD, 24);

  24. metrics = FontDesignMetrics.getMetrics(font);

  25. metrics2 = FontDesignMetrics.getMetrics(font2);

  26. }

  27.  
  28. /**

  29. * 加水印

  30. * @param bufImg 视频帧

  31. */

  32. public void mark(BufferedImage bufImg) {

  33. if (bufImg == null || logoImg == null) {

  34. return;

  35. }

  36. int width = bufImg.getWidth();

  37. int height = bufImg.getHeight();

  38. Graphics2D graphics = bufImg.createGraphics();

  39. graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

  40. graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

  41. //设置图片背景

  42. graphics.drawImage(bufImg, 0, 0, width, height, null);

  43. //添加右上角水印

  44. graphics.drawImage(logoImg, width - 130, 8, 121, 64, null);

  45. }

  46.  
  47. /**

  48. *

  49. * @param bufImg 视频帧

  50. */

  51. public void markTag(BufferedImage bufImg, String msg, int videoWidth) {

  52. int width = bufImg.getWidth();

  53. int height = bufImg.getHeight();

  54. Graphics2D graphics = bufImg.createGraphics();

  55. graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

  56. graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

  57. //设置图片背景

  58. graphics.drawImage(bufImg, 0, 0, width, height, null);

  59. //设置由上方标签号

  60. graphics.setColor(Color.orange);

  61. if (videoWidth <= 400) {

  62. graphics.setFont(font2);

  63. graphics.drawString(msg, width - metrics2.stringWidth(tagId) - 24, metrics2.getAscent());

  64. } else {

  65. graphics.setFont(font);

  66. graphics.drawString(msg, width - metrics.stringWidth(msg) - 12, metrics.getAscent());

  67. }

  68. graphics.dispose();

  69. }

  70.  
  71. }

至此可以调用了(还有其他方式是直接调用SDK和推送视频流媒体服务器,后续文章更新)

websocket 涉及到高并发阻塞情况 后续更新,建议使用netty

  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
实现WebSocket-FLV直播服务,您可以结合使用JavaCVSpring Boot来完成。 首先,JavaCV是一个基于Java的开源计算机视觉和机器学习库,它提供了许多常见的计算机视觉和机器学习功能,如图像处理、视频处理、特征提取等。 而Spring Boot则是一个用于创建独立的、生产级别的Spring应用程序的框架,它简化了Spring应用程序的配置和部署过程,使开发者能够快速构建可靠的Java应用程序。 要实现WebSocket-FLV直播服务,您可以按照以下步骤进行: 1. 首先,使用Spring Boot创建一个新的项目。可以使用Spring Initializr(https://start.spring.io/)来初始化一个基本的Spring Boot项目。 2. 然后,添加JavaCV的依赖。您可以在项目的pom.xml文件中添加以下依赖来引入JavaCV: ```xml <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv</artifactId> <version>1.5.3</version> </dependency> ``` 3. 接下来,实现WebSocket连接和FLV视频流的处理。使用Spring Boot提供的WebSocket支持来建立和管理WebSocket连接,并使用JavaCV视频源生成FLV格式的视频流。 4. 在WebSocket处理程序中,可以使用JavaCV视频处理功能来读取视频源,然后将读取到的每一帧编码为FLV格式的视频流。可以使用JavaCV提供的FFmpegFrameGrabber类来读取视频源,并使用JavaCV提供的FFmpegFrameRecorder类来编码视频流为FLV格式。 5. 最后,将生成的FLV视频流发送给连接的WebSocket客户端。使用Spring Boot提供的WebSocket发送功能将FLV视频流发送给与WebSocket连接建立的客户端。 以上就是使用JavaCVSpring Boot实现WebSocket-FLV直播服务的基本步骤。您可以根据具体需求来调整和扩展这个基本实现

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值