package com.ruoyi.media.service.impl; import com.alibaba.fastjson2.JSONObject; import com.dtflys.forest.Forest; import com.dtflys.forest.exceptions.ForestNetworkException; import com.dtflys.forest.exceptions.ForestRuntimeException; import com.dtflys.forest.http.ForestRequest; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.media.domain.*; import com.ruoyi.media.service.IMediaService; import com.ruoyi.utils.forest.MediaClient; import com.ruoyi.utils.tools.ArdTool; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.xml.soap.SOAPEnvelope; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @Description: 流媒体业务 * @ClassName: MediaService * @Author: 刘苏义 * @Date: 2023年07月13日9:28 * @Version: 1.0 **/ @Service @Slf4j(topic = "vtdu") @Order(2) public class MediaServiceImpl implements IMediaService, ApplicationRunner { public static List mediaNameList = new ArrayList<>(); @Resource MediaClient mediaClient; @Value("${mediamtx.host}") String mediamtxHost; @Override public void run(ApplicationArguments args) throws Exception { mediaNameList = getNameList(); if (mediaNameList.size() > 0) { removePath(mediaNameList.toArray(new String[0])); mediaNameList.clear(); } } /** * 添加流媒体 * name 相机ID * sourceUrl rtsp地址 * isCode 0-不转码 1-转码 * mode 0-gpu硬解码 1-cpu软解码 *

* 刘苏义 * 2023/10/12 9:03:41 */ @Override public Map addPath(String name, String sourceUrl, String mode, String isCode) { Map map = new HashMap<>(); try { String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name; String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name; String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name; Conf conf = new Conf(); String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/server/mediamtx/"; if (isCode.equals("1")) { conf.setSource("publisher"); //默认软解码 String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6 -b:v 2048k -acodec opus -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH"; if (mode.equals("0")) {//硬解码 cmd = rootPath + "ffmpeg -hwaccel cuvid -c:v hevc_cuvid -rtsp_transport tcp -i " + sourceUrl + " -c:v h264_nvenc -r 25 -g 60 -sc_threshold 0 -threads 6 -b:v 2048k -bf 0 -acodec opus -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH"; } conf.setRunOnDemand(cmd); conf.setRunOnDemandRestart(true); conf.setRunOnDemandCloseAfter("5s"); } else { conf.setSource(sourceUrl); conf.setSourceOnDemand(true); } conf.setMaxReaders(100); conf.setSourceProtocol("tcp"); if (!checkNameExist(name)) { mediaClient.addPath(name, conf); mediaNameList.add(name); } map.put("rtspUrl", rtspUrl); map.put("rtmpUrl", rtmpUrl); map.put("webrtcUrl", webrtcUrl); } catch (ForestNetworkException ex) { log.error("添加流媒体异常:" + ex.getMessage()); } catch (ForestRuntimeException ex) { log.error("添加流媒体异常:" + ex.getMessage()); } return map; } @Override public Map editPath(String name, String sourceUrl, String mode, String isCode) { Map map = new HashMap<>(); try { String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name; String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name; String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name; Conf conf = new Conf(); String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/lib/mediamtx/"; if (isCode.equals("1")) { conf.setSource("publisher"); //默认软解码 String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6 -b:v 2048k -acodec opus -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH"; if (mode.equals("0")) {//硬解码 cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid -rtsp_transport tcp -i " + sourceUrl + " -c:v h264_nvenc -r 25 -g 60 -sc_threshold 0 -threads 6 -b:v 2048k -bf 0 -acodec opus -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH"; } conf.setRunOnDemand(cmd); conf.setRunOnDemandRestart(true); conf.setRunOnDemandCloseAfter("5s"); } else { conf.setSource(sourceUrl); conf.setSourceOnDemand(true); conf.setRunOnDemand(""); conf.setRunOnDemandRestart(false); conf.setRunOnDemandCloseAfter("5s"); } conf.setMaxReaders(100); conf.setSourceProtocol("tcp"); if (checkNameExist(name)) { mediaClient.editPath(name, conf); } map.put("rtspUrl", rtspUrl); map.put("rtmpUrl", rtmpUrl); map.put("webrtcUrl", webrtcUrl); } catch (ForestRuntimeException ex) { log.error("修改流媒体异常:" + ex.getMessage()); } return map; } @Override public StreamInfo getPathInfo(String name) { Conf conf = mediaClient.getPathInfo(name); StreamInfo info = new StreamInfo(); //ID info.setName(name); String runOn = ""; if (StringUtils.isNotEmpty(conf.getRunOnDemand())) { runOn = conf.getRunOnDemand(); info.setMode("0"); } //RTSP源地址 Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn); if (matcher.find()) { info.setRtspSource(matcher.group()); info.setIsCode("1"); } else { info.setRtspSource(conf.getSource()); info.setIsCode("0"); } return info; } @Override public void removePath(String[] names) { try { for (String name : names) { if (checkNameExist(name)) { mediaClient.removePath(name); } } } catch (Exception ex) { log.error("批量移除流媒体异常:" + ex.getMessage()); } } @Override public void removePath(String name) { try { if (checkNameExist(name)) { mediaClient.removePath(name); mediaNameList.remove(name); } } catch (ForestRuntimeException ex) { log.error("移除流媒体异常:" + ex.getMessage()); } } @Override public List paths() { String list = mediaClient.paths(); JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class); List items = jsonsRoot.getItems(); List pathInfoList = new ArrayList<>(); for (Items item : items) { StreamInfo info = new StreamInfo(); //ID String name = item.getName(); info.setName(name); Conf conf = mediaClient.getPathInfo(name); String runOn = ""; if (StringUtils.isNotEmpty(conf.getRunOnDemand())) { runOn = conf.getRunOnDemand(); } //RTSP源地址 Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn); if (matcher.find()) { info.setRtspSource(matcher.group()); info.setIsCode("1"); } else { info.setRtspSource(conf.getSource()); info.setIsCode("0"); } //传输协议 matcher = Pattern.compile("-rtsp_transport\\s+(\\w+)").matcher(runOn); if (matcher.find()) { info.setProtocol(matcher.group(1)); } pathInfoList.add(info); } return pathInfoList; } @Override public RtspSession getRtspSessionById(String sessionId) { String list = mediaClient.getRtspsessionById(sessionId); RtspSession rtspSession = JSONObject.parseObject(list, RtspSession.class); return rtspSession; } @Override public WebrtcSession getWebrtcSessionById(String sessionId) { String list = mediaClient.getWebrtcsessionById(sessionId); WebrtcSession webrtcSession = JSONObject.parseObject(list, WebrtcSession.class); return webrtcSession; } @Override public RtmpSession getRtmpSessionById(String sessionId) { String list = mediaClient.getRtmpsessionById(sessionId); RtmpSession rtmpSession = JSONObject.parseObject(list, RtmpSession.class); return rtmpSession; } /** * 获取推流列表 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public List getPushStreamList() { List PushStreamInfoList = new ArrayList<>(); String list = mediaClient.paths(); JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class); List items = jsonsRoot.getItems(); for (Items item : items) { StreamInfo info = new StreamInfo(); //ID String name = item.getName(); info.setName(name); Conf conf = mediaClient.getPathInfo(name); //RTMP播放地址 String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name; info.setRtmpUrl(rtmpUrl); //RTSP播放地址 String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name; info.setRtspUrl(rtspUrl); //WEBRTC播放地址 String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name; info.setWebrtcUrl(webrtcUrl); Source source = item.getSource(); if (source == null || source.getId().equals("")) { //会话ID info.setId("0"); //上行流量 long bytesReceived = item.getBytesReceived(); String formatReceivedSize = ArdTool.formatFileSize(bytesReceived); info.setUpTraffic(formatReceivedSize); } else { RtspSession rtspSession = getRtspSessionById(source.getId()); //会话ID info.setId(rtspSession.getId()); //开始推流时间 info.setBeginTime(rtspSession.getCreated()); //上行流量 long bytesReceived = rtspSession.getBytesReceived(); String formatReceivedSize = ArdTool.formatFileSize(bytesReceived); info.setUpTraffic(formatReceivedSize); //下行流量 long bytesSent = rtspSession.getBytesSent(); String formatSentSize = ArdTool.formatFileSize(bytesSent); info.setDownTraffic(formatSentSize); //推流服务器 info.setRemoteAddr(rtspSession.getRemoteAddr()); } //RTSP源地址 String runOn = ""; if (StringUtils.isNotEmpty(conf.getRunOnDemand())) { runOn = conf.getRunOnDemand(); } Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn); if (matcher.find()) { info.setRtspSource(matcher.group()); } else { info.setRtspSource(conf.getSource()); } //传输协议 info.setProtocol(conf.getSourceProtocol()); //拉流数量 List readers = item.getReaders(); info.setNum(readers.size()); PushStreamInfoList.add(info); } return PushStreamInfoList; } /** * 获取拉流列表 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public List getPullStreamList() { List PullStreamInfoList = new ArrayList<>(); String list = mediaClient.paths(); JsonsRoot jsonsRoot = JSONObject.parseObject(list, JsonsRoot.class); List items = jsonsRoot.getItems(); for (Items item : items) { List readers = item.getReaders(); for (Readers reader : readers) { StreamInfo info = new StreamInfo(); //ID String name = item.getName(); info.setName(name); Conf conf = mediaClient.getPathInfo(name); //传输协议 info.setProtocol(conf.getSourceProtocol()); String type = reader.getType(); switch (type) { case "rtmpConn": info.setSessionType("rtmp"); //webrtc播放地址 String url = "rtmp://" + mediamtxHost + ":1935/" + name; info.setRtspUrl(url); RtmpSession rtmpSession = getRtmpSessionById(reader.getId()); //会话ID info.setId(rtmpSession.getId()); //开始拉流时间 info.setBeginTime(rtmpSession.getCreated()); //上行流量 long bytesReceived = rtmpSession.getBytesReceived(); String formatReceivedSize = ArdTool.formatFileSize(bytesReceived); info.setUpTraffic(formatReceivedSize); //下行流量 long bytesSent = rtmpSession.getBytesSent(); String formatSentSize = ArdTool.formatFileSize(bytesSent); info.setDownTraffic(formatSentSize); //拉流服务器 info.setRemoteAddr(rtmpSession.getRemoteAddr()); PullStreamInfoList.add(info); break; case "webRTCSession": info.setSessionType("webrtc"); //webrtc播放地址 url = "http://" + mediamtxHost + ":8889/" + name; info.setRtspUrl(url); WebrtcSession webrtcSession = getWebrtcSessionById(reader.getId()); //会话ID info.setId(webrtcSession.getId()); //开始拉流时间 info.setBeginTime(webrtcSession.getCreated()); //上行流量 bytesReceived = webrtcSession.getBytesReceived(); formatReceivedSize = ArdTool.formatFileSize(bytesReceived); info.setUpTraffic(formatReceivedSize); //下行流量 bytesSent = webrtcSession.getBytesSent(); formatSentSize = ArdTool.formatFileSize(bytesSent); info.setDownTraffic(formatSentSize); //拉流服务器 info.setRemoteAddr(webrtcSession.getRemoteAddr()); PullStreamInfoList.add(info); break; case "rtspSession": info.setSessionType("rtsp"); //RTSP播放地址 String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name; info.setRtspUrl(rtspUrl); RtspSession rtspSession = getRtspSessionById(reader.getId()); //会话ID info.setId(rtspSession.getId()); //开始拉流时间 info.setBeginTime(rtspSession.getCreated()); //上行流量 bytesReceived = rtspSession.getBytesReceived(); formatReceivedSize = ArdTool.formatFileSize(bytesReceived); info.setUpTraffic(formatReceivedSize); //下行流量 bytesSent = rtspSession.getBytesSent(); formatSentSize = ArdTool.formatFileSize(bytesSent); info.setDownTraffic(formatSentSize); //拉流服务器 info.setRemoteAddr(rtspSession.getRemoteAddr()); PullStreamInfoList.add(info); break; } } } Comparator comparator = Comparator.comparing(streamInfo -> streamInfo.getBeginTime()); // 使用Collections.sort方法进行排序 Collections.sort(personList, comparator); Collections.sort(PullStreamInfoList, comparator.reversed()); return PullStreamInfoList; } /** * 踢出rtsp会话 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public Boolean kickRtspSession(String sessionId) { try { mediaClient.kickRtspSessions(sessionId); return true; } catch (Exception ex) { return false; } } /** * 踢出rtmp会话 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public Boolean kickRtmpSession(String sessionId) { try { mediaClient.kickRtmpSessions(sessionId); return true; } catch (Exception ex) { return false; } } /** * 踢出webrtc会话 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public Boolean kickWebrtcSession(String sessionId) { try { mediaClient.kickWebrtcSessions(sessionId); return true; } catch (Exception ex) { return false; } } /** * 获取流媒体name列表 * 刘苏义 * 2023/10/13 14:19:07 */ @Override public List getNameList() { try { String paths = mediaClient.paths(); JsonsRoot jsonsRoot = JSONObject.parseObject(paths, JsonsRoot.class); List items = jsonsRoot.getItems(); for (Items item : items) { mediaNameList.add(item.getName()); } } catch (ForestNetworkException ex) { log.error("获取流媒体name列表异常:" + ex.getMessage()); } catch (ForestRuntimeException ex) { log.error("获取流媒体name列表异常:" + ex.getMessage()); } return mediaNameList; } /** * 检查名称是否存在 * 刘苏义 * 2023/10/19 15:18:45 */ @Override public boolean checkNameExist(String name) { boolean result = false; if (mediaNameList.contains(name)) { result = true; } return result; } /** * 配置流媒体参数 */ @Override public String setConfig(Config config) { return mediaClient.setConfig(config); } }