diff --git a/src/main/java/com/inspect/tcpserver/sip/service/SipClientService.java b/src/main/java/com/inspect/tcpserver/sip/service/SipClientService.java index 7bd0139..d247102 100644 --- a/src/main/java/com/inspect/tcpserver/sip/service/SipClientService.java +++ b/src/main/java/com/inspect/tcpserver/sip/service/SipClientService.java @@ -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 = "\n" + - "\n" + - " " + bodyText + "\n" + - ""; - ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("application", "xml"); - notifyRequest.setContent(xmlBody, contentTypeHeader); +// String xmlBody = "\n" + +// "\n" + +// " " + bodyText + "\n" + +// ""; +// 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 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);