package com.ruoyi.media.service.impl; import com.alibaba.fastjson2.JSONObject; import com.dtflys.forest.exceptions.ForestNetworkException; import com.dtflys.forest.exceptions.ForestRuntimeException; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.page.TableDataInfo; 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 io.swagger.models.auth.In; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.formula.functions.T; 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.Resource; 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 { @Resource MediaClient mediaClient; @Value("${mediamtx.host}") String mediamtxHost; /** * 添加流媒体 * 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); } else { mediaClient.editPath(name, conf); } 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"); } else { info.setMode("1"); } //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); } } catch (ForestRuntimeException ex) { log.error("移除流媒体异常:" + ex.getMessage()); } } @Override public List paths(Integer pageNum, Integer pageSize) { Paths paths = mediaClient.paths(pageNum - 1, pageSize); List items = paths.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) { return mediaClient.getRtspsessionById(sessionId); } @Override public WebrtcSession getWebrtcSessionById(String sessionId) { return mediaClient.getWebrtcsessionById(sessionId); } @Override public RtmpSession getRtmpSessionById(String sessionId) { return mediaClient.getRtmpsessionById(sessionId); } /** * 获取推流列表 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public TableDataInfo getPushStreamList(Integer pageNum, Integer pageSize) { TableDataInfo tableDataInfo = new TableDataInfo(); List PushStreamInfoList = new ArrayList<>(); Paths paths = mediaClient.paths(pageNum - 1, pageSize); int itemCount = paths.getItemCount(); tableDataInfo.setTotal(itemCount); List items = paths.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.setDownTraffic(formatReceivedSize); //上行流量 long bytesSent = item.getBytesSent(); String formatSentdSize = ArdTool.formatFileSize(bytesSent); info.setUpTraffic(formatSentdSize); info.setBeginTime(item.getReadyTime()); } 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); } tableDataInfo.setRows(PushStreamInfoList); tableDataInfo.setCode(HttpStatus.SUCCESS); tableDataInfo.setMsg("查询成功"); return tableDataInfo; } /** * 获取webrtc拉流列表 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public TableDataInfo getPullWebrtcStreamList(Integer pageNum, Integer pageSize) { TableDataInfo tableDataInfo = new TableDataInfo(); WebrtcSessions WebrtcSessions = mediaClient.webrtcsessions(pageNum-1, pageSize); List webrtcsessions = WebrtcSessions.getItems(); webrtcsessions.stream().forEach( webrtcSession -> { webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived())); webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent())); webrtcSession.setBeginTime(webrtcSession.getCreated()); } ); tableDataInfo.setTotal(WebrtcSessions.getItemCount()); tableDataInfo.setRows(webrtcsessions); tableDataInfo.setCode(HttpStatus.SUCCESS); tableDataInfo.setMsg("查询成功"); return tableDataInfo; } /** * 获取rtmp拉流列表 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public TableDataInfo getPullRtmpStreamList(Integer pageNum, Integer pageSize) { TableDataInfo tableDataInfo = new TableDataInfo(); RtmpSessions rtmpSessions = mediaClient.rtmpsessions(pageNum-1, pageSize); List webrtcsessions = rtmpSessions.getItems(); webrtcsessions.stream().forEach( webrtcSession -> { webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived())); webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent())); webrtcSession.setBeginTime(webrtcSession.getCreated()); } ); tableDataInfo.setTotal(rtmpSessions.getItemCount()); tableDataInfo.setRows(webrtcsessions); tableDataInfo.setCode(HttpStatus.SUCCESS); tableDataInfo.setMsg("查询成功"); return tableDataInfo; } /** * 获取rtsp拉流列表 * 刘苏义 * 2023/8/29 9:37:05 */ @Override public TableDataInfo getPullRtspStreamList(Integer pageNum, Integer pageSize) { TableDataInfo tableDataInfo = new TableDataInfo(); RtspSessions rtspSessions = mediaClient.rtspsessions(pageNum-1, pageSize); List webrtcsessions = rtspSessions.getItems(); webrtcsessions.stream().forEach( webrtcSession -> { webrtcSession.setUpStream(ArdTool.formatFileSize(webrtcSession.getBytesReceived())); webrtcSession.setDownStream(ArdTool.formatFileSize(webrtcSession.getBytesSent())); webrtcSession.setBeginTime(webrtcSession.getCreated()); } ); tableDataInfo.setTotal(rtspSessions.getItemCount()); tableDataInfo.setRows(webrtcsessions); tableDataInfo.setCode(HttpStatus.SUCCESS); tableDataInfo.setMsg("查询成功"); return tableDataInfo; } /** * 踢出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() { List nameList = new ArrayList<>(); try { Paths paths = mediaClient.paths(0, 1000); List items = paths.getItems(); for (Items item : items) { nameList.add(item.getName()); } } catch (Exception ex) { log.error("获取流媒体name列表异常:" + ex.getMessage()); } return nameList; } /** * 检查name是否存在 * 刘苏义 * 2023/10/19 15:18:45 */ @Override public boolean checkNameExist(String name) { boolean result = false; List nameList = getNameList(); if (nameList.contains(name)) { result = true; } return result; } }