Commit db6dd3f7550c433f06475540a13598cbeed12d08

Authored by 648540858
2 parents 9889f908 19533c8f

Merge branch 'wvp-28181-2.0' into main-dev

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java
#	src/main/java/com/genersoft/iot/vmp/vmanager/rtp/RtpController.java
Showing 38 changed files with 986 additions and 229 deletions
sql/2.6.8补丁更新.sql 0 → 100644
  1 +alter table media_server
  2 + add sendRtpPortRange varchar(50) not null;
0 3 \ No newline at end of file
... ...
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
... ... @@ -159,6 +159,7 @@ public class VideoManagerConstants {
159 159 public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_";
160 160 public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_";
161 161 public static final String WVP_OTHER_SEND_RTP_INFO = "VMP_OTHER_SEND_RTP_INFO_";
  162 + public static final String WVP_OTHER_RECEIVE_RTP_INFO = "VMP_OTHER_RECEIVE_RTP_INFO_";
162 163  
163 164 /**
164 165 * Redis Const
... ...
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
... ... @@ -233,5 +233,4 @@ public class MediaConfig{
233 233 }
234 234 return false;
235 235 }
236   -
237 236 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181;
2 2  
3 3 import com.genersoft.iot.vmp.conf.SipConfig;
4 4 import com.genersoft.iot.vmp.conf.UserSetting;
  5 +import com.genersoft.iot.vmp.gb28181.bean.GbStringMsgParserFactory;
5 6 import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties;
6 7 import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver;
7 8 import gov.nist.javax.sip.SipProviderImpl;
... ... @@ -63,6 +64,7 @@ public class SipLayer implements CommandLineRunner {
63 64 SipStackImpl sipStack;
64 65 try {
65 66 sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog()));
  67 + sipStack.setMessageParserFactory(new GbStringMsgParserFactory());
66 68 } catch (PeerUnavailableException e) {
67 69 logger.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
68 70 return;
... ... @@ -75,7 +77,6 @@ public class SipLayer implements CommandLineRunner {
75 77 tcpSipProvider.setDialogErrorsAutomaticallyHandled();
76 78 tcpSipProvider.addSipListener(sipProcessorObserver);
77 79 tcpSipProviderMap.put(monitorIp, tcpSipProvider);
78   -
79 80 logger.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port);
80 81 } catch (TransportNotSupportedException
81 82 | TooManyListenersException
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
... ... @@ -244,6 +244,9 @@ public class Device {
244 244 }
245 245  
246 246 public Integer getStreamModeForParam() {
  247 + if (streamMode == null) {
  248 + return 0;
  249 + }
247 250 if (streamMode.equalsIgnoreCase("UDP")) {
248 251 return 0;
249 252 }else if (streamMode.equalsIgnoreCase("TCP-PASSIVE")) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.bean;
  2 +
  3 +import gov.nist.core.CommonLogger;
  4 +import gov.nist.core.Host;
  5 +import gov.nist.core.HostNameParser;
  6 +import gov.nist.core.StackLogger;
  7 +import gov.nist.javax.sip.SIPConstants;
  8 +import gov.nist.javax.sip.address.AddressImpl;
  9 +import gov.nist.javax.sip.address.GenericURI;
  10 +import gov.nist.javax.sip.address.SipUri;
  11 +import gov.nist.javax.sip.address.TelephoneNumber;
  12 +import gov.nist.javax.sip.header.*;
  13 +import gov.nist.javax.sip.message.SIPMessage;
  14 +import gov.nist.javax.sip.message.SIPRequest;
  15 +import gov.nist.javax.sip.message.SIPResponse;
  16 +import gov.nist.javax.sip.parser.*;
  17 +
  18 +import java.io.UnsupportedEncodingException;
  19 +import java.text.ParseException;
  20 +
  21 +public class GBStringMsgParser implements MessageParser {
  22 +
  23 + protected static boolean computeContentLengthFromMessage = false;
  24 +
  25 + private static StackLogger logger = CommonLogger.getLogger(StringMsgParser.class);
  26 +
  27 + /**
  28 + * @since v0.9
  29 + */
  30 + public GBStringMsgParser() {
  31 + super();
  32 + }
  33 +
  34 + /**
  35 + * Parse a buffer containing a single SIP Message where the body is an array
  36 + * of un-interpreted bytes. This is intended for parsing the message from a
  37 + * memory buffer when the buffer. Incorporates a bug fix for a bug that was
  38 + * noted by Will Sullin of Callcast
  39 + *
  40 + * @param msgBuffer
  41 + * a byte buffer containing the messages to be parsed. This can
  42 + * consist of multiple SIP Messages concatenated together.
  43 + * @return a SIPMessage[] structure (request or response) containing the
  44 + * parsed SIP message.
  45 + * @exception ParseException
  46 + * is thrown when an illegal message has been encountered
  47 + * (and the rest of the buffer is discarded).
  48 + * @see ParseExceptionListener
  49 + */
  50 + public SIPMessage parseSIPMessage(byte[] msgBuffer, boolean readBody, boolean strict, ParseExceptionListener parseExceptionListener) throws ParseException {
  51 + if (msgBuffer == null || msgBuffer.length == 0)
  52 + return null;
  53 +
  54 + int i = 0;
  55 +
  56 + // Squeeze out any leading control character.
  57 + try {
  58 + while (msgBuffer[i] < 0x20)
  59 + i++;
  60 + }
  61 + catch (ArrayIndexOutOfBoundsException e) {
  62 + // Array contains only control char, return null.
  63 + if (logger.isLoggingEnabled(StackLogger.TRACE_DEBUG)) {
  64 + logger.logDebug("handled only control char so returning null");
  65 + }
  66 + return null;
  67 + }
  68 +
  69 + // Iterate thru the request/status line and headers.
  70 + String currentLine = null;
  71 + String currentHeader = null;
  72 + boolean isFirstLine = true;
  73 + SIPMessage message = null;
  74 + do
  75 + {
  76 + int lineStart = i;
  77 +
  78 + // Find the length of the line.
  79 + try {
  80 + while (msgBuffer[i] != '\r' && msgBuffer[i] != '\n')
  81 + i++;
  82 + }
  83 + catch (ArrayIndexOutOfBoundsException e) {
  84 + // End of the message.
  85 + break;
  86 + }
  87 + int lineLength = i - lineStart;
  88 +
  89 + // Make it a String.
  90 + try {
  91 + currentLine = new String(msgBuffer, lineStart, lineLength, "UTF-8");
  92 + } catch (UnsupportedEncodingException e) {
  93 + throw new ParseException("Bad message encoding!", 0);
  94 + }
  95 +
  96 + currentLine = trimEndOfLine(currentLine);
  97 +
  98 + if (currentLine.length() == 0) {
  99 + // Last header line, process the previous buffered header.
  100 + if (currentHeader != null && message != null) {
  101 + processHeader(currentHeader, message, parseExceptionListener, msgBuffer);
  102 + }
  103 +
  104 + }
  105 + else {
  106 + if (isFirstLine) {
  107 + message = processFirstLine(currentLine, parseExceptionListener, msgBuffer);
  108 + } else {
  109 + char firstChar = currentLine.charAt(0);
  110 + if (firstChar == '\t' || firstChar == ' ') {
  111 + if (currentHeader == null)
  112 + throw new ParseException("Bad header continuation.", 0);
  113 +
  114 + // This is a continuation, append it to the previous line.
  115 + currentHeader += currentLine.substring(1);
  116 + }
  117 + else {
  118 + if (currentHeader != null && message != null) {
  119 + processHeader(currentHeader, message, parseExceptionListener, msgBuffer);
  120 + }
  121 + currentHeader = currentLine;
  122 + }
  123 + }
  124 + }
  125 +
  126 + if (msgBuffer[i] == '\r' && msgBuffer.length > i+1 && msgBuffer[i+1] == '\n')
  127 + i++;
  128 +
  129 + i++;
  130 +
  131 + isFirstLine = false;
  132 + } while (currentLine.length() > 0); // End do - while
  133 +
  134 + if (message == null) throw new ParseException("Bad message", 0);
  135 + message.setSize(i);
  136 +
  137 + // Check for content legth header
  138 + if (readBody && message.getContentLength() != null ) {
  139 + if ( message.getContentLength().getContentLength() != 0) {
  140 + int bodyLength = msgBuffer.length - i;
  141 +
  142 + byte[] body = new byte[bodyLength];
  143 + System.arraycopy(msgBuffer, i, body, 0, bodyLength);
  144 + message.setMessageContent(body,!strict,computeContentLengthFromMessage,message.getContentLength().getContentLength());
  145 + } else if (message.getCSeqHeader().getMethod().equalsIgnoreCase("MESSAGE")) {
  146 + int bodyLength = msgBuffer.length - i;
  147 +
  148 + byte[] body = new byte[bodyLength];
  149 + System.arraycopy(msgBuffer, i, body, 0, bodyLength);
  150 + message.setMessageContent(body,!strict,computeContentLengthFromMessage,bodyLength);
  151 + }else if (!computeContentLengthFromMessage && strict) {
  152 + String last4Chars = new String(msgBuffer, msgBuffer.length - 4, 4);
  153 + if(!"\r\n\r\n".equals(last4Chars)) {
  154 + throw new ParseException("Extraneous characters at the end of the message ",i);
  155 + }
  156 + }
  157 + }
  158 +
  159 + return message;
  160 + }
  161 +
  162 + protected static String trimEndOfLine(String line) {
  163 + if (line == null)
  164 + return line;
  165 +
  166 + int i = line.length() - 1;
  167 + while (i >= 0 && line.charAt(i) <= 0x20)
  168 + i--;
  169 +
  170 + if (i == line.length() - 1)
  171 + return line;
  172 +
  173 + if (i == -1)
  174 + return "";
  175 +
  176 + return line.substring(0, i+1);
  177 + }
  178 +
  179 + protected SIPMessage processFirstLine(String firstLine, ParseExceptionListener parseExceptionListener, byte[] msgBuffer) throws ParseException {
  180 + SIPMessage message;
  181 + if (!firstLine.startsWith(SIPConstants.SIP_VERSION_STRING)) {
  182 + message = new SIPRequest();
  183 + try {
  184 + RequestLine requestLine = new RequestLineParser(firstLine + "\n")
  185 + .parse();
  186 + ((SIPRequest) message).setRequestLine(requestLine);
  187 + } catch (ParseException ex) {
  188 + if (parseExceptionListener != null)
  189 + try {
  190 + parseExceptionListener.handleException(ex, message,
  191 + RequestLine.class, firstLine, new String(msgBuffer, "UTF-8"));
  192 + } catch (UnsupportedEncodingException e) {
  193 + e.printStackTrace();
  194 + }
  195 + else
  196 + throw ex;
  197 +
  198 + }
  199 + } else {
  200 + message = new SIPResponse();
  201 + try {
  202 + StatusLine sl = new StatusLineParser(firstLine + "\n").parse();
  203 + ((SIPResponse) message).setStatusLine(sl);
  204 + } catch (ParseException ex) {
  205 + if (parseExceptionListener != null) {
  206 + try {
  207 + parseExceptionListener.handleException(ex, message,
  208 + StatusLine.class, firstLine, new String(msgBuffer, "UTF-8"));
  209 + } catch (UnsupportedEncodingException e) {
  210 + e.printStackTrace();
  211 + }
  212 + } else
  213 + throw ex;
  214 +
  215 + }
  216 + }
  217 + return message;
  218 + }
  219 +
  220 + protected void processHeader(String header, SIPMessage message, ParseExceptionListener parseExceptionListener, byte[] rawMessage) throws ParseException {
  221 + if (header == null || header.length() == 0)
  222 + return;
  223 +
  224 + HeaderParser headerParser = null;
  225 + try {
  226 + headerParser = ParserFactory.createParser(header + "\n");
  227 + } catch (ParseException ex) {
  228 + // https://java.net/jira/browse/JSIP-456
  229 + if (parseExceptionListener != null) {
  230 + parseExceptionListener.handleException(ex, message, null,
  231 + header, null);
  232 + return;
  233 + } else {
  234 + throw ex;
  235 + }
  236 + }
  237 +
  238 + try {
  239 + SIPHeader sipHeader = headerParser.parse();
  240 + message.attachHeader(sipHeader, false);
  241 + } catch (ParseException ex) {
  242 + if (parseExceptionListener != null) {
  243 + String headerName = Lexer.getHeaderName(header);
  244 + Class headerClass = NameMap.getClassFromName(headerName);
  245 + if (headerClass == null) {
  246 + headerClass = ExtensionHeaderImpl.class;
  247 +
  248 + }
  249 + try {
  250 + parseExceptionListener.handleException(ex, message,
  251 + headerClass, header, new String(rawMessage, "UTF-8"));
  252 + } catch (UnsupportedEncodingException e) {
  253 + e.printStackTrace();
  254 + }
  255 +
  256 + }
  257 + }
  258 + }
  259 +
  260 + /**
  261 + * Parse an address (nameaddr or address spec) and return and address
  262 + * structure.
  263 + *
  264 + * @param address
  265 + * is a String containing the address to be parsed.
  266 + * @return a parsed address structure.
  267 + * @since v1.0
  268 + * @exception ParseException
  269 + * when the address is badly formatted.
  270 + */
  271 + public AddressImpl parseAddress(String address) throws ParseException {
  272 + AddressParser addressParser = new AddressParser(address);
  273 + return addressParser.address(true);
  274 + }
  275 +
  276 + /**
  277 + * Parse a host:port and return a parsed structure.
  278 + *
  279 + * @param hostport
  280 + * is a String containing the host:port to be parsed
  281 + * @return a parsed address structure.
  282 + * @since v1.0
  283 + * @exception throws
  284 + * a ParseException when the address is badly formatted.
  285 + *
  286 + public HostPort parseHostPort(String hostport) throws ParseException {
  287 + Lexer lexer = new Lexer("charLexer", hostport);
  288 + return new HostNameParser(lexer).hostPort();
  289 +
  290 + }
  291 + */
  292 +
  293 + /**
  294 + * Parse a host name and return a parsed structure.
  295 + *
  296 + * @param host
  297 + * is a String containing the host name to be parsed
  298 + * @return a parsed address structure.
  299 + * @since v1.0
  300 + * @exception ParseException
  301 + * a ParseException when the hostname is badly formatted.
  302 + */
  303 + public Host parseHost(String host) throws ParseException {
  304 + Lexer lexer = new Lexer("charLexer", host);
  305 + return new HostNameParser(lexer).host();
  306 +
  307 + }
  308 +
  309 + /**
  310 + * Parse a telephone number return a parsed structure.
  311 + *
  312 + * @param telephone_number
  313 + * is a String containing the telephone # to be parsed
  314 + * @return a parsed address structure.
  315 + * @since v1.0
  316 + * @exception ParseException
  317 + * a ParseException when the address is badly formatted.
  318 + */
  319 + public TelephoneNumber parseTelephoneNumber(String telephone_number)
  320 + throws ParseException {
  321 + // Bug fix contributed by Will Scullin
  322 + return new URLParser(telephone_number).parseTelephoneNumber(true);
  323 +
  324 + }
  325 +
  326 + /**
  327 + * Parse a SIP url from a string and return a URI structure for it.
  328 + *
  329 + * @param url
  330 + * a String containing the URI structure to be parsed.
  331 + * @return A parsed URI structure
  332 + * @exception ParseException
  333 + * if there was an error parsing the message.
  334 + */
  335 +
  336 + public SipUri parseSIPUrl(String url) throws ParseException {
  337 + try {
  338 + return new URLParser(url).sipURL(true);
  339 + } catch (ClassCastException ex) {
  340 + throw new ParseException(url + " Not a SIP URL ", 0);
  341 + }
  342 + }
  343 +
  344 + /**
  345 + * Parse a uri from a string and return a URI structure for it.
  346 + *
  347 + * @param url
  348 + * a String containing the URI structure to be parsed.
  349 + * @return A parsed URI structure
  350 + * @exception ParseException
  351 + * if there was an error parsing the message.
  352 + */
  353 +
  354 + public GenericURI parseUrl(String url) throws ParseException {
  355 + return new URLParser(url).parse();
  356 + }
  357 +
  358 + /**
  359 + * Parse an individual SIP message header from a string.
  360 + *
  361 + * @param header
  362 + * String containing the SIP header.
  363 + * @return a SIPHeader structure.
  364 + * @exception ParseException
  365 + * if there was an error parsing the message.
  366 + */
  367 + public static SIPHeader parseSIPHeader(String header) throws ParseException {
  368 + int start = 0;
  369 + int end = header.length() - 1;
  370 + try {
  371 + // Squeeze out any leading control character.
  372 + while (header.charAt(start) <= 0x20)
  373 + start++;
  374 +
  375 + // Squeeze out any trailing control character.
  376 + while (header.charAt(end) <= 0x20)
  377 + end--;
  378 + }
  379 + catch (ArrayIndexOutOfBoundsException e) {
  380 + // Array contains only control char.
  381 + throw new ParseException("Empty header.", 0);
  382 + }
  383 +
  384 + StringBuilder buffer = new StringBuilder(end + 1);
  385 + int i = start;
  386 + int lineStart = start;
  387 + boolean endOfLine = false;
  388 + while (i <= end) {
  389 + char c = header.charAt(i);
  390 + if (c == '\r' || c == '\n') {
  391 + if (!endOfLine) {
  392 + buffer.append(header.substring(lineStart, i));
  393 + endOfLine = true;
  394 + }
  395 + }
  396 + else {
  397 + if (endOfLine) {
  398 + endOfLine = false;
  399 + if (c == ' ' || c == '\t') {
  400 + buffer.append(' ');
  401 + lineStart = i + 1;
  402 + }
  403 + else {
  404 + lineStart = i;
  405 + }
  406 + }
  407 + }
  408 +
  409 + i++;
  410 + }
  411 + buffer.append(header.substring(lineStart, i));
  412 + buffer.append('\n');
  413 +
  414 + HeaderParser hp = ParserFactory.createParser(buffer.toString());
  415 + if (hp == null)
  416 + throw new ParseException("could not create parser", 0);
  417 + return hp.parse();
  418 + }
  419 +
  420 + /**
  421 + * Parse the SIP Request Line
  422 + *
  423 + * @param requestLine
  424 + * a String containing the request line to be parsed.
  425 + * @return a RequestLine structure that has the parsed RequestLine
  426 + * @exception ParseException
  427 + * if there was an error parsing the requestLine.
  428 + */
  429 +
  430 + public RequestLine parseSIPRequestLine(String requestLine)
  431 + throws ParseException {
  432 + requestLine += "\n";
  433 + return new RequestLineParser(requestLine).parse();
  434 + }
  435 +
  436 + /**
  437 + * Parse the SIP Response message status line
  438 + *
  439 + * @param statusLine
  440 + * a String containing the Status line to be parsed.
  441 + * @return StatusLine class corresponding to message
  442 + * @exception ParseException
  443 + * if there was an error parsing
  444 + * @see StatusLine
  445 + */
  446 +
  447 + public StatusLine parseSIPStatusLine(String statusLine)
  448 + throws ParseException {
  449 + statusLine += "\n";
  450 + return new StatusLineParser(statusLine).parse();
  451 + }
  452 +
  453 + public static void setComputeContentLengthFromMessage(
  454 + boolean computeContentLengthFromMessage) {
  455 + GBStringMsgParser.computeContentLengthFromMessage = computeContentLengthFromMessage;
  456 + }
  457 +}
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/WvpSipDate.java renamed to src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSipDate.java
... ... @@ -8,7 +8,7 @@ import java.util.*;
8 8 /**
9 9 * 重写jain sip的SIPDate解决与国标时间格式不一致的问题
10 10 */
11   -public class WvpSipDate extends SIPDate {
  11 +public class GbSipDate extends SIPDate {
12 12  
13 13 /**
14 14 *
... ... @@ -17,7 +17,7 @@ public class WvpSipDate extends SIPDate {
17 17  
18 18 private Calendar javaCal;
19 19  
20   - public WvpSipDate(long timeMillis) {
  20 + public GbSipDate(long timeMillis) {
21 21 this.javaCal = new GregorianCalendar(TimeZone.getDefault(), Locale.getDefault());
22 22 Date date = new Date(timeMillis);
23 23 this.javaCal.setTime(date);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStringMsgParserFactory.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.bean;
  2 +
  3 +import gov.nist.javax.sip.parser.MessageParser;
  4 +import gov.nist.javax.sip.parser.MessageParserFactory;
  5 +import gov.nist.javax.sip.stack.SIPTransactionStack;
  6 +
  7 +public class GbStringMsgParserFactory implements MessageParserFactory {
  8 +
  9 + /**
  10 + * msg parser is completely stateless, reuse isntance for the whole stack
  11 + * fixes https://github.com/RestComm/jain-sip/issues/92
  12 + */
  13 + private static GBStringMsgParser msgParser = new GBStringMsgParser();
  14 + /*
  15 + * (non-Javadoc)
  16 + * @see gov.nist.javax.sip.parser.MessageParserFactory#createMessageParser(gov.nist.javax.sip.stack.SIPTransactionStack)
  17 + */
  18 + public MessageParser createMessageParser(SIPTransactionStack stack) {
  19 + return msgParser;
  20 + }
  21 +}
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/conf/DefaultProperties.java
... ... @@ -31,6 +31,8 @@ public class DefaultProperties {
31 31 properties.setProperty("gov.nist.javax.sip.CANCEL_CLIENT_TRANSACTION_CHECKED", "true");
32 32 // 为_NULL _对话框传递_终止的_事件
33 33 properties.setProperty("gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_NULL_DIALOG", "true");
  34 + // 是否自动计算content length的实际长度,默认不计算
  35 + properties.setProperty("gov.nist.javax.sip.COMPUTE_CONTENT_LENGTH_FROM_MESSAGE_BODY", "true");
34 36 // 会话清理策略
35 37 properties.setProperty("gov.nist.javax.sip.RELEASE_REFERENCES_STRATEGY", "Normal");
36 38 // 处理由该服务器处理的基于底层TCP的保持生存超时
... ... @@ -42,6 +44,8 @@ public class DefaultProperties {
42 44 // 定义应用程序打算多久审计一次 SIP 堆栈,了解其内部线程的健康状况(该属性指定连续审计之间的时间(以毫秒为单位))
43 45 properties.setProperty("gov.nist.javax.sip.THREAD_AUDIT_INTERVAL_IN_MILLISECS", "30000");
44 46  
  47 +// properties.setProperty("gov.nist.javax.sip.MESSAGE_PROCESSOR_FACTORY", "gov.nist.javax.sip.stack.NioMessageProcessorFactory");
  48 +
45 49 /**
46 50 * sip_server_log.log 和 sip_debug_log.log ERROR, INFO, WARNING, OFF, DEBUG, TRACE
47 51 */
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/conf/ServerLoggerImpl.java
1 1 package com.genersoft.iot.vmp.gb28181.conf;
2 2  
  3 +import gov.nist.core.CommonLogger;
3 4 import gov.nist.core.ServerLogger;
4 5 import gov.nist.core.StackLogger;
5 6 import gov.nist.javax.sip.message.SIPMessage;
... ... @@ -84,7 +85,7 @@ public class ServerLoggerImpl implements ServerLogger {
84 85 }
85 86 if(sipStack instanceof SIPTransactionStack) {
86 87 this.sipStack = (SIPTransactionStack)sipStack;
87   - this.stackLogger = this.sipStack.getStackLogger();
  88 + this.stackLogger = CommonLogger.getLogger(SIPTransactionStack.class);
88 89 }
89 90 }
90 91 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
... ... @@ -67,7 +67,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
67 67 private SipSubscribe sipSubscribe;
68 68  
69 69 @Autowired
70   - private ZLMServerFactory zlmserverfactory;
  70 + private ZLMServerFactory zlmServerFactory;
71 71  
72 72 @Autowired
73 73 private SipLayer sipLayer;
... ... @@ -753,7 +753,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
753 753 .append("<Response>\r\n")
754 754 .append("<CmdType>RecordInfo</CmdType>\r\n")
755 755 .append("<SN>" +recordInfo.getSn() + "</SN>\r\n")
756   - .append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n")
  756 + .append("<DeviceID>" + recordInfo.getChannelId() + "</DeviceID>\r\n")
757 757 .append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
758 758 if (recordInfo.getRecordList() == null ) {
759 759 recordXml.append("<RecordList Num=\"0\">\r\n");
... ... @@ -842,7 +842,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
842 842 MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
843 843 if (mediaServerItem != null) {
844 844 mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
845   - zlmserverfactory.closeRtpServer(mediaServerItem, sendRtpItem.getStream());
  845 + zlmServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStreamId());
846 846 }
847 847 SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem);
848 848 if (byeRequest == null) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
... ... @@ -206,7 +206,24 @@ public abstract class SIPRequestProcessorParent {
206 206 Byte[] bytes = new Byte[0];
207 207 byte[] bytesResult = ArrayUtils.toPrimitive(result.toArray(bytes));
208 208  
209   - Document xml = reader.read(new ByteArrayInputStream(bytesResult));
  209 + Document xml;
  210 + try {
  211 + xml = reader.read(new ByteArrayInputStream(bytesResult));
  212 + }catch (DocumentException e) {
  213 + logger.warn("[xml解析异常]: 愿文如下: \r\n{}", new String(bytesResult));
  214 + logger.warn("[xml解析异常]: 愿文如下: 尝试兼容性处理");
  215 + String[] xmlLineArray = new String(bytesResult).split("\\r?\\n");
  216 +
  217 + // 兼容海康的address字段带有<破换xml结构导致无法解析xml的问题
  218 + StringBuilder stringBuilder = new StringBuilder();
  219 + for (String s : xmlLineArray) {
  220 + if (s.startsWith("<Address")) {
  221 + continue;
  222 + }
  223 + stringBuilder.append(s);
  224 + }
  225 + xml = reader.read(new ByteArrayInputStream(stringBuilder.toString().getBytes()));
  226 + }
210 227 return xml.getRootElement();
211 228 }
212 229  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
... ... @@ -72,7 +72,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
72 72 private IVideoManagerStorage storager;
73 73  
74 74 @Autowired
75   - private ZLMServerFactory ZLMServerFactory;
  75 + private ZLMServerFactory zlmServerFactory;
76 76  
77 77 @Autowired
78 78 private SSRCFactory ssrcFactory;
... ... @@ -125,7 +125,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
125 125 MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
126 126 redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(),
127 127 callIdHeader.getCallId(), null);
128   - ZLMServerFactory.stopSendRtpStream(mediaInfo, param);
  128 + zlmServerFactory.stopSendRtpStream(mediaInfo, param);
129 129 if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
130 130 ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
131 131 if (platform != null) {
... ... @@ -139,7 +139,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
139 139 }
140 140 }
141 141  
142   - int totalReaderCount = ZLMServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
  142 + int totalReaderCount = zlmServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
143 143 if (totalReaderCount <= 0) {
144 144 logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
145 145 if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
... ... @@ -485,18 +485,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
485 485 errorEvent.run(code, msg, data);
486 486 }
487 487 });
488   - } else {
489   - sendRtpItem.setPlayType(InviteStreamType.PLAY);
490   - String streamId = null;
491   - if (mediaServerItem.isRtpEnable()) {
492   - streamId = String.format("%s_%s", device.getDeviceId(), channelId);
493   - } else {
494   - streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
495   - }
496   - sendRtpItem.setStream(streamId);
497   - redisCatchStorage.updateSendRTPSever(sendRtpItem);
498   - playService.play(mediaServerItem, device.getDeviceId(), channelId, ((code, msg, data) -> {
499   - if (code == InviteErrorCode.SUCCESS.getCode()) {
  488 + }else {
  489 +
  490 + SSRCInfo ssrcInfo = playService.play(mediaServerItem, device.getDeviceId(), channelId, ssrc, ((code, msg, data) -> {
  491 + if (code == InviteErrorCode.SUCCESS.getCode()){
500 492 hookEvent.run(code, msg, data);
501 493 } else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()) {
502 494 logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId);
... ... @@ -506,6 +498,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
506 498 errorEvent.run(code, msg, data);
507 499 }
508 500 }));
  501 + sendRtpItem.setPlayType(InviteStreamType.PLAY);
  502 + String streamId = null;
  503 + if (mediaServerItem.isRtpEnable()) {
  504 + streamId = String.format("%s_%s", device.getDeviceId(), channelId);
  505 + }else {
  506 + streamId = String.format("%08x", Integer.parseInt(ssrcInfo.getSsrc())).toUpperCase();
  507 + }
  508 + sendRtpItem.setStream(streamId);
  509 + sendRtpItem.setSsrc(ssrcInfo.getSsrc());
  510 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
509 511  
510 512 }
511 513 } else if (gbStream != null) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
... ... @@ -6,7 +6,7 @@ import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
6 6 import com.genersoft.iot.vmp.gb28181.bean.Device;
7 7 import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
8 8 import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
9   -import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
  9 +import com.genersoft.iot.vmp.gb28181.bean.GbSipDate;
10 10 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
11 11 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
12 12 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
... ... @@ -148,8 +148,8 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
148 148 // 添加date头
149 149 SIPDateHeader dateHeader = new SIPDateHeader();
150 150 // 使用自己修改的
151   - WvpSipDate wvpSipDate = new WvpSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
152   - dateHeader.setDate(wvpSipDate);
  151 + GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
  152 + dateHeader.setDate(gbSipDate);
153 153 response.addHeader(dateHeader);
154 154  
155 155 if (request.getExpires() == null) {
... ... @@ -169,7 +169,18 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
169 169 device.setGeoCoordSys("WGS84");
170 170 device.setDeviceId(deviceId);
171 171 device.setOnLine(false);
  172 + }else {
  173 + if (ObjectUtils.isEmpty(device.getStreamMode())) {
  174 + device.setStreamMode("UDP");
  175 + }
  176 + if (ObjectUtils.isEmpty(device.getCharset())) {
  177 + device.setCharset("GB2312");
  178 + }
  179 + if (ObjectUtils.isEmpty(device.getGeoCoordSys())) {
  180 + device.setGeoCoordSys("WGS84");
  181 + }
172 182 }
  183 +
173 184 device.setIp(remoteAddressInfo.getIp());
174 185 device.setPort(remoteAddressInfo.getPort());
175 186 device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
... ... @@ -210,8 +221,8 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
210 221 // 添加date头
211 222 SIPDateHeader dateHeader = new SIPDateHeader();
212 223 // 使用自己修改的
213   - WvpSipDate wvpSipDate = new WvpSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
214   - dateHeader.setDate(wvpSipDate);
  224 + GbSipDate gbSipDate = new GbSipDate(Calendar.getInstance(Locale.ENGLISH).getTimeInMillis());
  225 + dateHeader.setDate(gbSipDate);
215 226 response.addHeader(dateHeader);
216 227  
217 228 // 添加Contact头
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
... ... @@ -78,7 +78,9 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
78 78 device.setKeepaliveIntervalTime(60);
79 79 }else {
80 80 long lastTime = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(device.getKeepaliveTime());
81   - device.setKeepaliveIntervalTime(new Long(System.currentTimeMillis()/1000-lastTime).intValue());
  81 + if (System.currentTimeMillis()/1000-lastTime > 10) {
  82 + device.setKeepaliveIntervalTime(new Long(System.currentTimeMillis()/1000-lastTime).intValue());
  83 + }
82 84 }
83 85  
84 86 device.setKeepaliveTime(DateUtil.getNow());
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
... ... @@ -380,13 +380,18 @@ public class XmlUtil {
380 380 }
381 381 }
382 382 // 父设备/区域/系统ID
383   - String realParentId = parentID;
384   - if (!ObjectUtils.isEmpty(parentID)) {
  383 +
  384 + if (!ObjectUtils.isEmpty(parentID) ) {
385 385 if (parentID.contains("/")) {
386 386 String[] parentIdArray = parentID.split("/");
387   - realParentId = parentIdArray[parentIdArray.length - 1];
  387 + deviceChannel.setParentId(parentIdArray[parentIdArray.length - 1]);
  388 + }else {
  389 + if (parentID.length()%2 == 0) {
  390 + deviceChannel.setParentId(parentID);
  391 + }else {
  392 + logger.warn("[xml解析] 不规范的parentID:{}, 已舍弃", parentID);
  393 + }
388 394 }
389   - deviceChannel.setParentId(realParentId);
390 395 }else {
391 396 if (!ObjectUtils.isEmpty(businessGroupID)) {
392 397 deviceChannel.setParentId(businessGroupID);
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/SendRtpPortManager.java
1 1 package com.genersoft.iot.vmp.media.zlm;
2 2  
  3 +import com.genersoft.iot.vmp.common.VideoManagerConstants;
3 4 import com.genersoft.iot.vmp.conf.UserSetting;
  5 +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
4 6 import com.genersoft.iot.vmp.media.zlm.dto.MediaSendRtpPortInfo;
  7 +import com.genersoft.iot.vmp.utils.redis.RedisUtil;
5 8 import org.slf4j.Logger;
6 9 import org.slf4j.LoggerFactory;
7 10 import org.springframework.beans.factory.annotation.Autowired;
8 11 import org.springframework.data.redis.core.RedisTemplate;
9 12 import org.springframework.stereotype.Component;
10 13  
  14 +import java.util.HashMap;
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +
11 18 @Component
12 19 public class SendRtpPortManager {
13 20  
... ... @@ -29,27 +36,55 @@ public class SendRtpPortManager {
29 36 }
30 37  
31 38 public int getNextPort(String mediaServerId) {
32   - String key = KEY + userSetting.getServerId() + "_" + mediaServerId;
33   - MediaSendRtpPortInfo mediaSendRtpPortInfo = (MediaSendRtpPortInfo)redisTemplate.opsForValue().get(key);
  39 + String sendIndexKey = KEY + userSetting.getServerId() + "_" + mediaServerId;
  40 + MediaSendRtpPortInfo mediaSendRtpPortInfo = (MediaSendRtpPortInfo)redisTemplate.opsForValue().get(sendIndexKey);
34 41 if (mediaSendRtpPortInfo == null) {
35   - logger.warn("[发送端口管理] 获取{}的发送端口时未找到端口信息", mediaSendRtpPortInfo);
  42 + logger.warn("[发送端口管理] 获取{}的发送端口时未找到端口信息", mediaServerId);
36 43 return 0;
37 44 }
  45 +
  46 + String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX
  47 + + userSetting.getServerId() + "_*";
  48 + List<Object> queryResult = RedisUtil.scan(redisTemplate, key);
  49 + Map<Integer, SendRtpItem> sendRtpItemMap = new HashMap<>();
  50 +
  51 + for (Object o : queryResult) {
  52 + SendRtpItem sendRtpItem = (SendRtpItem) redisTemplate.opsForValue().get(o);
  53 + if (sendRtpItem != null) {
  54 + sendRtpItemMap.put(sendRtpItem.getLocalPort(), sendRtpItem);
  55 + }
  56 + }
  57 +
  58 + int port = getPort(mediaSendRtpPortInfo.getCurrent(),
  59 + mediaSendRtpPortInfo.getStart(),
  60 + mediaSendRtpPortInfo.getEnd(), checkPort -> sendRtpItemMap.get(checkPort) == null);
  61 +
  62 + mediaSendRtpPortInfo.setCurrent(port);
  63 + redisTemplate.opsForValue().set(sendIndexKey, mediaSendRtpPortInfo);
  64 + return port;
  65 + }
  66 +
  67 + interface CheckPortCallback{
  68 + boolean check(int port);
  69 + }
  70 +
  71 + private int getPort(int current, int start, int end, CheckPortCallback checkPortCallback) {
38 72 int port;
39   - if (mediaSendRtpPortInfo.getCurrent() %2 != 0) {
40   - port = mediaSendRtpPortInfo.getCurrent() + 1;
  73 + if (current %2 != 0) {
  74 + port = current + 1;
41 75 }else {
42   - port = mediaSendRtpPortInfo.getCurrent() + 2;
  76 + port = current + 2;
43 77 }
44   - if (port > mediaSendRtpPortInfo.getEnd()) {
45   - if (mediaSendRtpPortInfo.getStart() %2 != 0) {
46   - port = mediaSendRtpPortInfo.getStart() + 1;
  78 + if (port > end) {
  79 + if (start %2 != 0) {
  80 + port = start + 1;
47 81 }else {
48   - port = mediaSendRtpPortInfo.getStart();
  82 + port = start;
49 83 }
50 84 }
51   - mediaSendRtpPortInfo.setCurrent(port);
52   - redisTemplate.opsForValue().set(key, mediaSendRtpPortInfo);
  85 + if (!checkPortCallback.check(port)) {
  86 + return getPort(port, start, end, checkPortCallback);
  87 + }
53 88 return port;
54 89 }
55 90 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -5,6 +5,7 @@ import com.alibaba.fastjson2.JSONObject;
5 5 import com.genersoft.iot.vmp.common.InviteInfo;
6 6 import com.genersoft.iot.vmp.common.InviteSessionType;
7 7 import com.genersoft.iot.vmp.common.StreamInfo;
  8 +import com.genersoft.iot.vmp.common.VideoManagerConstants;
8 9 import com.genersoft.iot.vmp.conf.UserSetting;
9 10 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
10 11 import com.genersoft.iot.vmp.gb28181.bean.*;
... ... @@ -27,11 +28,13 @@ import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
27 28 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
28 29 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
29 30 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
  31 +import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo;
30 32 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
31 33 import org.slf4j.Logger;
32 34 import org.slf4j.LoggerFactory;
33 35 import org.springframework.beans.factory.annotation.Autowired;
34 36 import org.springframework.beans.factory.annotation.Qualifier;
  37 +import org.springframework.data.redis.core.RedisTemplate;
35 38 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
36 39 import org.springframework.util.ObjectUtils;
37 40 import org.springframework.web.bind.annotation.*;
... ... @@ -124,6 +127,9 @@ public class ZLMHttpHookListener {
124 127 @Autowired
125 128 private ThreadPoolTaskExecutor taskExecutor;
126 129  
  130 + @Autowired
  131 + private RedisTemplate<Object, Object> redisTemplate;
  132 +
127 133 /**
128 134 * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
129 135 */
... ... @@ -232,10 +238,7 @@ public class ZLMHttpHookListener {
232 238  
233 239  
234 240 HookResultForOnPublish result = HookResultForOnPublish.SUCCESS();
235   - if (!"rtp".equals(param.getApp())) {
236   - result.setEnable_audio(true);
237   - }
238   -
  241 + result.setEnable_audio(true);
239 242 taskExecutor.execute(() -> {
240 243 ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
241 244 if (subscribe != null) {
... ... @@ -264,7 +267,6 @@ public class ZLMHttpHookListener {
264 267 // 如果是录像下载就设置视频间隔十秒
265 268 if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
266 269 result.setMp4_max_second(10);
267   - result.setEnable_audio(true);
268 270 result.setEnable_mp4(true);
269 271 }
270 272 // 如果是talk对讲,则默认获取声音
... ... @@ -291,6 +293,14 @@ public class ZLMHttpHookListener {
291 293 }
292 294 }
293 295 }
  296 + if (param.getApp().equalsIgnoreCase("rtp")) {
  297 + String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream();
  298 + OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo)redisTemplate.opsForValue().get(receiveKey);
  299 + if (otherRtpSendInfo != null) {
  300 + result.setEnable_mp4(true);
  301 + }
  302 + }
  303 + logger.info("[ZLM HOOK]推流鉴权 响应:{}->{}->>>>{}", param.getMediaServerId(), param, result);
294 304 return result;
295 305 }
296 306  
... ... @@ -522,8 +532,6 @@ public class ZLMHttpHookListener {
522 532 if ("rtp".equals(param.getApp())) {
523 533 ret.put("close", userSetting.getStreamOnDemand());
524 534 // 国标流, 点播/录像回放/录像下载
525   -// StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(param.getStream());
526   -
527 535 InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
528 536 // 点播
529 537 if (inviteInfo != null) {
... ... @@ -588,7 +596,7 @@ public class ZLMHttpHookListener {
588 596 // 拉流代理
589 597 StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
590 598 if (streamProxyItem != null) {
591   - if (streamProxyItem.isEnableDisableNoneReader()) {
  599 + if (streamProxyItem.isEnableRemoveNoneReader()) {
592 600 // 无人观看自动移除
593 601 ret.put("close", true);
594 602 streamProxyService.del(param.getApp(), param.getStream());
... ... @@ -670,7 +678,7 @@ public class ZLMHttpHookListener {
670 678 resultHolder.put(key, uuid, result);
671 679  
672 680 if (!exist) {
673   - playService.play(mediaInfo, deviceId, channelId, (code, message, data) -> {
  681 + playService.play(mediaInfo, deviceId, channelId, null, (code, message, data) -> {
674 682 msg.setData(new HookResult(code, message));
675 683 resultHolder.invokeResult(msg);
676 684 });
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
... ... @@ -61,7 +61,7 @@ public class ZLMMediaListManager {
61 61 private UserSetting userSetting;
62 62  
63 63 @Autowired
64   - private ZLMServerFactory ZLMServerFactory;
  64 + private ZLMServerFactory zlmServerFactory;
65 65  
66 66 @Autowired
67 67 private IMediaServerService mediaServerService;
... ... @@ -97,7 +97,7 @@ public class ZLMMediaListManager {
97 97 public void sendStreamEvent(String app, String stream, String mediaServerId) {
98 98 MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
99 99 // 查看推流状态
100   - Boolean streamReady = ZLMServerFactory.isStreamReady(mediaServerItem, app, stream);
  100 + Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, app, stream);
101 101 if (streamReady != null && streamReady) {
102 102 ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(app, stream);
103 103 if (channelOnlineEventLister != null) {
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java
... ... @@ -86,10 +86,13 @@ public class ZLMServerFactory {
86 86 }else {
87 87 param.put("port", port);
88 88 }
89   - param.put("ssrc", ssrc);
90 89 if (onlyAuto != null) {
91 90 param.put("only_audio", onlyAuto?"1":"0");
92 91 }
  92 + if (ssrc != 0) {
  93 + param.put("ssrc", ssrc);
  94 + }
  95 +
93 96 JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
94 97 logger.info(JSONObject.toJSONString(openRtpServerResultJson));
95 98 if (openRtpServerResultJson != null) {
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookType.java
... ... @@ -21,5 +21,6 @@ public enum HookType {
21 21 on_server_started,
22 22  
23 23 on_rtp_server_timeout,
24   - on_server_keepalive
  24 + on_server_keepalive,
  25 + on_send_rtp_stopped
25 26 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java
... ... @@ -51,5 +51,13 @@ public class HookResultForOnPublish extends HookResult{
51 51 this.mp4_save_path = mp4_save_path;
52 52 }
53 53  
54   -
  54 + @Override
  55 + public String toString() {
  56 + return "HookResultForOnPublish{" +
  57 + "enable_audio=" + enable_audio +
  58 + ", enable_mp4=" + enable_mp4 +
  59 + ", mp4_max_second=" + mp4_max_second +
  60 + ", mp4_save_path='" + mp4_save_path + '\'' +
  61 + '}';
  62 + }
55 63 }
... ...
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
... ... @@ -27,7 +27,7 @@ public interface IPlayService {
27 27  
28 28 void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
29 29 ErrorCallback<Object> callback);
30   - SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, ErrorCallback<Object> callback);
  30 + SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback);
31 31  
32 32 StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId);
33 33  
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
... ... @@ -518,8 +518,12 @@ public class DeviceServiceImpl implements IDeviceService {
518 518 if (!ObjectUtils.isEmpty(device.getMediaServerId())) {
519 519 deviceInStore.setMediaServerId(device.getMediaServerId());
520 520 }
521   - deviceInStore.setSdpIp(device.getSdpIp());
522   - deviceInStore.setCharset(device.getCharset());
  521 + if (!ObjectUtils.isEmpty(device.getCharset())) {
  522 + deviceInStore.setCharset(device.getCharset());
  523 + }
  524 + if (!ObjectUtils.isEmpty(device.getSdpIp())) {
  525 + deviceInStore.setSdpIp(device.getSdpIp());
  526 + }
523 527  
524 528 // 目录订阅相关的信息
525 529 if (device.getSubscribeCycleForCatalog() > 0) {
... ... @@ -550,10 +554,18 @@ public class DeviceServiceImpl implements IDeviceService {
550 554 removeMobilePositionSubscribe(deviceInStore);
551 555 }
552 556 }
553   - // 坐标系变化,需要重新计算GCJ02坐标和WGS84坐标
554   - if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) {
555   - updateDeviceChannelGeoCoordSys(device);
  557 + if (deviceInStore.getGeoCoordSys() != null) {
  558 + // 坐标系变化,需要重新计算GCJ02坐标和WGS84坐标
  559 + if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) {
  560 + updateDeviceChannelGeoCoordSys(device);
  561 + }
  562 + }else {
  563 + device.setGeoCoordSys("WGS84");
556 564 }
  565 + if (device.getCharset() == null) {
  566 + device.setCharset("GB2312");
  567 + }
  568 +
557 569 // 更新redis
558 570 redisCatchStorage.updateDevice(device);
559 571 deviceMapper.updateCustom(device);
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
... ... @@ -183,7 +183,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
183 183 }
184 184 int rtpServerPort;
185 185 if (mediaServerItem.isRtpEnable()) {
186   - rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port, onlyAuto, reUsePort, tcpMode);
  186 + rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port, reUsePort, tcpMode);
187 187 } else {
188 188 rtpServerPort = mediaServerItem.getRtpProxyPort();
189 189 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
... ... @@ -100,7 +100,7 @@ public class PlayServiceImpl implements IPlayService {
100 100 private ZLMRESTfulUtils zlmresTfulUtils;
101 101  
102 102 @Autowired
103   - private ZLMServerFactory zlmserverfactory;
  103 + private ZLMServerFactory zlmServerFactory;
104 104  
105 105 @Autowired
106 106 private AssistRESTfulUtils assistRESTfulUtils;
... ... @@ -148,7 +148,7 @@ public class PlayServiceImpl implements IPlayService {
148 148  
149 149  
150 150 @Override
151   - public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, ErrorCallback<Object> callback) {
  151 + public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
152 152 if (mediaServerItem == null) {
153 153 throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
154 154 }
... ... @@ -174,7 +174,7 @@ public class PlayServiceImpl implements IPlayService {
174 174 String mediaServerId = streamInfo.getMediaServerId();
175 175 MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
176 176  
177   - Boolean ready = zlmserverfactory.isStreamReady(mediaInfo, "rtp", streamId);
  177 + Boolean ready = zlmServerFactory.isStreamReady(mediaInfo, "rtp", streamId);
178 178 if (ready != null && ready) {
179 179 callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
180 180 inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
... ... @@ -445,18 +445,7 @@ public class PlayServiceImpl implements IPlayService {
445 445 streamInfo);
446 446 logger.info("[点播成功] deviceId: {}, channelId:{}, 码流类型:{}", device.getDeviceId(),
447 447 device.isSwitchPrimarySubStream() ? "辅码流" : "主码流");
448   - String streamUrl;
449   - if (mediaServerItemInuse.getRtspPort() != 0) {
450   - streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", ssrcInfo.getStream());
451   - }else {
452   - streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServerItemInuse.getHttpPort(), "rtp", ssrcInfo.getStream());
453   - }
454   - String path = "snap";
455   - String fileName = device.getDeviceId() + "_" + channelId + ".jpg";
456   - // 请求截图
457   - logger.info("[请求截图]: " + fileName);
458   - zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
459   -
  448 + snapOnPlay(mediaServerItemInuse, device.getDeviceId(), channelId, ssrcInfo.getStream());
460 449 }, (event) -> {
461 450 inviteInfo.setStatus(InviteSessionStatus.ok);
462 451  
... ... @@ -539,6 +528,7 @@ public class PlayServiceImpl implements IPlayService {
539 528 InviteErrorCode.SUCCESS.getCode(),
540 529 InviteErrorCode.SUCCESS.getMsg(),
541 530 streamInfo);
  531 + snapOnPlay(mediaServerItemInUse, device.getDeviceId(), channelId, stream);
542 532 });
543 533 return;
544 534 }
... ... @@ -614,11 +604,33 @@ public class PlayServiceImpl implements IPlayService {
614 604 }
615 605 }
616 606  
617   - @Override
618   - public StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) {
619   - OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam) hookParam;
620   - StreamInfo streamInfo = onPublishHandler(mediaServerItem, streamChangedHookParam, deviceId, channelId);
  607 + /**
  608 + * 点播成功时调用截图.
  609 + *
  610 + * @param mediaServerItemInuse media
  611 + * @param deviceId 设备 ID
  612 + * @param channelId 通道 ID
  613 + * @param stream ssrc
  614 + */
  615 + private void snapOnPlay(MediaServerItem mediaServerItemInuse, String deviceId, String channelId, String stream) {
  616 + String streamUrl;
  617 + if (mediaServerItemInuse.getRtspPort() != 0) {
  618 + streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", stream);
  619 + } else {
  620 + streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.mp4", mediaServerItemInuse.getHttpPort(), "rtp", stream);
  621 + }
  622 + String path = "snap";
  623 + String fileName = deviceId + "_" + channelId + ".jpg";
  624 + // 请求截图
  625 + logger.info("[请求截图]: " + fileName);
  626 + zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
  627 + }
  628 +
  629 + private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) {
  630 + StreamInfo streamInfo = null;
621 631 Device device = redisCatchStorage.getDevice(deviceId);
  632 + OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
  633 + streamInfo = onPublishHandler(mediaServerItem, streamChangedHookParam, deviceId, channelId);
622 634 if (streamInfo != null) {
623 635 DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
624 636 if (deviceChannel != null) {
... ... @@ -1656,7 +1668,7 @@ public class PlayServiceImpl implements IPlayService {
1656 1668 }
1657 1669  
1658 1670 MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
1659   - play(newMediaServerItem, deviceId, channelId, (code, msg, data)->{
  1671 + play(newMediaServerItem, deviceId, channelId, null, (code, msg, data)->{
1660 1672 if (code == InviteErrorCode.SUCCESS.getCode()) {
1661 1673 InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
1662 1674 if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) {
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
... ... @@ -4,14 +4,13 @@ import com.alibaba.fastjson2.JSONArray;
4 4 import com.alibaba.fastjson2.JSONObject;
5 5 import com.genersoft.iot.vmp.common.GeneralCallback;
6 6 import com.genersoft.iot.vmp.common.StreamInfo;
  7 +import com.genersoft.iot.vmp.conf.DynamicTask;
7 8 import com.genersoft.iot.vmp.conf.UserSetting;
8 9 import com.genersoft.iot.vmp.conf.exception.ControllerException;
9 10 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
10 11 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
11 12 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
12 13 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
13   -import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
14   -import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
15 14 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
16 15 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
17 16 import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
... ... @@ -41,6 +40,7 @@ import org.springframework.util.ObjectUtils;
41 40 import java.util.HashMap;
42 41 import java.util.List;
43 42 import java.util.Map;
  43 +import java.util.UUID;
44 44  
45 45 /**
46 46 * 视频代理业务
... ... @@ -87,6 +87,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
87 87 private ZlmHttpHookSubscribe hookSubscribe;
88 88  
89 89 @Autowired
  90 + private DynamicTask dynamicTask;
  91 +
  92 + @Autowired
90 93 DataSourceTransactionManager dataSourceTransactionManager;
91 94  
92 95 @Autowired
... ... @@ -124,9 +127,6 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
124 127 port = mediaInfo.getRtspPort();
125 128 schemaForUri = schema;
126 129 }else if (schema.equalsIgnoreCase("flv")) {
127   - port = mediaInfo.getHttpPort();
128   - schemaForUri = "http";
129   - }else if (schema.equalsIgnoreCase("rtmp")) {
130 130 port = mediaInfo.getRtmpPort();
131 131 schemaForUri = schema;
132 132 }else {
... ... @@ -155,17 +155,28 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
155 155 return;
156 156 }
157 157  
158   - HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed(param.getApp(), param.getStream(), true, "rtsp", mediaInfo.getId());
159   - hookSubscribe.addSubscribe(hookSubscribeForStreamChange, (mediaServerItem, response) -> {
160   - StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(
161   - mediaInfo, param.getApp(), param.getStream(), null, null);
162   - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
163   - });
  158 + String talkKey = UUID.randomUUID().toString();
  159 + dynamicTask.startCron(talkKey, ()->{
  160 + StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStreamWithCheck(param.getApp(), param.getStream(), mediaInfo.getId(), false);
  161 + if (streamInfo != null) {
  162 + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
  163 + }
  164 + }, 1000);
  165 + String delayTalkKey = UUID.randomUUID().toString();
  166 + dynamicTask.startDelay(delayTalkKey, ()->{
  167 + StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStreamWithCheck(param.getApp(), param.getStream(), mediaInfo.getId(), false);
  168 + if (streamInfo != null) {
  169 + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
  170 + }else {
  171 + dynamicTask.stop(talkKey);
  172 + callback.run(ErrorCode.ERROR100.getCode(), "超时", null);
  173 + }
  174 + }, 5000);
164 175  
165 176 if (param.isEnable()) {
166 177 JSONObject jsonObject = addStreamProxyToZlm(param);
167 178 if (jsonObject != null && jsonObject.getInteger("code") == 0) {
168   - hookSubscribe.removeSubscribe(hookSubscribeForStreamChange);
  179 + dynamicTask.stop(talkKey);
169 180 StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(
170 181 mediaInfo, param.getApp(), param.getStream(), null, null);
171 182 callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
... ... @@ -292,10 +303,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
292 303 return null;
293 304 }
294 305 if ("default".equals(param.getType())){
295   - result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl(),
  306 + result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl().trim(),
296 307 param.isEnableAudio(), param.isEnableMp4(), param.getRtpType());
297 308 }else if ("ffmpeg".equals(param.getType())) {
298   - result = zlmresTfulUtils.addFFmpegSource(mediaServerItem, param.getSrcUrl(), param.getDstUrl(),
  309 + result = zlmresTfulUtils.addFFmpegSource(mediaServerItem, param.getSrcUrl().trim(), param.getDstUrl(),
299 310 param.getTimeoutMs() + "", param.isEnableAudio(), param.isEnableMp4(),
300 311 param.getFfmpegCmdKey());
301 312 }
... ...
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java
... ... @@ -72,7 +72,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
72 72 private RedisTemplate<Object, Object> redisTemplate;
73 73  
74 74 @Autowired
75   - private ZLMServerFactory ZLMServerFactory;
  75 + private ZLMServerFactory zlmServerFactory;
76 76  
77 77 @Autowired
78 78 private IMediaServerService mediaServerService;
... ... @@ -230,7 +230,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
230 230 param.put("pt", requestPushStreamMsg.getPt());
231 231 param.put("use_ps", requestPushStreamMsg.isPs() ? "1" : "0");
232 232 param.put("only_audio", requestPushStreamMsg.isOnlyAudio() ? "1" : "0");
233   - JSONObject jsonObject = ZLMServerFactory.startSendRtpStream(mediaInfo, param);
  233 + JSONObject jsonObject = zlmServerFactory.startSendRtpStream(mediaInfo, param);
234 234 // 回复消息
235 235 responsePushStream(jsonObject, fromId, serial);
236 236 }
... ... @@ -267,7 +267,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
267 267 return;
268 268 }
269 269 // 确定流是否在线
270   - Boolean streamReady = ZLMServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream());
  270 + Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream());
271 271 if (streamReady != null && streamReady) {
272 272 logger.info("[回复推流信息] {}/{}", content.getApp(), content.getStream());
273 273 responseSendItem(mediaServerItem, content, toId, serial);
... ... @@ -311,7 +311,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
311 311 * 将获取到的sendItem发送出去
312 312 */
313 313 private void responseSendItem(MediaServerItem mediaServerItem, RequestSendItemMsg content, String toId, String serial) {
314   - SendRtpItem sendRtpItem = ZLMServerFactory.createSendRtpItem(mediaServerItem, content.getIp(),
  314 + SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, content.getIp(),
315 315 content.getPort(), content.getSsrc(), content.getPlatformId(),
316 316 content.getApp(), content.getStream(), content.getChannelId(),
317 317 content.getTcp(), content.getRtcp());
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/bean/OtherRtpSendInfo.java
... ... @@ -5,12 +5,17 @@ public class OtherRtpSendInfo {
5 5 /**
6 6 * 发流IP
7 7 */
8   - private String ip;
  8 + private String sendLocalIp;
9 9  
10 10 /**
11   - * 发流端口
  11 + * 音频发流端口
12 12 */
13   - private int port;
  13 + private int sendLocalPortForAudio;
  14 +
  15 + /**
  16 + * 视频发流端口
  17 + */
  18 + private int sendLocalPortForVideo;
14 19  
15 20 /**
16 21 * 收流IP
... ... @@ -18,9 +23,14 @@ public class OtherRtpSendInfo {
18 23 private String receiveIp;
19 24  
20 25 /**
21   - * 收流端口
  26 + * 音频收流端口
  27 + */
  28 + private int receivePortForAudio;
  29 +
  30 + /**
  31 + * 视频收流端口
22 32 */
23   - private int receivePort;
  33 + private int receivePortForVideo;
24 34  
25 35 /**
26 36 * 会话ID
... ... @@ -48,23 +58,6 @@ public class OtherRtpSendInfo {
48 58 private String pushSSRC;
49 59  
50 60  
51   -
52   - public String getIp() {
53   - return ip;
54   - }
55   -
56   - public void setIp(String ip) {
57   - this.ip = ip;
58   - }
59   -
60   - public int getPort() {
61   - return port;
62   - }
63   -
64   - public void setPort(int port) {
65   - this.port = port;
66   - }
67   -
68 61 public String getReceiveIp() {
69 62 return receiveIp;
70 63 }
... ... @@ -73,12 +66,20 @@ public class OtherRtpSendInfo {
73 66 this.receiveIp = receiveIp;
74 67 }
75 68  
76   - public int getReceivePort() {
77   - return receivePort;
  69 + public int getReceivePortForAudio() {
  70 + return receivePortForAudio;
  71 + }
  72 +
  73 + public void setReceivePortForAudio(int receivePortForAudio) {
  74 + this.receivePortForAudio = receivePortForAudio;
  75 + }
  76 +
  77 + public int getReceivePortForVideo() {
  78 + return receivePortForVideo;
78 79 }
79 80  
80   - public void setReceivePort(int receivePort) {
81   - this.receivePort = receivePort;
  81 + public void setReceivePortForVideo(int receivePortForVideo) {
  82 + this.receivePortForVideo = receivePortForVideo;
82 83 }
83 84  
84 85 public String getCallId() {
... ... @@ -121,15 +122,45 @@ public class OtherRtpSendInfo {
121 122 this.pushSSRC = pushSSRC;
122 123 }
123 124  
  125 +
  126 + public String getSendLocalIp() {
  127 + return sendLocalIp;
  128 + }
  129 +
  130 + public void setSendLocalIp(String sendLocalIp) {
  131 + this.sendLocalIp = sendLocalIp;
  132 + }
  133 +
  134 + public int getSendLocalPortForAudio() {
  135 + return sendLocalPortForAudio;
  136 + }
  137 +
  138 + public void setSendLocalPortForAudio(int sendLocalPortForAudio) {
  139 + this.sendLocalPortForAudio = sendLocalPortForAudio;
  140 + }
  141 +
  142 + public int getSendLocalPortForVideo() {
  143 + return sendLocalPortForVideo;
  144 + }
  145 +
  146 + public void setSendLocalPortForVideo(int sendLocalPortForVideo) {
  147 + this.sendLocalPortForVideo = sendLocalPortForVideo;
  148 + }
  149 +
124 150 @Override
125 151 public String toString() {
126 152 return "OtherRtpSendInfo{" +
127   - "ip='" + ip + '\'' +
128   - ", port=" + port +
  153 + "sendLocalIp='" + sendLocalIp + '\'' +
  154 + ", sendLocalPortForAudio=" + sendLocalPortForAudio +
  155 + ", sendLocalPortForVideo=" + sendLocalPortForVideo +
129 156 ", receiveIp='" + receiveIp + '\'' +
130   - ", receivePort=" + receivePort +
  157 + ", receivePortForAudio=" + receivePortForAudio +
  158 + ", receivePortForVideo=" + receivePortForVideo +
131 159 ", callId='" + callId + '\'' +
132 160 ", stream='" + stream + '\'' +
  161 + ", pushApp='" + pushApp + '\'' +
  162 + ", pushStream='" + pushStream + '\'' +
  163 + ", pushSSRC='" + pushSSRC + '\'' +
133 164 '}';
134 165 }
135 166 }
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
... ... @@ -470,7 +470,6 @@ public class DeviceQuery {
470 470 public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId, @RequestParam(required = false) String mark) {
471 471  
472 472 try {
473   -
474 473 final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + (mark == null? ".jpg": ("_" + mark + ".jpg"))).toPath());
475 474 resp.setContentType(MediaType.IMAGE_PNG_VALUE);
476 475 IOUtils.copy(in, resp.getOutputStream());
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
... ... @@ -121,7 +121,7 @@ public class PlayController {
121 121 // 录像查询以channelId作为deviceId查询
122 122 resultHolder.put(key, uuid, result);
123 123  
124   - playService.play(newMediaServerItem, deviceId, channelId, (code, msg, data) -> {
  124 + playService.play(newMediaServerItem, deviceId, channelId, null, (code, msg, data) -> {
125 125 WVPResult<StreamContent> wvpResult = new WVPResult<>();
126 126 if (code == InviteErrorCode.SUCCESS.getCode()) {
127 127 wvpResult.setCode(ErrorCode.SUCCESS.getCode());
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java
... ... @@ -11,11 +11,9 @@ import com.genersoft.iot.vmp.gb28181.bean.Device;
11 11 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
12 12 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
13 13 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
14   -import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
15 14 import com.genersoft.iot.vmp.service.IInviteStreamService;
16 15 import com.genersoft.iot.vmp.service.IPlayService;
17 16 import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
18   -import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
19 17 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
20 18 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
21 19 import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
... ... @@ -54,15 +52,9 @@ public class PlaybackController {
54 52 private SIPCommander cmder;
55 53  
56 54 @Autowired
57   - private ZLMServerFactory ZLMServerFactory;
58   -
59   - @Autowired
60 55 private IVideoManagerStorage storager;
61 56  
62 57 @Autowired
63   - private IRedisCatchStorage redisCatchStorage;
64   -
65   - @Autowired
66 58 private IInviteStreamService inviteStreamService;
67 59  
68 60 @Autowired
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/rtp/RtpController.java
... ... @@ -3,21 +3,18 @@ package com.genersoft.iot.vmp.vmanager.rtp;
3 3 import com.alibaba.fastjson2.JSONObject;
4 4 import com.genersoft.iot.vmp.common.VideoManagerConstants;
5 5 import com.genersoft.iot.vmp.conf.DynamicTask;
6   -import com.genersoft.iot.vmp.conf.SipConfig;
7 6 import com.genersoft.iot.vmp.conf.UserSetting;
8   -import com.genersoft.iot.vmp.conf.VersionInfo;
9 7 import com.genersoft.iot.vmp.conf.exception.ControllerException;
10 8 import com.genersoft.iot.vmp.media.zlm.SendRtpPortManager;
11 9 import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
12 10 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
13 11 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
14 12 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout;
  13 +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
15 14 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
16 15 import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRtpServerTimeoutHookParam;
17   -import com.genersoft.iot.vmp.service.IDeviceChannelService;
18   -import com.genersoft.iot.vmp.service.IDeviceService;
19 16 import com.genersoft.iot.vmp.service.IMediaServerService;
20   -import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  17 +import com.genersoft.iot.vmp.utils.redis.RedisUtil;
21 18 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
22 19 import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo;
23 20 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -28,16 +25,15 @@ import okhttp3.Request;
28 25 import org.slf4j.Logger;
29 26 import org.slf4j.LoggerFactory;
30 27 import org.springframework.beans.factory.annotation.Autowired;
31   -import org.springframework.beans.factory.annotation.Value;
32 28 import org.springframework.data.redis.core.RedisTemplate;
33   -import org.springframework.web.bind.annotation.GetMapping;
34   -import org.springframework.web.bind.annotation.RequestMapping;
35   -import org.springframework.web.bind.annotation.ResponseBody;
36   -import org.springframework.web.bind.annotation.RestController;
  29 +import org.springframework.util.ObjectUtils;
  30 +import org.springframework.web.bind.annotation.*;
37 31  
38 32 import java.io.IOException;
39 33 import java.util.HashMap;
  34 +import java.util.List;
40 35 import java.util.Map;
  36 +import java.util.UUID;
41 37 import java.util.concurrent.TimeUnit;
42 38  
43 39 @SuppressWarnings("rawtypes")
... ... @@ -62,36 +58,15 @@ public class RtpController {
62 58 private IMediaServerService mediaServerService;
63 59  
64 60 @Autowired
65   - private VersionInfo versionInfo;
66   -
67   - @Autowired
68   - private SipConfig sipConfig;
69   -
70   - @Autowired
71 61 private UserSetting userSetting;
72 62  
73 63 @Autowired
74   - private IDeviceService deviceService;
75   -
76   - @Autowired
77   - private IDeviceChannelService channelService;
78   -
79   - @Autowired
80 64 private DynamicTask dynamicTask;
81 65  
82   -
83 66 @Autowired
84 67 private RedisTemplate<Object, Object> redisTemplate;
85 68  
86 69  
87   - @Value("${server.port}")
88   - private int serverPort;
89   -
90   -
91   - @Autowired
92   - private IRedisCatchStorage redisCatchStorage;
93   -
94   -
95 70 @GetMapping(value = "/receive/open")
96 71 @ResponseBody
97 72 @Operation(summary = "开启收流和获取发流信息")
... ... @@ -101,7 +76,7 @@ public class RtpController {
101 76 @Parameter(name = "stream", description = "形成的流的ID", required = true)
102 77 @Parameter(name = "tcpMode", description = "收流模式, 0为UDP, 1为TCP被动", required = true)
103 78 @Parameter(name = "callBack", description = "回调地址,如果收流超时会通道回调通知,回调为get请求,参数为callId", required = true)
104   - public OtherRtpSendInfo openRtpServer(Boolean isSend, String ssrc, String callId, String stream, Integer tcpMode, String callBack) {
  79 + public OtherRtpSendInfo openRtpServer(Boolean isSend, @RequestParam(required = false)String ssrc, String callId, String stream, Integer tcpMode, String callBack) {
105 80  
106 81 logger.info("[第三方服务对接->开启收流和获取发流信息] isSend->{}, ssrc->{}, callId->{}, stream->{}, tcpMode->{}, callBack->{}",
107 82 isSend, ssrc, callId, stream, tcpMode==0?"UDP":"TCP被动", callBack);
... ... @@ -123,12 +98,16 @@ public class RtpController {
123 98 }catch (NumberFormatException e) {
124 99 throw new ControllerException(ErrorCode.ERROR100.getCode(),"ssrc格式错误");
125 100 }
126   -
127 101 }
128   - int localPort = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, false, tcpMode);
  102 + String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + callId + "_" + stream;
  103 + int localPortForVideo = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, tcpMode);
  104 + int localPortForAudio = zlmServerFactory.createRTPServer(mediaServerItem, stream + "_a" , ssrcInt, null, false, tcpMode);
  105 + if (localPortForVideo == 0 || localPortForAudio == 0) {
  106 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败");
  107 + }
129 108 // 注册回调如果rtp收流超时则通过回调发送通知
130 109 if (callBack != null) {
131   - HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, mediaServerItem.getId());
  110 + HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(stream, String.valueOf(ssrcInt), mediaServerItem.getId());
132 111 // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
133 112 hookSubscribe.addSubscribe(hookSubscribeForRtpServerTimeout,
134 113 (mediaServerItemInUse, hookParam)->{
... ... @@ -142,22 +121,33 @@ public class RtpController {
142 121 try {
143 122 client.newCall(request).execute();
144 123 } catch (IOException e) {
145   - logger.error("[开启收流和获取发流信息] 等待收流超时 callId->{}, 发送回调失败", callId, e);
  124 + logger.error("[第三方服务对接->开启收流和获取发流信息] 等待收流超时 callId->{}, 发送回调失败", callId, e);
146 125 }
  126 + hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
147 127 }
148 128 });
149 129 }
150   - String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + callId;
  130 + String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + "_" + callId;
151 131 OtherRtpSendInfo otherRtpSendInfo = new OtherRtpSendInfo();
152 132 otherRtpSendInfo.setReceiveIp(mediaServerItem.getSdpIp());
153   - otherRtpSendInfo.setReceivePort(localPort);
  133 + otherRtpSendInfo.setReceivePortForVideo(localPortForVideo);
  134 + otherRtpSendInfo.setReceivePortForAudio(localPortForAudio);
154 135 otherRtpSendInfo.setCallId(callId);
155 136 otherRtpSendInfo.setStream(stream);
  137 +
  138 + // 将信息写入redis中,以备后用
  139 + redisTemplate.opsForValue().set(receiveKey, otherRtpSendInfo);
156 140 if (isSend != null && isSend) {
157   - int port = sendRtpPortManager.getNextPort(mediaServerItem.getId());
158   - otherRtpSendInfo.setIp(mediaServerItem.getSdpIp());
159   - otherRtpSendInfo.setPort(port);
160   - logger.info("[开启收流和获取发流信息] 结果,callId->{}, {}", callId, otherRtpSendInfo);
  141 + // 预创建发流信息
  142 + int portForVideo = sendRtpPortManager.getNextPort(mediaServerItem.getId());
  143 + int portForAudio = sendRtpPortManager.getNextPort(mediaServerItem.getId());
  144 +
  145 + otherRtpSendInfo.setSendLocalIp(mediaServerItem.getSdpIp());
  146 + otherRtpSendInfo.setSendLocalPortForVideo(portForVideo);
  147 + otherRtpSendInfo.setSendLocalPortForAudio(portForAudio);
  148 + // 将信息写入redis中,以备后用
  149 + redisTemplate.opsForValue().set(key, otherRtpSendInfo, 300, TimeUnit.SECONDS);
  150 + logger.info("[第三方服务对接->开启收流和获取发流信息] 结果,callId->{}, {}", callId, otherRtpSendInfo);
161 151 }
162 152 // 将信息写入redis中,以备后用
163 153 redisTemplate.opsForValue().set(key, otherRtpSendInfo, 300, TimeUnit.SECONDS);
... ... @@ -172,25 +162,69 @@ public class RtpController {
172 162 logger.info("[第三方服务对接->关闭收流] stream->{}", stream);
173 163 MediaServerItem mediaServerItem = mediaServerService.getDefaultMediaServer();
174 164 zlmServerFactory.closeRtpServer(mediaServerItem,stream);
  165 + zlmServerFactory.closeRtpServer(mediaServerItem,stream + "_a");
  166 + String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_*_" + stream;
  167 + List<Object> scan = RedisUtil.scan(redisTemplate, receiveKey);
  168 + if (scan.size() > 0) {
  169 + for (Object key : scan) {
  170 + // 将信息写入redis中,以备后用
  171 + redisTemplate.delete(key);
  172 + }
  173 + }
175 174 }
176 175  
177 176 @GetMapping(value = "/send/start")
178 177 @ResponseBody
179 178 @Operation(summary = "发送流")
180 179 @Parameter(name = "ssrc", description = "发送流的SSRC", required = true)
181   - @Parameter(name = "ip", description = "目标IP", required = true)
182   - @Parameter(name = "port", description = "目标端口", required = true)
  180 + @Parameter(name = "dstIpForAudio", description = "目标音频收流IP", required = false)
  181 + @Parameter(name = "dstIpForVideo", description = "目标视频收流IP", required = false)
  182 + @Parameter(name = "dstPortForAudio", description = "目标音频收流端口", required = false)
  183 + @Parameter(name = "dstPortForVideo", description = "目标视频收流端口", required = false)
183 184 @Parameter(name = "app", description = "待发送应用名", required = true)
184 185 @Parameter(name = "stream", description = "待发送流Id", required = true)
185 186 @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true)
186   - @Parameter(name = "onlyAudio", description = "是否只有音频", required = true)
187 187 @Parameter(name = "isUdp", description = "是否为UDP", required = true)
188   - @Parameter(name = "streamType", description = "流类型,1为es流,2为ps流, 默认es流", required = false)
189   - public void sendRTP(String ssrc, String ip, Integer port, String app, String stream, String callId, Boolean onlyAudio, Boolean isUdp, Integer streamType) {
190   - logger.info("[第三方服务对接->发送流] ssrc->{}, ip->{}, port->{}, app->{}, stream->{}, callId->{}, onlyAudio->{}, streamType->{}",
191   - ssrc, ip, port, app, stream, callId, onlyAudio, streamType == 1? "ES":"PS");
  188 + @Parameter(name = "ptForAudio", description = "rtp的音频pt", required = false)
  189 + @Parameter(name = "ptForVideo", description = "rtp的视频pt", required = false)
  190 + public void sendRTP(String ssrc,
  191 + @RequestParam(required = false)String dstIpForAudio,
  192 + @RequestParam(required = false)String dstIpForVideo,
  193 + @RequestParam(required = false)Integer dstPortForAudio,
  194 + @RequestParam(required = false)Integer dstPortForVideo,
  195 + String app,
  196 + String stream,
  197 + String callId,
  198 + Boolean isUdp,
  199 + @RequestParam(required = false)Integer ptForAudio,
  200 + @RequestParam(required = false)Integer ptForVideo
  201 + ) {
  202 + logger.info("[第三方服务对接->发送流] " +
  203 + "ssrc->{}, \r\n" +
  204 + "dstIpForAudio->{}, \n" +
  205 + "dstIpForAudio->{}, \n" +
  206 + "dstPortForAudio->{}, \n" +
  207 + "dstPortForVideo->{}, \n" +
  208 + "app->{}, \n" +
  209 + "stream->{}, \n" +
  210 + "callId->{}, \n" +
  211 + "ptForAudio->{}, \n" +
  212 + "ptForVideo->{}",
  213 + ssrc,
  214 + dstIpForAudio,
  215 + dstIpForVideo,
  216 + dstPortForAudio,
  217 + dstPortForVideo,
  218 + app,
  219 + stream,
  220 + callId,
  221 + ptForAudio,
  222 + ptForVideo);
  223 + if (!((dstPortForAudio > 0 && !ObjectUtils.isEmpty(dstPortForAudio) || (dstPortForVideo > 0 && !ObjectUtils.isEmpty(dstIpForVideo))))) {
  224 + throw new ControllerException(ErrorCode.ERROR400.getCode(), "至少应该存在一组音频或视频发送参数");
  225 + }
192 226 MediaServerItem mediaServerItem = mediaServerService.getDefaultMediaServer();
193   - String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + callId;
  227 + String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + "_" + callId;
194 228 OtherRtpSendInfo sendInfo = (OtherRtpSendInfo)redisTemplate.opsForValue().get(key);
195 229 if (sendInfo == null) {
196 230 sendInfo = new OtherRtpSendInfo();
... ... @@ -199,32 +233,123 @@ public class RtpController {
199 233 sendInfo.setPushStream(stream);
200 234 sendInfo.setPushSSRC(ssrc);
201 235  
202   - Map<String, Object> param = new HashMap<>(12);
203   - param.put("vhost","__defaultVhost__");
204   - param.put("app",app);
205   - param.put("stream",stream);
206   - param.put("ssrc", ssrc);
207   -
208   - param.put("dst_url",ip);
209   - param.put("dst_port", port);
210   - String is_Udp = isUdp ? "1" : "0";
211   - param.put("is_udp", is_Udp);
212   - param.put("src_port", sendInfo.getPort());
213   - param.put("use_ps", streamType==2 ? "1" : "0");
214   - param.put("only_audio", onlyAudio ? "1" : "0");
215   -
216   - JSONObject jsonObject = zlmServerFactory.startSendRtpStream(mediaServerItem, param);
217   - if (jsonObject.getInteger("code") == 0) {
218   - logger.info("[第三方服务对接->发送流] 发流成功,callId->{}", callId);
219   - redisTemplate.opsForValue().set(key, sendInfo);
220   - }else {
221   - redisTemplate.delete(key);
222   - logger.info("[第三方服务对接->发送流] 发流失败,callId->{}, {}", callId, jsonObject.getString("msg"));
223   - throw new ControllerException(ErrorCode.ERROR100.getCode(), "[发流失败] " + jsonObject.getString("msg"));
  236 + Map<String, Object> paramForAudio;
  237 + Map<String, Object> paramForVideo;
  238 + if (!ObjectUtils.isEmpty(dstIpForAudio) && dstPortForAudio > 0) {
  239 + paramForAudio = new HashMap<>();
  240 + paramForAudio.put("vhost","__defaultVhost__");
  241 + paramForAudio.put("app",app);
  242 + paramForAudio.put("stream",stream);
  243 + paramForAudio.put("ssrc", ssrc);
  244 +
  245 + paramForAudio.put("dst_url", dstIpForAudio);
  246 + paramForAudio.put("dst_port", dstPortForAudio);
  247 + String is_Udp = isUdp ? "1" : "0";
  248 + paramForAudio.put("is_udp", is_Udp);
  249 + paramForAudio.put("src_port", sendInfo.getSendLocalPortForAudio());
  250 + paramForAudio.put("use_ps", "0");
  251 + paramForAudio.put("only_audio", "1");
  252 + if (ptForAudio != null) {
  253 + paramForAudio.put("pt", ptForAudio);
  254 + }
  255 +
  256 + } else {
  257 + paramForAudio = null;
  258 + }
  259 + if (!ObjectUtils.isEmpty(dstIpForVideo) && dstPortForVideo > 0) {
  260 + paramForVideo = new HashMap<>();
  261 + paramForVideo.put("vhost","__defaultVhost__");
  262 + paramForVideo.put("app",app);
  263 + paramForVideo.put("stream",stream);
  264 + paramForVideo.put("ssrc", ssrc);
  265 +
  266 + paramForVideo.put("dst_url", dstIpForVideo);
  267 + paramForVideo.put("dst_port", dstPortForVideo);
  268 + String is_Udp = isUdp ? "1" : "0";
  269 + paramForVideo.put("is_udp", is_Udp);
  270 + paramForVideo.put("src_port", sendInfo.getSendLocalPortForVideo());
  271 + paramForVideo.put("use_ps", "0");
  272 + paramForVideo.put("only_audio", "0");
  273 + if (ptForVideo != null) {
  274 + paramForVideo.put("pt", ptForVideo);
  275 + }
  276 +
  277 + } else {
  278 + paramForVideo = null;
224 279 }
225   - }
226 280  
  281 + Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, app, stream);
  282 + if (streamReady) {
  283 + if (paramForVideo != null) {
  284 + JSONObject jsonObject = zlmServerFactory.startSendRtpStream(mediaServerItem, paramForVideo);
  285 + if (jsonObject.getInteger("code") == 0) {
  286 + logger.info("[第三方服务对接->发送流] 视频流发流成功,callId->{},param->{}", callId, paramForVideo);
  287 + redisTemplate.opsForValue().set(key, sendInfo);
  288 + }else {
  289 + redisTemplate.delete(key);
  290 + logger.info("[第三方服务对接->发送流] 视频流发流失败,callId->{}, {}", callId, jsonObject.getString("msg"));
  291 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "[视频流发流失败] " + jsonObject.getString("msg"));
  292 + }
  293 + }
  294 + if(paramForAudio != null) {
  295 + JSONObject jsonObject = zlmServerFactory.startSendRtpStream(mediaServerItem, paramForAudio);
  296 + if (jsonObject.getInteger("code") == 0) {
  297 + logger.info("[第三方服务对接->发送流] 音频流发流成功,callId->{},param->{}", callId, paramForAudio);
  298 + redisTemplate.opsForValue().set(key, sendInfo);
  299 + }else {
  300 + redisTemplate.delete(key);
  301 + logger.info("[第三方服务对接->发送流] 音频流发流失败,callId->{}, {}", callId, jsonObject.getString("msg"));
  302 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "[音频流发流失败] " + jsonObject.getString("msg"));
  303 + }
  304 + }
  305 + }else {
  306 + logger.info("[第三方服务对接->发送流] 流不存在,等待流上线,callId->{}", callId);
  307 + String uuid = UUID.randomUUID().toString();
  308 + HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed(app, stream, true, "rtsp", mediaServerItem.getId());
  309 + dynamicTask.startDelay(uuid, ()->{
  310 + logger.info("[第三方服务对接->发送流] 等待流上线超时 callId->{}", callId);
  311 + redisTemplate.delete(key);
  312 + hookSubscribe.removeSubscribe(hookSubscribeForStreamChange);
  313 + }, 10000);
227 314  
  315 + // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
  316 + OtherRtpSendInfo finalSendInfo = sendInfo;
  317 + hookSubscribe.removeSubscribe(hookSubscribeForStreamChange);
  318 + hookSubscribe.addSubscribe(hookSubscribeForStreamChange,
  319 + (mediaServerItemInUse, response)->{
  320 + dynamicTask.stop(uuid);
  321 + logger.info("[第三方服务对接->发送流] 流上线,开始发流 callId->{}", callId);
  322 + try {
  323 + Thread.sleep(400);
  324 + } catch (InterruptedException e) {
  325 + throw new RuntimeException(e);
  326 + }
  327 + if (paramForVideo != null) {
  328 + JSONObject jsonObject = zlmServerFactory.startSendRtpStream(mediaServerItem, paramForVideo);
  329 + if (jsonObject.getInteger("code") == 0) {
  330 + logger.info("[第三方服务对接->发送流] 视频流发流成功,callId->{},param->{}", callId, paramForVideo);
  331 + redisTemplate.opsForValue().set(key, finalSendInfo);
  332 + }else {
  333 + redisTemplate.delete(key);
  334 + logger.info("[第三方服务对接->发送流] 视频流发流失败,callId->{}, {}", callId, jsonObject.getString("msg"));
  335 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "[视频流发流失败] " + jsonObject.getString("msg"));
  336 + }
  337 + }
  338 + if(paramForAudio != null) {
  339 + JSONObject jsonObject = zlmServerFactory.startSendRtpStream(mediaServerItem, paramForAudio);
  340 + if (jsonObject.getInteger("code") == 0) {
  341 + logger.info("[第三方服务对接->发送流] 音频流发流成功,callId->{},param->{}", callId, paramForAudio);
  342 + redisTemplate.opsForValue().set(key, finalSendInfo);
  343 + }else {
  344 + redisTemplate.delete(key);
  345 + logger.info("[第三方服务对接->发送流] 音频流发流失败,callId->{}, {}", callId, jsonObject.getString("msg"));
  346 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "[音频流发流失败] " + jsonObject.getString("msg"));
  347 + }
  348 + }
  349 + hookSubscribe.removeSubscribe(hookSubscribeForStreamChange);
  350 + });
  351 + }
  352 + }
228 353  
229 354 @GetMapping(value = "/send/stop")
230 355 @ResponseBody
... ... @@ -232,7 +357,7 @@ public class RtpController {
232 357 @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true)
233 358 public void closeSendRTP(String callId) {
234 359 logger.info("[第三方服务对接->关闭发送流] callId->{}", callId);
235   - String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + callId;
  360 + String key = VideoManagerConstants.WVP_OTHER_SEND_RTP_INFO + userSetting.getServerId() + "_" + callId;
236 361 OtherRtpSendInfo sendInfo = (OtherRtpSendInfo)redisTemplate.opsForValue().get(key);
237 362 if (sendInfo == null){
238 363 throw new ControllerException(ErrorCode.ERROR100.getCode(), "未开启发流");
... ... @@ -250,6 +375,7 @@ public class RtpController {
250 375 }else {
251 376 logger.info("[第三方服务对接->关闭发送流] 成功 callId->{}", callId);
252 377 }
  378 + redisTemplate.delete(key);
253 379 }
254 380  
255 381 }
... ...
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java
... ... @@ -114,17 +114,8 @@ public class ApiDeviceController {
114 114 @RequestParam(required = false)String q,
115 115 @RequestParam(required = false)Boolean online ){
116 116  
117   -// if (logger.isDebugEnabled()) {
118   -// logger.debug("查询所有视频设备API调用");
119   -// }
  117 +
120 118 JSONObject result = new JSONObject();
121   - // 查询设备是否存在
122   -// Device device = storager.queryVideoDevice(serial);
123   -// if (device == null) {
124   -// result.put("ChannelCount", 0);
125   -// result.put("ChannelList", "[]");
126   -// return result;
127   -// }
128 119 List<DeviceChannelExtend> deviceChannels;
129 120 List<String> channelIds = null;
130 121 if (!ObjectUtils.isEmpty(code)) {
... ...
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java
... ... @@ -122,7 +122,7 @@ public class ApiStreamController {
122 122 MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
123 123  
124 124  
125   - playService.play(newMediaServerItem, serial, code, (errorCode, msg, data) -> {
  125 + playService.play(newMediaServerItem, serial, code, null, (errorCode, msg, data) -> {
126 126 if (errorCode == InviteErrorCode.SUCCESS.getCode()) {
127 127 InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, serial, code);
128 128 if (inviteInfo != null && inviteInfo.getStreamInfo() != null) {
... ...
src/main/resources/all-application.yml
... ... @@ -145,6 +145,8 @@ media:
145 145 enable: true
146 146 # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功
147 147 port-range: 30000,30500 # 端口范围
  148 + # [可选] 国标级联在此范围内选择端口发送媒体流
  149 + send-port-range: 30000,30500 # 端口范围
148 150 # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
149 151 record-assist-port: 0
150 152  
... ...
src/main/resources/wvpssl.jks deleted 100644 → 0
No preview for this file type