增加sip会议视频接入能力
parent
db3240d918
commit
508ee15066
42
sql/初始化.sql
42
sql/初始化.sql
|
@ -279,8 +279,50 @@ create table wvp_user_role (
|
||||||
update_time character varying(50)
|
update_time character varying(50)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table wvp_sip_server (
|
||||||
|
id serial primary key ,
|
||||||
|
local_ip character varying(50) not null ,
|
||||||
|
local_port integer,
|
||||||
|
server_ip character varying(50) not null ,
|
||||||
|
server_port integer,
|
||||||
|
create_time character varying(50),
|
||||||
|
update_time character varying(50),
|
||||||
|
transport character varying(50),
|
||||||
|
status bool default false,
|
||||||
|
constraint uk_sip_server_server_ip_server_port unique (server_ip, server_port)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table wvp_sip_server_account (
|
||||||
|
id serial primary key ,
|
||||||
|
sip_server_id integer,
|
||||||
|
username character varying(50) not null ,
|
||||||
|
password character varying(50) not null ,
|
||||||
|
device_channel_id integer ,
|
||||||
|
push_stream_id integer,
|
||||||
|
proxy_stream_id integer,
|
||||||
|
create_time character varying(50),
|
||||||
|
update_time character varying(50),
|
||||||
|
status bool default false,
|
||||||
|
constraint uk_sip_server_account_username unique (username)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table wvp_sip_video (
|
||||||
|
id serial primary key ,
|
||||||
|
sip_server_id integer,
|
||||||
|
sip_account_id integer,
|
||||||
|
media_server_id character varying(50) not null ,
|
||||||
|
request_no character varying(50) not null ,
|
||||||
|
create_time character varying(50),
|
||||||
|
update_time character varying(50),
|
||||||
|
auto_reconnect_on_reboot bool default false,
|
||||||
|
status bool default false,
|
||||||
|
constraint uk_sip_server_account_username unique (request_no)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
/*初始数据*/
|
/*初始数据*/
|
||||||
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
|
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
|
||||||
|
|
||||||
INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57');
|
INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ public class VersionInfo {
|
||||||
@Autowired
|
@Autowired
|
||||||
GitUtil gitUtil;
|
GitUtil gitUtil;
|
||||||
|
|
||||||
public VersionPo getVersion() {
|
public VersionPo getVersion () {
|
||||||
VersionPo versionPo = new VersionPo();
|
VersionPo versionPo = new VersionPo();
|
||||||
versionPo.setGIT_Revision(gitUtil.getGitCommitId());
|
versionPo.setGIT_Revision(gitUtil.getGitCommitId());
|
||||||
versionPo.setGIT_BRANCH(gitUtil.getBranch());
|
versionPo.setGIT_BRANCH(gitUtil.getBranch());
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class SipLayer implements CommandLineRunner {
|
||||||
try {
|
try {
|
||||||
sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog()));
|
sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog()));
|
||||||
} catch (PeerUnavailableException e) {
|
} catch (PeerUnavailableException e) {
|
||||||
logger.error("[Sip Server] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
|
logger.error("[国标28181服务] 启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,12 +76,12 @@ public class SipLayer implements CommandLineRunner {
|
||||||
tcpSipProvider.addSipListener(sipProcessorObserver);
|
tcpSipProvider.addSipListener(sipProcessorObserver);
|
||||||
tcpSipProviderMap.put(monitorIp, tcpSipProvider);
|
tcpSipProviderMap.put(monitorIp, tcpSipProvider);
|
||||||
|
|
||||||
logger.info("[Sip Server] tcp://{}:{} 启动成功", monitorIp, port);
|
logger.info("[国标28181服务] tcp://{}:{} 启动成功", monitorIp, port);
|
||||||
} catch (TransportNotSupportedException
|
} catch (TransportNotSupportedException
|
||||||
| TooManyListenersException
|
| TooManyListenersException
|
||||||
| ObjectInUseException
|
| ObjectInUseException
|
||||||
| InvalidArgumentException e) {
|
| InvalidArgumentException e) {
|
||||||
logger.error("[Sip Server] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
|
logger.error("[国标28181服务] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
|
||||||
, monitorIp, port);
|
, monitorIp, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,12 +93,12 @@ public class SipLayer implements CommandLineRunner {
|
||||||
|
|
||||||
udpSipProviderMap.put(monitorIp, udpSipProvider);
|
udpSipProviderMap.put(monitorIp, udpSipProvider);
|
||||||
|
|
||||||
logger.info("[Sip Server] udp://{}:{} 启动成功", monitorIp, port);
|
logger.info("[国标28181服务] udp://{}:{} 启动成功", monitorIp, port);
|
||||||
} catch (TransportNotSupportedException
|
} catch (TransportNotSupportedException
|
||||||
| TooManyListenersException
|
| TooManyListenersException
|
||||||
| ObjectInUseException
|
| ObjectInUseException
|
||||||
| InvalidArgumentException e) {
|
| InvalidArgumentException e) {
|
||||||
logger.error("[Sip Server] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
|
logger.error("[国标28181服务] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
|
||||||
, monitorIp, port);
|
, monitorIp, port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.genersoft.iot.vmp.gb28181.bean;
|
package com.genersoft.iot.vmp.gb28181.bean;
|
||||||
|
|
||||||
import gov.nist.javax.sip.message.SIPRequest;
|
|
||||||
import gov.nist.javax.sip.message.SIPResponse;
|
import gov.nist.javax.sip.message.SIPResponse;
|
||||||
|
|
||||||
public class SipTransactionInfo {
|
public class SipTransactionInfo {
|
||||||
|
|
|
@ -50,10 +50,6 @@ public class SipUtils {
|
||||||
return uri.getUser();
|
return uri.getUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getNewViaTag() {
|
|
||||||
return "z9hG4bK" + System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException {
|
public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException {
|
||||||
List<String> agentParam = new ArrayList<>();
|
List<String> agentParam = new ArrayList<>();
|
||||||
agentParam.add("WVP-Pro ");
|
agentParam.add("WVP-Pro ");
|
||||||
|
@ -69,6 +65,10 @@ public class SipUtils {
|
||||||
return SipFactory.getInstance().createHeaderFactory().createUserAgentHeader(agentParam);
|
return SipFactory.getInstance().createHeaderFactory().createUserAgentHeader(agentParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getNewViaTag() {
|
||||||
|
return "z9hG4bK" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
public static String getNewFromTag(){
|
public static String getNewFromTag(){
|
||||||
return UUID.randomUUID().toString().replace("-", "");
|
return UUID.randomUUID().toString().replace("-", "");
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.genersoft.iot.vmp.sip;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipEvent;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
import com.genersoft.iot.vmp.sip.utils.SIPRequestFactory;
|
||||||
|
import com.genersoft.iot.vmp.sip.utils.SipUtils;
|
||||||
|
import gov.nist.javax.sip.SipProviderImpl;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.sip.InvalidArgumentException;
|
||||||
|
import javax.sip.SipException;
|
||||||
|
import javax.sip.header.WWWAuthenticateHeader;
|
||||||
|
import javax.sip.message.Request;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SipCommander {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SipSender sipSender;
|
||||||
|
|
||||||
|
public void register(SipServer server, SipServerAccount account, SipProviderImpl sipProvider, boolean isRegister,
|
||||||
|
SipEvent okEvent, SipEvent errorEvent)
|
||||||
|
throws InvalidArgumentException, SipException, ParseException, NoSuchAlgorithmException {
|
||||||
|
register(server, account, sipProvider, null, isRegister, okEvent, errorEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(SipServer server, SipServerAccount account, SipProviderImpl sipProvider,
|
||||||
|
WWWAuthenticateHeader wwwAuthenticateHeader, boolean isRegister,
|
||||||
|
SipEvent okEvent, SipEvent errorEvent)
|
||||||
|
throws InvalidArgumentException, SipException, ParseException, NoSuchAlgorithmException {
|
||||||
|
if (server == null || account == null || sipProvider == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String callId = SipUtils.getNewCallId(sipProvider);
|
||||||
|
Request request = SIPRequestFactory.createRegisterRequest(server, account, callId, isRegister, wwwAuthenticateHeader);
|
||||||
|
sipSender.transmitRequest(sipProvider, request, okEvent, errorEvent);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.genersoft.iot.vmp.sip;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipEvent;
|
||||||
|
import com.genersoft.iot.vmp.sip.service.SipSubscribe;
|
||||||
|
import com.genersoft.iot.vmp.sip.utils.SipUtils;
|
||||||
|
import com.genersoft.iot.vmp.utils.GitUtil;
|
||||||
|
import gov.nist.javax.sip.SipProviderImpl;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.sip.SipException;
|
||||||
|
import javax.sip.header.CallIdHeader;
|
||||||
|
import javax.sip.header.UserAgentHeader;
|
||||||
|
import javax.sip.message.Message;
|
||||||
|
import javax.sip.message.Request;
|
||||||
|
import javax.sip.message.Response;
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息发送器
|
||||||
|
* @author lin
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SipSender {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(SipSender.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private GitUtil gitUtil;
|
||||||
|
|
||||||
|
public void transmitRequest(SipProviderImpl sipProvider, Message message) throws SipException, ParseException {
|
||||||
|
transmitRequest(sipProvider, message, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transmitRequest(SipProviderImpl sipProvider, String ip, Message message, SipEvent errorEvent) throws SipException, ParseException {
|
||||||
|
transmitRequest(sipProvider, message, null, errorEvent );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transmitRequest(SipProviderImpl sipProvider, Message message, SipEvent okEvent, SipEvent errorEvent) throws SipException, ParseException {
|
||||||
|
if (message.getHeader(UserAgentHeader.NAME) == null) {
|
||||||
|
try {
|
||||||
|
message.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
||||||
|
} catch (ParseException e) {
|
||||||
|
logger.error("添加UserAgentHeader失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CallIdHeader callIdHeader = (CallIdHeader) message.getHeader(CallIdHeader.NAME);
|
||||||
|
// 添加错误订阅
|
||||||
|
if (errorEvent != null) {
|
||||||
|
SipSubscribe.getInstance().addErrorSubscribe(callIdHeader.getCallId(), (code, msg, data) -> {
|
||||||
|
errorEvent.response(code, msg, data);
|
||||||
|
SipSubscribe.getInstance().removeErrorSubscribe(callIdHeader.getCallId());
|
||||||
|
SipSubscribe.getInstance().removeOkSubscribe(callIdHeader.getCallId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 添加订阅
|
||||||
|
if (okEvent != null) {
|
||||||
|
SipSubscribe.getInstance().addOkSubscribe(callIdHeader.getCallId(), (code, msg, data) -> {
|
||||||
|
okEvent.response(code, msg, data);
|
||||||
|
SipSubscribe.getInstance().removeOkSubscribe(callIdHeader.getCallId());
|
||||||
|
SipSubscribe.getInstance().removeErrorSubscribe(callIdHeader.getCallId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (message instanceof Request) {
|
||||||
|
sipProvider.sendRequest((Request)message);
|
||||||
|
}else if (message instanceof Response) {
|
||||||
|
sipProvider.sendResponse((Response)message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.genersoft.iot.vmp.sip;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SipSessionManager {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.bean;
|
||||||
|
|
||||||
|
public interface ResultCallback<T> {
|
||||||
|
|
||||||
|
void run(int code, String msg, T data);
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.bean;
|
||||||
|
|
||||||
|
public interface SipEvent {
|
||||||
|
|
||||||
|
void response(int code, String msg, Object data);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.bean;
|
||||||
|
|
||||||
|
public class SipResult {
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.bean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sip系统的帐号信息
|
||||||
|
*/
|
||||||
|
public class SipServer {
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本机IP
|
||||||
|
*/
|
||||||
|
private String localIp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本机端口
|
||||||
|
*/
|
||||||
|
private Integer localPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器IP
|
||||||
|
*/
|
||||||
|
private String serverIp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器端口
|
||||||
|
*/
|
||||||
|
private Integer serverPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令传输模式, 默认UDP, 可选UDP/TCP
|
||||||
|
*/
|
||||||
|
private String transport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private boolean status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private String updateTime;
|
||||||
|
|
||||||
|
|
||||||
|
public static SipServer getInstance(String serverIp, Integer serverPort, String localIp, Integer localPort, String transport) {
|
||||||
|
SipServer sipServer = new SipServer();
|
||||||
|
sipServer.setTransport(transport);
|
||||||
|
sipServer.setServerIp(serverIp);
|
||||||
|
sipServer.setServerPort(serverPort);
|
||||||
|
sipServer.setLocalIp(localIp);
|
||||||
|
sipServer.setLocalPort(localPort);
|
||||||
|
return sipServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalIp() {
|
||||||
|
return localIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalIp(String localIp) {
|
||||||
|
this.localIp = localIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLocalPort() {
|
||||||
|
return localPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalPort(Integer localPort) {
|
||||||
|
this.localPort = localPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerIp() {
|
||||||
|
return serverIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerIp(String serverIp) {
|
||||||
|
this.serverIp = serverIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getServerPort() {
|
||||||
|
return serverPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerPort(Integer serverPort) {
|
||||||
|
this.serverPort = serverPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTransport() {
|
||||||
|
return transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransport(String transport) {
|
||||||
|
this.transport = transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(boolean status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateTime() {
|
||||||
|
return createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTime(String createTime) {
|
||||||
|
this.createTime = createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateTime() {
|
||||||
|
return updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTime(String updateTime) {
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.bean;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sip用户帐号,用于接收sip视频以及推送视频到sip服务中
|
||||||
|
*/
|
||||||
|
public class SipServerAccount {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID
|
||||||
|
*/
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的SIP服务器ID
|
||||||
|
*/
|
||||||
|
private Integer sipServerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联一个国标设备作为帐号视频,用于将自己的视频加入到sip会议中
|
||||||
|
* deviceChannelId/pushStreamId/proxyStreamId任选其一
|
||||||
|
*/
|
||||||
|
private Integer deviceChannelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联一个推流设备作为帐号视频,用于将自己的视频加入到sip会议中
|
||||||
|
* deviceChannelId/pushStreamId/proxyStreamId任选其一
|
||||||
|
*/
|
||||||
|
private Integer pushStreamId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联一个拉流代理作为帐号视频,用于将自己的视频加入到sip会议中
|
||||||
|
* deviceChannelId/pushStreamId/proxyStreamId任选其一
|
||||||
|
*/
|
||||||
|
private Integer proxyStreamId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private boolean status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private String updateTime;
|
||||||
|
|
||||||
|
private final List<SipVideo> sipVideoList = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSipServerId() {
|
||||||
|
return sipServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSipServerId(Integer sipServerId) {
|
||||||
|
this.sipServerId = sipServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getDeviceChannelId() {
|
||||||
|
return deviceChannelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceChannelId(Integer deviceChannelId) {
|
||||||
|
this.deviceChannelId = deviceChannelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPushStreamId() {
|
||||||
|
return pushStreamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPushStreamId(Integer pushStreamId) {
|
||||||
|
this.pushStreamId = pushStreamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getProxyStreamId() {
|
||||||
|
return proxyStreamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProxyStreamId(Integer proxyStreamId) {
|
||||||
|
this.proxyStreamId = proxyStreamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(boolean status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateTime() {
|
||||||
|
return createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTime(String createTime) {
|
||||||
|
this.createTime = createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateTime() {
|
||||||
|
return updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTime(String updateTime) {
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addVideo(SipVideo sipVideo) {
|
||||||
|
sipVideoList.add(sipVideo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeVideoById(int id) {
|
||||||
|
if (sipVideoList.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < sipVideoList.size(); i++) {
|
||||||
|
if (sipVideoList.get(i).getId() == id) {
|
||||||
|
sipVideoList.remove(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.bean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从一个sip服务中获取一个视频
|
||||||
|
*/
|
||||||
|
public class SipVideo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID
|
||||||
|
*/
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sip 服务器帐号信息ID
|
||||||
|
*/
|
||||||
|
private int sipAccountId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定使用的流媒体,为空则自动获取
|
||||||
|
*/
|
||||||
|
private String mediaServerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从sip服务中获取的视频的设备编号
|
||||||
|
*/
|
||||||
|
private String requestNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务重启时释放自动拉起视频
|
||||||
|
*/
|
||||||
|
private boolean autoReconnectOnReboot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private String updateTime;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public int getSipAccountId() {
|
||||||
|
return sipAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSipAccountId(int sipAccountId) {
|
||||||
|
this.sipAccountId = sipAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMediaServerId() {
|
||||||
|
return mediaServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediaServerId(String mediaServerId) {
|
||||||
|
this.mediaServerId = mediaServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestNo() {
|
||||||
|
return requestNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestNo(String requestNo) {
|
||||||
|
this.requestNo = requestNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoReconnectOnReboot() {
|
||||||
|
return autoReconnectOnReboot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoReconnectOnReboot(boolean autoReconnectOnReboot) {
|
||||||
|
this.autoReconnectOnReboot = autoReconnectOnReboot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateTime() {
|
||||||
|
return createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTime(String createTime) {
|
||||||
|
this.createTime = createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateTime() {
|
||||||
|
return updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateTime(String updateTime) {
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.dao;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
@Repository
|
||||||
|
public interface SipServerAccountMapper {
|
||||||
|
|
||||||
|
@Insert("INSERT INTO wvp_sip_server_account (sip_server_id, username, password, device_channel_id, " +
|
||||||
|
"create_time, update_time, push_stream_id, proxy_stream_id, status ) " +
|
||||||
|
"VALUES (#{sipServerId}, #{username}, #{password}, #{deviceChannelId}, " +
|
||||||
|
"#{createTime}, #{updateTime}, #{pushStreamId}, #{proxyStreamId}, #{status})")
|
||||||
|
int add(SipServerAccount sipServerAccount);
|
||||||
|
|
||||||
|
@Delete("DELETE FROM wvp_sip_server_account WHERE id = #{id}")
|
||||||
|
int remove(int id);
|
||||||
|
|
||||||
|
@Update(value = {" <script>" +
|
||||||
|
"UPDATE wvp_sip_server_account " +
|
||||||
|
"SET update_time=#{updateTime}" +
|
||||||
|
"<if test='sip_server_id != null'>, sip_server_id=#{sipServerId}</if>" +
|
||||||
|
"<if test='username != null'>, username=#{username}</if>" +
|
||||||
|
"<if test='password != null'>, password=#{password}</if>" +
|
||||||
|
"<if test='device_channel_id != null'>, device_channel_id=#{deviceChannelId}</if>" +
|
||||||
|
"<if test='push_stream_id != null'>, push_stream_id=#{pushStreamId}</if>" +
|
||||||
|
"<if test='proxy_stream_id != null'>, proxy_stream_id=#{proxyStreamId}</if>" +
|
||||||
|
"<if test='status != null'>, status=#{status}</if>" +
|
||||||
|
"WHERE id = #{id}"+
|
||||||
|
" </script>"})
|
||||||
|
int update(SipServerAccount sipServerAccount);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM wvp_sip_server_account WHERE id = #{id}")
|
||||||
|
SipServerAccount query(int id);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM wvp_sip_server_account")
|
||||||
|
List<SipServerAccount> all(int id);
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.dao;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
@Repository
|
||||||
|
public interface SipServerMapper {
|
||||||
|
|
||||||
|
@Insert("INSERT INTO wvp_sip_server (local_ip, local_port, server_ip, server_port, create_time, update_time, transport, status ) " +
|
||||||
|
"VALUES (#{localIp}, #{localPort}, #{serverIp}, #{serverPort}, #{createTime}, #{updateTime}, #{transport}, #{status})")
|
||||||
|
int add(SipServer sipServer);
|
||||||
|
|
||||||
|
@Delete("DELETE FROM wvp_sip_server WHERE id = #{sipServerId}")
|
||||||
|
int remove(int sipServerId);
|
||||||
|
|
||||||
|
@Update(value = {" <script>" +
|
||||||
|
"UPDATE wvp_sip_server " +
|
||||||
|
"SET update_time=#{updateTime}" +
|
||||||
|
"<if test='local_ip != null'>, local_ip=#{localIp}</if>" +
|
||||||
|
"<if test='local_port != null'>, local_port=#{localPort}</if>" +
|
||||||
|
"<if test='server_ip != null'>, server_ip=#{serverIp}</if>" +
|
||||||
|
"<if test='server_port != null'>, server_port=#{server_port}</if>" +
|
||||||
|
"<if test='transport != null'>, transport=#{transport}</if>" +
|
||||||
|
"<if test='status != null'>, status=#{status}</if>" +
|
||||||
|
"WHERE id = #{id}"+
|
||||||
|
" </script>"})
|
||||||
|
int update(SipServer sipServer);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM wvp_sip_server WHERE id = #{sipServerId}")
|
||||||
|
SipServer query(int sipServerId);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM wvp_sip_server WHERE id = #{sipServerId}")
|
||||||
|
List<SipServer> all();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.dao;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipVideo;
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
@Repository
|
||||||
|
public interface SipVideoMapper {
|
||||||
|
|
||||||
|
@Select("SELECT * FROM wvp_sip_video WHERE sip_server_id = #{serverId} AND sip_account_id = #{accountId}")
|
||||||
|
List<SipVideo> all(int serverId, int accountId);
|
||||||
|
|
||||||
|
@Insert("INSERT INTO wvp_sip_video (sip_server_id, sip_account_id, media_server_id, request_no, create_time, update_time, auto_reconnect_on_reboot, status ) " +
|
||||||
|
"VALUES (#{sipServerId}, #{sipAccountId}, #{mediaServerId}, #{requestNo}, #{createTime}, #{updateTime}, #{autoReconnectOnReboot}, #{status})")
|
||||||
|
int add(SipVideo sipVideo);
|
||||||
|
|
||||||
|
@Update(value = {" <script>" +
|
||||||
|
"UPDATE wvp_sip_video " +
|
||||||
|
"SET update_time=#{updateTime}" +
|
||||||
|
"<if test='media_server_id != null'>, media_server_id=#{mediaServerId}</if>" +
|
||||||
|
"<if test='request_no != null'>, request_no=#{requestNo}</if>" +
|
||||||
|
"<if test='auto_reconnect_on_reboot != null'>, auto_reconnect_on_reboot=#{autoReconnectOnReboot}</if>" +
|
||||||
|
"<if test='status != null'>, status=#{status}</if>" +
|
||||||
|
"WHERE id = #{id}"+
|
||||||
|
" </script>"})
|
||||||
|
int update(SipVideo video);
|
||||||
|
|
||||||
|
@Delete("DELETE FROM wvp_sip_video WHERE id = #{videoId}")
|
||||||
|
void remove(Integer videoId);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.service;
|
||||||
|
|
||||||
|
import javax.sip.ResponseEvent;
|
||||||
|
|
||||||
|
|
||||||
|
public interface ISIPResponseProcessor {
|
||||||
|
|
||||||
|
void process(ResponseEvent evt);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.service;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipVideo;
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接入SIP系统的接口
|
||||||
|
*/
|
||||||
|
public interface ISipService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加服务器信息
|
||||||
|
*/
|
||||||
|
SipServer getSipServer(int sipServerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加服务器信息
|
||||||
|
*/
|
||||||
|
void addSipServer(SipServer sipServer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除服务器信息
|
||||||
|
*/
|
||||||
|
void removeSipServer(int sipServerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新服务器信息
|
||||||
|
*/
|
||||||
|
void updateSipServer(SipServer sipServer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始连接服务器
|
||||||
|
*/
|
||||||
|
void startSipServer(int sipServerId, SipServerAccount account, SipVideo video);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与服务器断开连接
|
||||||
|
*/
|
||||||
|
void stopSipServer(int sipServerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器上线
|
||||||
|
*/
|
||||||
|
void sipServerOnline(int sipServerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器下线
|
||||||
|
*/
|
||||||
|
void sipServerOffline(int sipServerId);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页获取服务器列表
|
||||||
|
*/
|
||||||
|
PageInfo<SipServer> getServerList(int page, int count);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个sip视频
|
||||||
|
*/
|
||||||
|
void addSipVideo(SipVideo sipVideo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取帐号列表
|
||||||
|
*/
|
||||||
|
PageInfo<SipServerAccount> getAccountList(int serverId, Integer page, Integer count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频列表
|
||||||
|
*/
|
||||||
|
PageInfo<SipVideo> getVideoList(int serverId, int accountId, Integer page, Integer count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加帐号
|
||||||
|
*/
|
||||||
|
void addSipServerAccount(SipServerAccount account);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新帐号
|
||||||
|
*/
|
||||||
|
void updateSipServerAccount(SipServerAccount account);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除帐号
|
||||||
|
*/
|
||||||
|
void removeSipServerAccount(Integer accountId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新SIP推送视频
|
||||||
|
*/
|
||||||
|
void updateSipVideo(SipVideo video);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除
|
||||||
|
*/
|
||||||
|
void removeSipVideo(Integer videoId);
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.service.Impl;
|
||||||
|
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipEvent;
|
||||||
|
import com.genersoft.iot.vmp.sip.service.ISIPResponseProcessor;
|
||||||
|
import com.genersoft.iot.vmp.sip.service.SipSubscribe;
|
||||||
|
import gov.nist.javax.sip.message.SIPResponse;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.sip.*;
|
||||||
|
import javax.sip.header.CSeqHeader;
|
||||||
|
import javax.sip.header.CallIdHeader;
|
||||||
|
import javax.sip.message.Response;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听sip消息
|
||||||
|
* @author lin
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SipListenerImpl implements SipListener {
|
||||||
|
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(SipListenerImpl.class);
|
||||||
|
|
||||||
|
private Map<String, ISIPResponseProcessor> responseProcessorMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Async("taskExecutor")
|
||||||
|
public void processRequest(RequestEvent requestEvent) {
|
||||||
|
String method = requestEvent.getRequest().getMethod();
|
||||||
|
// TODO 暂不处理
|
||||||
|
// if (method.equalsIgnoreCase("")) {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Async("taskExecutor")
|
||||||
|
public void processResponse(ResponseEvent responseEvent) {
|
||||||
|
SIPResponse response = (SIPResponse) responseEvent.getResponse();
|
||||||
|
int status = response.getStatusCode();
|
||||||
|
|
||||||
|
// Success
|
||||||
|
if (((status >= Response.OK) && (status < Response.MULTIPLE_CHOICES)) || status == Response.UNAUTHORIZED) {
|
||||||
|
CSeqHeader cseqHeader = (CSeqHeader) responseEvent.getResponse().getHeader(CSeqHeader.NAME);
|
||||||
|
String method = cseqHeader.getMethod();
|
||||||
|
ISIPResponseProcessor sipRequestProcessor = responseProcessorMap.get(method);
|
||||||
|
if (sipRequestProcessor != null) {
|
||||||
|
sipRequestProcessor.process(responseEvent);
|
||||||
|
}
|
||||||
|
if (status != Response.UNAUTHORIZED && responseEvent.getResponse() != null && SipSubscribe.getInstance().getOkSubscribesSize() > 0 ) {
|
||||||
|
CallIdHeader callIdHeader = (CallIdHeader)responseEvent.getResponse().getHeader(CallIdHeader.NAME);
|
||||||
|
if (callIdHeader != null) {
|
||||||
|
SipEvent subscribe = SipSubscribe.getInstance().getOkSubscribe(callIdHeader.getCallId());
|
||||||
|
if (subscribe != null) {
|
||||||
|
SipSubscribe.getInstance().removeOkSubscribe(callIdHeader.getCallId());
|
||||||
|
subscribe.response(response.getStatusCode(), response.getReasonPhrase(), responseEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ((status >= Response.TRYING) && (status < Response.OK)) {
|
||||||
|
// 增加其它无需回复的响应,如101、180等
|
||||||
|
} else {
|
||||||
|
log.warn("[SIP] 接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase());
|
||||||
|
if (responseEvent.getResponse() != null && SipSubscribe.getInstance().getErrorSubscribesSize() > 0 ) {
|
||||||
|
CallIdHeader callIdHeader = (CallIdHeader)responseEvent.getResponse().getHeader(CallIdHeader.NAME);
|
||||||
|
if (callIdHeader != null) {
|
||||||
|
SipEvent subscribe = SipSubscribe.getInstance().getErrorSubscribe(callIdHeader.getCallId());
|
||||||
|
if (subscribe != null) {
|
||||||
|
SipSubscribe.getInstance().removeErrorSubscribe(callIdHeader.getCallId());
|
||||||
|
subscribe.response(response.getStatusCode(), response.getReasonPhrase(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (responseEvent.getDialog() != null) {
|
||||||
|
responseEvent.getDialog().delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processTimeout(TimeoutEvent timeoutEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processIOException(IOExceptionEvent exceptionEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.service.Impl;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties;
|
||||||
|
import com.genersoft.iot.vmp.sip.SipCommander;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.ResultCallback;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipEvent;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
import com.genersoft.iot.vmp.sip.service.SipSdk;
|
||||||
|
import com.genersoft.iot.vmp.sip.utils.SipUtils;
|
||||||
|
import gov.nist.javax.sip.SipProviderImpl;
|
||||||
|
import gov.nist.javax.sip.SipStackImpl;
|
||||||
|
import gov.nist.javax.sip.message.SIPResponse;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.sip.*;
|
||||||
|
import javax.sip.header.WWWAuthenticateHeader;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TooManyListenersException;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SipSdkImpl implements SipSdk {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(SipSdkImpl.class);
|
||||||
|
|
||||||
|
private Map<String, SipProviderImpl> sipProviderMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SipListener sipListenerImpl;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SipCommander sipCommander;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(SipServer sipServer, SipServerAccount account, ResultCallback<Object> callback) throws PeerUnavailableException, TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException {
|
||||||
|
|
||||||
|
if (sipServer == null || account == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sipServer.getLocalIp() == null) {
|
||||||
|
sipServer.setLocalIp("0.0.0.0");
|
||||||
|
}
|
||||||
|
if (sipServer.getLocalPort() == null) {
|
||||||
|
if (sipServer.getTransport().equalsIgnoreCase("UDP")) {
|
||||||
|
sipServer.setLocalPort(SipUtils.getRandomUdpPort());
|
||||||
|
}else {
|
||||||
|
sipServer.setLocalPort(SipUtils.getRandomTcpPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SipProviderImpl sipProvider = initSipLister(sipServer);
|
||||||
|
try {
|
||||||
|
SipEvent successEvent = (code, msg, data)->{
|
||||||
|
// 注册成功后开启定时任务
|
||||||
|
};
|
||||||
|
|
||||||
|
sipCommander.register(sipServer, account, sipProvider, true, (code, msg, data)->{
|
||||||
|
if (code == 200) {
|
||||||
|
// 注册成功
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run(code, msg, data);
|
||||||
|
}
|
||||||
|
successEvent.response(code, msg, data);
|
||||||
|
}else if (code == 401) {
|
||||||
|
ResponseEvent responseEvent = (ResponseEvent) data;
|
||||||
|
SIPResponse response = (SIPResponse) responseEvent.getResponse();
|
||||||
|
WWWAuthenticateHeader authenticateHeader = (WWWAuthenticateHeader) response.getHeader(WWWAuthenticateHeader.NAME);
|
||||||
|
try {
|
||||||
|
sipCommander.register(sipServer, account, sipProvider, authenticateHeader, true, (code1, msg1, data1)->{
|
||||||
|
if (code1 == 200) {
|
||||||
|
// 注册成功
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run(code1, msg1, data1);
|
||||||
|
}
|
||||||
|
successEvent.response(code1, msg1, data1);
|
||||||
|
}else {
|
||||||
|
// 注册失败
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run(code1, msg1, data1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, (code2, msg2, data2)->{
|
||||||
|
// 注册失败
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run(code2, msg2, data2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (SipException | ParseException | NoSuchAlgorithmException| InvalidArgumentException e) {
|
||||||
|
logger.warn("[SIP] 发送注册消息失败", e);
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run(-1, e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
// 注册失败
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run(code, msg, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, (code, msg, data)->{
|
||||||
|
|
||||||
|
// 注册失败
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run(code, msg, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (SipException | ParseException | NoSuchAlgorithmException e) {
|
||||||
|
logger.warn("[SIP] 发送注册消息失败", e);
|
||||||
|
if (callback != null) {
|
||||||
|
callback.run(-1, e.getMessage(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private SipProviderImpl initSipLister(SipServer sipServer) throws PeerUnavailableException, TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException {
|
||||||
|
SipFactory.getInstance().setPathName("gov.nist");
|
||||||
|
SipStackImpl sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(
|
||||||
|
DefaultProperties.getProperties(sipServer.getLocalIp(), false));
|
||||||
|
|
||||||
|
ListeningPoint listeningPoint = sipStack.createListeningPoint(sipServer.getLocalIp(), sipServer.getLocalPort(), sipServer.getTransport());
|
||||||
|
SipProviderImpl sipProvider = (SipProviderImpl)sipStack.createSipProvider(listeningPoint);
|
||||||
|
sipProvider.setDialogErrorsAutomaticallyHandled();
|
||||||
|
sipProvider.addSipListener(sipListenerImpl);
|
||||||
|
sipProviderMap.put(sipServer.getLocalIp() + ":" + sipServer.getLocalPort(), sipProvider);
|
||||||
|
return sipProvider;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.service.Impl;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipVideo;
|
||||||
|
import com.genersoft.iot.vmp.sip.dao.SipServerAccountMapper;
|
||||||
|
import com.genersoft.iot.vmp.sip.dao.SipServerMapper;
|
||||||
|
import com.genersoft.iot.vmp.sip.dao.SipVideoMapper;
|
||||||
|
import com.genersoft.iot.vmp.sip.service.ISipService;
|
||||||
|
import com.genersoft.iot.vmp.sip.service.SipSdk;
|
||||||
|
import com.github.pagehelper.PageHelper;
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.sip.InvalidArgumentException;
|
||||||
|
import javax.sip.ObjectInUseException;
|
||||||
|
import javax.sip.PeerUnavailableException;
|
||||||
|
import javax.sip.TransportNotSupportedException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TooManyListenersException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SipServiceImpl implements ISipService {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(SipServiceImpl.class);
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SipServerMapper serverMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SipServerAccountMapper accountMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SipVideoMapper videoMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SipSdk sipSdk;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SipServer getSipServer(int sipServerId) {
|
||||||
|
return serverMapper.query(sipServerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addSipServer(SipServer sipServer) {
|
||||||
|
serverMapper.add(sipServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeSipServer(int sipServerId) {
|
||||||
|
serverMapper.remove(sipServerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSipServer(SipServer sipServer) {
|
||||||
|
serverMapper.update(sipServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startSipServer(int sipServerId, SipServerAccount account, SipVideo video) {
|
||||||
|
SipServer sipServer = getSipServer(sipServerId);
|
||||||
|
if (sipServer == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (account.getId() == null) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopSipServer(int sipAccountId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sipServerOnline(int sipAccountId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sipServerOffline(int sipAccountId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageInfo<SipServer> getServerList(int page, int count) {
|
||||||
|
PageHelper.startPage(page, count);
|
||||||
|
List<SipServer> all = serverMapper.all();
|
||||||
|
return new PageInfo<>(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addSipVideo(SipVideo sipVideo) {
|
||||||
|
videoMapper.add(sipVideo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageInfo<SipServerAccount> getAccountList(int serverId, Integer page, Integer count) {
|
||||||
|
PageHelper.startPage(page, count);
|
||||||
|
List<SipServerAccount> all = accountMapper.all(serverId);
|
||||||
|
return new PageInfo<>(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageInfo<SipVideo> getVideoList(int serverId, int accountId, Integer page, Integer count) {
|
||||||
|
PageHelper.startPage(page, count);
|
||||||
|
List<SipVideo> all = videoMapper.all(serverId, accountId);
|
||||||
|
return new PageInfo<>(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addSipServerAccount(SipServerAccount account) {
|
||||||
|
if (account.getSipServerId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SipServer server = serverMapper.query(account.getSipServerId());
|
||||||
|
if (server == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
accountMapper.add(account);
|
||||||
|
try {
|
||||||
|
sipSdk.register(server, account, (code, msg, data) -> {
|
||||||
|
if (code == 200) {
|
||||||
|
|
||||||
|
}else {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (PeerUnavailableException | TransportNotSupportedException | InvalidArgumentException |
|
||||||
|
ObjectInUseException | TooManyListenersException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSipServerAccount(SipServerAccount account) {
|
||||||
|
accountMapper.update(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeSipServerAccount(Integer accountId) {
|
||||||
|
accountMapper.remove(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSipVideo(SipVideo video) {
|
||||||
|
videoMapper.update(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeSipVideo(Integer videoId) {
|
||||||
|
videoMapper.remove(videoId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.service;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.ResultCallback;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
|
||||||
|
import javax.sip.InvalidArgumentException;
|
||||||
|
import javax.sip.ObjectInUseException;
|
||||||
|
import javax.sip.PeerUnavailableException;
|
||||||
|
import javax.sip.TransportNotSupportedException;
|
||||||
|
import java.util.TooManyListenersException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* java实现SIP SDK
|
||||||
|
*/
|
||||||
|
public interface SipSdk {
|
||||||
|
|
||||||
|
void register(SipServer sipServer, SipServerAccount account, ResultCallback<Object> callback) throws PeerUnavailableException, TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException;
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.service;
|
||||||
|
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipEvent;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lin
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class SipSubscribe {
|
||||||
|
|
||||||
|
private static SipSubscribe instance;
|
||||||
|
|
||||||
|
private SipSubscribe() {}
|
||||||
|
|
||||||
|
public static SipSubscribe getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (SipSubscribe.class) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new SipSubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, SipEvent> errorSubscribes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private Map<String, SipEvent> okSubscribes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private Map<String, Instant> okTimeSubscribes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private Map<String, Instant> errorTimeSubscribes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public void addErrorSubscribe(String key, SipEvent event) {
|
||||||
|
errorSubscribes.put(key, event);
|
||||||
|
errorTimeSubscribes.put(key, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addOkSubscribe(String key, SipEvent event) {
|
||||||
|
okSubscribes.put(key, event);
|
||||||
|
okTimeSubscribes.put(key, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public SipEvent getErrorSubscribe(String key) {
|
||||||
|
return errorSubscribes.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeErrorSubscribe(String key) {
|
||||||
|
if(key == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
errorSubscribes.remove(key);
|
||||||
|
errorTimeSubscribes.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SipEvent getOkSubscribe(String key) {
|
||||||
|
return okSubscribes.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeOkSubscribe(String key) {
|
||||||
|
if(key == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
okSubscribes.remove(key);
|
||||||
|
okTimeSubscribes.remove(key);
|
||||||
|
}
|
||||||
|
public int getErrorSubscribesSize(){
|
||||||
|
return errorSubscribes.size();
|
||||||
|
}
|
||||||
|
public int getOkSubscribesSize(){
|
||||||
|
return okSubscribes.size();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.utils;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.sip.PeerUnavailableException;
|
||||||
|
import javax.sip.SipFactory;
|
||||||
|
import javax.sip.address.SipURI;
|
||||||
|
import javax.sip.address.URI;
|
||||||
|
import javax.sip.header.AuthorizationHeader;
|
||||||
|
import javax.sip.message.Request;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class DigestClientAuthenticationHelper {
|
||||||
|
private Logger logger = LoggerFactory.getLogger(DigestClientAuthenticationHelper.class);
|
||||||
|
|
||||||
|
public static final String DEFAULT_ALGORITHM = "MD5";
|
||||||
|
public static final String DEFAULT_SCHEME = "Digest";
|
||||||
|
|
||||||
|
private static final char[] toHex = { '0', '1', '2', '3', '4', '5', '6',
|
||||||
|
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||||
|
|
||||||
|
|
||||||
|
public static String toHexString(byte b[]) {
|
||||||
|
int pos = 0;
|
||||||
|
char[] c = new char[b.length * 2];
|
||||||
|
for (int i = 0; i < b.length; i++) {
|
||||||
|
c[pos++] = toHex[(b[i] >> 4) & 0x0F];
|
||||||
|
c[pos++] = toHex[b[i] & 0x0f];
|
||||||
|
}
|
||||||
|
return new String(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the challenge string.
|
||||||
|
*
|
||||||
|
* @return a generated nonce.
|
||||||
|
*/
|
||||||
|
private static String generateNonce() throws NoSuchAlgorithmException {
|
||||||
|
long time = Instant.now().toEpochMilli();
|
||||||
|
Random rand = new Random();
|
||||||
|
long pad = rand.nextLong();
|
||||||
|
String nonceString = Long.valueOf(time).toString()
|
||||||
|
+ Long.valueOf(pad).toString();
|
||||||
|
byte mdbytes[] = MessageDigest.getInstance(DEFAULT_ALGORITHM).digest(nonceString.getBytes());
|
||||||
|
return toHexString(mdbytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthorizationHeader getAuthorizationHeader(SipServer server, SipServerAccount account)
|
||||||
|
throws PeerUnavailableException, ParseException, NoSuchAlgorithmException {
|
||||||
|
return getAuthorizationHeader(server, account, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthorizationHeader getAuthorizationHeader(SipServer server, SipServerAccount account,
|
||||||
|
String realm, String nonce, String algorithm, String qop)
|
||||||
|
throws PeerUnavailableException, ParseException, NoSuchAlgorithmException {
|
||||||
|
|
||||||
|
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(account.getUsername(),
|
||||||
|
server.getServerIp() + ":" + server.getServerPort());
|
||||||
|
|
||||||
|
AuthorizationHeader authorizationHeader = SipFactory.getInstance().createHeaderFactory().createAuthorizationHeader(DEFAULT_SCHEME);
|
||||||
|
authorizationHeader.setUsername(account.getUsername());
|
||||||
|
if (realm == null) {
|
||||||
|
authorizationHeader.setRealm(server.getServerIp());
|
||||||
|
}else {
|
||||||
|
authorizationHeader.setRealm(realm);
|
||||||
|
}
|
||||||
|
if (nonce == null) {
|
||||||
|
authorizationHeader.setRealm(generateNonce());
|
||||||
|
}else {
|
||||||
|
authorizationHeader.setRealm(realm);
|
||||||
|
}
|
||||||
|
if (algorithm == null) {
|
||||||
|
authorizationHeader.setAlgorithm("MD5");
|
||||||
|
}else {
|
||||||
|
authorizationHeader.setAlgorithm(algorithm);
|
||||||
|
}
|
||||||
|
if (qop == null) {
|
||||||
|
authorizationHeader.setQop("auth");
|
||||||
|
}else {
|
||||||
|
authorizationHeader.setQop(qop);
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizationHeader.setNonce(generateNonce());
|
||||||
|
authorizationHeader.setNonceCount(1);
|
||||||
|
authorizationHeader.setURI(requestURI);
|
||||||
|
authorizationHeader.setCNonce(UUID.randomUUID().toString());
|
||||||
|
|
||||||
|
String responseForAuth = getResponseForAuth(authorizationHeader, account.getPassword());
|
||||||
|
authorizationHeader.setResponse(responseForAuth);
|
||||||
|
return authorizationHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getResponseForAuth(AuthorizationHeader authHeader, String password) throws NoSuchAlgorithmException {
|
||||||
|
|
||||||
|
String realm = authHeader.getRealm();
|
||||||
|
String username = authHeader.getUsername();
|
||||||
|
|
||||||
|
if ( username == null || realm == null ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String nonce = authHeader.getNonce();
|
||||||
|
URI uri = authHeader.getURI();
|
||||||
|
if (uri == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String A2 = "REGISTER:" + uri.toString();
|
||||||
|
String HA1 = password;
|
||||||
|
|
||||||
|
MessageDigest messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
|
||||||
|
|
||||||
|
byte[] mdbytes = messageDigest.digest(A2.getBytes());
|
||||||
|
String HA2 = toHexString(mdbytes);
|
||||||
|
|
||||||
|
String cnonce = authHeader.getCNonce();
|
||||||
|
String KD = HA1 + ":" + nonce;
|
||||||
|
if (cnonce != null) {
|
||||||
|
KD += ":" + cnonce;
|
||||||
|
}
|
||||||
|
KD += ":" + HA2;
|
||||||
|
mdbytes = messageDigest.digest(KD.getBytes());
|
||||||
|
|
||||||
|
return toHexString(mdbytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate the inbound request given plain text password.
|
||||||
|
*
|
||||||
|
* @param request - the request to authenticate.
|
||||||
|
* @param pass -- the plain text password.
|
||||||
|
*
|
||||||
|
* @return true if authentication succeded and false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean doAuthenticatePlainTextPassword(Request request, String pass) throws NoSuchAlgorithmException {
|
||||||
|
AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME);
|
||||||
|
if ( authHeader == null || authHeader.getRealm() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String realm = authHeader.getRealm().trim();
|
||||||
|
String username = authHeader.getUsername().trim();
|
||||||
|
|
||||||
|
if ( username == null || realm == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String nonce = authHeader.getNonce();
|
||||||
|
URI uri = authHeader.getURI();
|
||||||
|
if (uri == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略
|
||||||
|
String qop = authHeader.getQop();
|
||||||
|
|
||||||
|
// 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。
|
||||||
|
// 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
|
||||||
|
String cnonce = authHeader.getCNonce();
|
||||||
|
|
||||||
|
// nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量
|
||||||
|
int nc = authHeader.getNonceCount();
|
||||||
|
String ncStr = String.format("%08x", nc).toUpperCase();
|
||||||
|
// String ncStr = new DecimalFormat("00000000").format(nc);
|
||||||
|
// String ncStr = new DecimalFormat("00000000").format(Integer.parseInt(nc + "", 16));
|
||||||
|
|
||||||
|
String A1 = username + ":" + realm + ":" + pass;
|
||||||
|
|
||||||
|
String A2 = request.getMethod().toUpperCase() + ":" + uri.toString();
|
||||||
|
|
||||||
|
MessageDigest messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
|
||||||
|
|
||||||
|
byte mdbytes[] = messageDigest.digest(A1.getBytes());
|
||||||
|
String HA1 = toHexString(mdbytes);
|
||||||
|
logger.debug("A1: " + A1);
|
||||||
|
logger.debug("A2: " + A2);
|
||||||
|
mdbytes = messageDigest.digest(A2.getBytes());
|
||||||
|
String HA2 = toHexString(mdbytes);
|
||||||
|
logger.debug("HA1: " + HA1);
|
||||||
|
logger.debug("HA2: " + HA2);
|
||||||
|
// String cnonce = authHeader.getCNonce();
|
||||||
|
logger.debug("nonce: " + nonce);
|
||||||
|
logger.debug("nc: " + ncStr);
|
||||||
|
logger.debug("cnonce: " + cnonce);
|
||||||
|
logger.debug("qop: " + qop);
|
||||||
|
String KD = HA1 + ":" + nonce;
|
||||||
|
|
||||||
|
if (qop != null && qop.equalsIgnoreCase("auth") ) {
|
||||||
|
if (nc != -1) {
|
||||||
|
KD += ":" + ncStr;
|
||||||
|
}
|
||||||
|
if (cnonce != null) {
|
||||||
|
KD += ":" + cnonce;
|
||||||
|
}
|
||||||
|
KD += ":" + qop;
|
||||||
|
}
|
||||||
|
KD += ":" + HA2;
|
||||||
|
logger.debug("KD: " + KD);
|
||||||
|
mdbytes = messageDigest.digest(KD.getBytes());
|
||||||
|
String mdString = toHexString(mdbytes);
|
||||||
|
logger.debug("mdString: " + mdString);
|
||||||
|
String response = authHeader.getResponse();
|
||||||
|
logger.debug("response: " + response);
|
||||||
|
return mdString.equals(response);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.utils;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
|
||||||
|
import javax.sip.InvalidArgumentException;
|
||||||
|
import javax.sip.PeerUnavailableException;
|
||||||
|
import javax.sip.SipFactory;
|
||||||
|
import javax.sip.address.Address;
|
||||||
|
import javax.sip.address.SipURI;
|
||||||
|
import javax.sip.header.*;
|
||||||
|
import javax.sip.message.Request;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class SIPRequestFactory {
|
||||||
|
|
||||||
|
public static Request createRegisterRequest(SipServer server, SipServerAccount account, String callId, boolean isRegister, WWWAuthenticateHeader wwwAuthenticateHeader) throws ParseException, PeerUnavailableException, InvalidArgumentException, NoSuchAlgorithmException {
|
||||||
|
Request request = null;
|
||||||
|
String serverAddress = server.getServerIp()+ ":" + server.getServerPort();
|
||||||
|
String clientAddress = server.getLocalIp()+ ":" + server.getLocalPort();
|
||||||
|
//请求行
|
||||||
|
SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(account.getUsername(),
|
||||||
|
serverAddress);
|
||||||
|
//via
|
||||||
|
ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
|
||||||
|
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(server.getLocalIp(),
|
||||||
|
server.getLocalPort(), server.getTransport(), SipUtils.getNewViaTag());
|
||||||
|
viaHeader.setRPort();
|
||||||
|
viaHeaders.add(viaHeader);
|
||||||
|
//from
|
||||||
|
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(account.getUsername(), serverAddress);
|
||||||
|
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
|
||||||
|
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, SipUtils.getNewFromTag());
|
||||||
|
//to
|
||||||
|
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(account.getUsername(), serverAddress);
|
||||||
|
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
|
||||||
|
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
|
||||||
|
|
||||||
|
//Forwards
|
||||||
|
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
|
||||||
|
|
||||||
|
CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(callId);
|
||||||
|
|
||||||
|
//ceq
|
||||||
|
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(SipUtils.getCSEQ(), Request.REGISTER);
|
||||||
|
request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader,
|
||||||
|
cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
|
||||||
|
|
||||||
|
Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory()
|
||||||
|
.createSipURI(account.getUsername(), clientAddress));
|
||||||
|
ContactHeader contactHeader = SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress);
|
||||||
|
contactHeader.setParameter("methods", "INVITE,ACK,BYE,CANCEL,OPTIONS,PRACK,MESSAGE,SUBSCRIBE,NOTIFY,REFER,UPDATE");
|
||||||
|
// contactHeader.setQValue();
|
||||||
|
request.addHeader(contactHeader);
|
||||||
|
|
||||||
|
ExpiresHeader expires = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(isRegister ? 180 : 0);
|
||||||
|
request.addHeader(expires);
|
||||||
|
|
||||||
|
AllowHeader allowHeader = SipFactory.getInstance().createHeaderFactory().createAllowHeader("INVITE, ACK, BYE, CANCEL, OPTIONS, PRACK, MESSAGE, SUBSCRIBE, NOTIFY, REFER, UPDATE");
|
||||||
|
request.addHeader(allowHeader);
|
||||||
|
|
||||||
|
SupportedHeader supportedHeader = SipFactory.getInstance().createHeaderFactory().createSupportedHeader("timer, 100rel, replaces, gruu, outbound");
|
||||||
|
request.addHeader(supportedHeader);
|
||||||
|
|
||||||
|
AuthorizationHeader authorizationHeader;
|
||||||
|
if (wwwAuthenticateHeader != null) {
|
||||||
|
String qop = wwwAuthenticateHeader.getQop();
|
||||||
|
String algorithm = wwwAuthenticateHeader.getAlgorithm();
|
||||||
|
String nonce = wwwAuthenticateHeader.getNonce();
|
||||||
|
String realm = wwwAuthenticateHeader.getRealm();
|
||||||
|
|
||||||
|
authorizationHeader = DigestClientAuthenticationHelper.getAuthorizationHeader(server, account, realm, nonce, algorithm, qop);
|
||||||
|
}else {
|
||||||
|
authorizationHeader = DigestClientAuthenticationHelper.getAuthorizationHeader(server, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.addHeader(authorizationHeader);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package com.genersoft.iot.vmp.sip.utils;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.utils.GitUtil;
|
||||||
|
import gov.nist.javax.sip.SipProviderImpl;
|
||||||
|
import gov.nist.javax.sip.Utils;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
import javax.sip.PeerUnavailableException;
|
||||||
|
import javax.sip.SipFactory;
|
||||||
|
import javax.sip.header.UserAgentHeader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SipUtils {
|
||||||
|
|
||||||
|
public static int getRandomTcpPort(){
|
||||||
|
try {
|
||||||
|
ServerSocket serverSocket = new ServerSocket(0);
|
||||||
|
int localPort = serverSocket.getLocalPort();
|
||||||
|
serverSocket.close();
|
||||||
|
return localPort;
|
||||||
|
}catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getRandomUdpPort(){
|
||||||
|
try {
|
||||||
|
DatagramSocket datagramSocket = new DatagramSocket(0);
|
||||||
|
int localPort = datagramSocket.getLocalPort();
|
||||||
|
datagramSocket.close();
|
||||||
|
return localPort;
|
||||||
|
}catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getNewCallId(SipProviderImpl sipProvider) {
|
||||||
|
return Utils.getInstance().generateCallIdentifier(sipProvider.getListeningPoint()
|
||||||
|
.getIPAddress());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException {
|
||||||
|
List<String> agentParam = new ArrayList<>();
|
||||||
|
agentParam.add("WVP-Pro ");
|
||||||
|
if (gitUtil != null ) {
|
||||||
|
if (!ObjectUtils.isEmpty(gitUtil.getBuildVersion())) {
|
||||||
|
agentParam.add("v");
|
||||||
|
agentParam.add(gitUtil.getBuildVersion() + ".");
|
||||||
|
}
|
||||||
|
if (!ObjectUtils.isEmpty(gitUtil.getCommitTime())) {
|
||||||
|
agentParam.add(gitUtil.getCommitTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SipFactory.getInstance().createHeaderFactory().createUserAgentHeader(agentParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long cseq = 0L;
|
||||||
|
|
||||||
|
public static long getCSEQ() {
|
||||||
|
return cseq++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getNewViaTag() {
|
||||||
|
return "z9hG4bK" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getNewFromTag(){
|
||||||
|
return UUID.randomUUID().toString().replace("-", "");
|
||||||
|
|
||||||
|
// return getNewTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getNewTag(){
|
||||||
|
return String.valueOf(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package com.genersoft.iot.vmp.vmanager.sip;
|
||||||
|
|
||||||
|
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServer;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipServerAccount;
|
||||||
|
import com.genersoft.iot.vmp.sip.bean.SipVideo;
|
||||||
|
import com.genersoft.iot.vmp.sip.service.ISipService;
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SIP接口
|
||||||
|
*/
|
||||||
|
@Tag(name = "SIP接口", description = "")
|
||||||
|
@Controller
|
||||||
|
|
||||||
|
@RequestMapping(value = "/api/sip")
|
||||||
|
public class SipController {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(SipController.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IMediaServerService mediaServerService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISipService sipService;
|
||||||
|
|
||||||
|
@Operation(summary = "分页获取SIP服务")
|
||||||
|
@Parameter(name = "page", description = "当前页")
|
||||||
|
@Parameter(name = "count", description = "每页查询数量")
|
||||||
|
@GetMapping(value = "/server/list")
|
||||||
|
@ResponseBody
|
||||||
|
public PageInfo<SipServer> getServerList(@RequestParam(required = false)Integer page,
|
||||||
|
@RequestParam(required = false)Integer count ){
|
||||||
|
|
||||||
|
return sipService.getServerList(page, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "添加SIP服务")
|
||||||
|
@PostMapping(value = "/server/add")
|
||||||
|
@ResponseBody
|
||||||
|
public void addServer(@RequestBody SipServer server){
|
||||||
|
sipService.addSipServer(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新SIP服务")
|
||||||
|
@PostMapping(value = "/server/update")
|
||||||
|
@ResponseBody
|
||||||
|
public void updateServer(@RequestBody SipServer server){
|
||||||
|
sipService.updateSipServer(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除SIP服务")
|
||||||
|
@DeleteMapping(value = "/server/remove")
|
||||||
|
@ResponseBody
|
||||||
|
public void deleteServer(Integer serverId){
|
||||||
|
sipService.removeSipServer(serverId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Operation(summary = "分页获取SIP帐号")
|
||||||
|
@Parameter(name = "page", description = "当前页")
|
||||||
|
@Parameter(name = "count", description = "每页查询数量")
|
||||||
|
@GetMapping(value = "/account/list")
|
||||||
|
@ResponseBody
|
||||||
|
public PageInfo<SipServerAccount> getAccountList(@RequestParam(required = false)Integer page,
|
||||||
|
@RequestParam(required = false)Integer count,
|
||||||
|
Integer serverId){
|
||||||
|
|
||||||
|
return sipService.getAccountList(serverId, page, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "添加SIP帐号")
|
||||||
|
@PostMapping(value = "/account/add")
|
||||||
|
@ResponseBody
|
||||||
|
public void addServerAccount(@RequestBody SipServerAccount account){
|
||||||
|
sipService.addSipServerAccount(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新SIP帐号")
|
||||||
|
@PostMapping(value = "/account/update")
|
||||||
|
@ResponseBody
|
||||||
|
public void updateServerAccount(@RequestBody SipServerAccount account){
|
||||||
|
sipService.updateSipServerAccount(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除SIP帐号")
|
||||||
|
@DeleteMapping(value = "/account/remove")
|
||||||
|
@ResponseBody
|
||||||
|
public void deleteServerAccount(Integer accountId){
|
||||||
|
sipService.removeSipServerAccount(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "分页获取SIP推送视频")
|
||||||
|
@Parameter(name = "page", description = "当前页")
|
||||||
|
@Parameter(name = "count", description = "每页查询数量")
|
||||||
|
@GetMapping(value = "/video/list")
|
||||||
|
@ResponseBody
|
||||||
|
public PageInfo<SipVideo> getVideoList(@RequestParam(required = false)Integer page,
|
||||||
|
@RequestParam(required = false)Integer count,
|
||||||
|
Integer serverId,
|
||||||
|
Integer accountId){
|
||||||
|
|
||||||
|
return sipService.getVideoList(serverId, accountId, page, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "添加SIP推送视频")
|
||||||
|
@PostMapping(value = "/video/add")
|
||||||
|
@ResponseBody
|
||||||
|
public void addVideo(@RequestBody SipVideo video){
|
||||||
|
sipService.addSipVideo(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新SIP推送视频")
|
||||||
|
@PostMapping(value = "/video/update")
|
||||||
|
@ResponseBody
|
||||||
|
public void updateVideo(@RequestBody SipVideo video){
|
||||||
|
sipService.updateSipVideo(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除SIP推送视频")
|
||||||
|
@DeleteMapping(value = "/video/remove")
|
||||||
|
@ResponseBody
|
||||||
|
public void deleteVideo(Integer videoId){
|
||||||
|
sipService.removeSipVideo(videoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue