package com.ard.gb28181.service.impl; import com.ard.common.core.constant.SecurityConstants; import com.ard.common.core.domain.RtpServerParam; import com.ard.gb28181.api.bean.ErrorCallback; import com.ard.gb28181.api.bean.Preset; import com.ard.gb28181.api.common.InviteSessionType; import com.ard.gb28181.api.domain.Device; import com.ard.gb28181.api.domain.SsrcTransaction; import com.ard.gb28181.api.utils.SipUtils; import com.ard.gb28181.common.ErrorCode; import com.ard.gb28181.config.SipConfig; import com.ard.gb28181.config.UserSetting; import com.ard.gb28181.runner.SipLayer; import com.ard.gb28181.service.ISIPCommander; import com.ard.gb28181.session.SipInviteSessionManager; import com.ard.gb28181.transmit.SIPSender; import com.ard.gb28181.transmit.cmd.SIPRequestHeaderProvider; import com.ard.gb28181.transmit.event.MessageSubscribe; import com.ard.gb28181.transmit.event.SipSubscribe; import com.ard.gb28181.transmit.event.sip.MessageEvent; import com.ard.zlm.api.RemoteZlmService; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; import javax.sip.PeerUnavailableException; import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.message.Request; import java.text.ParseException; import java.util.List; /** * @description:设备能力接口,用于定义设备的控制、查询能力 * @author: swwheihei * @date: 2020年5月3日 下午9:22:48 */ @Component @DependsOn("sipLayer") @Slf4j public class SIPCommander implements ISIPCommander { @Autowired private SipConfig sipConfig; @Autowired private SipLayer sipLayer; @Autowired private SIPSender sipSender; @Autowired private SIPRequestHeaderProvider headerProvider; @Autowired private MessageSubscribe messageSubscribe; @Autowired private UserSetting userSetting; @Autowired private RemoteZlmService remoteZlmService; @Autowired private SipInviteSessionManager sessionManager; /** * 查询设备信息 * * @param device 视频设备 * @param callback * @return */ @Override public void deviceInfoQuery(Device device, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "DeviceInfo"; String sn = (int) ((Math.random() * 9 + 1) * 100000) + ""; StringBuffer catalogXml = new StringBuffer(200); String charset = device.getCharset(); catalogXml.append("\r\n"); catalogXml.append("\r\n"); catalogXml.append("" + cmdType + "\r\n"); catalogXml.append("" + sn + "\r\n"); catalogXml.append("" + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn, device.getDeviceId(), 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); if (callback != null) { callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); } }); } /** * 查询设备配置 * * @param device 视频设备 * @param channelId 通道编码(可选) * @param configType 配置类型: */ @Override public void deviceConfigQuery(Device device, String channelId, String configType, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "ConfigDownload"; int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); if (ObjectUtils.isEmpty(channelId)) { cmdXml.append("" + device.getDeviceId() + "\r\n"); } else { cmdXml.append("" + channelId + "\r\n"); } cmdXml.append("" + configType + "\r\n"); cmdXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); if (callback != null) { callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); } }); } @Override public void catalogQuery(Device device, int sn, ErrorCallback callback) throws SipException, InvalidArgumentException, ParseException { String cmdType = "Catalog"; StringBuffer catalogXml = new StringBuffer(200); String charset = device.getCharset(); catalogXml.append("\r\n"); catalogXml.append("\r\n"); catalogXml.append(" " + cmdType + "\r\n"); catalogXml.append(" " + sn + "\r\n"); catalogXml.append(" " + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { if (callback != null) { callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); } }); } /** * 查询设备状态 * * @param device * @param callback */ @Override public void deviceStatusQuery(Device device, ErrorCallback callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "DeviceStatus"; int sn = (int) ((Math.random() * 9 + 1) * 100000); String charset = device.getCharset(); StringBuffer catalogXml = new StringBuffer(200); catalogXml.append("\r\n"); catalogXml.append("\r\n"); catalogXml.append("" + cmdType + "\r\n"); catalogXml.append("" + sn + "\r\n"); catalogXml.append("" + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); MessageEvent messageEvent = MessageEvent.getInstance(cmdType, sn + "", device.getDeviceId(), 1000L, callback); messageSubscribe.addSubscribe(messageEvent); Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 请求预览视频流 * * @param device * @param rtpServer * @param okEvent * @param errorEvent * @param timeout */ @Override public void playStreamCmd(Device device, RtpServerParam rtpServer, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent, Long timeout) throws SipException, InvalidArgumentException, ParseException { String sdpIp = rtpServer.getIp(); StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); content.append("o=" + rtpServer.getGbDeviceId() + " 0 0 IN IP4 " + sdpIp + "\r\n"); content.append("s=Play\r\n"); content.append("c=IN IP4 " + sdpIp + "\r\n"); content.append("t=0 0\r\n"); if (userSetting.getSeniorSdp()) { if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + rtpServer.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + rtpServer.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + rtpServer.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n"); } content.append("a=recvonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=fmtp:126 profile-level-id=42e01e\r\n"); content.append("a=rtpmap:126 H264/90000\r\n"); content.append("a=rtpmap:125 H264S/90000\r\n"); content.append("a=fmtp:125 profile-level-id=42e01e\r\n"); content.append("a=rtpmap:99 H265/90000\r\n"); content.append("a=rtpmap:98 H264/90000\r\n"); content.append("a=rtpmap:97 MPEG4/90000\r\n"); if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } else { if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + rtpServer.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + rtpServer.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n"); } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) { content.append("m=video " + rtpServer.getPort() + " RTP/AVP 96 97 98 99\r\n"); } content.append("a=recvonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=rtpmap:98 H264/90000\r\n"); content.append("a=rtpmap:97 MPEG4/90000\r\n"); content.append("a=rtpmap:99 H265/90000\r\n"); if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } // if (!ObjectUtils.isEmpty(channel.getStreamIdentification())) { // content.append("a=" + channel.getStreamIdentification() + "\r\n"); // } content.append("y=" + rtpServer.getSsrc() + "\r\n");//ssrc // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 // content.append("f= v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备 Request request = headerProvider.createInviteRequest(device, rtpServer.getGbChannelId(), content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, rtpServer.getSsrc(), sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> { sessionManager.removeByStream(rtpServer.getApp(), rtpServer.getStream()); remoteZlmService.releaseSsrc(rtpServer.getMediaServerId(), rtpServer.getSsrc(), SecurityConstants.INNER); remoteZlmService.closeRTPServer(rtpServer.getMediaServerId(), rtpServer, SecurityConstants.INNER); errorEvent.response(e); }), e -> { ResponseEvent responseEvent = (ResponseEvent) e.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); String callId = response.getCallIdHeader().getCallId(); SsrcTransaction ssrcTransaction = SsrcTransaction.buildForDevice(device.getDeviceId(), rtpServer.getGbChannelId(), callId, rtpServer.getApp(), rtpServer.getStream(), rtpServer.getSsrc(), rtpServer.getMediaServerId(), response, InviteSessionType.PLAY); ssrcTransaction.setApp(rtpServer.getApp()); ssrcTransaction.setStream(rtpServer.getStream()); sessionManager.put(ssrcTransaction); okEvent.response(e); }, timeout); } @Override public void stopStreamCmd(Device device, RtpServerParam rtpServer) throws SipException, InvalidArgumentException, ParseException { SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransactionByStream(rtpServer.getApp(), rtpServer.getStream()); if (ssrcTransaction != null) { log.info("[停止播放] 发送 BYE 请求 deviceId: {}, channelId: {}", rtpServer.getGbDeviceId(), rtpServer.getGbChannelId()); Request byeRequest = headerProvider.createByteRequestForDeviceInvite( device, ssrcTransaction.getChannelId(), ssrcTransaction.getSipTransactionInfo() ); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byeRequest); sessionManager.removeByCallId(ssrcTransaction.getCallId()); remoteZlmService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc(), SecurityConstants.INNER); remoteZlmService.closeRTPServer(ssrcTransaction.getMediaServerId(), rtpServer, SecurityConstants.INNER); } else { log.warn("[停止播放] 未找到会话信息 app: {}, stream: {}", rtpServer.getApp(), rtpServer.getStream()); } } /** * 通用前端控制命令(参考国标文档A.3.1指令格式) * * @param device 设备 * @param channelId 通道国标编号 * @param cmdCode 指令码(对应国标文档指令格式中的字节4) * @param parameter1 数据一(对应国标文档指令格式中的字节5, 范围0-255) * @param parameter2 数据二(对应国标文档指令格式中的字节6, 范围0-255) * @param combindCode2 组合码二(对应国标文档指令格式中的字节7, 范围0-15) */ @Override public void frontEndCmd(Device device, String channelId, Integer cmdCode, Integer parameter1, Integer parameter2, Integer combindCode2) throws InvalidArgumentException, SipException, ParseException { String cmdStr = frontEndCmdString(cmdCode, parameter1, parameter2, combindCode2); StringBuffer ptzXml = new StringBuffer(200); String charset = device.getCharset(); ptzXml.append("\r\n"); ptzXml.append("\r\n"); ptzXml.append("DeviceControl\r\n"); ptzXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); ptzXml.append("" + channelId + "\r\n"); ptzXml.append("" + cmdStr + "\r\n"); ptzXml.append("\r\n"); ptzXml.append("5\r\n"); ptzXml.append("\r\n"); ptzXml.append("\r\n"); SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); } /** * 查询预置位 * * @param device 设备国标编号 * @param channelId 通道国标编号 * @param callback */ @Override public void presetQuery(Device device, String channelId, ErrorCallback> callback) throws InvalidArgumentException, SipException, ParseException { String cmdType = "PresetQuery"; int sn = (int) ((Math.random() * 9 + 1) * 100000); StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); cmdXml.append("\r\n"); cmdXml.append("\r\n"); cmdXml.append("" + cmdType + "\r\n"); cmdXml.append("" + sn + "\r\n"); if (ObjectUtils.isEmpty(channelId)) { cmdXml.append("" + device.getDeviceId() + "\r\n"); } else { cmdXml.append("" + channelId + "\r\n"); } cmdXml.append("\r\n"); MessageEvent> messageEvent = MessageEvent.getInstance(cmdType, sn + "", channelId, 4000L, callback); messageSubscribe.addSubscribe(messageEvent); log.info("[预置位查询] 设备编号: {}, 通道编号: {}, SN: {}", device.getDeviceId(), channelId, sn); Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, eventResult -> { messageSubscribe.removeSubscribe(messageEvent.getKey()); callback.run(ErrorCode.ERROR100.getCode(), "失败," + eventResult.msg, null); }); } /** * 云台指令码计算 * * @param cmdCode 指令码 * @param parameter1 数据1 * @param parameter2 数据2 * @param combineCode2 组合码2 */ public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) { StringBuilder builder = new StringBuilder("A50F01"); String strTmp; strTmp = String.format("%02X", cmdCode); builder.append(strTmp, 0, 2); strTmp = String.format("%02X", parameter1); builder.append(strTmp, 0, 2); strTmp = String.format("%02X", parameter2); builder.append(strTmp, 0, 2); strTmp = String.format("%02X", combineCode2 << 4); builder.append(strTmp, 0, 2); //计算校验码 int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 << 4)) % 0X100; strTmp = String.format("%02X", checkCode); builder.append(strTmp, 0, 2); return builder.toString(); } }