Commit 3cada227435934da8a2db49939695f870b7f477e
Merge branch 'wvp-28181-2.0' into main-dev
# Conflicts: # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java # src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
Showing
10 changed files
with
153 additions
and
31 deletions
sql/2.6.9更新.sql
0 → 100644
sql/初始化.sql
| ... | ... | @@ -79,7 +79,7 @@ create table wvp_device_channel ( |
| 79 | 79 | custom_longitude double precision, |
| 80 | 80 | latitude double precision, |
| 81 | 81 | custom_latitude double precision, |
| 82 | - stream_id character varying(50), | |
| 82 | + stream_id character varying(255), | |
| 83 | 83 | device_id character varying(50) not null, |
| 84 | 84 | parental character varying(50), |
| 85 | 85 | has_audio bool default false, | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
| ... | ... | @@ -98,6 +98,10 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 98 | 98 | logger.warn("[收到ACK]:未找到来自{},目标为({})的推流信息",fromUserId, toUserId); |
| 99 | 99 | return; |
| 100 | 100 | } |
| 101 | + // tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤 | |
| 102 | + if (sendRtpItem.isTcpActive()) { | |
| 103 | + return; | |
| 104 | + } | |
| 101 | 105 | logger.info("[收到ACK]:rtp/{}开始级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStream(), |
| 102 | 106 | sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp()); |
| 103 | 107 | // 取消设置的超时任务 | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| ... | ... | @@ -50,6 +50,8 @@ import javax.sip.header.CallIdHeader; |
| 50 | 50 | import javax.sip.message.Response; |
| 51 | 51 | import java.text.ParseException; |
| 52 | 52 | import java.time.Instant; |
| 53 | +import java.util.HashMap; | |
| 54 | +import java.util.Map; | |
| 53 | 55 | import java.util.Random; |
| 54 | 56 | import java.util.Vector; |
| 55 | 57 | |
| ... | ... | @@ -406,6 +408,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 406 | 408 | content.append("y=" + sendRtpItem.getSsrc() + "\r\n"); |
| 407 | 409 | content.append("f=\r\n"); |
| 408 | 410 | |
| 411 | + | |
| 409 | 412 | try { |
| 410 | 413 | // 超时未收到Ack应该回复bye,当前等待时间为10秒 |
| 411 | 414 | dynamicTask.startDelay(callIdHeader.getCallId(), () -> { |
| ... | ... | @@ -418,8 +421,34 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 418 | 421 | logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); |
| 419 | 422 | } |
| 420 | 423 | }, 60 * 1000); |
| 421 | - | |
| 422 | - responseSdpAck(request, content.toString(), platform); | |
| 424 | + responseSdpAck(request, content.toString(), platform); | |
| 425 | + // tcp主动模式,回复sdp后开启监听 | |
| 426 | + if (sendRtpItem.isTcpActive()) { | |
| 427 | + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 428 | + Map<String, Object> param = new HashMap<>(12); | |
| 429 | + param.put("vhost","__defaultVhost__"); | |
| 430 | + param.put("app",sendRtpItem.getApp()); | |
| 431 | + param.put("stream",sendRtpItem.getStreamId()); | |
| 432 | + param.put("ssrc", sendRtpItem.getSsrc()); | |
| 433 | + if (!sendRtpItem.isTcpActive()) { | |
| 434 | + param.put("dst_url",sendRtpItem.getIp()); | |
| 435 | + param.put("dst_port", sendRtpItem.getPort()); | |
| 436 | + } | |
| 437 | + String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; | |
| 438 | + param.put("is_udp", is_Udp); | |
| 439 | + param.put("src_port", localPort); | |
| 440 | + param.put("pt", sendRtpItem.getPt()); | |
| 441 | + param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); | |
| 442 | + param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); | |
| 443 | + if (!sendRtpItem.isTcp()) { | |
| 444 | + // 开启rtcp保活 | |
| 445 | + param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0"); | |
| 446 | + } | |
| 447 | + JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpStreamForPassive(mediaInfo, param); | |
| 448 | + if (startSendRtpStreamResult != null) { | |
| 449 | + startSendRtpStreamHand(evt, sendRtpItem, null, startSendRtpStreamResult, param, callIdHeader); | |
| 450 | + } | |
| 451 | + } | |
| 423 | 452 | } catch (SipException | InvalidArgumentException | ParseException e) { |
| 424 | 453 | logger.error("[命令发送失败] 国标级联 回复SdpAck", e); |
| 425 | 454 | } |
| ... | ... | @@ -553,6 +582,18 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 553 | 582 | } |
| 554 | 583 | } |
| 555 | 584 | |
| 585 | + private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform, | |
| 586 | + JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) { | |
| 587 | + if (jsonObject == null) { | |
| 588 | + logger.error("下级TCP被动启动监听失败: 请检查ZLM服务"); | |
| 589 | + } else if (jsonObject.getInteger("code") == 0) { | |
| 590 | + logger.info("调用ZLM-TCP被动推流接口, 结果: {}", jsonObject); | |
| 591 | + logger.info("启动监听TCP被动推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port")); | |
| 592 | + } else { | |
| 593 | + logger.error("启动监听TCP被动推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param)); | |
| 594 | + } | |
| 595 | + } | |
| 596 | + | |
| 556 | 597 | /** |
| 557 | 598 | * 安排推流 |
| 558 | 599 | */ | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
| ... | ... | @@ -59,6 +59,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp |
| 59 | 59 | // 未注册的设备不做处理 |
| 60 | 60 | return; |
| 61 | 61 | } |
| 62 | + logger.info("[收到心跳], device: {}", device.getDeviceId()); | |
| 62 | 63 | SIPRequest request = (SIPRequest) evt.getRequest(); |
| 63 | 64 | // 回复200 OK |
| 64 | 65 | try { | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
| ... | ... | @@ -25,8 +25,10 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; |
| 25 | 25 | import com.genersoft.iot.vmp.media.zlm.dto.hook.*; |
| 26 | 26 | import com.genersoft.iot.vmp.service.*; |
| 27 | 27 | import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; |
| 28 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | |
| 28 | 29 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 29 | 30 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 31 | +import com.genersoft.iot.vmp.utils.DateUtil; | |
| 30 | 32 | import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; |
| 31 | 33 | import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo; |
| 32 | 34 | import com.genersoft.iot.vmp.vmanager.bean.StreamContent; |
| ... | ... | @@ -641,7 +643,7 @@ public class ZLMHttpHookListener { |
| 641 | 643 | |
| 642 | 644 | if ("rtp".equals(param.getApp())) { |
| 643 | 645 | String[] s = param.getStream().split("_"); |
| 644 | - if (!mediaInfo.isRtpEnable() || s.length != 2) { | |
| 646 | + if (!mediaInfo.isRtpEnable() || (s.length != 2 && s.length != 4)) { | |
| 645 | 647 | defaultResult.setResult(HookResult.SUCCESS()); |
| 646 | 648 | return defaultResult; |
| 647 | 649 | } |
| ... | ... | @@ -657,33 +659,79 @@ public class ZLMHttpHookListener { |
| 657 | 659 | defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); |
| 658 | 660 | return defaultResult; |
| 659 | 661 | } |
| 660 | - logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 661 | - | |
| 662 | - RequestMessage msg = new RequestMessage(); | |
| 663 | - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; | |
| 664 | - boolean exist = resultHolder.exist(key, null); | |
| 665 | - msg.setKey(key); | |
| 666 | - String uuid = UUID.randomUUID().toString(); | |
| 667 | - msg.setId(uuid); | |
| 668 | - DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); | |
| 669 | - | |
| 670 | - result.onTimeout(() -> { | |
| 671 | - logger.info("[ZLM HOOK] 自动点播, 等待超时"); | |
| 672 | - // 释放rtpserver | |
| 673 | - msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); | |
| 674 | - resultHolder.invokeResult(msg); | |
| 675 | - }); | |
| 676 | - | |
| 677 | - // 录像查询以channelId作为deviceId查询 | |
| 678 | - resultHolder.put(key, uuid, result); | |
| 679 | - | |
| 680 | - if (!exist) { | |
| 681 | - playService.play(mediaInfo, deviceId, channelId, null, (code, message, data) -> { | |
| 682 | - msg.setData(new HookResult(code, message)); | |
| 662 | + if (s.length == 2) { | |
| 663 | + logger.info("[ZLM HOOK] 预览流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | |
| 664 | + | |
| 665 | + RequestMessage msg = new RequestMessage(); | |
| 666 | + String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; | |
| 667 | + boolean exist = resultHolder.exist(key, null); | |
| 668 | + msg.setKey(key); | |
| 669 | + String uuid = UUID.randomUUID().toString(); | |
| 670 | + msg.setId(uuid); | |
| 671 | + DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); | |
| 672 | + | |
| 673 | + result.onTimeout(() -> { | |
| 674 | + logger.info("[ZLM HOOK] 预览流自动点播, 等待超时"); | |
| 675 | + // 释放rtpserver | |
| 676 | + msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); | |
| 683 | 677 | resultHolder.invokeResult(msg); |
| 684 | 678 | }); |
| 679 | + | |
| 680 | + resultHolder.put(key, uuid, result); | |
| 681 | + | |
| 682 | + if (!exist) { | |
| 683 | + playService.play(mediaInfo, deviceId, channelId, null, (code, message, data) -> { | |
| 684 | + msg.setData(new HookResult(code, message)); | |
| 685 | + resultHolder.invokeResult(msg); | |
| 686 | + }); | |
| 687 | + } | |
| 688 | + return result; | |
| 689 | + }else if(s.length == 4){ | |
| 690 | + // 此时为录像回放, 录像回放格式为> 设备ID_通道ID_开始时间_结束时间 | |
| 691 | + String startTimeStr = s[2]; | |
| 692 | + String endTimeStr = s[3]; | |
| 693 | + if (startTimeStr == null || endTimeStr == null || startTimeStr.length() != 14 || endTimeStr.length() != 14) { | |
| 694 | + defaultResult.setResult(HookResult.SUCCESS()); | |
| 695 | + return defaultResult; | |
| 696 | + } | |
| 697 | + String startTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(startTimeStr); | |
| 698 | + String endTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(endTimeStr); | |
| 699 | + logger.info("[ZLM HOOK] 回放流未找到, 发起自动点播:{}->{}->{}/{}-{}-{}", | |
| 700 | + param.getMediaServerId(), param.getSchema(), | |
| 701 | + param.getApp(), param.getStream(), | |
| 702 | + startTime, endTime | |
| 703 | + ); | |
| 704 | + RequestMessage msg = new RequestMessage(); | |
| 705 | + String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId; | |
| 706 | + boolean exist = resultHolder.exist(key, null); | |
| 707 | + msg.setKey(key); | |
| 708 | + String uuid = UUID.randomUUID().toString(); | |
| 709 | + msg.setId(uuid); | |
| 710 | + DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); | |
| 711 | + | |
| 712 | + result.onTimeout(() -> { | |
| 713 | + logger.info("[ZLM HOOK] 回放流自动点播, 等待超时"); | |
| 714 | + // 释放rtpserver | |
| 715 | + msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); | |
| 716 | + resultHolder.invokeResult(msg); | |
| 717 | + }); | |
| 718 | + | |
| 719 | + resultHolder.put(key, uuid, result); | |
| 720 | + | |
| 721 | + if (!exist) { | |
| 722 | + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null, | |
| 723 | + device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam()); | |
| 724 | + playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> { | |
| 725 | + msg.setData(new HookResult(code, message)); | |
| 726 | + resultHolder.invokeResult(msg); | |
| 727 | + }); | |
| 728 | + } | |
| 729 | + return result; | |
| 730 | + }else { | |
| 731 | + defaultResult.setResult(HookResult.SUCCESS()); | |
| 732 | + return defaultResult; | |
| 685 | 733 | } |
| 686 | - return result; | |
| 734 | + | |
| 687 | 735 | } else { |
| 688 | 736 | // 拉流代理 |
| 689 | 737 | StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
| ... | ... | @@ -40,7 +40,6 @@ public interface IPlayService { |
| 40 | 40 | |
| 41 | 41 | void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback); |
| 42 | 42 | void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback); |
| 43 | - | |
| 44 | 43 | void zlmServerOffline(String mediaServerId); |
| 45 | 44 | |
| 46 | 45 | void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback); |
| ... | ... | @@ -72,4 +71,6 @@ public interface IPlayService { |
| 72 | 71 | void stopTalk(Device device, String channelId, Boolean streamIsReady); |
| 73 | 72 | |
| 74 | 73 | void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); |
| 74 | + | |
| 75 | + | |
| 75 | 76 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java
| ... | ... | @@ -126,7 +126,15 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService { |
| 126 | 126 | List<DeviceChannel> deviceChannelList = new ArrayList<>(); |
| 127 | 127 | if (channelReduces.size() > 0){ |
| 128 | 128 | PlatformCatalog catalog = catalogManager.selectByPlatFormAndCatalogId(platform.getServerGBId(),catalogId); |
| 129 | - if (catalog == null || !catalogId.equals(platform.getDeviceGBId())) { | |
| 129 | + if (catalog == null && catalogId.equals(platform.getDeviceGBId())) { | |
| 130 | + for (ChannelReduce channelReduce : channelReduces) { | |
| 131 | + DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId()); | |
| 132 | + deviceChannel.setParental(0); | |
| 133 | + deviceChannel.setCivilCode(platform.getServerGBDomain()); | |
| 134 | + deviceChannelList.add(deviceChannel); | |
| 135 | + } | |
| 136 | + return deviceChannelList; | |
| 137 | + } else if (catalog == null || !catalogId.equals(platform.getDeviceGBId())) { | |
| 130 | 138 | logger.warn("未查询到目录{}的信息", catalogId); |
| 131 | 139 | return null; |
| 132 | 140 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| ... | ... | @@ -710,7 +710,19 @@ public class PlayServiceImpl implements IPlayService { |
| 710 | 710 | return; |
| 711 | 711 | } |
| 712 | 712 | MediaServerItem newMediaServerItem = getNewMediaServerItem(device); |
| 713 | - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,false, device.getStreamModeForParam()); | |
| 713 | + String stream = null; | |
| 714 | + if (newMediaServerItem.isRtpEnable()) { | |
| 715 | + String startTimeStr = startTime.replace("-", "") | |
| 716 | + .replace(":", "") | |
| 717 | + .replace(" ", ""); | |
| 718 | + System.out.println(startTimeStr); | |
| 719 | + String endTimeTimeStr = endTime.replace("-", "") | |
| 720 | + .replace(":", "") | |
| 721 | + .replace(" ", ""); | |
| 722 | + System.out.println(endTimeTimeStr); | |
| 723 | + stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr; | |
| 724 | + } | |
| 725 | + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false,false, device.getStreamModeForParam()); | |
| 714 | 726 | playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback); |
| 715 | 727 | } |
| 716 | 728 | ... | ... |
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
| ... | ... | @@ -53,6 +53,10 @@ public class DateUtil { |
| 53 | 53 | return formatter.format(formatterCompatibleISO8601.parse(formatTime)); |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | + public static String urlToyyyy_MM_dd_HH_mm_ss(String formatTime) { | |
| 57 | + return formatter.format(urlFormatter.parse(formatTime)); | |
| 58 | + } | |
| 59 | + | |
| 56 | 60 | /** |
| 57 | 61 | * yyyy_MM_dd_HH_mm_ss 转时间戳 |
| 58 | 62 | * @param formatTime |
| ... | ... | @@ -82,6 +86,7 @@ public class DateUtil { |
| 82 | 86 | return urlFormatter.format(nowDateTime); |
| 83 | 87 | } |
| 84 | 88 | |
| 89 | + | |
| 85 | 90 | /** |
| 86 | 91 | * 格式校验 |
| 87 | 92 | * @param timeStr 时间字符串 | ... | ... |