RtspPushSrs.java 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package com.zhiqiyun.open.camera.rtsp;
  2. import cn.hutool.crypto.SecureUtil;
  3. import com.baomidou.mybatisplus.core.toolkit.IdWorker;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.bytedeco.ffmpeg.avcodec.AVCodecParameters;
  6. import org.bytedeco.ffmpeg.avformat.AVFormatContext;
  7. import org.bytedeco.ffmpeg.avformat.AVStream;
  8. import org.bytedeco.ffmpeg.global.avcodec;
  9. import org.bytedeco.ffmpeg.global.avutil;
  10. import org.bytedeco.javacv.FFmpegFrameGrabber;
  11. import org.bytedeco.javacv.FFmpegFrameRecorder;
  12. import org.bytedeco.javacv.FFmpegLogCallback;
  13. import org.bytedeco.javacv.Frame;
  14. /**
  15. * @author xin yang
  16. * @date 2022/10/13
  17. */
  18. @Slf4j
  19. public class RtspPushSrs {
  20. // //内网推流地址
  21. // public final static String INTRANET_SRS_PUSH_ADDRESS = "rtmp://116.8.106.156:7011/live/livestream/%s";
  22. //
  23. // //外网拉流地址
  24. // public final static String SRS_PUSH_ADDRESS = "http://116.8.106.156:9800/live/livestream/%s.flv";
  25. //内网推流地址
  26. public final static String INTRANET_SRS_PUSH_ADDRESS = "rtmp://172.31.119.9:1935/live/livestream/%s";
  27. //外网拉流地址
  28. // public final static String SRS_PUSH_ADDRESS = "http://222.84.255.194:9800/live/livestream/%s.flv";
  29. public final static String SRS_PUSH_ADDRESS = "http://live.zhlc.liucheng.gov.cn:8000/live/livestream/%s.flv";
  30. /**
  31. * 读取指定的rtsp视频流,推送到SRS服务器
  32. *
  33. * @param rtspId 视频流ID
  34. * @throws Exception
  35. */
  36. public static void grabberAndPush(String rtspId) throws Exception {
  37. String rtspUrl = RtspTransfer.PATH_MAP.get(rtspId);
  38. // ffmepg日志级别
  39. avutil.av_log_set_level(avutil.AV_LOG_ERROR);
  40. FFmpegLogCallback.set();
  41. // 实例化帧抓取器对象,将文件路径传入
  42. FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(rtspUrl);
  43. long startTime = System.currentTimeMillis();
  44. // grabber.setFormat("hevc");
  45. // 设置读取的最大数据,单位字节
  46. // grabber.setOption("probesize", "10000");
  47. // 设置分析的最长时间,单位微秒
  48. // grabber.setOption("analyzeduration", "20000");
  49. log.info("开始初始化帧抓取器");
  50. grabber.setOption("rtsp_transport", "tcp");
  51. // grabber.setOption("stimeout", "20000000");
  52. // 初始化帧抓取器,例如数据结构(时间戳、编码器上下文、帧对象等),
  53. // 如果入参等于true,还会调用avformat_find_stream_info方法获取流的信息,放入AVFormatContext类型的成员变量oc中
  54. // grabber.setImageWidth(640);
  55. // grabber.setImageHeight(480);
  56. try {
  57. grabber.start();
  58. } catch (Exception e) {
  59. log.error("grabberAndPush error:" + e.getMessage(), e);
  60. }
  61. log.info("帧抓取器初始化完成,耗时[{}]毫秒", System.currentTimeMillis() - startTime);
  62. // grabber.start方法中,初始化的解码器信息存在放在grabber的成员变量oc中
  63. AVFormatContext avFormatContext = grabber.getFormatContext();
  64. // 文件内有几个媒体流(一般是视频流+音频流)
  65. int streamNum = avFormatContext.nb_streams();
  66. // 没有媒体流就不用继续了
  67. if (streamNum < 1) {
  68. log.error("文件内不存在媒体流");
  69. return;
  70. }
  71. // 取得视频的帧率
  72. int frameRate = (int) grabber.getVideoFrameRate();
  73. log.info("视频帧率[{}],视频时长[{}]秒,媒体流数量[{}]", frameRate, avFormatContext.duration() / 1000000, avFormatContext.nb_streams());
  74. // 遍历每一个流,检查其类型
  75. for (int i = 0; i < streamNum; i++) {
  76. AVStream avStream = avFormatContext.streams(i);
  77. AVCodecParameters avCodecParameters = avStream.codecpar();
  78. log.info("流的索引[{}],编码器类型[{}],编码器ID[{}]", i, avCodecParameters.codec_type(), avCodecParameters.codec_id());
  79. }
  80. // 视频宽度
  81. int frameWidth = grabber.getImageWidth();
  82. // 视频高度
  83. int frameHeight = grabber.getImageHeight();
  84. // 音频通道数量
  85. int audioChannels = grabber.getAudioChannels();
  86. //编码格式
  87. // int videoCodec = grabber.getVideoCodec();
  88. //封装格式
  89. String format = grabber.getFormat();
  90. log.info("视频宽度[{}],视频高度[{}],音频通道数[{}],封装格式{{}]", frameWidth, frameHeight, audioChannels, format);
  91. // 实例化FFmpegFrameRecorder,将SRS的推送地址传入
  92. String rtmpUrl = String.format(INTRANET_SRS_PUSH_ADDRESS, SecureUtil.md5(rtspId));
  93. log.info("流媒体服务地址:{}", rtmpUrl);
  94. FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(rtmpUrl, frameWidth, frameHeight, audioChannels);
  95. // recorder.setInterleaved(true);
  96. // recorder.setVideoOption("crf", "28");
  97. // https://trac.ffmpeg.org/wiki/StreamingGuide)
  98. recorder.setVideoOption("tune", "zerolatency");
  99. // ultrafast对CPU消耗最低
  100. recorder.setVideoOption("preset", "ultrafast");
  101. // Constant Rate Factor (see: https://trac.ffmpeg.org/wiki/Encode/H.264)
  102. recorder.setVideoOption("crf", "28");
  103. // 设置编码格式
  104. recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
  105. // 设置封装格式
  106. recorder.setFormat("flv");
  107. // 一秒内的帧数
  108. recorder.setFrameRate(frameRate);
  109. // 两个关键帧之间的帧数
  110. recorder.setGopSize(frameRate * 2);
  111. // 设置音频通道数,与视频源的通道数相等
  112. recorder.setAudioChannels(audioChannels);
  113. // yuv420p
  114. recorder.setPixelFormat(0);
  115. startTime = System.currentTimeMillis();
  116. log.info("开始初始化帧抓取器");
  117. // 初始化帧录制器,例如数据结构(音频流、视频流指针,编码器),
  118. // 调用av_guess_format方法,确定视频输出时的封装方式,
  119. // 媒体上下文对象的内存分配,
  120. // 编码器的各项参数设置
  121. recorder.start();
  122. log.info("帧录制初始化完成,耗时[{}]毫秒", System.currentTimeMillis() - startTime);
  123. Frame frame;
  124. startTime = System.currentTimeMillis();
  125. log.info("开始推流");
  126. long videoTS = 0;
  127. int videoFrameNum = 0;
  128. int audioFrameNum = 0;
  129. int dataFrameNum = 0;
  130. // 假设一秒钟15帧,那么两帧间隔就是(1000/15)毫秒
  131. int interVal = 1000 / frameRate;
  132. // 发送完一帧后sleep的时间,不能完全等于(1000/frameRate),不然会卡顿,
  133. // 要更小一些,这里取八分之一
  134. interVal /= 8;
  135. // 持续从视频源取帧
  136. while (null != (frame = grabber.grab())) {
  137. if (videoTS == 0) {
  138. RtspTransfer.FRAME_GRABBER_CONCURRENT_HASH_MAP.put(rtspId, grabber);
  139. RtspTransfer.FRAME_RECORDER_CONCURRENT_HASH_MAP.put(rtspId, recorder);
  140. }
  141. videoTS = 1000 * (System.currentTimeMillis() - startTime);
  142. log.info("持续从视频源取帧 videoTS:{}", videoTS);
  143. // 时间戳
  144. recorder.setTimestamp(videoTS);
  145. // 有图像,就把视频帧加一
  146. if (null != frame.image) {
  147. videoFrameNum++;
  148. }
  149. // 有声音,就把音频帧加一
  150. if (null != frame.samples) {
  151. audioFrameNum++;
  152. }
  153. // 有数据,就把数据帧加一
  154. if (null != frame.data) {
  155. dataFrameNum++;
  156. }
  157. // 取出的每一帧,都推送到SRS
  158. recorder.record(frame);
  159. // 停顿一下再推送
  160. Thread.sleep(interVal);
  161. }
  162. log.info("推送完成,视频帧[{}],音频帧[{}],数据帧[{}],耗时[{}]秒",
  163. videoFrameNum,
  164. audioFrameNum,
  165. dataFrameNum,
  166. (System.currentTimeMillis() - startTime) / 1000);
  167. // 关闭帧录制器
  168. recorder.close();
  169. // 关闭帧抓取器
  170. grabber.close();
  171. }
  172. public static void main(String[] args) throws Exception {
  173. String rtspId = IdWorker.get32UUID();
  174. System.out.println(rtspId);
  175. RtspTransfer.PATH_MAP.put(rtspId, "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4");
  176. // rtmp://121.40.185.233:1935/live/livestream/9ee705f6d05676b02d036088c2f09718
  177. grabberAndPush(rtspId);
  178. }
  179. }