From 88dd1aca45c5d2c0fca78a60844112ee53eccbfa Mon Sep 17 00:00:00 2001 From: lawrencehj <1934378145@qq.com> Date: Fri, 26 Mar 2021 19:54:29 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=9B=B4=E5=90=8DdeviceL?= =?UTF-8?q?ist.vue=E5=90=8E=E8=B7=B3=E8=BD=AC=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web_src/src/components/channelList.vue | 4 ++-- web_src/src/components/devicePosition.vue | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web_src/src/components/channelList.vue b/web_src/src/components/channelList.vue index 943fe3a6..6d893ce4 100644 --- a/web_src/src/components/channelList.vue +++ b/web_src/src/components/channelList.vue @@ -99,7 +99,7 @@ export default { currentPage: parseInt(this.$route.params.page), count: parseInt(this.$route.params.count), total: 0, - beforeUrl: "/videoList", + beforeUrl: "/deviceList", isLoging: false, autoList: true }; @@ -131,7 +131,7 @@ export default { this.currentPage = parseInt(this.$route.params.page); this.count = parseInt(this.$route.params.count); if (this.parentChannelId == "" || this.parentChannelId == 0) { - this.beforeUrl = "/videoList" + this.beforeUrl = "/deviceList" } }, diff --git a/web_src/src/components/devicePosition.vue b/web_src/src/components/devicePosition.vue index f6cf19e4..4a52c743 100644 --- a/web_src/src/components/devicePosition.vue +++ b/web_src/src/components/devicePosition.vue @@ -81,7 +81,7 @@ export default { parentChannelId: this.$route.params.parentChannelId, updateLooper: 0, //数据刷新轮训标志 total: 0, - beforeUrl: "/videoList", + beforeUrl: "/deviceList", isLoging: false, autoList: false, }; @@ -111,7 +111,7 @@ export default { // this.currentPage = parseInt(this.$route.params.page); // this.count = parseInt(this.$route.params.count); // if (this.parentChannelId == "" || this.parentChannelId == 0) { - // this.beforeUrl = "/videoList"; + // this.beforeUrl = "/deviceList"; // } }, initBaiduMap() { From c5a05c15df6c01602f259a49ff470fa996579b0b Mon Sep 17 00:00:00 2001 From: lawrencehj <1934378145@qq.com> Date: Fri, 26 Mar 2021 19:58:46 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=97=B6=E6=B8=85?= =?UTF-8?q?=E9=99=A4=E5=85=88=E5=89=8D=E6=B3=A8=E5=86=8C=E4=B8=8A=E7=BA=A7?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E7=9A=84=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/genersoft/iot/vmp/conf/SipPlatformRunner.java | 3 +++ .../genersoft/iot/vmp/storager/IRedisCatchStorage.java | 2 ++ .../iot/vmp/storager/impl/RedisCatchStorageImpl.java | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java b/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java index dd7c7a0c..35862078 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java @@ -33,6 +33,9 @@ public class SipPlatformRunner implements CommandLineRunner { // 设置所有平台离线 storager.outlineForAllParentPlatform(); + // 清理所有平台注册缓存 + redisCatchStorage.cleanPlatformRegisterInfos(); + List parentPlatforms = storager.queryEnableParentPlatformList(true); for (ParentPlatform parentPlatform : parentPlatforms) { diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java index c9f5fff8..2866611f 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -81,6 +81,8 @@ public interface IRedisCatchStorage { void delPlatformRegisterInfo(String callId); + void cleanPlatformRegisterInfos(); + void updateSendRTPSever(SendRtpItem sendRtpItem); /** diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java index c5e31d62..98710833 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java @@ -13,6 +13,7 @@ import org.springframework.stereotype.Component; import java.util.*; +@SuppressWarnings("rawtypes") @Component public class RedisCatchStorageImpl implements IRedisCatchStorage { @@ -212,6 +213,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { redis.del(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + callId); } + @Override + public void cleanPlatformRegisterInfos() { + List regInfos = redis.scan(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + "*"); + for (Object key : regInfos) { + redis.del(key.toString()); + } + } + @Override public void updateSendRTPSever(SendRtpItem sendRtpItem) { String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + sendRtpItem.getPlatformId() + "_" + sendRtpItem.getChannelId(); From f9d30bdfadd68e61f545e1c5f6260d4f58dca040 Mon Sep 17 00:00:00 2001 From: lawrencehj <1934378145@qq.com> Date: Fri, 26 Mar 2021 20:15:12 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E5=B9=BF=E6=92=AD=E4=BF=A1=E4=BB=A4=EF=BC=88web=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E6=8E=A8=E6=B5=81=E5=BC=80=E5=8F=91=E4=B8=AD=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genersoft/iot/vmp/gb28181/SipLayer.java | 2 +- .../callback/DeferredResultHolder.java | 2 + .../gb28181/transmit/cmd/ISIPCommander.java | 8 + .../cmd/SIPRequestHeaderPlarformProvider.java | 2 +- .../cmd/SIPRequestHeaderProvider.java | 4 +- .../transmit/cmd/impl/SIPCommander.java | 63 +++- .../cmd/impl/SIPCommanderFroPlatform.java | 2 +- .../request/impl/InviteRequestProcessor.java | 301 +++++++++++------- .../request/impl/MessageRequestProcessor.java | 41 ++- .../impl/RegisterResponseProcessor.java | 1 - .../iot/vmp/vmanager/play/PlayController.java | 45 +++ 11 files changed, 344 insertions(+), 127 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java index 257a5898..c0b49855 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java @@ -16,7 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; +// import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java index ab72be56..b7f592e1 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java @@ -41,6 +41,8 @@ public class DeferredResultHolder { public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM"; + public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST"; + private Map map = new ConcurrentHashMap(); public void put(String key, DeferredResult result) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java index 27000bb7..dd50e065 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java @@ -119,6 +119,14 @@ public interface ISIPCommander { */ boolean audioBroadcastCmd(Device device,String channelId); + /** + * 语音广播 + * + * @param device 视频设备 + */ + void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent); + boolean audioBroadcastCmd(Device device); + /** * 音视频录像控制 * diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java index 8fe4cbc4..5bf9236a 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java @@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; +// import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.util.DigestUtils; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java index c764d5e7..aaf3c63e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java @@ -6,14 +6,14 @@ import java.util.ArrayList; import javax.sip.InvalidArgumentException; import javax.sip.PeerUnavailableException; import javax.sip.SipFactory; -import javax.sip.SipProvider; +// import javax.sip.SipProvider; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.*; import javax.sip.message.Request; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; +// import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.genersoft.iot.vmp.conf.SipConfig; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java index abf9cf7f..313348e2 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java @@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.ComponentScan; +// import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -47,8 +47,7 @@ import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; public class SIPCommander implements ISIPCommander { private final Logger logger = LoggerFactory.getLogger(SIPCommander.class); - - + @Autowired private SipConfig sipConfig; @@ -623,10 +622,66 @@ public class SIPCommander implements ISIPCommander { */ @Override public boolean audioBroadcastCmd(Device device, String channelId) { - // TODO Auto-generated method stub + // 改为新的实现 return false; } + /** + * 语音广播 + * + * @param device 视频设备 + * @param channelId 预览通道 + */ + @Override + public boolean audioBroadcastCmd(Device device) { + try { + StringBuffer broadcastXml = new StringBuffer(200); + broadcastXml.append("\r\n"); + broadcastXml.append("\r\n"); + broadcastXml.append("Broadcast\r\n"); + broadcastXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); + broadcastXml.append("" + sipConfig.getSipId() + "\r\n"); + broadcastXml.append("" + device.getDeviceId() + "\r\n"); + broadcastXml.append("\r\n"); + + String tm = Long.toString(System.currentTimeMillis()); + + CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() + : udpSipProvider.getNewCallId(); + + Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "z9hG4bK-ViaBcst-" + tm, "FromBcst" + tm, null, callIdHeader); + transmitRequest(device, request); + return true; + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + return false; + } + @Override + public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) { + try { + StringBuffer broadcastXml = new StringBuffer(200); + broadcastXml.append("\r\n"); + broadcastXml.append("\r\n"); + broadcastXml.append("Broadcast\r\n"); + broadcastXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); + broadcastXml.append("" + sipConfig.getSipId() + "\r\n"); + broadcastXml.append("" + device.getDeviceId() + "\r\n"); + broadcastXml.append("\r\n"); + + String tm = Long.toString(System.currentTimeMillis()); + + CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() + : udpSipProvider.getNewCallId(); + + Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "z9hG4bK-ViaBcst-" + tm, "FromBcst" + tm, null, callIdHeader); + transmitRequest(device, request, errorEvent); + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } + + /** * 音视频录像控制 * diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java index eac13efc..08925261 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java @@ -15,7 +15,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.ComponentScan; +// import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Lazy; import org.springframework.lang.Nullable; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java index 51ce21ea..a7b3d94e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java @@ -14,6 +14,7 @@ import javax.sip.message.Response; import com.genersoft.iot.vmp.conf.MediaServerConfig; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; @@ -74,144 +75,216 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor { Request request = evt.getRequest(); SipURI sipURI = (SipURI) request.getRequestURI(); String channelId = sipURI.getUser(); - String platformId = null; + String requesterId = null; FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME); AddressImpl address = (AddressImpl) fromHeader.getAddress(); SipUri uri = (SipUri) address.getURI(); - platformId = uri.getUser(); + requesterId = uri.getUser(); - if (platformId == null || channelId == null) { - logger.info("无法从FromHeader的Address中获取到平台id,返回404"); + if (requesterId == null || channelId == null) { + logger.info("无法从FromHeader的Address中获取到平台id,返回400"); responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误 return; } - // 查询平台下是否有该通道 - DeviceChannel channel = storager.queryChannelInParentPlatform(platformId, channelId); - if (channel == null) { - logger.info("通道不存在,返回404"); - responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在 - return; - }else { - responseAck(evt, Response.TRYING); // 通道存在,发100,trying - } - // 解析sdp消息, 使用jainsip 自带的sdp解析方式 - String contentString = new String(request.getRawContent()); - // jainSip不支持y=字段, 移除移除以解析。 - int ssrcIndex = contentString.indexOf("y="); - String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 - // String ssrc = contentString.substring(ssrcIndex + 2, contentString.length()) - // .replace("\r\n", "").replace("\n", ""); + // 查询请求方是否上级平台 + ParentPlatform platform = storager.queryParentPlatById(requesterId); + if (platform != null) { + // 查询平台下是否有该通道 + DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId); + if (channel == null) { + logger.info("通道不存在,返回404"); + responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在 + return; + }else { + responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 + } + // 解析sdp消息, 使用jainsip 自带的sdp解析方式 + String contentString = new String(request.getRawContent()); - String substring = contentString.substring(0, contentString.indexOf("y=")); - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); + // jainSip不支持y=字段, 移除移除以解析。 + int ssrcIndex = contentString.indexOf("y="); + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 + String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); + String substring = contentString.substring(0, contentString.indexOf("y=")); + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); - // 获取支持的格式 - Vector mediaDescriptions = sdp.getMediaDescriptions(true); - // 查看是否支持PS 负载96 - //String ip = null; - int port = -1; - //boolean recvonly = false; - boolean mediaTransmissionTCP = false; - Boolean tcpActive = null; - for (int i = 0; i < mediaDescriptions.size(); i++) { - MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i); - Media media = mediaDescription.getMedia(); + // 获取支持的格式 + Vector mediaDescriptions = sdp.getMediaDescriptions(true); + // 查看是否支持PS 负载96 + //String ip = null; + int port = -1; + //boolean recvonly = false; + boolean mediaTransmissionTCP = false; + Boolean tcpActive = null; + for (int i = 0; i < mediaDescriptions.size(); i++) { + MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i); + Media media = mediaDescription.getMedia(); - Vector mediaFormats = media.getMediaFormats(false); - if (mediaFormats.contains("96")) { - port = media.getMediaPort(); - //String mediaType = media.getMediaType(); - String protocol = media.getProtocol(); + Vector mediaFormats = media.getMediaFormats(false); + if (mediaFormats.contains("96")) { + port = media.getMediaPort(); + //String mediaType = media.getMediaType(); + String protocol = media.getProtocol(); - // 区分TCP发流还是udp, 当前默认udp - if ("TCP/RTP/AVP".equals(protocol)) { - String setup = mediaDescription.getAttribute("setup"); - if (setup != null) { - mediaTransmissionTCP = true; - if ("active".equals(setup)) { - tcpActive = true; - }else if ("passive".equals(setup)) { - tcpActive = false; + // 区分TCP发流还是udp, 当前默认udp + if ("TCP/RTP/AVP".equals(protocol)) { + String setup = mediaDescription.getAttribute("setup"); + if (setup != null) { + mediaTransmissionTCP = true; + if ("active".equals(setup)) { + tcpActive = true; + }else if ("passive".equals(setup)) { + tcpActive = false; + } } } + break; } - break; } - } - if (port == -1) { - logger.info("不支持的媒体格式,返回415"); - // 回复不支持的格式 - responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 - return; - } - String username = sdp.getOrigin().getUsername(); - String addressStr = sdp.getOrigin().getAddress(); - //String sessionName = sdp.getSessionName().getValue(); - logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc); + if (port == -1) { + logger.info("不支持的媒体格式,返回415"); + // 回复不支持的格式 + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 + return; + } + String username = sdp.getOrigin().getUsername(); + String addressStr = sdp.getOrigin().getAddress(); + //String sessionName = sdp.getSessionName().getValue(); + logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc); - Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, channelId); - if (device == null) { - logger.warn("点播平台{}的通道{}时未找到设备信息", platformId, channel); - responseAck(evt, Response.SERVER_INTERNAL_ERROR); - return; - } - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, platformId, device.getDeviceId(), channelId, - mediaTransmissionTCP); - if (tcpActive != null) { - sendRtpItem.setTcpActive(tcpActive); - } - if (sendRtpItem == null) { - logger.warn("服务器端口资源不足"); - responseAck(evt, Response.BUSY_HERE); - return; - } + Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId); + if (device == null) { + logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel); + responseAck(evt, Response.SERVER_INTERNAL_ERROR); + return; + } + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, requesterId, device.getDeviceId(), channelId, + mediaTransmissionTCP); + if (tcpActive != null) { + sendRtpItem.setTcpActive(tcpActive); + } + if (sendRtpItem == null) { + logger.warn("服务器端口资源不足"); + responseAck(evt, Response.BUSY_HERE); + return; + } - // 写入redis, 超时时回复 - redisCatchStorage.updateSendRTPSever(sendRtpItem); - // 通知下级推流, - PlayResult playResult = playService.play(device.getDeviceId(), channelId, (responseJSON)->{ - // 收到推流, 回复200OK, 等待ack - sendRtpItem.setStatus(1); + // 写入redis, 超时时回复 redisCatchStorage.updateSendRTPSever(sendRtpItem); - // TODO 添加对tcp的支持 - MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); - StringBuffer content = new StringBuffer(200); - content.append("v=0\r\n"); - content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n"); - content.append("s=Play\r\n"); - content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n"); - content.append("t=0 0\r\n"); - content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n"); - content.append("a=sendonly\r\n"); - content.append("a=rtpmap:96 PS/90000\r\n"); - content.append("y="+ ssrc + "\r\n"); - content.append("f=\r\n"); + // 通知下级推流, + PlayResult playResult = playService.play(device.getDeviceId(), channelId, (responseJSON)->{ + // 收到推流, 回复200OK, 等待ack + sendRtpItem.setStatus(1); + redisCatchStorage.updateSendRTPSever(sendRtpItem); + // TODO 添加对tcp的支持 + MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); + StringBuffer content = new StringBuffer(200); + content.append("v=0\r\n"); + content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n"); + content.append("s=Play\r\n"); + content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n"); + content.append("t=0 0\r\n"); + content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n"); + content.append("a=sendonly\r\n"); + content.append("a=rtpmap:96 PS/90000\r\n"); + content.append("y="+ ssrc + "\r\n"); + content.append("f=\r\n"); - try { - responseAck(evt, content.toString()); - } catch (SipException e) { - e.printStackTrace(); - } catch (InvalidArgumentException e) { - e.printStackTrace(); - } catch (ParseException e) { - e.printStackTrace(); + try { + responseAck(evt, content.toString()); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + },(event -> { + // 未知错误。直接转发设备点播的错误 + Response response = null; + try { + response = getMessageFactory().createResponse(event.getResponse().getStatusCode(), evt.getRequest()); + getServerTransaction(evt).sendResponse(response); + } catch (ParseException | SipException | InvalidArgumentException e) { + e.printStackTrace(); + } + })); + if (logger.isDebugEnabled()) { + logger.debug(playResult.getResult().toString()); } - },(event -> { - // 未知错误。直接转发设备点播的错误 - Response response = null; - try { - response = getMessageFactory().createResponse(event.getResponse().getStatusCode(), evt.getRequest()); - getServerTransaction(evt).sendResponse(response); + } else { + // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备) + Device device = storager.queryVideoDevice(requesterId); + if (device != null) { + logger.info("收到设备" + requesterId + "的语音广播Invite请求"); + responseAck(evt, Response.TRYING); - } catch (ParseException | SipException | InvalidArgumentException e) { - e.printStackTrace(); + String contentString = new String(request.getRawContent()); + // jainSip不支持y=字段, 移除移除以解析。 + String substring = contentString; + String ssrc = "0000000404"; + int ssrcIndex = contentString.indexOf("y="); + if (ssrcIndex > 0) { + substring = contentString.substring(0, ssrcIndex); + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); + } + ssrcIndex = substring.indexOf("f="); + if (ssrcIndex > 0) { + substring = contentString.substring(0, ssrcIndex); + } + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); + + // 获取支持的格式 + Vector mediaDescriptions = sdp.getMediaDescriptions(true); + // 查看是否支持PS 负载96 + int port = -1; + //boolean recvonly = false; + boolean mediaTransmissionTCP = false; + Boolean tcpActive = null; + for (int i = 0; i < mediaDescriptions.size(); i++) { + MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i); + Media media = mediaDescription.getMedia(); + + Vector mediaFormats = media.getMediaFormats(false); + if (mediaFormats.contains("8")) { + port = media.getMediaPort(); + String protocol = media.getProtocol(); + // 区分TCP发流还是udp, 当前默认udp + if ("TCP/RTP/AVP".equals(protocol)) { + String setup = mediaDescription.getAttribute("setup"); + if (setup != null) { + mediaTransmissionTCP = true; + if ("active".equals(setup)) { + tcpActive = true; + } else if ("passive".equals(setup)) { + tcpActive = false; + } + } + } + break; + } + } + if (port == -1) { + logger.info("不支持的媒体格式,返回415"); + // 回复不支持的格式 + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 + return; + } + String username = sdp.getOrigin().getUsername(); + String addressStr = sdp.getOrigin().getAddress(); + logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc); + + + + + + + } else { + logger.warn("来自无效设备/平台的请求"); + responseAck(evt, Response.BAD_REQUEST); } - })); - if (logger.isDebugEnabled()) { - logger.debug(playResult.getResult().toString()); } } catch (SipException | InvalidArgumentException | ParseException e) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java index e97629b8..5a242021 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java @@ -93,7 +93,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { private static final String MESSAGE_ALARM = "Alarm"; private static final String MESSAGE_RECORD_INFO = "RecordInfo"; private static final String MESSAGE_MEDIA_STATUS = "MediaStatus"; - // private static final String MESSAGE_BROADCAST = "Broadcast"; + private static final String MESSAGE_BROADCAST = "Broadcast"; private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus"; private static final String MESSAGE_DEVICE_CONTROL = "DeviceControl"; private static final String MESSAGE_DEVICE_CONFIG = "DeviceConfig"; @@ -123,7 +123,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { logger.info("接收到Catalog消息"); processMessageCatalogList(evt); } else if (MESSAGE_DEVICE_INFO.equals(cmd)) { - //DeviceInfo消息处理 + // DeviceInfo消息处理 processMessageDeviceInfo(evt); } else if (MESSAGE_DEVICE_STATUS.equals(cmd)) { // DeviceStatus消息处理 @@ -149,6 +149,9 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { } else if (MESSAGE_PRESET_QUERY.equals(cmd)) { logger.info("接收到PresetQuery消息"); processMessagePresetQuery(evt); + } else if (MESSAGE_BROADCAST.equals(cmd)) { + // Broadcast消息处理 + processMessageBroadcast(evt); } else { logger.info("接收到消息:" + cmd); responseAck(evt); @@ -298,7 +301,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { // 远程启动功能 if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) { if (deviceId.equals(targetGBId)) { - // 远程启动功能:需要在重新启动程序后先对SipStack解绑 + // 远程启动本平台:需要在重新启动程序后先对SipStack解绑 logger.info("执行远程启动本平台命令"); ParentPlatform parentPlatform = storager.queryParentPlatById(platformId); cmderFroPlatform.unregister(parentPlatform, null, null); @@ -333,6 +336,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { // 远程启动指定设备 } } + // 云台/前端控制命令 if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) { String cmdString = XmlUtil.getText(rootElement,"PTZCmd"); Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId); @@ -895,6 +899,37 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { } } + /** + * 处理AudioBroadcast语音广播Message + * + * @param evt + */ + private void processMessageBroadcast(RequestEvent evt) { + try { + Element rootElement = getRootElement(evt); + String deviceId = XmlUtil.getText(rootElement, "DeviceID"); + // 回复200 OK + responseAck(evt); + if (rootElement.getName().equals("Response")) { + // 此处是对本平台发出Broadcast指令的应答 + JSONObject json = new JSONObject(); + XmlUtil.node2Json(rootElement, json); + if (logger.isDebugEnabled()) { + logger.debug(json.toJSONString()); + } + RequestMessage msg = new RequestMessage(); + msg.setDeviceId(deviceId); + msg.setType(DeferredResultHolder.CALLBACK_CMD_BROADCAST); + msg.setData(json); + deferredResultHolder.invokeResult(msg); + } else { + // 此处是上级发出的Broadcast指令 + } + } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) { + e.printStackTrace(); + } + } + /*** * 回复200 OK diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/RegisterResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/RegisterResponseProcessor.java index a3952ff2..38b0f079 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/RegisterResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/RegisterResponseProcessor.java @@ -50,7 +50,6 @@ public class RegisterResponseProcessor implements ISIPResponseProcessor { */ @Override public void process(ResponseEvent evt, SipLayer layer, SipConfig config) { - // TODO Auto-generated method stub Response response = evt.getResponse(); CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME); String callId = callIdHeader.getCallId(); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java index b0d136ef..0c773e63 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.vmanager.play; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.MediaServerConfig; +import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; @@ -27,6 +28,8 @@ import org.springframework.web.context.request.async.DeferredResult; import java.util.UUID; +import javax.sip.message.Response; + @CrossOrigin @RestController @RequestMapping("/api") @@ -204,5 +207,47 @@ public class PlayController { } return new ResponseEntity( result.toJSONString(), HttpStatus.OK); } + + /** + * 语音广播命令API接口 + * + * @param deviceId + */ + @GetMapping("/broadcast/{deviceId}") + @PostMapping("/broadcast/{deviceId}") + public DeferredResult> broadcastApi(@PathVariable String deviceId) { + if (logger.isDebugEnabled()) { + logger.debug("语音广播API调用"); + } + Device device = storager.queryVideoDevice(deviceId); + cmder.audioBroadcastCmd(device, event -> { + Response response = event.getResponse(); + RequestMessage msg = new RequestMessage(); + msg.setId(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId); + JSONObject json = new JSONObject(); + json.put("DeviceID", deviceId); + json.put("CmdType", "Broadcast"); + json.put("Result", "Failed"); + json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase())); + msg.setData(json); + resultHolder.invokeResult(msg); + }); + DeferredResult> result = new DeferredResult>(3 * 1000L); + result.onTimeout(() -> { + logger.warn(String.format("语音广播操作超时, 设备未返回应答指令")); + RequestMessage msg = new RequestMessage(); + msg.setId(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId); + JSONObject json = new JSONObject(); + json.put("DeviceID", deviceId); + json.put("CmdType", "Broadcast"); + json.put("Result", "Failed"); + json.put("Error", "Timeout. Device did not response to broadcast command."); + msg.setData(json); + resultHolder.invokeResult(msg); + }); + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId, result); + return result; + } + }