Merge branch 'wvp-28181-2.0' into main-dev

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
#	src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
This commit is contained in:
648540858
2024-02-27 10:22:13 +08:00
32 changed files with 1104 additions and 253 deletions

View File

@@ -452,27 +452,11 @@ public class Device {
public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
this.sipTransactionInfo = sipTransactionInfo;
}
public boolean isBroadcastPushAfterAck() {
return broadcastPushAfterAck;
}
/*======================设备主子码流逻辑START=========================*/
@Schema(description = "开启主子码流切换的开关false-不开启true-开启)")
private boolean switchPrimarySubStream;
public boolean isSwitchPrimarySubStream() {
return switchPrimarySubStream;
}
public void setSwitchPrimarySubStream(boolean switchPrimarySubStream) {
this.switchPrimarySubStream = switchPrimarySubStream;
}
/*======================设备主子码流逻辑END=========================*/
public boolean isBroadcastPushAfterAck() {
return broadcastPushAfterAck;
}
public void setBroadcastPushAfterAck(boolean broadcastPushAfterAck) {
this.broadcastPushAfterAck = broadcastPushAfterAck;
}
public void setBroadcastPushAfterAck(boolean broadcastPushAfterAck) {
this.broadcastPushAfterAck = broadcastPushAfterAck;
}
}

View File

@@ -246,6 +246,10 @@ public class DeviceChannel {
@Schema(description = "GPS的更新时间")
private String gpsTime;
@Schema(description = "码流标识,优先级高于设备中码流标识," +
"用于选择码流时组成码流标识。默认为null不设置。可选值: stream/streamnumber/streamprofile/streamMode")
private String streamIdentification;
public int getId() {
return id;
}
@@ -574,4 +578,12 @@ public class DeviceChannel {
public void setGpsTime(String gpsTime) {
this.gpsTime = gpsTime;
}
public String getStreamIdentification() {
return streamIdentification;
}
public void setStreamIdentification(String streamIdentification) {
this.streamIdentification = streamIdentification;
}
}

View File

@@ -0,0 +1,44 @@
package com.genersoft.iot.vmp.gb28181.bean;
/**
* 码流索引标识
*/
public enum GbSteamIdentification {
/**
* 主码流 stream:0
* 子码流 stream:1s
*/
streamMain("stream", new String[]{"0","1"}),
/**
* 国标28181-2022定义的方式
* 主码流 streamnumber:0
* 子码流 streamnumber:1
*/
streamnumber("streamnumber", new String[]{"0","1"}),
/**
* 主码流 streamprofile:0
* 子码流 streamprofile:1
*/
streamprofile("streamprofile", new String[]{"0","1"}),
/**
* 适用的品牌: TP-LINK
*/
streamMode("streamMode", new String[]{"main","sub"}),
;
GbSteamIdentification(String value, String[] indexArray) {
this.value = value;
this.indexArray = indexArray;
}
private String value;
private String[] indexArray;
public String getValue() {
return value;
}
public String[] getIndexArray() {
return indexArray;
}
}

View File

@@ -52,7 +52,7 @@ public class SubscribeHolder {
Runnable runnable = dynamicTask.get(taskOverdueKey);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop();
subscribeTask.stop(null);
}
// 添加任务处理订阅过期
dynamicTask.stop(taskOverdueKey);
@@ -87,7 +87,7 @@ public class SubscribeHolder {
Runnable runnable = dynamicTask.get(taskOverdueKey);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop();
subscribeTask.stop(null);
}
// 添加任务处理订阅过期
dynamicTask.stop(taskOverdueKey);

View File

@@ -2,6 +2,8 @@ package com.genersoft.iot.vmp.gb28181.event;
import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
@@ -11,8 +13,7 @@ import javax.sip.DialogTerminatedEvent;
import javax.sip.ResponseEvent;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Response;
import javax.sip.header.WarningHeader;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -97,14 +98,27 @@ public class SipSubscribe {
this.event = event;
if (event instanceof ResponseEvent) {
ResponseEvent responseEvent = (ResponseEvent)event;
Response response = responseEvent.getResponse();
SIPResponse response = (SIPResponse)responseEvent.getResponse();
this.type = EventResultType.response;
if (response != null) {
this.msg = response.getReasonPhrase();
WarningHeader warningHeader = (WarningHeader)response.getHeader(WarningHeader.NAME);
if (warningHeader != null && !ObjectUtils.isEmpty(warningHeader.getText())) {
this.msg = "";
if (warningHeader.getCode() > 0) {
this.msg += warningHeader.getCode() + ":";
}
if (warningHeader.getAgent() != null) {
this.msg += warningHeader.getCode() + ":";
}
if (warningHeader.getText() != null) {
this.msg += warningHeader.getText();
}
}else {
this.msg = response.getReasonPhrase();
}
this.statusCode = response.getStatusCode();
this.callId = response.getCallIdHeader().getCallId();
}
this.callId = ((CallIdHeader)response.getHeader(CallIdHeader.NAME)).getCallId();
}else if (event instanceof TimeoutEvent) {
TimeoutEvent timeoutEvent = (TimeoutEvent)event;
this.type = EventResultType.timeout;

View File

@@ -0,0 +1,86 @@
package com.genersoft.iot.vmp.gb28181.session;
import com.genersoft.iot.vmp.common.CommonCallback;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 通用回调管理
*/
@Component
public class CommonSessionManager {
public static Map<String, CommonSession> callbackMap = new ConcurrentHashMap<>();
/**
* 存储回调相关的信息
*/
class CommonSession{
public String session;
public long createTime;
public int timeout;
public CommonCallback<Object> callback;
public CommonCallback<String> timeoutCallback;
}
/**
* 添加回调
* @param sessionId 唯一标识
* @param callback 回调
* @param timeout 超时时间, 单位分钟
*/
public void add(String sessionId, CommonCallback<Object> callback, CommonCallback<String> timeoutCallback,
Integer timeout) {
CommonSession commonSession = new CommonSession();
commonSession.session = sessionId;
commonSession.callback = callback;
commonSession.createTime = System.currentTimeMillis();
if (timeoutCallback != null) {
commonSession.timeoutCallback = timeoutCallback;
}
if (timeout != null) {
commonSession.timeout = timeout;
}
callbackMap.put(sessionId, commonSession);
}
public void add(String sessionId, CommonCallback<Object> callback) {
add(sessionId, callback, null, 1);
}
public CommonCallback<Object> get(String sessionId, boolean destroy) {
CommonSession commonSession = callbackMap.get(sessionId);
if (destroy) {
callbackMap.remove(sessionId);
}
return commonSession.callback;
}
public CommonCallback<Object> get(String sessionId) {
return get(sessionId, false);
}
public void delete(String sessionID) {
callbackMap.remove(sessionID);
}
@Scheduled(fixedRate= 60) //每分钟执行一次
public void execute(){
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE, -1);
for (String session : callbackMap.keySet()) {
if (callbackMap.get(session).createTime < cal.getTimeInMillis()) {
// 超时
if (callbackMap.get(session).timeoutCallback != null) {
callbackMap.get(session).timeoutCallback.run("timeout");
}
callbackMap.remove(session);
}
}
}
}

View File

@@ -1,10 +1,10 @@
package com.genersoft.iot.vmp.gb28181.task;
import javax.sip.DialogState;
import com.genersoft.iot.vmp.common.CommonCallback;
/**
* @author lin
*/
public interface ISubscribeTask extends Runnable{
void stop();
void stop(CommonCallback<Boolean> callback);
}

View File

@@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.gb28181.task.impl;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
@@ -7,14 +8,13 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import gov.nist.javax.sip.message.SIPRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import javax.sip.*;
import javax.sip.DialogState;
import javax.sip.InvalidArgumentException;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.header.ToHeader;
import java.text.ParseException;
import java.util.Timer;
import java.util.TimerTask;
/**
* 目录订阅任务
@@ -71,7 +71,7 @@ public class CatalogSubscribeTask implements ISubscribeTask {
}
@Override
public void stop() {
public void stop(CommonCallback<Boolean> callback) {
/**
* dialog 的各个状态
* EARLY-> Early state状态-初始请求发送以后,收到了一个临时响应消息
@@ -94,6 +94,9 @@ public class CatalogSubscribeTask implements ISubscribeTask {
// 成功
logger.info("[取消目录订阅]成功: {}", device.getDeviceId());
}
if (callback != null) {
callback.run(event.getResponse().getRawContent() != null);
}
},eventResult -> {
// 失败
logger.warn("[取消目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);

View File

@@ -1,20 +1,9 @@
package com.genersoft.iot.vmp.gb28181.task.impl;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.service.IPlatformService;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import javax.sip.DialogState;
import java.util.List;
/**
* 向已经订阅(移动位置)的上级发送MobilePosition消息
@@ -38,7 +27,7 @@ public class MobilePositionSubscribeHandlerTask implements ISubscribeTask {
}
@Override
public void stop() {
public void stop(CommonCallback<Boolean> callback) {
}
}

View File

@@ -1,21 +1,19 @@
package com.genersoft.iot.vmp.gb28181.task.impl;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import javax.sip.*;
import javax.sip.InvalidArgumentException;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.header.ToHeader;
import java.text.ParseException;
import java.util.Timer;
import java.util.TimerTask;
/**
* 移动位置订阅的定时更新
@@ -70,7 +68,7 @@ public class MobilePositionSubscribeTask implements ISubscribeTask {
}
@Override
public void stop() {
public void stop(CommonCallback<Boolean> callback) {
/**
* dialog 的各个状态
* EARLY-> Early state状态-初始请求发送以后,收到了一个临时响应消息
@@ -92,6 +90,9 @@ public class MobilePositionSubscribeTask implements ISubscribeTask {
// 成功
logger.info("[取消移动位置订阅]成功: {}", device.getDeviceId());
}
if (callback != null) {
callback.run(event.getResponse().getRawContent() != null);
}
},eventResult -> {
// 失败
logger.warn("[取消移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg);

View File

@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@@ -97,9 +98,9 @@ public interface ISIPCommander {
/**
* 请求预览视频流
* @param device 视频设备
* @param channelId 预览通道
* @param channel 预览通道
*/
void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
/**
* 请求回放视频流

View File

@@ -7,6 +7,10 @@ import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
@@ -266,12 +270,12 @@ public class SIPCommander implements ISIPCommander {
* 请求预览视频流
*
* @param device 视频设备
* @param channelId 预览通道
* @param channel 预览通道
* @param event hook订阅
* @param errorEvent sip错误订阅
*/
@Override
public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel,
ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
String stream = ssrcInfo.getStream();
@@ -295,7 +299,7 @@ public class SIPCommander implements ISIPCommander {
}
StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n");
content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
content.append("o=" + channel.getChannelId() + " 0 0 IN IP4 " + sdpIp + "\r\n");
content.append("s=Play\r\n");
content.append("c=IN IP4 " + sdpIp + "\r\n");
content.append("t=0 0\r\n");
@@ -346,20 +350,8 @@ public class SIPCommander implements ISIPCommander {
}
}
if( device.isSwitchPrimarySubStream() ){
if("TP-LINK".equals(device.getManufacturer())){
if (device.isSwitchPrimarySubStream()){
content.append("a=streamMode:sub\r\n");
}else {
content.append("a=streamMode:main\r\n");
}
}else {
if (device.isSwitchPrimarySubStream()){
content.append("a=streamprofile:1\r\n");
}else {
content.append("a=streamprofile:0\r\n");
}
}
if (!ObjectUtils.isEmpty(channel.getStreamIdentification())) {
content.append("a=" + channel.getStreamIdentification() + "\r\n");
}
content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
@@ -368,16 +360,16 @@ public class SIPCommander implements ISIPCommander {
Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
Request request = headerProvider.createInviteRequest(device, channel.getChannelId(), content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> {
streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream());
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
errorEvent.response(e);
}), e -> {
ResponseEvent responseEvent = (ResponseEvent) e.event;
SIPResponse response = (SIPResponse) responseEvent.getResponse();
String callId = response.getCallIdHeader().getCallId();
streamSession.put(device.getDeviceId(), channelId, callId, stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response,
streamSession.put(device.getDeviceId(), channel.getChannelId(), callId, stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response,
InviteSessionType.PLAY);
okEvent.response(e);
});

View File

@@ -6,7 +6,6 @@ import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
@@ -38,7 +37,6 @@ import javax.sip.SipException;
import javax.sip.header.FromHeader;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -222,7 +220,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
mobilePosition.getLongitude(), mobilePosition.getLatitude());
mobilePosition.setReportSource("Mobile Position");
// 更新device channel 的经纬度
DeviceChannel deviceChannel = new DeviceChannel();
deviceChannel.setDeviceId(device.getDeviceId());
@@ -242,6 +239,8 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
}
storager.updateChannelPosition(deviceChannel);
// 向关联了该通道并且开启移动位置订阅的上级平台发送移动位置订阅消息
// 发送redis消息。 通知位置信息的变化
JSONObject jsonObject = new JSONObject();

View File

@@ -82,8 +82,9 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
device.setIp(remoteAddressInfo.getIp());
// 设备地址变化会引起目录订阅任务失效,需要重新添加
if (device.getSubscribeCycleForCatalog() > 0) {
deviceService.removeCatalogSubscribe(device);
deviceService.addCatalogSubscribe(device);
deviceService.removeCatalogSubscribe(device, result->{
deviceService.addCatalogSubscribe(device);
});
}
}
if (device.getKeepaliveTime() == null) {