Commit c2aaae9325db012c9960b69784330ced5ec15ab9

Authored by 648540858
1 parent 9f0ef439

初步实现语音喊话

Showing 20 changed files with 761 additions and 179 deletions
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.bean;
  2 +
  3 +
  4 +/**
  5 + * 缓存语音广播的状态
  6 + * @author lin
  7 + */
  8 +public class AudioBroadcastCatch {
  9 +
  10 +
  11 + public AudioBroadcastCatch(String deviceId, String channelId, AudioBroadcastCatchStatus status) {
  12 + this.deviceId = deviceId;
  13 + this.channelId = channelId;
  14 + this.status = status;
  15 + }
  16 +
  17 + public AudioBroadcastCatch() {
  18 + }
  19 +
  20 + /**
  21 + * 设备编号
  22 + */
  23 + private String deviceId;
  24 +
  25 + /**
  26 + * 通道编号
  27 + */
  28 + private String channelId;
  29 +
  30 + /**
  31 + * 语音广播状态
  32 + */
  33 + private AudioBroadcastCatchStatus status;
  34 +
  35 +
  36 + public String getDeviceId() {
  37 + return deviceId;
  38 + }
  39 +
  40 + public void setDeviceId(String deviceId) {
  41 + this.deviceId = deviceId;
  42 + }
  43 +
  44 + public String getChannelId() {
  45 + return channelId;
  46 + }
  47 +
  48 + public void setChannelId(String channelId) {
  49 + this.channelId = channelId;
  50 + }
  51 +
  52 + public AudioBroadcastCatchStatus getStatus() {
  53 + return status;
  54 + }
  55 +
  56 + public void setStatus(AudioBroadcastCatchStatus status) {
  57 + this.status = status;
  58 + }
  59 +}
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatchStatus.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.bean;
  2 +
  3 +/**
  4 + * 语音广播状态
  5 + * @author lin
  6 + */
  7 +public enum AudioBroadcastCatchStatus {
  8 +
  9 + // 发送语音广播消息等待对方回复语音广播
  10 + Ready,
  11 + // 收到回复等待invite消息
  12 + WaiteInvite,
  13 + // 收到invite消息
  14 + Ok,
  15 +}
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
... ... @@ -134,16 +134,6 @@ public class Device {
134 134 */
135 135 private boolean ssrcCheck;
136 136  
137   - /**
138   - * 设备用于接收语音消息的通道
139   - */
140   - private String audioChannelForReceive;
141   -
142   - /**
143   - * 设备用于发送语音消息的通道
144   - */
145   - private String audioChannelForSend;
146   -
147 137  
148 138 public String getDeviceId() {
149 139 return deviceId;
... ... @@ -345,11 +335,4 @@ public class Device {
345 335 this.ssrcCheck = ssrcCheck;
346 336 }
347 337  
348   - public String getAudioChannelForReceive() {
349   - return audioChannelForReceive;
350   - }
351   -
352   - public void setAudioChannelForReceive(String audioChannelForReceive) {
353   - this.audioChannelForReceive = audioChannelForReceive;
354   - }
355 338 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.session;
  2 +
  3 +import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch;
  4 +import org.springframework.stereotype.Component;
  5 +
  6 +import java.util.ArrayList;
  7 +import java.util.Collection;
  8 +import java.util.List;
  9 +import java.util.Map;
  10 +import java.util.concurrent.ConcurrentHashMap;
  11 +
  12 +/**
  13 + * 语音广播消息管理类
  14 + * @author lin
  15 + */
  16 +@Component
  17 +public class AudioBroadcastManager {
  18 +
  19 + public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>();
  20 +
  21 + public void add(AudioBroadcastCatch audioBroadcastCatch) {
  22 + this.update(audioBroadcastCatch);
  23 + }
  24 +
  25 + public void update(AudioBroadcastCatch audioBroadcastCatch) {
  26 + data.put(audioBroadcastCatch.getDeviceId() + audioBroadcastCatch.getChannelId(), audioBroadcastCatch);
  27 + }
  28 +
  29 + public void del(String deviceId, String channelId) {
  30 + data.remove(deviceId + channelId);
  31 + }
  32 +
  33 + public void delByDeviceId(String deviceId) {
  34 + for (String key : data.keySet()) {
  35 + if (key.startsWith(deviceId)) {
  36 + data.remove(key);
  37 + }
  38 + }
  39 + }
  40 +
  41 + public List<AudioBroadcastCatch> getAll(){
  42 + Collection<AudioBroadcastCatch> values = data.values();
  43 + return new ArrayList<>(values);
  44 + }
  45 +
  46 +
  47 + public boolean exit(String deviceId, String channelId) {
  48 + for (String key : data.keySet()) {
  49 + if (key.equals(deviceId + channelId)) {
  50 + return true;
  51 + }
  52 + }
  53 + return false;
  54 + }
  55 +
  56 + public AudioBroadcastCatch get(String deviceId, String channelId) {
  57 + return data.get(deviceId + channelId);
  58 + }
  59 +}
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java
... ... @@ -21,9 +21,6 @@ public class CatalogDataCatch {
21 21 public static Map<String, CatalogData> data = new ConcurrentHashMap<>();
22 22  
23 23 @Autowired
24   - private DeferredResultHolder deferredResultHolder;
25   -
26   - @Autowired
27 24 private IVideoManagerStorage storager;
28 25  
29 26 public void addReady(Device device, int sn ) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
... ... @@ -6,8 +6,12 @@ import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
6 6 import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
7 7 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
8 8 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
  9 +import gov.nist.javax.sip.message.SIPRequest;
  10 +import gov.nist.javax.sip.stack.SIPDialog;
9 11  
10 12 import javax.sip.Dialog;
  13 +import javax.sip.SipException;
  14 +import java.text.ParseException;
11 15  
12 16 /**
13 17 * @description:设备能力接口,用于定义设备的控制、查询能力
... ... @@ -123,6 +127,7 @@ public interface ISIPCommander {
123 127 */
124 128 void streamByeCmd(String deviceId, String channelId, String stream, String callId, SipSubscribe.Event okEvent);
125 129 void streamByeCmd(String deviceId, String channelId, String stream, String callId);
  130 + void streamByeCmd(SIPDialog dialog, SIPRequest request, SipSubscribe.Event okEvent) throws SipException, ParseException;
126 131  
127 132 /**
128 133 * 回放暂停
... ... @@ -144,21 +149,13 @@ public interface ISIPCommander {
144 149 */
145 150 void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed);
146 151  
147   - /**
148   - * 语音广播
149   - *
150   - * @param device 视频设备
151   - * @param channelId 预览通道
152   - */
153   - boolean audioBroadcastCmd(Device device,String channelId);
154 152  
155 153 /**
156 154 * 语音广播
157 155 *
158 156 * @param device 视频设备
159 157 */
160   - void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent);
161   - boolean audioBroadcastCmd(Device device);
  158 + boolean audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
162 159  
163 160 /**
164 161 * 音视频录像控制
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -733,42 +733,34 @@ public class SIPCommander implements ISIPCommander {
733 733 }
734 734 }
735 735  
736   - Request byeRequest = dialog.createRequest(Request.BYE);
737   - SipURI byeURI = (SipURI) byeRequest.getRequestURI();
738   - SIPRequest request = (SIPRequest)transaction.getRequest();
739   - byeURI.setHost(request.getRemoteAddress().getHostAddress());
740   - byeURI.setPort(request.getRemotePort());
741   - ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME);
742   - String protocol = viaHeader.getTransport().toUpperCase();
743   - ClientTransaction clientTransaction = null;
744   - if("TCP".equals(protocol)) {
745   - clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
746   - } else if("UDP".equals(protocol)) {
747   - clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
748   - }
749   -
750   - CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
751   - if (okEvent != null) {
752   - sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
753   - }
754   -
755   - dialog.sendRequest(clientTransaction);
  736 + streamByeCmd(dialog, (SIPRequest)transaction.getRequest(), okEvent);
756 737  
757 738 } catch (SipException | ParseException e) {
758 739 e.printStackTrace();
759 740 }
760 741 }
761 742  
762   - /**
763   - * 语音广播
764   - *
765   - * @param device 视频设备
766   - * @param channelId 预览通道
767   - */
768 743 @Override
769   - public boolean audioBroadcastCmd(Device device, String channelId) {
770   - // 改为新的实现
771   - return false;
  744 + public void streamByeCmd(SIPDialog dialog, SIPRequest request, SipSubscribe.Event okEvent) throws SipException, ParseException {
  745 + Request byeRequest = dialog.createRequest(Request.BYE);
  746 + SipURI byeURI = (SipURI) byeRequest.getRequestURI();
  747 + byeURI.setHost(request.getRemoteAddress().getHostAddress());
  748 + byeURI.setPort(request.getRemotePort());
  749 + ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME);
  750 + String protocol = viaHeader.getTransport().toUpperCase();
  751 + ClientTransaction clientTransaction = null;
  752 + if("TCP".equals(protocol)) {
  753 + clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
  754 + } else if("UDP".equals(protocol)) {
  755 + clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
  756 + }
  757 +
  758 + CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
  759 + if (okEvent != null) {
  760 + sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
  761 + }
  762 +
  763 + dialog.sendRequest(clientTransaction);
772 764 }
773 765  
774 766 /**
... ... @@ -777,7 +769,7 @@ public class SIPCommander implements ISIPCommander {
777 769 * @param device 视频设备
778 770 */
779 771 @Override
780   - public boolean audioBroadcastCmd(Device device) {
  772 + public boolean audioBroadcastCmd(Device device,String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
781 773 try {
782 774 StringBuffer broadcastXml = new StringBuffer(200);
783 775 String charset = device.getCharset();
... ... @@ -786,7 +778,7 @@ public class SIPCommander implements ISIPCommander {
786 778 broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
787 779 broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
788 780 broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
789   - broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
  781 + broadcastXml.append("<TargetID>" + channelId + "</TargetID>\r\n");
790 782 broadcastXml.append("</Notify>\r\n");
791 783  
792 784 String tm = Long.toString(System.currentTimeMillis());
... ... @@ -795,39 +787,14 @@ public class SIPCommander implements ISIPCommander {
795 787 : udpSipProvider.getNewCallId();
796 788  
797 789 Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "z9hG4bK-ViaBcst-" + tm, "FromBcst" + tm, null, callIdHeader);
798   - transmitRequest(device, request);
  790 + transmitRequest(device, request, errorEvent, okEvent);
799 791 return true;
800 792 } catch (SipException | ParseException | InvalidArgumentException e) {
801 793 e.printStackTrace();
802 794 }
803 795 return false;
804 796 }
805   - @Override
806   - public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) {
807   - try {
808   - StringBuffer broadcastXml = new StringBuffer(200);
809   - String charset = device.getCharset();
810   - broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
811   - broadcastXml.append("<Notify>\r\n");
812   - broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
813   - broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
814   - broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
815   - broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
816   - broadcastXml.append("</Notify>\r\n");
817   -
818   - String tm = Long.toString(System.currentTimeMillis());
819 797  
820   - CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
821   - : udpSipProvider.getNewCallId();
822   -
823   - Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "z9hG4bK-ViaBcst-" + tm, "FromBcst" + tm, null, callIdHeader);
824   - transmitRequest(device, request, errorEvent);
825   - } catch (SipException | ParseException | InvalidArgumentException e) {
826   - e.printStackTrace();
827   - }
828   - }
829   -
830   -
831 798 /**
832 799 * 音视频录像控制
833 800 *
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
... ... @@ -94,6 +94,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
94 94 param.put("dst_port", sendRtpItem.getPort());
95 95 param.put("is_udp", is_Udp);
96 96 param.put("src_port", sendRtpItem.getLocalPort());
  97 + param.put("pt", 8);
  98 + param.put("use_ps", 0);
  99 + param.put("only_audio", 1);
97 100 zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
98 101  
99 102  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
... ... @@ -2,21 +2,27 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
2 2  
3 3 import com.alibaba.fastjson.JSONObject;
4 4 import com.genersoft.iot.vmp.conf.DynamicTask;
  5 +import com.genersoft.iot.vmp.conf.SipConfig;
5 6 import com.genersoft.iot.vmp.conf.UserSetting;
6 7 import com.genersoft.iot.vmp.gb28181.bean.*;
7 8 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  9 +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
8 10 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
9 11 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
  12 +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
  13 +import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
10 14 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
11 15 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
12 16 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
13 17 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
14 18 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
15 19 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
  20 +import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
16 21 import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
17 22 import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
18 23 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
19 24 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  25 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItemLite;
20 26 import com.genersoft.iot.vmp.service.IMediaServerService;
21 27 import com.genersoft.iot.vmp.service.IPlayService;
22 28 import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
... ... @@ -24,8 +30,12 @@ import com.genersoft.iot.vmp.service.bean.SSRCInfo;
24 30 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
25 31 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
26 32 import com.genersoft.iot.vmp.utils.SerializeUtils;
  33 +import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
  34 +import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
27 35 import gov.nist.javax.sdp.TimeDescriptionImpl;
28 36 import gov.nist.javax.sdp.fields.TimeField;
  37 +import gov.nist.javax.sip.message.SIPRequest;
  38 +import gov.nist.javax.sip.stack.SIPDialog;
29 39 import org.slf4j.Logger;
30 40 import org.slf4j.LoggerFactory;
31 41 import org.springframework.beans.factory.InitializingBean;
... ... @@ -41,6 +51,7 @@ import javax.sip.message.Response;
41 51 import java.text.ParseException;
42 52 import java.text.SimpleDateFormat;
43 53 import java.util.Date;
  54 +import java.util.List;
44 55 import java.util.Vector;
45 56  
46 57 /**
... ... @@ -73,7 +84,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
73 84 private IPlayService playService;
74 85  
75 86 @Autowired
76   - private ISIPCommander commander;
  87 + private AudioBroadcastManager audioBroadcastManager;
77 88  
78 89 @Autowired
79 90 private ZLMRTPServerFactory zlmrtpServerFactory;
... ... @@ -93,6 +104,15 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
93 104 @Autowired
94 105 private ZLMMediaListManager mediaListManager;
95 106  
  107 + @Autowired
  108 + private DeferredResultHolder resultHolder;
  109 +
  110 + @Autowired
  111 + private ZLMHttpHookSubscribe subscribe;
  112 +
  113 + @Autowired
  114 + private SipConfig config;
  115 +
96 116  
97 117 @Override
98 118 public void afterPropertiesSet() throws Exception {
... ... @@ -126,7 +146,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
126 146 // 查询请求是否来自上级平台\设备
127 147 ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
128 148 if (platform == null) {
129   - inviteFromDeviceHandle(evt, requesterId);
  149 + inviteFromDeviceHandle(evt, requesterId, channelId);
130 150 }else {
131 151 // 查询平台下是否有该通道
132 152 DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
... ... @@ -542,10 +562,25 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
542 562 }
543 563 }
544 564  
545   - public void inviteFromDeviceHandle(RequestEvent evt, String requesterId) throws InvalidArgumentException, ParseException, SipException, SdpException {
546   -
  565 + public void inviteFromDeviceHandle(RequestEvent evt, String requesterId, String channelId) throws InvalidArgumentException, ParseException, SipException, SdpException {
  566 +
  567 + // 兼容奇葩的海康这里使用的不是通道编号而是本平台编号
  568 +// if (channelId.equals(config.getId())) {
  569 +// List<AudioBroadcastCatch> all = audioBroadcastManager.getAll();
  570 +// for (AudioBroadcastCatch audioBroadcastCatch : all) {
  571 +// if (audioBroadcastCatch.getDeviceId().equals(requesterId)) {
  572 +// channelId = audioBroadcastCatch.getChannelId();
  573 +// }
  574 +// }
  575 +// }
  576 +// // 兼容失败
  577 +// if (channelId.equals(config.getId())) {
  578 +// responseAck(evt, Response.BAD_REQUEST);
  579 +// return;
  580 +// }
547 581 // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
548 582 Device device = redisCatchStorage.getDevice(requesterId);
  583 +
549 584 Request request = evt.getRequest();
550 585 if (device != null) {
551 586 logger.info("收到设备" + requesterId + "的语音广播Invite请求");
... ... @@ -558,7 +593,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
558 593 int ssrcIndex = contentString.indexOf("y=");
559 594 if (ssrcIndex > 0) {
560 595 substring = contentString.substring(0, ssrcIndex);
561   - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  596 + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim();
562 597 }
563 598 ssrcIndex = substring.indexOf("f=");
564 599 if (ssrcIndex > 0) {
... ... @@ -568,6 +603,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
568 603  
569 604 // 获取支持的格式
570 605 Vector mediaDescriptions = sdp.getMediaDescriptions(true);
  606 +
571 607 // 查看是否支持PS 负载96
572 608 int port = -1;
573 609 //boolean recvonly = false;
... ... @@ -602,10 +638,150 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
602 638 responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
603 639 return;
604 640 }
605   - String username = sdp.getOrigin().getUsername();
  641 + String sessionName = sdp.getSessionName().getValue();
606 642 String addressStr = sdp.getOrigin().getAddress();
607   - logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
  643 + logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", requesterId, addressStr, port, ssrc);
  644 +
  645 + MediaServerItem mediaServerItem = playService.getNewMediaServerItem(device);
  646 + if (mediaServerItem == null) {
  647 + logger.warn("未找到可用的zlm");
  648 + responseAck(evt, Response.BUSY_HERE);
  649 + return;
  650 + }
  651 + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
  652 + device.getDeviceId(), channelId,
  653 + mediaTransmissionTCP);
  654 + sendRtpItem.setTcp(mediaTransmissionTCP);
  655 + if (tcpActive != null) {
  656 + sendRtpItem.setTcpActive(tcpActive);
  657 + }
  658 + if (sendRtpItem == null) {
  659 + logger.warn("服务器端口资源不足");
  660 + responseAck(evt, Response.BUSY_HERE);
  661 + return;
  662 + }
  663 +
  664 + String app = "broadcast";
  665 + String stream = device.getDeviceId() + "_" + channelId;
  666 +
  667 + CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
  668 + sendRtpItem.setPlayType(InviteStreamType.PLAY);
  669 + sendRtpItem.setCallId(callIdHeader.getCallId());
  670 + sendRtpItem.setPlatformId(requesterId);
  671 + sendRtpItem.setStatus(1);
  672 + sendRtpItem.setApp(app);
  673 + sendRtpItem.setStreamId(stream);
  674 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  675 +
  676 + // hook监听等待设备推流上来
  677 + // 添加订阅
  678 + JSONObject subscribeKey = new JSONObject();
  679 + subscribeKey.put("app", app);
  680 + subscribeKey.put("stream", stream);
  681 + subscribeKey.put("regist", true);
  682 + subscribeKey.put("schema", "rtmp");
  683 + subscribeKey.put("mediaServerId", mediaServerItem.getId());
  684 + String finalSsrc = ssrc;
  685 + String waiteStreamTimeoutTaskKey = "waite-stream-" + device.getDeviceId() + channelId;
  686 + if (zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream)) {
  687 + logger.info("发现已经在推流");
  688 + dynamicTask.stop(waiteStreamTimeoutTaskKey);
  689 + sendRtpItem.setStatus(2);
  690 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  691 + StringBuffer content = new StringBuffer(200);
  692 + content.append("v=0\r\n");
  693 + content.append("o="+ config.getId() +" "+ sdp.getOrigin().getSessionId() +" " + sdp.getOrigin().getSessionVersion() + " IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
  694 + content.append("s=Play\r\n");
  695 + content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
  696 + content.append("t=0 0\r\n");
  697 + content.append("m=audio "+ sendRtpItem.getLocalPort()+" RTP/AVP 8\r\n");
  698 + content.append("a=sendonly\r\n");
  699 + content.append("a=rtpmap:8 PCMA/8000\r\n");
  700 + content.append("y="+ finalSsrc + "\r\n");
  701 + content.append("f=v/////a/1/8/1\r\n");
  702 +
  703 + ParentPlatform parentPlatform = new ParentPlatform();
  704 + parentPlatform.setServerIP(device.getIp());
  705 + parentPlatform.setServerPort(device.getPort());
  706 + parentPlatform.setServerGBId(device.getDeviceId());
  707 + try {
  708 + responseSdpAck(evt, content.toString(), parentPlatform);
  709 + } catch (SipException e) {
  710 + throw new RuntimeException(e);
  711 + } catch (InvalidArgumentException e) {
  712 + throw new RuntimeException(e);
  713 + } catch (ParseException e) {
  714 + throw new RuntimeException(e);
  715 + }
  716 + }else {
  717 + // 设置等待推流的超时; 默认20s
  718 + String finalChannelId = channelId;
  719 + dynamicTask.startDelay(waiteStreamTimeoutTaskKey, ()->{
  720 + logger.info("等待推流超时: {}/{}", app, stream);
  721 + if (audioBroadcastManager.exit(device.getDeviceId(), finalChannelId)) {
  722 + audioBroadcastManager.del(device.getDeviceId(), finalChannelId);
  723 + }else {
  724 + // 兼容海康使用了错误的通道ID的情况
  725 + audioBroadcastManager.delByDeviceId(device.getDeviceId());
  726 + }
608 727  
  728 + // 发送bye
  729 + try {
  730 + cmder.streamByeCmd((SIPDialog)evt.getServerTransaction().getDialog(), (SIPRequest) evt.getRequest(), null);
  731 + } catch (SipException e) {
  732 + throw new RuntimeException(e);
  733 + } catch (ParseException e) {
  734 + throw new RuntimeException(e);
  735 + }
  736 + }, 20*1000);
  737 +
  738 + subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
  739 + (MediaServerItem mediaServerItemInUse, JSONObject json)->{
  740 + sendRtpItem.setStatus(2);
  741 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  742 + StringBuffer content = new StringBuffer(200);
  743 + content.append("v=0\r\n");
  744 + content.append("o="+ finalChannelId +" 0 0 IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
  745 + content.append("s=Play\r\n");
  746 + content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
  747 + content.append("t=0 0\r\n");
  748 + content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 8\r\n");
  749 + content.append("a=sendonly\r\n");
  750 + content.append("a=rtpmap:8 PCMA/8000\r\n");
  751 + content.append("y="+ finalSsrc + "\r\n");
  752 + content.append("f=v/////a/1/8/1\r\n");
  753 +
  754 + ParentPlatform parentPlatform = new ParentPlatform();
  755 + parentPlatform.setServerIP(device.getIp());
  756 + parentPlatform.setServerPort(device.getPort());
  757 + parentPlatform.setServerGBId(device.getDeviceId());
  758 + try {
  759 + responseSdpAck(evt, content.toString(), parentPlatform);
  760 + } catch (SipException e) {
  761 + throw new RuntimeException(e);
  762 + } catch (InvalidArgumentException e) {
  763 + throw new RuntimeException(e);
  764 + } catch (ParseException e) {
  765 + throw new RuntimeException(e);
  766 + }
  767 + });
  768 + }
  769 + String timeOutTaskKey = "audio-broadcast-" + device.getDeviceId() + channelId;
  770 + dynamicTask.stop(timeOutTaskKey);
  771 + String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId();
  772 + WVPResult<AudioBroadcastResult> wvpResult = new WVPResult<>();
  773 + wvpResult.setCode(0);
  774 + wvpResult.setMsg("success");
  775 + AudioBroadcastResult audioBroadcastResult = new AudioBroadcastResult();
  776 + audioBroadcastResult.setApp(app);
  777 + audioBroadcastResult.setStream(stream);
  778 + audioBroadcastResult.setMediaServerItem(new MediaServerItemLite(mediaServerItem));
  779 + audioBroadcastResult.setCodec("G.711");
  780 + wvpResult.setData(audioBroadcastResult);
  781 + RequestMessage requestMessage = new RequestMessage();
  782 + requestMessage.setKey(key);
  783 + requestMessage.setData(wvpResult);
  784 + resultHolder.invokeAllResult(requestMessage);
609 785 } else {
610 786 logger.warn("来自无效设备/平台的请求");
611 787 responseAck(evt, Response.BAD_REQUEST);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java
... ... @@ -6,7 +6,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
6 6 import org.dom4j.Element;
7 7 import org.springframework.beans.factory.annotation.Autowired;
8 8  
  9 +import javax.sip.InvalidArgumentException;
9 10 import javax.sip.RequestEvent;
  11 +import javax.sip.SipException;
  12 +import javax.sip.message.Response;
  13 +import java.text.ParseException;
10 14 import java.util.Map;
11 15 import java.util.concurrent.ConcurrentHashMap;
12 16  
... ... @@ -23,6 +27,10 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
23 27 @Override
24 28 public void handForDevice(RequestEvent evt, Device device, Element element) {
25 29 String cmd = getText(element, "CmdType");
  30 + if (cmd == null) {
  31 + handNullCmd(evt);
  32 + return;
  33 + }
26 34 IMessageHandler messageHandler = messageHandlerMap.get(cmd);
27 35 if (messageHandler != null) {
28 36 messageHandler.handForDevice(evt, device, element);
... ... @@ -37,4 +45,17 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
37 45 messageHandler.handForPlatform(evt, parentPlatform, element);
38 46 }
39 47 }
  48 +
  49 + public void handNullCmd(RequestEvent evt){
  50 + try {
  51 + responseAck(evt, Response.OK);
  52 + } catch (SipException e) {
  53 + throw new RuntimeException(e);
  54 + } catch (InvalidArgumentException e) {
  55 + throw new RuntimeException(e);
  56 + } catch (ParseException e) {
  57 + throw new RuntimeException(e);
  58 + }
  59 + return;
  60 + }
40 61 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
2 2  
3 3 import com.alibaba.fastjson.JSONObject;
  4 +import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch;
  5 +import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus;
4 6 import com.genersoft.iot.vmp.gb28181.bean.Device;
5 7 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
  8 +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
6 9 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
7 10 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
8 11 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
... ... @@ -36,6 +39,9 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
36 39 @Autowired
37 40 private DeferredResultHolder deferredResultHolder;
38 41  
  42 + @Autowired
  43 + private AudioBroadcastManager audioBroadcastManager;
  44 +
39 45 @Override
40 46 public void afterPropertiesSet() throws Exception {
41 47 responseMessageHandler.addHandler(cmdType, this);
... ... @@ -45,21 +51,16 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
45 51 public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
46 52 try {
47 53 String channelId = getText(rootElement, "DeviceID");
48   - String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId;
49   - // 回复200 OK
50   - responseAck(evt, Response.OK);
51   - // 此处是对本平台发出Broadcast指令的应答
52   - JSONObject json = new JSONObject();
53   - XmlUtil.node2Json(rootElement, json);
54   - if (logger.isDebugEnabled()) {
55   - logger.debug(json.toJSONString());
  54 + if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
  55 + // 回复410
  56 + responseAck(evt, Response.GONE);
  57 + return;
56 58 }
57   - RequestMessage msg = new RequestMessage();
58   - msg.setKey(key);
59   - msg.setData(json);
60   - deferredResultHolder.invokeAllResult(msg);
61   -
62   -
  59 + logger.info("收到语音广播的回复:{}/{}", device.getDeviceId(), channelId );
  60 + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId);
  61 + audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite);
  62 + audioBroadcastManager.update(audioBroadcastCatch);
  63 + responseAck(evt, Response.OK);
63 64 } catch (ParseException | SipException | InvalidArgumentException e) {
64 65 e.printStackTrace();
65 66 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
... ... @@ -271,7 +271,7 @@ public class ZLMRTPServerFactory {
271 271 * 查询待转推的流是否就绪
272 272 */
273 273 public Boolean isStreamReady(MediaServerItem mediaServerItem, String app, String streamId) {
274   - JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
  274 + JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtsp", streamId);
275 275 return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
276 276 }
277 277  
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItemLite.java 0 → 100644
  1 +package com.genersoft.iot.vmp.media.zlm.dto;
  2 +
  3 +
  4 +import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
  5 +import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
  6 +import org.springframework.util.StringUtils;
  7 +
  8 +import java.util.HashMap;
  9 +
  10 +/**
  11 + * 精简的MediaServerItem信息,方便给前端返回数据
  12 + */
  13 +public class MediaServerItemLite {
  14 +
  15 + private String id;
  16 +
  17 + private String ip;
  18 +
  19 + private String hookIp;
  20 +
  21 + private String sdpIp;
  22 +
  23 + private String streamIp;
  24 +
  25 + private int httpPort;
  26 +
  27 + private int httpSSlPort;
  28 +
  29 + private int rtmpPort;
  30 +
  31 + private int rtmpSSlPort;
  32 +
  33 + private int rtpProxyPort;
  34 +
  35 + private int rtspPort;
  36 +
  37 + private int rtspSSLPort;
  38 +
  39 + private String secret;
  40 +
  41 + private int streamNoneReaderDelayMS;
  42 +
  43 + private int hookAliveInterval;
  44 +
  45 + private int recordAssistPort;
  46 +
  47 +
  48 +
  49 + public MediaServerItemLite(MediaServerItem mediaServerItem) {
  50 + this.id = mediaServerItem.getId();
  51 + this.ip = mediaServerItem.getIp();
  52 + this.hookIp = mediaServerItem.getHookIp();
  53 + this.sdpIp = mediaServerItem.getSdpIp();
  54 + this.streamIp = mediaServerItem.getStreamIp();
  55 + this.httpPort = mediaServerItem.getHttpPort();
  56 + this.httpSSlPort = mediaServerItem.getHttpSSlPort();
  57 + this.rtmpPort = mediaServerItem.getRtmpPort();
  58 + this.rtmpSSlPort = mediaServerItem.getRtmpSSlPort();
  59 + this.rtpProxyPort = mediaServerItem.getRtpProxyPort();
  60 + this.rtspPort = mediaServerItem.getRtspPort();
  61 + this.rtspSSLPort = mediaServerItem.getRtspSSLPort();
  62 + this.secret = mediaServerItem.getSecret();
  63 + this.streamNoneReaderDelayMS = mediaServerItem.getStreamNoneReaderDelayMS();
  64 + this.hookAliveInterval = mediaServerItem.getHookAliveInterval();
  65 + this.streamNoneReaderDelayMS = mediaServerItem.getStreamNoneReaderDelayMS();
  66 + this.recordAssistPort = mediaServerItem.getRecordAssistPort();
  67 + }
  68 +
  69 + public String getId() {
  70 + return id;
  71 + }
  72 +
  73 + public void setId(String id) {
  74 + this.id = id;
  75 + }
  76 +
  77 + public String getIp() {
  78 + return ip;
  79 + }
  80 +
  81 + public void setIp(String ip) {
  82 + this.ip = ip;
  83 + }
  84 +
  85 + public String getHookIp() {
  86 + return hookIp;
  87 + }
  88 +
  89 + public void setHookIp(String hookIp) {
  90 + this.hookIp = hookIp;
  91 + }
  92 +
  93 + public String getSdpIp() {
  94 + return sdpIp;
  95 + }
  96 +
  97 + public void setSdpIp(String sdpIp) {
  98 + this.sdpIp = sdpIp;
  99 + }
  100 +
  101 + public String getStreamIp() {
  102 + return streamIp;
  103 + }
  104 +
  105 + public void setStreamIp(String streamIp) {
  106 + this.streamIp = streamIp;
  107 + }
  108 +
  109 + public int getHttpPort() {
  110 + return httpPort;
  111 + }
  112 +
  113 + public void setHttpPort(int httpPort) {
  114 + this.httpPort = httpPort;
  115 + }
  116 +
  117 + public int getHttpSSlPort() {
  118 + return httpSSlPort;
  119 + }
  120 +
  121 + public void setHttpSSlPort(int httpSSlPort) {
  122 + this.httpSSlPort = httpSSlPort;
  123 + }
  124 +
  125 + public int getRtmpPort() {
  126 + return rtmpPort;
  127 + }
  128 +
  129 + public void setRtmpPort(int rtmpPort) {
  130 + this.rtmpPort = rtmpPort;
  131 + }
  132 +
  133 + public int getRtmpSSlPort() {
  134 + return rtmpSSlPort;
  135 + }
  136 +
  137 + public void setRtmpSSlPort(int rtmpSSlPort) {
  138 + this.rtmpSSlPort = rtmpSSlPort;
  139 + }
  140 +
  141 + public int getRtpProxyPort() {
  142 + return rtpProxyPort;
  143 + }
  144 +
  145 + public void setRtpProxyPort(int rtpProxyPort) {
  146 + this.rtpProxyPort = rtpProxyPort;
  147 + }
  148 +
  149 + public int getRtspPort() {
  150 + return rtspPort;
  151 + }
  152 +
  153 + public void setRtspPort(int rtspPort) {
  154 + this.rtspPort = rtspPort;
  155 + }
  156 +
  157 + public int getRtspSSLPort() {
  158 + return rtspSSLPort;
  159 + }
  160 +
  161 + public void setRtspSSLPort(int rtspSSLPort) {
  162 + this.rtspSSLPort = rtspSSLPort;
  163 + }
  164 +
  165 +
  166 + public String getSecret() {
  167 + return secret;
  168 + }
  169 +
  170 + public void setSecret(String secret) {
  171 + this.secret = secret;
  172 + }
  173 +
  174 + public int getStreamNoneReaderDelayMS() {
  175 + return streamNoneReaderDelayMS;
  176 + }
  177 +
  178 + public void setStreamNoneReaderDelayMS(int streamNoneReaderDelayMS) {
  179 + this.streamNoneReaderDelayMS = streamNoneReaderDelayMS;
  180 + }
  181 +
  182 + public int getHookAliveInterval() {
  183 + return hookAliveInterval;
  184 + }
  185 +
  186 + public void setHookAliveInterval(int hookAliveInterval) {
  187 + this.hookAliveInterval = hookAliveInterval;
  188 + }
  189 +
  190 + public int getRecordAssistPort() {
  191 + return recordAssistPort;
  192 + }
  193 +
  194 + public void setRecordAssistPort(int recordAssistPort) {
  195 + this.recordAssistPort = recordAssistPort;
  196 + }
  197 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
... ... @@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
11 11 import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
12 12 import com.genersoft.iot.vmp.service.bean.PlayBackCallback;
13 13 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
  14 +import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
14 15 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
15 16 import org.springframework.http.ResponseEntity;
16 17 import org.springframework.web.context.request.async.DeferredResult;
... ... @@ -40,4 +41,6 @@ public interface IPlayService {
40 41 DeferredResult<ResponseEntity<String>> download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack);
41 42  
42 43 StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream);
  44 +
  45 + void audioBroadcast(Device device, String channelId, int timeout, AudioBroadcastEvent event);
43 46 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
... ... @@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.conf.DynamicTask;
8 8 import com.genersoft.iot.vmp.conf.UserSetting;
9 9 import com.genersoft.iot.vmp.gb28181.bean.*;
10 10 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  11 +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
11 12 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
12 13 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
13 14 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
... ... @@ -26,7 +27,9 @@ import com.genersoft.iot.vmp.service.bean.SSRCInfo;
26 27 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
27 28 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
28 29 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
  30 +import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
29 31 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
  32 +import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
30 33 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
31 34 import com.genersoft.iot.vmp.service.IMediaService;
32 35 import com.genersoft.iot.vmp.service.IPlayService;
... ... @@ -58,6 +61,9 @@ public class PlayServiceImpl implements IPlayService {
58 61 private SIPCommander cmder;
59 62  
60 63 @Autowired
  64 + private AudioBroadcastManager audioBroadcastManager;
  65 +
  66 + @Autowired
61 67 private SIPCommanderFroPlatform sipCommanderFroPlatform;
62 68  
63 69 @Autowired
... ... @@ -621,4 +627,42 @@ public class PlayServiceImpl implements IPlayService {
621 627 }
622 628 }
623 629 }
  630 +
  631 + @Override
  632 + public void audioBroadcast(Device device, String channelId, int timeout, AudioBroadcastEvent event) {
  633 + if (device == null || channelId == null) {
  634 + return;
  635 + }
  636 + DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
  637 + if (deviceChannel == null) {
  638 + logger.warn("开启语音广播的时候未找到通道: {}", channelId);
  639 + event.call("开启语音广播的时候未找到通道");
  640 + return;
  641 + }
  642 + // 查询通道使用状态
  643 + if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
  644 + logger.warn("语音广播已经开启: {}", channelId);
  645 + event.call("语音广播已经开启");
  646 + return;
  647 + }
  648 + String timeOutTaskKey = "audio-broadcast-" + device.getDeviceId() + channelId;
  649 + dynamicTask.startDelay(timeOutTaskKey, ()->{
  650 + logger.error("语音广播发送超时: {}:{}", device.getDeviceId(), channelId);
  651 + event.call("语音广播发送超时");
  652 + audioBroadcastManager.del(device.getDeviceId(), channelId);
  653 + }, timeout * 1000);
  654 +
  655 + // 发送通知
  656 + cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> {
  657 + // 发送成功
  658 + AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, AudioBroadcastCatchStatus.Ready);
  659 + audioBroadcastManager.add(audioBroadcastCatch);
  660 + }, eventResultForError -> {
  661 + dynamicTask.stop(timeOutTaskKey);
  662 + // 发送失败
  663 + logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg);
  664 + event.call("语音广播发送失败");
  665 + audioBroadcastManager.del(device.getDeviceId(), channelId);
  666 + });
  667 + }
624 668 }
... ...
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
... ... @@ -37,8 +37,6 @@ public interface DeviceMapper {
37 37 "subscribeCycleForMobilePosition," +
38 38 "mobilePositionSubmissionInterval," +
39 39 "subscribeCycleForAlarm," +
40   - "audioChannelForReceive," +
41   - "audioChannelForSend," +
42 40 "ssrcCheck," +
43 41 "online" +
44 42 ") VALUES (" +
... ... @@ -62,8 +60,6 @@ public interface DeviceMapper {
62 60 "#{subscribeCycleForMobilePosition}," +
63 61 "#{mobilePositionSubmissionInterval}," +
64 62 "#{subscribeCycleForAlarm}," +
65   - "#{audioChannelForReceive}," +
66   - "#{audioChannelForSend}," +
67 63 "#{ssrcCheck}," +
68 64 "#{online}" +
69 65 ")")
... ... @@ -90,8 +86,6 @@ public interface DeviceMapper {
90 86 "<if test=\"subscribeCycleForMobilePosition != null\">, subscribeCycleForMobilePosition=${subscribeCycleForMobilePosition}</if>" +
91 87 "<if test=\"mobilePositionSubmissionInterval != null\">, mobilePositionSubmissionInterval=${mobilePositionSubmissionInterval}</if>" +
92 88 "<if test=\"subscribeCycleForAlarm != null\">, subscribeCycleForAlarm=${subscribeCycleForAlarm}</if>" +
93   - "<if test=\"audioChannelForReceive != null\">, audioChannelForReceive=#{audioChannelForReceive}</if>" +
94   - "<if test=\"audioChannelForSend != null\">, audioChannelForSend=#{audioChannelForSend}</if>" +
95 89 "<if test=\"ssrcCheck != null\">, ssrcCheck=${ssrcCheck}</if>" +
96 90 "WHERE deviceId='${deviceId}'"+
97 91 " </script>"})
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/bean/AudioBroadcastResult.java 0 → 100644
  1 +package com.genersoft.iot.vmp.vmanager.bean;
  2 +
  3 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  4 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItemLite;
  5 +
  6 +/**
  7 + * @author lin
  8 + */
  9 +public class AudioBroadcastResult {
  10 + /**
  11 + * 推流的媒体节点信息
  12 + */
  13 + private MediaServerItemLite mediaServerItem;
  14 +
  15 + /**
  16 + * 编码格式
  17 + */
  18 + private String codec;
  19 +
  20 + /**
  21 + * 向zlm推流的应用名
  22 + */
  23 + private String app;
  24 +
  25 + /**
  26 + * 向zlm推流的流ID
  27 + */
  28 + private String stream;
  29 +
  30 +
  31 + public MediaServerItemLite getMediaServerItem() {
  32 + return mediaServerItem;
  33 + }
  34 +
  35 + public void setMediaServerItem(MediaServerItemLite mediaServerItem) {
  36 + this.mediaServerItem = mediaServerItem;
  37 + }
  38 +
  39 + public String getCodec() {
  40 + return codec;
  41 + }
  42 +
  43 + public void setCodec(String codec) {
  44 + this.codec = codec;
  45 + }
  46 +
  47 + public String getApp() {
  48 + return app;
  49 + }
  50 +
  51 + public void setApp(String app) {
  52 + this.app = app;
  53 + }
  54 +
  55 + public String getStream() {
  56 + return stream;
  57 + }
  58 +
  59 + public void setStream(String stream) {
  60 + this.stream = stream;
  61 + }
  62 +}
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
... ... @@ -11,6 +11,7 @@ import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
11 11 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
12 12 import com.genersoft.iot.vmp.service.IMediaServerService;
13 13 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  14 +import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
14 15 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
15 16 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
16 17 import com.genersoft.iot.vmp.service.IMediaService;
... ... @@ -39,6 +40,9 @@ import org.springframework.web.context.request.async.DeferredResult;
39 40 import java.util.List;
40 41 import java.util.UUID;
41 42  
  43 +/**
  44 + * @author lin
  45 + */
42 46 @Api(tags = "国标设备点播")
43 47 @CrossOrigin
44 48 @RestController
... ... @@ -102,7 +106,7 @@ public class PlayController {
102 106 logger.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId ));
103 107  
104 108 String uuid = UUID.randomUUID().toString();
105   - DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
  109 + DeferredResult<ResponseEntity<String>> result = new DeferredResult<>();
106 110  
107 111 // 录像查询以channelId作为deviceId查询
108 112 String key = DeferredResultHolder.CALLBACK_CMD_STOP + deviceId + channelId;
... ... @@ -123,7 +127,7 @@ public class PlayController {
123 127 RequestMessage msgForSuccess = new RequestMessage();
124 128 msgForSuccess.setId(uuid);
125 129 msgForSuccess.setKey(key);
126   - msgForSuccess.setData(String.format("success"));
  130 + msgForSuccess.setData("success");
127 131 resultHolder.invokeAllResult(msgForSuccess);
128 132 });
129 133  
... ... @@ -251,81 +255,73 @@ public class PlayController {
251 255 @ApiOperation("语音广播命令")
252 256 @ApiImplicitParams({
253 257 @ApiImplicitParam(name = "deviceId", value = "设备Id", dataTypeClass = String.class),
254   - @ApiImplicitParam(name = "channelForSend", value = "设备用于发送语音数据的通道", dataTypeClass = String.class),
255   - @ApiImplicitParam(name = "channelForReceive", value = "设备用于接收语音数据的通道", dataTypeClass = String.class),
  258 + @ApiImplicitParam(name = "channelId", value = "通道Id", dataTypeClass = String.class),
  259 + @ApiImplicitParam(name = "timeout", value = "推流超时时间(秒)", dataTypeClass = Integer.class),
256 260 })
257   - @GetMapping("/broadcast/{deviceId}")
258   - @PostMapping("/broadcast/{deviceId}")
259   - public DeferredResult<ResponseEntity<String>> broadcastApi(@PathVariable String deviceId,
260   - String channelForSend,
261   - String channelForReceive) {
  261 + @GetMapping("/broadcast/{deviceId}/{channelId}")
  262 + @PostMapping("/broadcast/{deviceId}/{channelId}")
  263 + public DeferredResult<WVPResult<AudioBroadcastResult>> broadcastApi(@PathVariable String deviceId, @PathVariable String channelId, Integer timeout) {
262 264 if (logger.isDebugEnabled()) {
263 265 logger.debug("语音广播API调用");
264 266 }
265 267 Device device = storager.queryVideoDevice(deviceId);
266   - DeferredResult<ResponseEntity<String>> result = new DeferredResult<>(3 * 1000L);
267   - String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
268   - if (resultHolder.exist(key, null)) {
269   - result.setResult(new ResponseEntity<>("设备使用中",HttpStatus.OK));
270   - return result;
  268 + if (device == null) {
  269 + WVPResult<AudioBroadcastResult> result = new WVPResult<>();
  270 + result.setCode(-1);
  271 + result.setMsg("未找到设备: " + deviceId);
  272 + DeferredResult<WVPResult<AudioBroadcastResult>> deferredResult = new DeferredResult<>();
  273 + deferredResult.setResult(result);
  274 + return deferredResult;
  275 + }
  276 + if (channelId == null) {
  277 + WVPResult<AudioBroadcastResult> result = new WVPResult<>();
  278 + result.setCode(-1);
  279 + result.setMsg("未找到通道: " + channelId);
  280 + DeferredResult<WVPResult<AudioBroadcastResult>> deferredResult = new DeferredResult<>();
  281 + deferredResult.setResult(result);
  282 + return deferredResult;
271 283 }
272 284  
273   -// playService.audioBroadcast(deviceId, channelForSend, channelForReceive);
274   -
275   -
276   -
277   -
278   -
279   -
280   - String uuid = UUID.randomUUID().toString();
281   - if (device == null) {
282   -
283   - resultHolder.put(key, key, result);
284   - RequestMessage msg = new RequestMessage();
285   - msg.setKey(key);
286   - msg.setId(uuid);
287   - JSONObject json = new JSONObject();
288   - json.put("DeviceID", deviceId);
289   - json.put("CmdType", "Broadcast");
290   - json.put("Result", "Failed");
291   - json.put("Description", "Device 不存在");
292   - msg.setData(json);
293   - resultHolder.invokeResult(msg);
294   - return result;
  285 + String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
  286 + if (resultHolder.exist(key, null)) {
  287 + WVPResult<AudioBroadcastResult> wvpResult = new WVPResult<>();
  288 + wvpResult.setCode(-1);
  289 + wvpResult.setMsg("设备使用中");
  290 + DeferredResult<WVPResult<AudioBroadcastResult>> deferredResult = new DeferredResult<>();
  291 + deferredResult.setResult(wvpResult);
  292 + return deferredResult;
295 293 }
296   - cmder.audioBroadcastCmd(device, (event) -> {
297   - RequestMessage msg = new RequestMessage();
298   - msg.setKey(key);
299   - msg.setId(uuid);
300   - JSONObject json = new JSONObject();
301   - json.put("DeviceID", deviceId);
302   - json.put("CmdType", "Broadcast");
303   - json.put("Result", "Failed");
304   - json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", event.statusCode, event.msg));
305   - msg.setData(json);
306   - resultHolder.invokeResult(msg);
  294 + if (timeout == null){
  295 + timeout = 30;
  296 + }
  297 + DeferredResult<WVPResult<AudioBroadcastResult>> result = new DeferredResult<>(timeout.longValue()*1000 + 2000);
  298 + String uuid = UUID.randomUUID().toString();
  299 + result.onTimeout(()->{
  300 + WVPResult<AudioBroadcastResult> wvpResult = new WVPResult<>();
  301 + wvpResult.setCode(-1);
  302 + wvpResult.setMsg("请求超时");
  303 + RequestMessage requestMessage = new RequestMessage();
  304 + requestMessage.setKey(key);
  305 + requestMessage.setData(wvpResult);
  306 + resultHolder.invokeAllResult(requestMessage);
307 307 });
308   -
309   - result.onTimeout(() -> {
310   - logger.warn(String.format("语音广播操作超时, 设备未返回应答指令"));
311   - RequestMessage msg = new RequestMessage();
312   - msg.setKey(key);
313   - msg.setId(uuid);
314   - JSONObject json = new JSONObject();
315   - json.put("DeviceID", deviceId);
316   - json.put("CmdType", "Broadcast");
317   - json.put("Result", "Failed");
318   - json.put("Error", "Timeout. Device did not response to broadcast command.");
319   - msg.setData(json);
320   - resultHolder.invokeResult(msg);
  308 + playService.audioBroadcast(device, channelId, timeout, (msg)->{
  309 + WVPResult<AudioBroadcastResult> wvpResult = new WVPResult<>();
  310 + wvpResult.setCode(-1);
  311 + wvpResult.setMsg(msg);
  312 + RequestMessage requestMessage = new RequestMessage();
  313 + requestMessage.setKey(key);
  314 + requestMessage.setData(wvpResult);
  315 + resultHolder.invokeAllResult(requestMessage);
321 316 });
322 317 resultHolder.put(key, uuid, result);
  318 +
323 319 return result;
324 320 }
325 321  
326 322 @ApiOperation("获取所有的ssrc")
327 323 @GetMapping("/ssrc")
328   - public WVPResult<JSONObject> getSSRC() {
  324 + public WVPResult<JSONObject> getSsrc() {
329 325 if (logger.isDebugEnabled()) {
330 326 logger.debug("获取所有的ssrc");
331 327 }
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/bean/AudioBroadcastEvent.java 0 → 100644
  1 +package com.genersoft.iot.vmp.vmanager.gb28181.play.bean;
  2 +
  3 +
  4 +/**
  5 + * @author lin
  6 + */
  7 +public interface AudioBroadcastEvent {
  8 + void call(String msg);
  9 +}
... ...
web_src/src/components/dialog/deviceEdit.vue
... ... @@ -37,9 +37,6 @@
37 37 </el-select>
38 38 </el-form-item>
39 39 <el-form-item label="语音发送通道" prop="name">
40   - <el-input v-model="form.audioChannelForSend" clearable></el-input>
41   - </el-form-item>
42   - <el-form-item label="语音接收送通道" prop="name">
43 40 <el-input v-model="form.audioChannelForReceive" clearable></el-input>
44 41 </el-form-item>
45 42 <el-form-item label="目录订阅" title="0为取消订阅" prop="subscribeCycleForCatalog" >
... ... @@ -105,6 +102,8 @@ export default {
105 102 })
106 103 },
107 104 onSubmit: function () {
  105 + console.log("onSubmit");
  106 + console.log(this.form);
108 107 this.form.subscribeCycleForCatalog = this.form.subscribeCycleForCatalog||0
109 108 this.form.subscribeCycleForMobilePosition = this.form.subscribeCycleForMobilePosition||0
110 109 this.form.mobilePositionSubmissionInterval = this.form.mobilePositionSubmissionInterval||0
... ... @@ -124,7 +123,7 @@ export default {
124 123 });
125 124 }
126 125 }).catch(function (error) {
127   - console.error(error);
  126 + console.log(error);
128 127 });
129 128 },
130 129 close: function () {
... ...