Commit a77628e8759901abc3219412fc2c4aced940db28

Authored by 648540858
1 parent ed035b74

优化端口预占用,防止占用无法释放

src/main/java/com/genersoft/iot/vmp/gb28181/bean/Gb28181Sdp.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.bean;
  2 +
  3 +import javax.sdp.SessionDescription;
  4 +
  5 +/**
  6 + * 28181 的SDP解析器
  7 + */
  8 +public class Gb28181Sdp {
  9 + private SessionDescription baseSdb;
  10 + private String ssrc;
  11 +
  12 + private String mediaDescription;
  13 +
  14 + public static Gb28181Sdp getInstance(SessionDescription baseSdb, String ssrc, String mediaDescription) {
  15 + Gb28181Sdp gb28181Sdp = new Gb28181Sdp();
  16 + gb28181Sdp.setBaseSdb(baseSdb);
  17 + gb28181Sdp.setSsrc(ssrc);
  18 + gb28181Sdp.setMediaDescription(mediaDescription);
  19 + return gb28181Sdp;
  20 + }
  21 +
  22 +
  23 + public SessionDescription getBaseSdb() {
  24 + return baseSdb;
  25 + }
  26 +
  27 + public void setBaseSdb(SessionDescription baseSdb) {
  28 + this.baseSdb = baseSdb;
  29 + }
  30 +
  31 + public String getSsrc() {
  32 + return ssrc;
  33 + }
  34 +
  35 + public void setSsrc(String ssrc) {
  36 + this.ssrc = ssrc;
  37 + }
  38 +
  39 + public String getMediaDescription() {
  40 + return mediaDescription;
  41 + }
  42 +
  43 + public void setMediaDescription(String mediaDescription) {
  44 + this.mediaDescription = mediaDescription;
  45 + }
  46 +}
0 47 \ No newline at end of file
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
... ... @@ -241,21 +241,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
241 241 // 解析sdp消息, 使用jainsip 自带的sdp解析方式
242 242 String contentString = new String(request.getRawContent());
243 243  
244   - // jainSip不支持y=字段, 移除以解析。
245   - int ssrcIndex = contentString.indexOf("y=");
246   - // 检查是否有y字段
247   - String ssrcDefault = "0000000000";
248   - String ssrc;
249   - SessionDescription sdp;
250   - if (ssrcIndex >= 0) {
251   - //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段
252   - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
253   - String substring = contentString.substring(0, contentString.indexOf("y="));
254   - sdp = SdpFactory.getInstance().createSessionDescription(substring);
255   - } else {
256   - ssrc = ssrcDefault;
257   - sdp = SdpFactory.getInstance().createSessionDescription(contentString);
258   - }
  244 + Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
  245 + SessionDescription sdp = gb28181Sdp.getBaseSdb();
259 246 String sessionName = sdp.getSessionName().getValue();
260 247  
261 248 Long startTime = null;
... ... @@ -317,7 +304,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
317 304 String username = sdp.getOrigin().getUsername();
318 305 String addressStr = sdp.getConnection().getAddress();
319 306  
320   - logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc);
  307 +
321 308 Device device = null;
322 309 // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标
323 310 if (channel != null) {
... ... @@ -341,8 +328,30 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
341 328 }
342 329 return;
343 330 }
  331 +
  332 + String ssrc;
  333 + if (gb28181Sdp.getSsrc() == null) {
  334 + // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式
  335 + ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
  336 + logger.warn("[上级Invite] {} 平台:{}, 通道:{}, 缺少 ssrc,补充为: {}", sessionName, username, channelId, ssrc);
  337 + }else {
  338 + ssrc = gb28181Sdp.getSsrc();
  339 + }
  340 + String streamTypeStr = null;
  341 + if (mediaTransmissionTCP) {
  342 + if (tcpActive) {
  343 + streamTypeStr = "TCP-ACTIVE";
  344 + }else {
  345 + streamTypeStr = "TCP-PASSIVE";
  346 + }
  347 + }else {
  348 + streamTypeStr = "UDP";
  349 + }
  350 + logger.info("[上级Invite] {}, 平台:{}, 通道:{}, 收流地址:{}:{},收流方式:{}, ssrc:{}", sessionName, username, channelId, addressStr, port, streamTypeStr, ssrc);
344 351 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
345   - device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp());
  352 + device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback -> {
  353 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  354 + });
346 355  
347 356 if (tcpActive != null) {
348 357 sendRtpItem.setTcpActive(tcpActive);
... ... @@ -469,7 +478,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
469 478 SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam());
470 479 logger.info(JSONObject.toJSONString(ssrcInfo));
471 480 sendRtpItem.setStreamId(ssrcInfo.getStream());
472   - sendRtpItem.setSsrc(ssrc.equals(ssrcDefault) ? ssrcInfo.getSsrc() : ssrc);
  481 + sendRtpItem.setSsrc(ssrc);
473 482  
474 483 // 写入redis, 超时时回复
475 484 redisCatchStorage.updateSendRTPSever(sendRtpItem);
... ... @@ -480,12 +489,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
480 489 });
481 490 } else {
482 491 // 当前系统作为下级平台使用,当上级平台点播时不携带ssrc时,并且设备在当前系统中已经点播了。这个时候需要重新给生成一个ssrc,不使用默认的"0000000000"。
483   - if (ssrc.equals(ssrcDefault)) {
484   - ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId());
485   - ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc);
486   - sendRtpItem.setSsrc(ssrc);
487   - }
488   -
  492 + sendRtpItem.setSsrc(ssrc);
489 493 sendRtpItem.setStreamId(playTransaction.getStream());
490 494 // 写入redis, 超时时回复
491 495 redisCatchStorage.updateSendRTPSever(sendRtpItem);
... ... @@ -496,11 +500,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
496 500 }
497 501 }
498 502 } else if (gbStream != null) {
499   - if(ssrc.equals(ssrcDefault))
500   - {
501   - ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId());
502   - ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc);
  503 +
  504 + String ssrc;
  505 + if (gb28181Sdp.getSsrc() == null) {
  506 + // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式
  507 + ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
  508 + }else {
  509 + ssrc = gb28181Sdp.getSsrc();
503 510 }
  511 +
504 512 if("push".equals(gbStream.getStreamType())) {
505 513 if (streamPushItem != null && streamPushItem.isPushIng()) {
506 514 // 推流状态
... ... @@ -545,7 +553,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
545 553 if (streamReady) {
546 554 // 自平台内容
547 555 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
548   - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
  556 + gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback ->{
  557 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  558 + });
549 559  
550 560 if (sendRtpItem == null) {
551 561 logger.warn("服务器端口资源不足");
... ... @@ -584,7 +594,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
584 594 if (streamReady) {
585 595 // 自平台内容
586 596 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
587   - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
  597 + gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback ->{
  598 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  599 + });
588 600  
589 601 if (sendRtpItem == null) {
590 602 logger.warn("服务器端口资源不足");
... ... @@ -701,7 +713,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
701 713 dynamicTask.stop(callIdHeader.getCallId());
702 714 if (serverId.equals(userSetting.getServerId())) {
703 715 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId,
704   - app, stream, channelId, mediaTransmissionTCP, platform.isRtcp());
  716 + app, stream, channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback -> {
  717 + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null;
  718 + });
705 719  
706 720 if (sendRtpItem == null) {
707 721 logger.warn("上级点时创建sendRTPItem失败,可能是服务器端口资源不足");
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
1 1 package com.genersoft.iot.vmp.gb28181.utils;
2 2  
3 3 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
  4 +import com.genersoft.iot.vmp.gb28181.bean.Gb28181Sdp;
4 5 import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
  6 +import com.genersoft.iot.vmp.utils.DateUtil;
5 7 import com.genersoft.iot.vmp.utils.GitUtil;
6 8 import gov.nist.javax.sip.address.AddressImpl;
7 9 import gov.nist.javax.sip.address.SipUri;
8 10 import gov.nist.javax.sip.header.Subject;
9 11 import gov.nist.javax.sip.message.SIPRequest;
  12 +import org.apache.commons.lang3.RandomStringUtils;
  13 +import org.slf4j.Logger;
  14 +import org.slf4j.LoggerFactory;
10 15 import org.springframework.util.ObjectUtils;
11 16  
  17 +import javax.sdp.SdpFactory;
  18 +import javax.sdp.SdpParseException;
  19 +import javax.sdp.SessionDescription;
12 20 import javax.sip.PeerUnavailableException;
13 21 import javax.sip.SipFactory;
14 22 import javax.sip.header.FromHeader;
... ... @@ -16,6 +24,8 @@ import javax.sip.header.Header;
16 24 import javax.sip.header.UserAgentHeader;
17 25 import javax.sip.message.Request;
18 26 import java.text.ParseException;
  27 +import java.time.LocalDateTime;
  28 +import java.time.format.DateTimeParseException;
19 29 import java.util.ArrayList;
20 30 import java.util.List;
21 31 import java.util.UUID;
... ... @@ -28,6 +38,8 @@ import java.util.UUID;
28 38 */
29 39 public class SipUtils {
30 40  
  41 + private final static Logger logger = LoggerFactory.getLogger(SipUtils.class);
  42 +
31 43 public static String getUserIdFromFromHeader(Request request) {
32 44 FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
33 45 return getUserIdFromFromHeader(fromHeader);
... ... @@ -51,7 +63,7 @@ public class SipUtils {
51 63 }
52 64  
53 65 public static String getNewViaTag() {
54   - return "z9hG4bK" + System.currentTimeMillis();
  66 + return "z9hG4bK" + RandomStringUtils.randomNumeric(10);
55 67 }
56 68  
57 69 public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException {
... ... @@ -113,6 +125,12 @@ public class SipUtils {
113 125 strTmp = String.format("%02X", moveSpeed);
114 126 builder.append(strTmp, 0, 2);
115 127 builder.append(strTmp, 0, 2);
  128 +
  129 + //优化zoom低倍速下的变倍速率
  130 + if ((zoomSpeed > 0) && (zoomSpeed <16))
  131 + {
  132 + zoomSpeed = 16;
  133 + }
116 134 strTmp = String.format("%X", zoomSpeed);
117 135 builder.append(strTmp, 0, 1).append("0");
118 136 //计算校验码
... ... @@ -183,4 +201,66 @@ public class SipUtils {
183 201 }
184 202 return deviceChannel;
185 203 }
186   -}
  204 +
  205 + public static Gb28181Sdp parseSDP(String sdpStr) throws SdpParseException {
  206 +
  207 + // jainSip不支持y= f=字段, 移除以解析。
  208 + int ssrcIndex = sdpStr.indexOf("y=");
  209 + int mediaDescriptionIndex = sdpStr.indexOf("f=");
  210 + // 检查是否有y字段
  211 + SessionDescription sdp;
  212 + String ssrc = null;
  213 + String mediaDescription = null;
  214 + if (mediaDescriptionIndex == 0 && ssrcIndex == 0) {
  215 + sdp = SdpFactory.getInstance().createSessionDescription(sdpStr);
  216 + }else {
  217 + String lines[] = sdpStr.split("\\r?\\n");
  218 + StringBuilder sdpBuffer = new StringBuilder();
  219 + for (String line : lines) {
  220 + if (line.trim().startsWith("y=")) {
  221 + ssrc = line.substring(2);
  222 + }else if (line.trim().startsWith("f=")) {
  223 + mediaDescription = line.substring(2);
  224 + }else {
  225 + sdpBuffer.append(line.trim()).append("\r\n");
  226 + }
  227 + }
  228 + sdp = SdpFactory.getInstance().createSessionDescription(sdpBuffer.toString());
  229 + }
  230 + return Gb28181Sdp.getInstance(sdp, ssrc, mediaDescription);
  231 + }
  232 +
  233 + public static String getSsrcFromSdp(String sdpStr) {
  234 +
  235 + // jainSip不支持y= f=字段, 移除以解析。
  236 + int ssrcIndex = sdpStr.indexOf("y=");
  237 + if (ssrcIndex == 0) {
  238 + return null;
  239 + }
  240 + String lines[] = sdpStr.split("\\r?\\n");
  241 + for (String line : lines) {
  242 + if (line.trim().startsWith("y=")) {
  243 + return line.substring(2);
  244 + }
  245 + }
  246 + return null;
  247 + }
  248 +
  249 + public static String parseTime(String timeStr) {
  250 + if (ObjectUtils.isEmpty(timeStr)){
  251 + return null;
  252 + }
  253 + LocalDateTime localDateTime;
  254 + try {
  255 + localDateTime = LocalDateTime.parse(timeStr);
  256 + }catch (DateTimeParseException e) {
  257 + try {
  258 + localDateTime = LocalDateTime.parse(timeStr, DateUtil.formatterISO8601);
  259 + }catch (DateTimeParseException e2) {
  260 + logger.error("[格式化时间] 无法格式化时间: {}", timeStr);
  261 + return null;
  262 + }
  263 + }
  264 + return localDateTime.format(DateUtil.formatterISO8601);
  265 + }
  266 +}
187 267 \ No newline at end of file
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
... ... @@ -219,13 +219,14 @@ public class ZLMRTPServerFactory {
219 219 * @param tcp 是否为tcp
220 220 * @return SendRtpItem
221 221 */
222   - public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp, boolean rtcp){
  222 + public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId,
  223 + String deviceId, String channelId, boolean tcp, boolean rtcp, KeepPortCallback callback){
223 224  
224 225 // 默认为随机端口
225 226 int localPort = 0;
226 227 if (userSetting.getGbSendStreamStrict()) {
227 228 if (userSetting.getGbSendStreamStrict()) {
228   - localPort = keepPort(serverItem, ssrc);
  229 + localPort = keepPort(serverItem, ssrc, localPort, callback);
229 230 if (localPort == 0) {
230 231 return null;
231 232 }
... ... @@ -257,11 +258,12 @@ public class ZLMRTPServerFactory {
257 258 * @param tcp 是否为tcp
258 259 * @return SendRtpItem
259 260 */
260   - public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp, boolean rtcp){
  261 + public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId,
  262 + String app, String stream, String channelId, boolean tcp, boolean rtcp, KeepPortCallback callback){
261 263 // 默认为随机端口
262 264 int localPort = 0;
263 265 if (userSetting.getGbSendStreamStrict()) {
264   - localPort = keepPort(serverItem, ssrc);
  266 + localPort = keepPort(serverItem, ssrc, localPort, callback);
265 267 if (localPort == 0) {
266 268 return null;
267 269 }
... ... @@ -282,13 +284,16 @@ public class ZLMRTPServerFactory {
282 284 return sendRtpItem;
283 285 }
284 286  
  287 + public interface KeepPortCallback{
  288 + Boolean keep(String ssrc);
  289 + }
  290 +
285 291 /**
286 292 * 保持端口,直到需要需要发流时再释放
287 293 */
288   - public int keepPort(MediaServerItem serverItem, String ssrc) {
289   - int localPort = 0;
  294 + public int keepPort(MediaServerItem serverItem, String ssrc, int localPort, KeepPortCallback keepPortCallback) {
290 295 Map<String, Object> param = new HashMap<>(3);
291   - param.put("port", 0);
  296 + param.put("port", localPort);
292 297 param.put("enable_tcp", 1);
293 298 param.put("stream_id", ssrc);
294 299 JSONObject jsonObject = zlmresTfulUtils.openRtpServer(serverItem, param);
... ... @@ -296,10 +301,21 @@ public class ZLMRTPServerFactory {
296 301 localPort = jsonObject.getInteger("port");
297 302 HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, serverItem.getId());
298 303 // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
  304 + Integer finalLocalPort = localPort;
299 305 hookSubscribe.addSubscribe(hookSubscribeForRtpServerTimeout,
300 306 (MediaServerItem mediaServerItem, JSONObject response)->{
301   - logger.info("[上级点播] {}->监听端口到期继续保持监听", ssrc);
302   - keepPort(serverItem, ssrc);
  307 + System.out.println("监听端口到期继续保持监听");
  308 + System.out.println(response);
  309 + if (ssrc.equals(response.getString("stream_id"))) {
  310 + if (keepPortCallback.keep(ssrc)) {
  311 + logger.info("[上级点播] {}->监听端口到期继续保持监听", ssrc);
  312 + keepPort(serverItem, ssrc, finalLocalPort, keepPortCallback);
  313 + }else {
  314 + logger.info("[上级点播] {}->发送取消,无需继续监听", ssrc);
  315 + releasePort(serverItem, ssrc);
  316 + }
  317 + }
  318 +
303 319 });
304 320 logger.info("[上级点播] {}->监听端口: {}", ssrc, localPort);
305 321 }else {
... ...
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java
... ... @@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
13 13 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
14 14 import com.genersoft.iot.vmp.service.IMediaServerService;
15 15 import com.genersoft.iot.vmp.service.bean.*;
  16 +import com.genersoft.iot.vmp.utils.redis.RedisUtil;
16 17 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
17 18 import org.slf4j.Logger;
18 19 import org.slf4j.LoggerFactory;
... ... @@ -26,6 +27,7 @@ import org.springframework.stereotype.Component;
26 27  
27 28 import java.text.ParseException;
28 29 import java.util.HashMap;
  30 +import java.util.List;
29 31 import java.util.Map;
30 32 import java.util.UUID;
31 33 import java.util.concurrent.ConcurrentHashMap;
... ... @@ -314,7 +316,9 @@ public class RedisGbPlayMsgListener implements MessageListener {
314 316 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, content.getIp(),
315 317 content.getPort(), content.getSsrc(), content.getPlatformId(),
316 318 content.getApp(), content.getStream(), content.getChannelId(),
317   - content.getTcp(), content.getRtcp());
  319 + content.getTcp(), content.getRtcp(), ssrcFromCallback -> {
  320 + return querySendRTPServer(content.getPlatformId(), content.getChannelId(), content.getStream(), null) != null;
  321 + });
318 322  
319 323 WVPResult<ResponseSendItemMsg> result = new WVPResult<>();
320 324 result.setCode(0);
... ... @@ -391,4 +395,31 @@ public class RedisGbPlayMsgListener implements MessageListener {
391 395 });
392 396 redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
393 397 }
  398 +
  399 + private SendRtpItem querySendRTPServer(String platformGbId, String channelId, String streamId, String callId) {
  400 + if (platformGbId == null) {
  401 + platformGbId = "*";
  402 + }
  403 + if (channelId == null) {
  404 + channelId = "*";
  405 + }
  406 + if (streamId == null) {
  407 + streamId = "*";
  408 + }
  409 + if (callId == null) {
  410 + callId = "*";
  411 + }
  412 + String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX
  413 + + userSetting.getServerId() + "_*_"
  414 + + platformGbId + "_"
  415 + + channelId + "_"
  416 + + streamId + "_"
  417 + + callId;
  418 + List<Object> scan = RedisUtil.scan(redisTemplate, key);
  419 + if (scan.size() > 0) {
  420 + return (SendRtpItem)redisTemplate.opsForValue().get(scan.get(0));
  421 + }else {
  422 + return null;
  423 + }
  424 + }
394 425 }
... ...