|
|
|
@ -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.*; |
|
|
|
@ -777,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); |
|
|
|
} |
|
|
|
@ -804,20 +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); |
|
|
|
@ -833,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); |
|
|
|
@ -977,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); |
|
|
|
@ -995,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); |
|
|
|
|