Browse Source

Merge remote-tracking branch 'origin/master'

master^2
wangguangyuan 4 weeks ago
parent
commit
3097b4f327
7 changed files with 241 additions and 19 deletions
  1. +21
    -0
      src/main/java/com/inspect/tcpserver/sip/items/DeviceInfoItem.java
  2. +1
    -0
      src/main/java/com/inspect/tcpserver/sip/registry/SipEventRegistry.java
  3. +191
    -14
      src/main/java/com/inspect/tcpserver/sip/service/SipClientService.java
  4. +1
    -0
      src/main/java/com/inspect/tcpserver/sip/service/SipVideoClient.java
  5. +24
    -2
      src/main/java/com/inspect/tcpserver/sip/utils/SipXmlParser.java
  6. +1
    -1
      src/main/resources/application-dev.yml
  7. +2
    -2
      src/main/resources/application.yml

+ 21
- 0
src/main/java/com/inspect/tcpserver/sip/items/DeviceInfoItem.java View File

@ -0,0 +1,21 @@
package com.inspect.tcpserver.sip.items;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import lombok.Getter;
import lombok.Setter;
/**
* 图片抓拍请求信令
*/
@Getter
@Setter
public class DeviceInfoItem {
@JacksonXmlProperty(isAttribute = true, localName = "CmdType")
private String cmdType;
@JacksonXmlProperty(isAttribute = true, localName = "sn")
private String sn;
@JacksonXmlProperty(isAttribute = true, localName = "DeviceID")
private String deviceID;
}

+ 1
- 0
src/main/java/com/inspect/tcpserver/sip/registry/SipEventRegistry.java View File

@ -9,6 +9,7 @@ public class SipEventRegistry {
private static final Map<String, Class<?>> EVENT_MAP = new HashMap<>();
static {
EVENT_MAP.put("Device_Info", DeviceInfoItem.class);
EVENT_MAP.put("Request_Resource", RequestResourceItem.class);
EVENT_MAP.put("Request_History_Alarm", RequestHistoryAlarmItem.class);
EVENT_MAP.put("Request_History_Video", RequestHistoryVideoItem.class);


+ 191
- 14
src/main/java/com/inspect/tcpserver/sip/service/SipClientService.java View File

@ -20,6 +20,7 @@ import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.*;
@ -211,6 +212,11 @@ public class SipClientService implements SipListener {
Properties properties = new Properties();
properties.setProperty("javax.sip.STACK_NAME", "spring-boot-sip-stack");
properties.setProperty("gov.nist.javax.sip.IP_ADDRESS", localIp);
// 新增这些JAIN-SIP RI 支持的 NAT 参数
properties.setProperty("gov.nist.javax.sip.OUTBOUND_PROXY", domain + ":" + port + ";" + transport); // 可选强制出站代理
properties.setProperty("gov.nist.javax.sip.AUTOMATIC_NAT_SUPPORT", "true"); // 如果版本支持
properties.setProperty("gov.nist.javax.sip.USE_RPORT_AS_OUTBOUND", "true"); // 使用 rport 作为出站
properties.setProperty("gov.nist.javax.sip.FIX_CONTACT_HEADER", "true"); // 修复 Contact
properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sipdebug.txt");
properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sipserverlog.txt");
@ -267,7 +273,7 @@ public class SipClientService implements SipListener {
public void sendRegister(AuthorizationHeader authHeader) throws Exception {
SipURI requestUri = addressFactory.createSipURI(null, domain);
// requestUri.setTransportParam(transport.toUpperCase());
requestUri.setTransportParam(transport.toUpperCase());
requestUri.setPort(port);
Address fromAddress = addressFactory.createAddress(username, addressFactory.createSipURI(username, domain));
@ -279,11 +285,18 @@ public class SipClientService implements SipListener {
CSeqHeader cSeq = headerFactory.createCSeqHeader(1L, Request.REGISTER);
MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);
//String natIp = "172.19.1.1"; // 避免硬编码用环境变量注入
//int natPort = localPort; // 通常保持本地端口或映射后端口
String natIp = System.getenv("SIP_NAT_IP") != null ? System.getenv("SIP_NAT_IP") : localIp;
int natPort = System.getenv("SIP_NAT_PORT") != null ? Integer.parseInt(System.getenv("SIP_NAT_PORT")) : localPort;
log.info("SEND_REGISTER NAT_IP: {}, NAT_PORT: {}", natIp, natPort);
// 创建带自定义deviceid参数的Contact URI
SipURI contactUri = addressFactory.createSipURI(username, localIp);
contactUri.setPort(localPort);
//SipURI contactUri = addressFactory.createSipURI(username, localIp);
SipURI contactUri = addressFactory.createSipURI(username, natIp);
//contactUri.setPort(localPort);
contactUri.setPort(natPort);
contactUri.setTransportParam(transport.toUpperCase());
// contactUri.setParameter("deviceid", "123456");
contactUri.setParameter("deviceid", "123456");
Address contactAddress = addressFactory.createAddress(contactUri);
ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
@ -291,6 +304,7 @@ public class SipClientService implements SipListener {
List<ViaHeader> viaHeaders = new ArrayList<>();
ViaHeader viaHeader = headerFactory.createViaHeader(localIp, localPort, transport.toUpperCase(), null);
viaHeader.setRPort();
viaHeaders.add(viaHeader);
Request request = messageFactory.createRequest(
@ -528,7 +542,8 @@ public class SipClientService implements SipListener {
}, delay * 1000L);
//sendNotify(username, domain, port, testXml);
sendSubscribe(username, domain, port, "Push_Resource");
// 向SIP服务器订阅消息不符合SIP/GB28181标准
//sendSubscribe(username, domain, port, "Push_Resource");
} else {
log.warn("REGISTER response: {}", status);
}
@ -741,9 +756,11 @@ public class SipClientService implements SipListener {
log.info("Received SIP request: {}", method);
try {
if (Request.MESSAGE.equalsIgnoreCase(method)) {
log.info("Processing MESSAGE request from {}", ((FromHeader) request.getHeader(FromHeader.NAME)).getAddress());
ContentTypeHeader contentTypeHeader = (ContentTypeHeader) request.getHeader(ContentTypeHeader.NAME);
log.info("Processing MESSAGE request from {}, contentType: {}, contentSubType: {}",
((FromHeader) request.getHeader(FromHeader.NAME)).getAddress(),
contentTypeHeader != null ? contentTypeHeader.getContentType() : "null",
contentTypeHeader != null ? contentTypeHeader.getContentSubType() : "null");
if (contentTypeHeader != null &&
"application".equalsIgnoreCase(contentTypeHeader.getContentType()) &&
"xml".equalsIgnoreCase(contentTypeHeader.getContentSubType())) {
@ -761,6 +778,14 @@ public class SipClientService implements SipListener {
return;
}
// 200 OK -> SIP Server
ServerTransaction st = requestEvent.getServerTransaction();
if (st == null) {
st = sipProvider.getNewServerTransaction(request);
}
Response ok = messageFactory.createResponse(Response.OK, request);
st.sendResponse(ok);
SipXmlEnvelope<?> envelope = SipXmlParser.parse(xml, itemClass);
//handleSipEvent(envelope);
}
@ -788,19 +813,29 @@ public class SipClientService implements SipListener {
ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(ExpiresHeader.NAME);
int expires = (expiresHeader != null) ? expiresHeader.getExpires() : 0;
log.info("Received SUBSCRIBE expires: {}, XML:\n{}", expires, xml);
String eventType = SipXmlParser.peekEventType(xml);
log.info("SUBSCRIBE EventType: {}", eventType);
ServerTransaction transaction = sipProvider.getNewServerTransaction(request);
// 对应协议B.9.1.2 F2 前端系统返回200 OK响应指示已经接受订阅请求
Response response = messageFactory.createResponse(Response.OK, request);
response.setExpires(expiresHeader);
transaction.sendResponse(response);
if (expires > 0) {
saveSubscription(request, expires);
// 对应协议B.9.1.2 F3 SIP客户端(前端系统)发送没有消息体的NOTIFY给平台其中Subscription-State头部字段值为active, 指示订阅关系建立
sendInitialNotify(request, transaction, "initial");
} else {
removeSubscription(request);
}
} else if (Request.INVITE.equals(request.getMethod())) {
handleInvite(requestEvent);
} else if(Request.ACK.equals(request.getMethod())) {
// RFC 3261: The ACK request does not generate a response.
// ACK 事务内请求不是普通 SIP 方法,不生成任何 Response,ACK 事务内请求不是普通 SIP 方法
startRtspToRtp();
}
else {
Response notImpl = messageFactory.createResponse(Response.NOT_IMPLEMENTED, request);
requestEvent.getServerTransaction().sendResponse(notImpl);
log.info("Method {} not implemented, replied 501", method);
@ -816,6 +851,100 @@ public class SipClientService implements SipListener {
}
}
private Process ffmpegProcess;
private void startRtspToRtp() {
try {
String cmd =
"ffmpeg -rtsp_transport tcp " +
"-i \"rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream\" " +
"-an -vcodec copy " +
"-f rtp -payload_type 96 " +
"\"rtp://192.168.1.116:50000?tcp\"";
ProcessBuilder pb = new ProcessBuilder(
"bash", "-c", cmd
);
pb.redirectErrorStream(true);
ffmpegProcess = pb.start();
new Thread(() -> {
try (BufferedReader br =
new BufferedReader(new InputStreamReader(
ffmpegProcess.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println("[FFmpeg] " + line);
}
} catch (IOException ignored) {}
}).start();
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleInvite(RequestEvent requestEvent) throws Exception {
Request request = requestEvent.getRequest();
log.info("Received INVITE:\n{}", request);
SipProvider provider = (SipProvider) requestEvent.getSource();
ServerTransaction st = requestEvent.getServerTransaction();
if (st == null) {
st = provider.getNewServerTransaction(request);
}
// 100 Trying非常推荐
Response trying = messageFactory.createResponse(Response.TRYING, request);
st.sendResponse(trying);
// 解析对方 SDP可先只打印
byte[] raw = request.getRawContent();
if (raw != null) {
String sdp = new String(raw, StandardCharsets.UTF_8);
log.info("INVITE SDP:\n{}", sdp);
}
// 构造 200 OK + SDP
Response ok = messageFactory.createResponse(Response.OK, request);
String localSipId = "29001002120107000001";
int localRtpPort = 554;
// Contact 必须
Address contactAddress = addressFactory.createAddress(
"sip:" + localSipId + "@" + localIp + ":" + localPort
);
ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
ok.addHeader(contactHeader);
// Content-Type
ContentTypeHeader contentType =
headerFactory.createContentTypeHeader("application", "sdp");
// 返回 SDP示例
String sdp =
"v=0\r\n" +
"o=" + localSipId + " 0 0 IN IP4 " + localIp + "\r\n" +
"s=Play\r\n" +
"c=IN IP4 " + localIp + "\r\n" +
"t=0 0\r\n" +
"m=video " + localRtpPort + " TCP/RTP/AVP 96\r\n" +
"a=sendonly\r\n" +
"a=setup:active\r\n" +
"a=connection:new\r\n" +
"a=rtpmap:96 PS/90000\r\n";
ok.setContent(sdp, contentType);
// 发送 200 OK
st.sendResponse(ok);
log.info("INVITE handled: 200 OK sent");
}
@Override
public void processTimeout(TimeoutEvent timeoutEvent) {
log.warn("SIP timeout: {}", timeoutEvent);
@ -960,12 +1089,12 @@ public class SipClientService implements SipListener {
notifyRequest.addHeader(ssHeader);
// 消息体
String xmlBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<SIP_XML>\n" +
" <Type>" + bodyText + "</Type>\n" +
"</SIP_XML>";
ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("application", "xml");
notifyRequest.setContent(xmlBody, contentTypeHeader);
// String xmlBody = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
// "<SIP_XML>\n" +
// " <Type>" + bodyText + "</Type>\n" +
// "</SIP_XML>";
// ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("application", "xml");
// notifyRequest.setContent(xmlBody, contentTypeHeader);
// 发送
ClientTransaction ct = sipProvider.getNewClientTransaction(notifyRequest);
@ -978,6 +1107,54 @@ public class SipClientService implements SipListener {
}
}
private void sendSubscribeNotifyToSipServer(Request subscribeRequest) {
try {
// 获取必要头字段
CallIdHeader callIdHeader = (CallIdHeader) subscribeRequest.getHeader(CallIdHeader.NAME);
CSeqHeader cseqHeader = (CSeqHeader) subscribeRequest.getHeader(CSeqHeader.NAME);
FromHeader fromHeader = (FromHeader) subscribeRequest.getHeader(FromHeader.NAME);
ToHeader toHeader = (ToHeader) subscribeRequest.getHeader(ToHeader.NAME);
EventHeader eventHeader = (EventHeader) subscribeRequest.getHeader(EventHeader.NAME);
ContactHeader contactHeader = (ContactHeader) subscribeRequest.getHeader(ContactHeader.NAME);
List<ViaHeader> viaHeaders = new ArrayList<>();
ViaHeader viaHeader = headerFactory.createViaHeader(localIp, localPort, transport.toUpperCase(), null);
viaHeaders.add(viaHeader);
// 创建 NOTIFY 请求
Request notifyRequest = messageFactory.createRequest(
subscribeRequest.getRequestURI(),
Request.NOTIFY,
callIdHeader,
headerFactory.createCSeqHeader(cseqHeader.getSeqNumber() + 1, Request.NOTIFY),
fromHeader,
toHeader,
viaHeaders,
headerFactory.createMaxForwardsHeader(70)
);
// 加入 Contact
notifyRequest.addHeader(contactHeader);
// 必须的事件头
notifyRequest.addHeader(eventHeader);
// Subscription-State
SubscriptionStateHeader ssHeader = headerFactory.createSubscriptionStateHeader("active");
ssHeader.setExpires(3600);
notifyRequest.addHeader(ssHeader);
ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("application", "xml");
notifyRequest.setContent(null, contentTypeHeader);
// 发送
ClientTransaction ct = sipProvider.getNewClientTransaction(notifyRequest);
ct.sendRequest();
log.info("sendSubscribeNotifyToSipServer: callId: {}", callIdHeader.getCallId());
} catch (Exception e) {
e.printStackTrace();
}
}
private void removeSubscription(Request request) {
CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);


+ 1
- 0
src/main/java/com/inspect/tcpserver/sip/service/SipVideoClient.java View File

@ -66,6 +66,7 @@ public class SipVideoClient implements SipListener {
sipStack = sipFactory.createSipStack(properties);
ListeningPoint lp = sipStack.createListeningPoint(localIp, localPort, transport);
//ListeningPoint lp = sipStack.createListeningPoint("0.0.0.0", localPort, transport);
sipProvider = sipStack.createSipProvider(lp);
sipProvider.addSipListener(this);
}


+ 24
- 2
src/main/java/com/inspect/tcpserver/sip/utils/SipXmlParser.java View File

@ -1,6 +1,10 @@
package com.inspect.tcpserver.sip.utils;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ -17,12 +21,30 @@ public class SipXmlParser {
XML_MAPPER.getTypeFactory().constructParametricType(SipXmlEnvelope.class, itemClass));
}
@Setter
@Getter
@JacksonXmlRootElement(localName = "SIP_XML")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class EventTypeOnly {
@com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty(isAttribute = true, localName = "EventType")
private String eventType;
public String getEventType() { return eventType; }
public void setEventType(String eventType) { this.eventType = eventType; }
}
public static void main(String[] args) {
try {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<SIP_XML EventType=\"Device_Info\">\n" +
" <Item CmdType=\"DeviceInfo\" sn=\"255152\" DeviceID=\"290010021201070000\" />\n" +
"</SIP_XML>";
//String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SIP_XML EventType=\"Device_Info\"></SIP_XML>";//ok
//String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SIP_XML EventType=\"Device_Info\"><Item /></SIP_XML>";
String eventType = SipXmlParser.peekEventType(xml);
System.out.println(eventType);
} catch (Exception e) {
e.printStackTrace();
}
}
}


+ 1
- 1
src/main/resources/application-dev.yml View File

@ -52,7 +52,7 @@ sip:
domain: 192.168.1.116 # 平台SIP服务IP或域名
port: 5060
transport: tcp # 也可以为udp
local-ip: 192.168.1.11 # 本服务的外网或可达IP
local-ip: 192.168.1.8 # 本服务的外网或可达IP
local-port: 5061
expires: 3600

+ 2
- 2
src/main/resources/application.yml View File

@ -47,13 +47,13 @@ minio:
bucket-name: mybucket
sip:
enable: false
enable: true
username: 290010021201070000
password: 123456
domain: 192.168.1.116 # 平台SIP服务IP或域名
port: 5060
transport: tcp # 也可以为udp
local-ip: 192.168.1.11 # 本服务的外网或可达IP
local-ip: 192.168.1.8 # 本服务的外网或可达IP
local-port: 5061
expires: 3600


Loading…
Cancel
Save