Commit f8f65d473bec182abeecd6fd17a9d4c4c4cfc7c5
1 parent
65fa75fb
优化语音广播流程
Showing
8 changed files
with
158 additions
and
60 deletions
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.bean; |
| 2 | 2 | |
| 3 | 3 | |
| 4 | +import gov.nist.javax.sip.message.SIPRequest; | |
| 5 | +import gov.nist.javax.sip.stack.SIPDialog; | |
| 6 | + | |
| 7 | +import javax.sip.Dialog; | |
| 8 | + | |
| 4 | 9 | /** |
| 5 | 10 | * 缓存语音广播的状态 |
| 6 | 11 | * @author lin |
| ... | ... | @@ -32,6 +37,16 @@ public class AudioBroadcastCatch { |
| 32 | 37 | */ |
| 33 | 38 | private AudioBroadcastCatchStatus status; |
| 34 | 39 | |
| 40 | + /** | |
| 41 | + * 请求信息 | |
| 42 | + */ | |
| 43 | + private SIPRequest request; | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * 会话信息 | |
| 47 | + */ | |
| 48 | + private SIPDialog dialog; | |
| 49 | + | |
| 35 | 50 | |
| 36 | 51 | public String getDeviceId() { |
| 37 | 52 | return deviceId; |
| ... | ... | @@ -56,4 +71,20 @@ public class AudioBroadcastCatch { |
| 56 | 71 | public void setStatus(AudioBroadcastCatchStatus status) { |
| 57 | 72 | this.status = status; |
| 58 | 73 | } |
| 74 | + | |
| 75 | + public void setDialog(SIPDialog dialog) { | |
| 76 | + this.dialog = dialog; | |
| 77 | + } | |
| 78 | + | |
| 79 | + public SIPDialog getDialog() { | |
| 80 | + return dialog; | |
| 81 | + } | |
| 82 | + | |
| 83 | + public SIPRequest getRequest() { | |
| 84 | + return request; | |
| 85 | + } | |
| 86 | + | |
| 87 | + public void setRequest(SIPRequest request) { | |
| 88 | + this.request = request; | |
| 89 | + } | |
| 59 | 90 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java
| 1 | 1 | package com.genersoft.iot.vmp.gb28181.session; |
| 2 | 2 | |
| 3 | +import com.genersoft.iot.vmp.conf.SipConfig; | |
| 3 | 4 | import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch; |
| 5 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 4 | 6 | import org.springframework.stereotype.Component; |
| 5 | 7 | |
| 6 | -import java.util.ArrayList; | |
| 7 | -import java.util.Collection; | |
| 8 | -import java.util.List; | |
| 9 | -import java.util.Map; | |
| 8 | +import java.util.*; | |
| 10 | 9 | import java.util.concurrent.ConcurrentHashMap; |
| 10 | +import java.util.stream.Collectors; | |
| 11 | +import java.util.stream.Stream; | |
| 11 | 12 | |
| 12 | 13 | /** |
| 13 | 14 | * 语音广播消息管理类 |
| ... | ... | @@ -16,6 +17,9 @@ import java.util.concurrent.ConcurrentHashMap; |
| 16 | 17 | @Component |
| 17 | 18 | public class AudioBroadcastManager { |
| 18 | 19 | |
| 20 | + @Autowired | |
| 21 | + private SipConfig config; | |
| 22 | + | |
| 19 | 23 | public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>(); |
| 20 | 24 | |
| 21 | 25 | public void add(AudioBroadcastCatch audioBroadcastCatch) { |
| ... | ... | @@ -54,6 +58,16 @@ public class AudioBroadcastManager { |
| 54 | 58 | } |
| 55 | 59 | |
| 56 | 60 | public AudioBroadcastCatch get(String deviceId, String channelId) { |
| 57 | - return data.get(deviceId + channelId); | |
| 61 | + AudioBroadcastCatch audioBroadcastCatch = data.get(deviceId + channelId); | |
| 62 | + if (audioBroadcastCatch == null) { | |
| 63 | + Stream<AudioBroadcastCatch> allAudioBroadcastCatchStreamForDevice = data.values().stream().filter( | |
| 64 | + audioBroadcastCatchItem -> Objects.equals(audioBroadcastCatchItem.getDeviceId(), deviceId)); | |
| 65 | + List<AudioBroadcastCatch> audioBroadcastCatchList = allAudioBroadcastCatchStreamForDevice.collect(Collectors.toList()); | |
| 66 | + if (audioBroadcastCatchList.size() == 1 && Objects.equals(config.getId(), channelId)) { | |
| 67 | + audioBroadcastCatch = audioBroadcastCatchList.get(0); | |
| 68 | + } | |
| 69 | + } | |
| 70 | + | |
| 71 | + return audioBroadcastCatch; | |
| 58 | 72 | } |
| 59 | 73 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
| ... | ... | @@ -18,6 +18,8 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 18 | 18 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 19 | 19 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 20 | 20 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 21 | +import gov.nist.javax.sip.message.SIPRequest; | |
| 22 | +import gov.nist.javax.sip.stack.SIPDialog; | |
| 21 | 23 | import org.ehcache.shadow.org.terracotta.offheapstore.storage.IntegerStorageEngine; |
| 22 | 24 | import org.slf4j.Logger; |
| 23 | 25 | import org.slf4j.LoggerFactory; |
| ... | ... | @@ -28,15 +30,18 @@ import org.springframework.stereotype.Component; |
| 28 | 30 | import javax.sip.Dialog; |
| 29 | 31 | import javax.sip.DialogState; |
| 30 | 32 | import javax.sip.RequestEvent; |
| 33 | +import javax.sip.SipException; | |
| 31 | 34 | import javax.sip.address.SipURI; |
| 32 | 35 | import javax.sip.header.CallIdHeader; |
| 33 | 36 | import javax.sip.header.FromHeader; |
| 34 | 37 | import javax.sip.header.HeaderAddress; |
| 35 | 38 | import javax.sip.header.ToHeader; |
| 39 | +import java.text.ParseException; | |
| 36 | 40 | import java.util.*; |
| 37 | 41 | |
| 38 | 42 | /** |
| 39 | 43 | * SIP命令类型: ACK请求 |
| 44 | + * @author lin | |
| 40 | 45 | */ |
| 41 | 46 | @Component |
| 42 | 47 | public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { |
| ... | ... | @@ -96,8 +101,8 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 96 | 101 | ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformGbId); |
| 97 | 102 | // 取消设置的超时任务 |
| 98 | 103 | dynamicTask.stop(callIdHeader.getCallId()); |
| 99 | - String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); | |
| 100 | - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId()); | |
| 104 | +// String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); | |
| 105 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, null, null, callIdHeader.getCallId()); | |
| 101 | 106 | String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; |
| 102 | 107 | MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); |
| 103 | 108 | logger.info("收到ACK,开始向上级推流 rtp/{}", sendRtpItem.getStreamId()); |
| ... | ... | @@ -121,7 +126,14 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 121 | 126 | } else { |
| 122 | 127 | logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param)); |
| 123 | 128 | if (sendRtpItem.isOnlyAudio()) { |
| 124 | - // TODO 可能是语音对讲 | |
| 129 | + // 语音对讲 | |
| 130 | + try { | |
| 131 | + cmder.streamByeCmd((SIPDialog) evt.getDialog(), (SIPRequest)evt.getRequest(), null); | |
| 132 | + } catch (SipException e) { | |
| 133 | + throw new RuntimeException(e); | |
| 134 | + } catch (ParseException e) { | |
| 135 | + throw new RuntimeException(e); | |
| 136 | + } | |
| 125 | 137 | }else { |
| 126 | 138 | // 向上级平台 |
| 127 | 139 | commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId()); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
| ... | ... | @@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP |
| 13 | 13 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| 14 | 14 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 15 | 15 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 16 | +import com.genersoft.iot.vmp.service.IPlayService; | |
| 16 | 17 | import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; |
| 17 | 18 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 18 | 19 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| ... | ... | @@ -65,6 +66,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 65 | 66 | @Autowired |
| 66 | 67 | private VideoStreamSessionManager streamSession; |
| 67 | 68 | |
| 69 | + @Autowired | |
| 70 | + private IPlayService playService; | |
| 71 | + | |
| 68 | 72 | @Override |
| 69 | 73 | public void afterPropertiesSet() throws Exception { |
| 70 | 74 | // 添加消息处理的订阅 |
| ... | ... | @@ -106,6 +110,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 106 | 110 | if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { |
| 107 | 111 | cmder.streamByeCmd(sendRtpItem.getDeviceId(), channelId, streamId, null); |
| 108 | 112 | } |
| 113 | + if (sendRtpItem.isOnlyAudio()) { | |
| 114 | + playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), channelId); | |
| 115 | + } | |
| 109 | 116 | if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { |
| 110 | 117 | MessageForPushChannel messageForPushChannel = new MessageForPushChannel(); |
| 111 | 118 | messageForPushChannel.setType(0); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| ... | ... | @@ -114,6 +114,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 114 | 114 | private SipConfig config; |
| 115 | 115 | |
| 116 | 116 | |
| 117 | + | |
| 117 | 118 | @Override |
| 118 | 119 | public void afterPropertiesSet() throws Exception { |
| 119 | 120 | // 添加消息处理的订阅 |
| ... | ... | @@ -492,7 +493,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 492 | 493 | gbStream.getApp(), gbStream.getStream(), channelId, |
| 493 | 494 | mediaTransmissionTCP); |
| 494 | 495 | |
| 495 | - | |
| 496 | 496 | if (sendRtpItem == null) { |
| 497 | 497 | logger.warn("服务器端口资源不足"); |
| 498 | 498 | responseAck(evt, Response.BUSY_HERE); |
| ... | ... | @@ -562,25 +562,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 562 | 562 | } |
| 563 | 563 | } |
| 564 | 564 | |
| 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 | -// } | |
| 565 | + public void inviteFromDeviceHandle(RequestEvent evt, String requesterId, String channelId1) throws InvalidArgumentException, ParseException, SipException, SdpException { | |
| 566 | + | |
| 581 | 567 | // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备) |
| 582 | 568 | Device device = redisCatchStorage.getDevice(requesterId); |
| 583 | - | |
| 569 | + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(requesterId, channelId1); | |
| 570 | + if (audioBroadcastCatch == null) { | |
| 571 | + logger.warn("来自设备的Invite请求非语音广播,已忽略"); | |
| 572 | + responseAck(evt, Response.FORBIDDEN); | |
| 573 | + return; | |
| 574 | + } | |
| 584 | 575 | Request request = evt.getRequest(); |
| 585 | 576 | if (device != null) { |
| 586 | 577 | logger.info("收到设备" + requesterId + "的语音广播Invite请求"); |
| ... | ... | @@ -606,7 +597,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 606 | 597 | |
| 607 | 598 | // 查看是否支持PS 负载96 |
| 608 | 599 | int port = -1; |
| 609 | - //boolean recvonly = false; | |
| 610 | 600 | boolean mediaTransmissionTCP = false; |
| 611 | 601 | Boolean tcpActive = null; |
| 612 | 602 | for (int i = 0; i < mediaDescriptions.size(); i++) { |
| ... | ... | @@ -638,7 +628,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 638 | 628 | responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 |
| 639 | 629 | return; |
| 640 | 630 | } |
| 641 | - String sessionName = sdp.getSessionName().getValue(); | |
| 642 | 631 | String addressStr = sdp.getOrigin().getAddress(); |
| 643 | 632 | logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", requesterId, addressStr, port, ssrc); |
| 644 | 633 | |
| ... | ... | @@ -649,20 +638,19 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 649 | 638 | return; |
| 650 | 639 | } |
| 651 | 640 | SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, |
| 652 | - device.getDeviceId(), channelId, | |
| 641 | + device.getDeviceId(), audioBroadcastCatch.getChannelId(), | |
| 653 | 642 | mediaTransmissionTCP); |
| 654 | - sendRtpItem.setTcp(mediaTransmissionTCP); | |
| 655 | - if (tcpActive != null) { | |
| 656 | - sendRtpItem.setTcpActive(tcpActive); | |
| 657 | - } | |
| 658 | 643 | if (sendRtpItem == null) { |
| 659 | 644 | logger.warn("服务器端口资源不足"); |
| 660 | 645 | responseAck(evt, Response.BUSY_HERE); |
| 661 | 646 | return; |
| 662 | 647 | } |
| 663 | - | |
| 648 | + sendRtpItem.setTcp(mediaTransmissionTCP); | |
| 649 | + if (tcpActive != null) { | |
| 650 | + sendRtpItem.setTcpActive(tcpActive); | |
| 651 | + } | |
| 664 | 652 | String app = "broadcast"; |
| 665 | - String stream = device.getDeviceId() + "_" + channelId; | |
| 653 | + String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId(); | |
| 666 | 654 | |
| 667 | 655 | CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); |
| 668 | 656 | sendRtpItem.setPlayType(InviteStreamType.PLAY); |
| ... | ... | @@ -685,12 +673,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 685 | 673 | subscribeKey.put("schema", "rtmp"); |
| 686 | 674 | subscribeKey.put("mediaServerId", mediaServerItem.getId()); |
| 687 | 675 | String finalSsrc = ssrc; |
| 688 | - String waiteStreamTimeoutTaskKey = "waite-stream-" + device.getDeviceId() + channelId; | |
| 689 | - | |
| 690 | 676 | // 流已经存在时直接推流 |
| 691 | 677 | if (zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream)) { |
| 692 | 678 | logger.info("发现已经在推流"); |
| 693 | - dynamicTask.stop(waiteStreamTimeoutTaskKey); | |
| 694 | 679 | sendRtpItem.setStatus(2); |
| 695 | 680 | redisCatchStorage.updateSendRTPSever(sendRtpItem); |
| 696 | 681 | StringBuffer content = new StringBuffer(200); |
| ... | ... | @@ -711,6 +696,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 711 | 696 | parentPlatform.setServerGBId(device.getDeviceId()); |
| 712 | 697 | try { |
| 713 | 698 | responseSdpAck(evt, content.toString(), parentPlatform); |
| 699 | + Dialog dialog = evt.getDialog(); | |
| 700 | + audioBroadcastCatch.setDialog((SIPDialog) dialog); | |
| 701 | + audioBroadcastCatch.setRequest((SIPRequest) request); | |
| 702 | + audioBroadcastManager.update(audioBroadcastCatch); | |
| 714 | 703 | } catch (SipException e) { |
| 715 | 704 | throw new RuntimeException(e); |
| 716 | 705 | } catch (InvalidArgumentException e) { |
| ... | ... | @@ -721,20 +710,17 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 721 | 710 | }else { |
| 722 | 711 | // 流不存在时监听流上线 |
| 723 | 712 | // 设置等待推流的超时; 默认20s |
| 713 | + String waiteStreamTimeoutTaskKey = "waite-stream-" + device.getDeviceId() + audioBroadcastCatch.getChannelId(); | |
| 724 | 714 | dynamicTask.startDelay(waiteStreamTimeoutTaskKey, ()->{ |
| 725 | 715 | logger.info("等待推流超时: {}/{}", app, stream); |
| 726 | - if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { | |
| 727 | - audioBroadcastManager.del(device.getDeviceId(), channelId); | |
| 728 | - }else { | |
| 729 | - // 兼容海康使用了错误的通道ID的情况 | |
| 730 | - audioBroadcastManager.delByDeviceId(device.getDeviceId()); | |
| 731 | - } | |
| 732 | - | |
| 716 | + playService.stopAudioBroadcast(device.getDeviceId(), audioBroadcastCatch.getChannelId()); | |
| 733 | 717 | // 发送bye |
| 734 | 718 | try { |
| 735 | - cmder.streamByeCmd((SIPDialog)evt.getServerTransaction().getDialog(), (SIPRequest) evt.getRequest(), null); | |
| 719 | + responseAck(evt, Response.BUSY_HERE); | |
| 736 | 720 | } catch (SipException e) { |
| 737 | 721 | throw new RuntimeException(e); |
| 722 | + } catch (InvalidArgumentException e) { | |
| 723 | + throw new RuntimeException(e); | |
| 738 | 724 | } catch (ParseException e) { |
| 739 | 725 | throw new RuntimeException(e); |
| 740 | 726 | } |
| ... | ... | @@ -743,10 +729,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 743 | 729 | subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, |
| 744 | 730 | (MediaServerItem mediaServerItemInUse, JSONObject json)->{ |
| 745 | 731 | sendRtpItem.setStatus(2); |
| 732 | + dynamicTask.stop(waiteStreamTimeoutTaskKey); | |
| 746 | 733 | redisCatchStorage.updateSendRTPSever(sendRtpItem); |
| 747 | 734 | StringBuffer content = new StringBuffer(200); |
| 748 | 735 | content.append("v=0\r\n"); |
| 749 | - content.append("o="+ channelId +" 0 0 IN IP4 "+mediaServerItem.getSdpIp()+"\r\n"); | |
| 736 | + content.append("o="+ audioBroadcastCatch.getChannelId() +" 0 0 IN IP4 "+mediaServerItem.getSdpIp()+"\r\n"); | |
| 750 | 737 | content.append("s=Play\r\n"); |
| 751 | 738 | content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n"); |
| 752 | 739 | content.append("t=0 0\r\n"); |
| ... | ... | @@ -771,8 +758,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 771 | 758 | } |
| 772 | 759 | }); |
| 773 | 760 | } |
| 774 | - String timeOutTaskKey = "audio-broadcast-" + device.getDeviceId() + channelId; | |
| 775 | - dynamicTask.stop(timeOutTaskKey); | |
| 776 | 761 | String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId(); |
| 777 | 762 | WVPResult<AudioBroadcastResult> wvpResult = new WVPResult<>(); |
| 778 | 763 | wvpResult.setCode(0); | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
| ... | ... | @@ -43,4 +43,5 @@ public interface IPlayService { |
| 43 | 43 | StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream); |
| 44 | 44 | |
| 45 | 45 | void audioBroadcast(Device device, String channelId, int timeout, AudioBroadcastEvent event); |
| 46 | + void stopAudioBroadcast(String deviceId, String channelId); | |
| 46 | 47 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| ... | ... | @@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONArray; |
| 5 | 5 | import com.alibaba.fastjson.JSONObject; |
| 6 | 6 | import com.genersoft.iot.vmp.common.StreamInfo; |
| 7 | 7 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 8 | +import com.genersoft.iot.vmp.conf.SipConfig; | |
| 8 | 9 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 9 | 10 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 10 | 11 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| ... | ... | @@ -44,9 +45,13 @@ import org.springframework.util.ResourceUtils; |
| 44 | 45 | import org.springframework.web.context.request.async.DeferredResult; |
| 45 | 46 | |
| 46 | 47 | import javax.sip.ResponseEvent; |
| 48 | +import javax.sip.SipException; | |
| 47 | 49 | import java.io.FileNotFoundException; |
| 48 | 50 | import java.math.BigDecimal; |
| 51 | +import java.text.ParseException; | |
| 49 | 52 | import java.util.*; |
| 53 | +import java.util.stream.Collectors; | |
| 54 | +import java.util.stream.Stream; | |
| 50 | 55 | |
| 51 | 56 | @SuppressWarnings(value = {"rawtypes", "unchecked"}) |
| 52 | 57 | @Service |
| ... | ... | @@ -94,6 +99,9 @@ public class PlayServiceImpl implements IPlayService { |
| 94 | 99 | private UserSetting userSetting; |
| 95 | 100 | |
| 96 | 101 | @Autowired |
| 102 | + private SipConfig sipConfig; | |
| 103 | + | |
| 104 | + @Autowired | |
| 97 | 105 | private DynamicTask dynamicTask; |
| 98 | 106 | |
| 99 | 107 | |
| ... | ... | @@ -641,16 +649,13 @@ public class PlayServiceImpl implements IPlayService { |
| 641 | 649 | } |
| 642 | 650 | // 查询通道使用状态 |
| 643 | 651 | if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { |
| 644 | - logger.warn("语音广播已经开启: {}", channelId); | |
| 645 | - event.call("语音广播已经开启"); | |
| 646 | - return; | |
| 652 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null); | |
| 653 | + if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) { | |
| 654 | + logger.warn("语音广播已经开启: {}", channelId); | |
| 655 | + event.call("语音广播已经开启"); | |
| 656 | + return; | |
| 657 | + } | |
| 647 | 658 | } |
| 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 | 659 | |
| 655 | 660 | // 发送通知 |
| 656 | 661 | cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> { |
| ... | ... | @@ -658,11 +663,38 @@ public class PlayServiceImpl implements IPlayService { |
| 658 | 663 | AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, AudioBroadcastCatchStatus.Ready); |
| 659 | 664 | audioBroadcastManager.add(audioBroadcastCatch); |
| 660 | 665 | }, eventResultForError -> { |
| 661 | - dynamicTask.stop(timeOutTaskKey); | |
| 662 | 666 | // 发送失败 |
| 663 | 667 | logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg); |
| 664 | 668 | event.call("语音广播发送失败"); |
| 665 | - audioBroadcastManager.del(device.getDeviceId(), channelId); | |
| 669 | + stopAudioBroadcast(device.getDeviceId(), channelId); | |
| 666 | 670 | }); |
| 667 | 671 | } |
| 672 | + | |
| 673 | + @Override | |
| 674 | + public void stopAudioBroadcast(String deviceId, String channelId){ | |
| 675 | + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(deviceId, channelId); | |
| 676 | + if (audioBroadcastCatch != null) { | |
| 677 | + audioBroadcastManager.del(deviceId, audioBroadcastCatch.getChannelId()); | |
| 678 | + } | |
| 679 | + try { | |
| 680 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, channelId, null, null); | |
| 681 | + if (sendRtpItem != null) { | |
| 682 | + redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null); | |
| 683 | + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 684 | + Map<String, Object> param = new HashMap<>(); | |
| 685 | + param.put("vhost", "__defaultVhost__"); | |
| 686 | + param.put("app", sendRtpItem.getApp()); | |
| 687 | + param.put("stream", sendRtpItem.getStreamId()); | |
| 688 | + zlmresTfulUtils.stopSendRtp(mediaInfo, param); | |
| 689 | + } | |
| 690 | + if (audioBroadcastCatch.getStatus() == AudioBroadcastCatchStatus.Ok) { | |
| 691 | + cmder.streamByeCmd(audioBroadcastCatch.getDialog(), audioBroadcastCatch.getRequest(), null); | |
| 692 | + } | |
| 693 | + } catch (SipException e) { | |
| 694 | + throw new RuntimeException(e); | |
| 695 | + } catch (ParseException e) { | |
| 696 | + throw new RuntimeException(e); | |
| 697 | + } | |
| 698 | + | |
| 699 | + } | |
| 668 | 700 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
| ... | ... | @@ -319,6 +319,22 @@ public class PlayController { |
| 319 | 319 | return result; |
| 320 | 320 | } |
| 321 | 321 | |
| 322 | + | |
| 323 | + @ApiOperation("停止语音广播") | |
| 324 | + @ApiImplicitParams({ | |
| 325 | + @ApiImplicitParam(name = "deviceId", value = "设备Id", dataTypeClass = String.class), | |
| 326 | + @ApiImplicitParam(name = "channelId", value = "通道Id", dataTypeClass = String.class), | |
| 327 | + }) | |
| 328 | + @GetMapping("/broadcast/stop/{deviceId}/{channelId}") | |
| 329 | + @PostMapping("/broadcast/stop/{deviceId}/{channelId}") | |
| 330 | + public WVPResult<String> stopBroadcastA(@PathVariable String deviceId, @PathVariable String channelId) { | |
| 331 | + if (logger.isDebugEnabled()) { | |
| 332 | + logger.debug("停止语音广播API调用"); | |
| 333 | + } | |
| 334 | + playService.stopAudioBroadcast(deviceId, channelId); | |
| 335 | + return new WVPResult<>(0, "success", null); | |
| 336 | + } | |
| 337 | + | |
| 322 | 338 | @ApiOperation("获取所有的ssrc") |
| 323 | 339 | @GetMapping("/ssrc") |
| 324 | 340 | public WVPResult<JSONObject> getSsrc() { | ... | ... |