实现语音广播信令(web语音推流开发中)
parent
c5a05c15df
commit
f9d30bdfad
|
@ -16,7 +16,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
// import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,8 @@ public class DeferredResultHolder {
|
||||||
|
|
||||||
public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM";
|
public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM";
|
||||||
|
|
||||||
|
public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
|
||||||
|
|
||||||
private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>();
|
private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>();
|
||||||
|
|
||||||
public void put(String key, DeferredResult result) {
|
public void put(String key, DeferredResult result) {
|
||||||
|
|
|
@ -119,6 +119,14 @@ public interface ISIPCommander {
|
||||||
*/
|
*/
|
||||||
boolean audioBroadcastCmd(Device device,String channelId);
|
boolean audioBroadcastCmd(Device device,String channelId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音广播
|
||||||
|
*
|
||||||
|
* @param device 视频设备
|
||||||
|
*/
|
||||||
|
void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent);
|
||||||
|
boolean audioBroadcastCmd(Device device);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 音视频录像控制
|
* 音视频录像控制
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,7 +3,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
|
||||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
// import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@ import java.util.ArrayList;
|
||||||
import javax.sip.InvalidArgumentException;
|
import javax.sip.InvalidArgumentException;
|
||||||
import javax.sip.PeerUnavailableException;
|
import javax.sip.PeerUnavailableException;
|
||||||
import javax.sip.SipFactory;
|
import javax.sip.SipFactory;
|
||||||
import javax.sip.SipProvider;
|
// import javax.sip.SipProvider;
|
||||||
import javax.sip.address.Address;
|
import javax.sip.address.Address;
|
||||||
import javax.sip.address.SipURI;
|
import javax.sip.address.SipURI;
|
||||||
import javax.sip.header.*;
|
import javax.sip.header.*;
|
||||||
import javax.sip.message.Request;
|
import javax.sip.message.Request;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
// import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||||
|
|
|
@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
// import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -47,8 +47,7 @@ import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
|
||||||
public class SIPCommander implements ISIPCommander {
|
public class SIPCommander implements ISIPCommander {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
|
private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SipConfig sipConfig;
|
private SipConfig sipConfig;
|
||||||
|
|
||||||
|
@ -623,10 +622,66 @@ public class SIPCommander implements ISIPCommander {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean audioBroadcastCmd(Device device, String channelId) {
|
public boolean audioBroadcastCmd(Device device, String channelId) {
|
||||||
// TODO Auto-generated method stub
|
// 改为新的实现
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音广播
|
||||||
|
*
|
||||||
|
* @param device 视频设备
|
||||||
|
* @param channelId 预览通道
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean audioBroadcastCmd(Device device) {
|
||||||
|
try {
|
||||||
|
StringBuffer broadcastXml = new StringBuffer(200);
|
||||||
|
broadcastXml.append("<?xml version=\"1.0\" ?>\r\n");
|
||||||
|
broadcastXml.append("<Notify>\r\n");
|
||||||
|
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||||
|
broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
|
||||||
|
broadcastXml.append("<SourceID>" + sipConfig.getSipId() + "</SourceID>\r\n");
|
||||||
|
broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
|
||||||
|
broadcastXml.append("</Notify>\r\n");
|
||||||
|
|
||||||
|
String tm = Long.toString(System.currentTimeMillis());
|
||||||
|
|
||||||
|
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
|
||||||
|
: udpSipProvider.getNewCallId();
|
||||||
|
|
||||||
|
Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "z9hG4bK-ViaBcst-" + tm, "FromBcst" + tm, null, callIdHeader);
|
||||||
|
transmitRequest(device, request);
|
||||||
|
return true;
|
||||||
|
} catch (SipException | ParseException | InvalidArgumentException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) {
|
||||||
|
try {
|
||||||
|
StringBuffer broadcastXml = new StringBuffer(200);
|
||||||
|
broadcastXml.append("<?xml version=\"1.0\" ?>\r\n");
|
||||||
|
broadcastXml.append("<Notify>\r\n");
|
||||||
|
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||||
|
broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
|
||||||
|
broadcastXml.append("<SourceID>" + sipConfig.getSipId() + "</SourceID>\r\n");
|
||||||
|
broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
|
||||||
|
broadcastXml.append("</Notify>\r\n");
|
||||||
|
|
||||||
|
String tm = Long.toString(System.currentTimeMillis());
|
||||||
|
|
||||||
|
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
|
||||||
|
: udpSipProvider.getNewCallId();
|
||||||
|
|
||||||
|
Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), "z9hG4bK-ViaBcst-" + tm, "FromBcst" + tm, null, callIdHeader);
|
||||||
|
transmitRequest(device, request, errorEvent);
|
||||||
|
} catch (SipException | ParseException | InvalidArgumentException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 音视频录像控制
|
* 音视频录像控制
|
||||||
*
|
*
|
||||||
|
|
|
@ -15,7 +15,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
// import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
|
@ -14,6 +14,7 @@ import javax.sip.message.Response;
|
||||||
import com.genersoft.iot.vmp.conf.MediaServerConfig;
|
import com.genersoft.iot.vmp.conf.MediaServerConfig;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||||
|
@ -74,144 +75,216 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
|
||||||
Request request = evt.getRequest();
|
Request request = evt.getRequest();
|
||||||
SipURI sipURI = (SipURI) request.getRequestURI();
|
SipURI sipURI = (SipURI) request.getRequestURI();
|
||||||
String channelId = sipURI.getUser();
|
String channelId = sipURI.getUser();
|
||||||
String platformId = null;
|
String requesterId = null;
|
||||||
|
|
||||||
FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
|
FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
|
||||||
AddressImpl address = (AddressImpl) fromHeader.getAddress();
|
AddressImpl address = (AddressImpl) fromHeader.getAddress();
|
||||||
SipUri uri = (SipUri) address.getURI();
|
SipUri uri = (SipUri) address.getURI();
|
||||||
platformId = uri.getUser();
|
requesterId = uri.getUser();
|
||||||
|
|
||||||
if (platformId == null || channelId == null) {
|
if (requesterId == null || channelId == null) {
|
||||||
logger.info("无法从FromHeader的Address中获取到平台id,返回404");
|
logger.info("无法从FromHeader的Address中获取到平台id,返回400");
|
||||||
responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误
|
responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 查询平台下是否有该通道
|
|
||||||
DeviceChannel channel = storager.queryChannelInParentPlatform(platformId, channelId);
|
|
||||||
if (channel == null) {
|
|
||||||
logger.info("通道不存在,返回404");
|
|
||||||
responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在
|
|
||||||
return;
|
|
||||||
}else {
|
|
||||||
responseAck(evt, Response.TRYING); // 通道存在,发100,trying
|
|
||||||
}
|
|
||||||
// 解析sdp消息, 使用jainsip 自带的sdp解析方式
|
|
||||||
String contentString = new String(request.getRawContent());
|
|
||||||
|
|
||||||
// jainSip不支持y=字段, 移除移除以解析。
|
// 查询请求方是否上级平台
|
||||||
int ssrcIndex = contentString.indexOf("y=");
|
ParentPlatform platform = storager.queryParentPlatById(requesterId);
|
||||||
String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
if (platform != null) {
|
||||||
//ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段
|
// 查询平台下是否有该通道
|
||||||
// String ssrc = contentString.substring(ssrcIndex + 2, contentString.length())
|
DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
|
||||||
// .replace("\r\n", "").replace("\n", "");
|
if (channel == null) {
|
||||||
|
logger.info("通道不存在,返回404");
|
||||||
|
responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在
|
||||||
|
return;
|
||||||
|
}else {
|
||||||
|
responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
|
||||||
|
}
|
||||||
|
// 解析sdp消息, 使用jainsip 自带的sdp解析方式
|
||||||
|
String contentString = new String(request.getRawContent());
|
||||||
|
|
||||||
String substring = contentString.substring(0, contentString.indexOf("y="));
|
// jainSip不支持y=字段, 移除移除以解析。
|
||||||
SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
int ssrcIndex = contentString.indexOf("y=");
|
||||||
|
//ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段
|
||||||
|
String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||||
|
String substring = contentString.substring(0, contentString.indexOf("y="));
|
||||||
|
SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
||||||
|
|
||||||
// 获取支持的格式
|
// 获取支持的格式
|
||||||
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
||||||
// 查看是否支持PS 负载96
|
// 查看是否支持PS 负载96
|
||||||
//String ip = null;
|
//String ip = null;
|
||||||
int port = -1;
|
int port = -1;
|
||||||
//boolean recvonly = false;
|
//boolean recvonly = false;
|
||||||
boolean mediaTransmissionTCP = false;
|
boolean mediaTransmissionTCP = false;
|
||||||
Boolean tcpActive = null;
|
Boolean tcpActive = null;
|
||||||
for (int i = 0; i < mediaDescriptions.size(); i++) {
|
for (int i = 0; i < mediaDescriptions.size(); i++) {
|
||||||
MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
|
MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
|
||||||
Media media = mediaDescription.getMedia();
|
Media media = mediaDescription.getMedia();
|
||||||
|
|
||||||
Vector mediaFormats = media.getMediaFormats(false);
|
Vector mediaFormats = media.getMediaFormats(false);
|
||||||
if (mediaFormats.contains("96")) {
|
if (mediaFormats.contains("96")) {
|
||||||
port = media.getMediaPort();
|
port = media.getMediaPort();
|
||||||
//String mediaType = media.getMediaType();
|
//String mediaType = media.getMediaType();
|
||||||
String protocol = media.getProtocol();
|
String protocol = media.getProtocol();
|
||||||
|
|
||||||
// 区分TCP发流还是udp, 当前默认udp
|
// 区分TCP发流还是udp, 当前默认udp
|
||||||
if ("TCP/RTP/AVP".equals(protocol)) {
|
if ("TCP/RTP/AVP".equals(protocol)) {
|
||||||
String setup = mediaDescription.getAttribute("setup");
|
String setup = mediaDescription.getAttribute("setup");
|
||||||
if (setup != null) {
|
if (setup != null) {
|
||||||
mediaTransmissionTCP = true;
|
mediaTransmissionTCP = true;
|
||||||
if ("active".equals(setup)) {
|
if ("active".equals(setup)) {
|
||||||
tcpActive = true;
|
tcpActive = true;
|
||||||
}else if ("passive".equals(setup)) {
|
}else if ("passive".equals(setup)) {
|
||||||
tcpActive = false;
|
tcpActive = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
if (port == -1) {
|
||||||
if (port == -1) {
|
logger.info("不支持的媒体格式,返回415");
|
||||||
logger.info("不支持的媒体格式,返回415");
|
// 回复不支持的格式
|
||||||
// 回复不支持的格式
|
responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
|
||||||
responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
|
return;
|
||||||
return;
|
}
|
||||||
}
|
String username = sdp.getOrigin().getUsername();
|
||||||
String username = sdp.getOrigin().getUsername();
|
String addressStr = sdp.getOrigin().getAddress();
|
||||||
String addressStr = sdp.getOrigin().getAddress();
|
//String sessionName = sdp.getSessionName().getValue();
|
||||||
//String sessionName = sdp.getSessionName().getValue();
|
logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc);
|
||||||
logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc);
|
|
||||||
|
|
||||||
Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, channelId);
|
Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
logger.warn("点播平台{}的通道{}时未找到设备信息", platformId, channel);
|
logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel);
|
||||||
responseAck(evt, Response.SERVER_INTERNAL_ERROR);
|
responseAck(evt, Response.SERVER_INTERNAL_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, platformId, device.getDeviceId(), channelId,
|
SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, requesterId, device.getDeviceId(), channelId,
|
||||||
mediaTransmissionTCP);
|
mediaTransmissionTCP);
|
||||||
if (tcpActive != null) {
|
if (tcpActive != null) {
|
||||||
sendRtpItem.setTcpActive(tcpActive);
|
sendRtpItem.setTcpActive(tcpActive);
|
||||||
}
|
}
|
||||||
if (sendRtpItem == null) {
|
if (sendRtpItem == null) {
|
||||||
logger.warn("服务器端口资源不足");
|
logger.warn("服务器端口资源不足");
|
||||||
responseAck(evt, Response.BUSY_HERE);
|
responseAck(evt, Response.BUSY_HERE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入redis, 超时时回复
|
// 写入redis, 超时时回复
|
||||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
|
||||||
// 通知下级推流,
|
|
||||||
PlayResult playResult = playService.play(device.getDeviceId(), channelId, (responseJSON)->{
|
|
||||||
// 收到推流, 回复200OK, 等待ack
|
|
||||||
sendRtpItem.setStatus(1);
|
|
||||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||||
// TODO 添加对tcp的支持
|
// 通知下级推流,
|
||||||
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
|
PlayResult playResult = playService.play(device.getDeviceId(), channelId, (responseJSON)->{
|
||||||
StringBuffer content = new StringBuffer(200);
|
// 收到推流, 回复200OK, 等待ack
|
||||||
content.append("v=0\r\n");
|
sendRtpItem.setStatus(1);
|
||||||
content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||||
content.append("s=Play\r\n");
|
// TODO 添加对tcp的支持
|
||||||
content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
|
||||||
content.append("t=0 0\r\n");
|
StringBuffer content = new StringBuffer(200);
|
||||||
content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
|
content.append("v=0\r\n");
|
||||||
content.append("a=sendonly\r\n");
|
content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
||||||
content.append("a=rtpmap:96 PS/90000\r\n");
|
content.append("s=Play\r\n");
|
||||||
content.append("y="+ ssrc + "\r\n");
|
content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
||||||
content.append("f=\r\n");
|
content.append("t=0 0\r\n");
|
||||||
|
content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
|
||||||
|
content.append("a=sendonly\r\n");
|
||||||
|
content.append("a=rtpmap:96 PS/90000\r\n");
|
||||||
|
content.append("y="+ ssrc + "\r\n");
|
||||||
|
content.append("f=\r\n");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
responseAck(evt, content.toString());
|
responseAck(evt, content.toString());
|
||||||
} catch (SipException e) {
|
} catch (SipException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (InvalidArgumentException e) {
|
} catch (InvalidArgumentException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
},(event -> {
|
||||||
|
// 未知错误。直接转发设备点播的错误
|
||||||
|
Response response = null;
|
||||||
|
try {
|
||||||
|
response = getMessageFactory().createResponse(event.getResponse().getStatusCode(), evt.getRequest());
|
||||||
|
getServerTransaction(evt).sendResponse(response);
|
||||||
|
} catch (ParseException | SipException | InvalidArgumentException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug(playResult.getResult().toString());
|
||||||
}
|
}
|
||||||
},(event -> {
|
} else {
|
||||||
// 未知错误。直接转发设备点播的错误
|
// 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
|
||||||
Response response = null;
|
Device device = storager.queryVideoDevice(requesterId);
|
||||||
try {
|
if (device != null) {
|
||||||
response = getMessageFactory().createResponse(event.getResponse().getStatusCode(), evt.getRequest());
|
logger.info("收到设备" + requesterId + "的语音广播Invite请求");
|
||||||
getServerTransaction(evt).sendResponse(response);
|
responseAck(evt, Response.TRYING);
|
||||||
|
|
||||||
} catch (ParseException | SipException | InvalidArgumentException e) {
|
String contentString = new String(request.getRawContent());
|
||||||
e.printStackTrace();
|
// jainSip不支持y=字段, 移除移除以解析。
|
||||||
|
String substring = contentString;
|
||||||
|
String ssrc = "0000000404";
|
||||||
|
int ssrcIndex = contentString.indexOf("y=");
|
||||||
|
if (ssrcIndex > 0) {
|
||||||
|
substring = contentString.substring(0, ssrcIndex);
|
||||||
|
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||||
|
}
|
||||||
|
ssrcIndex = substring.indexOf("f=");
|
||||||
|
if (ssrcIndex > 0) {
|
||||||
|
substring = contentString.substring(0, ssrcIndex);
|
||||||
|
}
|
||||||
|
SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
||||||
|
|
||||||
|
// 获取支持的格式
|
||||||
|
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
||||||
|
// 查看是否支持PS 负载96
|
||||||
|
int port = -1;
|
||||||
|
//boolean recvonly = false;
|
||||||
|
boolean mediaTransmissionTCP = false;
|
||||||
|
Boolean tcpActive = null;
|
||||||
|
for (int i = 0; i < mediaDescriptions.size(); i++) {
|
||||||
|
MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
|
||||||
|
Media media = mediaDescription.getMedia();
|
||||||
|
|
||||||
|
Vector mediaFormats = media.getMediaFormats(false);
|
||||||
|
if (mediaFormats.contains("8")) {
|
||||||
|
port = media.getMediaPort();
|
||||||
|
String protocol = media.getProtocol();
|
||||||
|
// 区分TCP发流还是udp, 当前默认udp
|
||||||
|
if ("TCP/RTP/AVP".equals(protocol)) {
|
||||||
|
String setup = mediaDescription.getAttribute("setup");
|
||||||
|
if (setup != null) {
|
||||||
|
mediaTransmissionTCP = true;
|
||||||
|
if ("active".equals(setup)) {
|
||||||
|
tcpActive = true;
|
||||||
|
} else if ("passive".equals(setup)) {
|
||||||
|
tcpActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (port == -1) {
|
||||||
|
logger.info("不支持的媒体格式,返回415");
|
||||||
|
// 回复不支持的格式
|
||||||
|
responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String username = sdp.getOrigin().getUsername();
|
||||||
|
String addressStr = sdp.getOrigin().getAddress();
|
||||||
|
logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
logger.warn("来自无效设备/平台的请求");
|
||||||
|
responseAck(evt, Response.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(playResult.getResult().toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||||
private static final String MESSAGE_ALARM = "Alarm";
|
private static final String MESSAGE_ALARM = "Alarm";
|
||||||
private static final String MESSAGE_RECORD_INFO = "RecordInfo";
|
private static final String MESSAGE_RECORD_INFO = "RecordInfo";
|
||||||
private static final String MESSAGE_MEDIA_STATUS = "MediaStatus";
|
private static final String MESSAGE_MEDIA_STATUS = "MediaStatus";
|
||||||
// private static final String MESSAGE_BROADCAST = "Broadcast";
|
private static final String MESSAGE_BROADCAST = "Broadcast";
|
||||||
private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus";
|
private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus";
|
||||||
private static final String MESSAGE_DEVICE_CONTROL = "DeviceControl";
|
private static final String MESSAGE_DEVICE_CONTROL = "DeviceControl";
|
||||||
private static final String MESSAGE_DEVICE_CONFIG = "DeviceConfig";
|
private static final String MESSAGE_DEVICE_CONFIG = "DeviceConfig";
|
||||||
|
@ -123,7 +123,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||||
logger.info("接收到Catalog消息");
|
logger.info("接收到Catalog消息");
|
||||||
processMessageCatalogList(evt);
|
processMessageCatalogList(evt);
|
||||||
} else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
|
} else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
|
||||||
//DeviceInfo消息处理
|
// DeviceInfo消息处理
|
||||||
processMessageDeviceInfo(evt);
|
processMessageDeviceInfo(evt);
|
||||||
} else if (MESSAGE_DEVICE_STATUS.equals(cmd)) {
|
} else if (MESSAGE_DEVICE_STATUS.equals(cmd)) {
|
||||||
// DeviceStatus消息处理
|
// DeviceStatus消息处理
|
||||||
|
@ -149,6 +149,9 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||||
} else if (MESSAGE_PRESET_QUERY.equals(cmd)) {
|
} else if (MESSAGE_PRESET_QUERY.equals(cmd)) {
|
||||||
logger.info("接收到PresetQuery消息");
|
logger.info("接收到PresetQuery消息");
|
||||||
processMessagePresetQuery(evt);
|
processMessagePresetQuery(evt);
|
||||||
|
} else if (MESSAGE_BROADCAST.equals(cmd)) {
|
||||||
|
// Broadcast消息处理
|
||||||
|
processMessageBroadcast(evt);
|
||||||
} else {
|
} else {
|
||||||
logger.info("接收到消息:" + cmd);
|
logger.info("接收到消息:" + cmd);
|
||||||
responseAck(evt);
|
responseAck(evt);
|
||||||
|
@ -298,7 +301,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||||
// 远程启动功能
|
// 远程启动功能
|
||||||
if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
|
if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
|
||||||
if (deviceId.equals(targetGBId)) {
|
if (deviceId.equals(targetGBId)) {
|
||||||
// 远程启动功能:需要在重新启动程序后先对SipStack解绑
|
// 远程启动本平台:需要在重新启动程序后先对SipStack解绑
|
||||||
logger.info("执行远程启动本平台命令");
|
logger.info("执行远程启动本平台命令");
|
||||||
ParentPlatform parentPlatform = storager.queryParentPlatById(platformId);
|
ParentPlatform parentPlatform = storager.queryParentPlatById(platformId);
|
||||||
cmderFroPlatform.unregister(parentPlatform, null, null);
|
cmderFroPlatform.unregister(parentPlatform, null, null);
|
||||||
|
@ -333,6 +336,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||||
// 远程启动指定设备
|
// 远程启动指定设备
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 云台/前端控制命令
|
||||||
if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
|
if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
|
||||||
String cmdString = XmlUtil.getText(rootElement,"PTZCmd");
|
String cmdString = XmlUtil.getText(rootElement,"PTZCmd");
|
||||||
Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId);
|
Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId);
|
||||||
|
@ -895,6 +899,37 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理AudioBroadcast语音广播Message
|
||||||
|
*
|
||||||
|
* @param evt
|
||||||
|
*/
|
||||||
|
private void processMessageBroadcast(RequestEvent evt) {
|
||||||
|
try {
|
||||||
|
Element rootElement = getRootElement(evt);
|
||||||
|
String deviceId = XmlUtil.getText(rootElement, "DeviceID");
|
||||||
|
// 回复200 OK
|
||||||
|
responseAck(evt);
|
||||||
|
if (rootElement.getName().equals("Response")) {
|
||||||
|
// 此处是对本平台发出Broadcast指令的应答
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
XmlUtil.node2Json(rootElement, json);
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug(json.toJSONString());
|
||||||
|
}
|
||||||
|
RequestMessage msg = new RequestMessage();
|
||||||
|
msg.setDeviceId(deviceId);
|
||||||
|
msg.setType(DeferredResultHolder.CALLBACK_CMD_BROADCAST);
|
||||||
|
msg.setData(json);
|
||||||
|
deferredResultHolder.invokeResult(msg);
|
||||||
|
} else {
|
||||||
|
// 此处是上级发出的Broadcast指令
|
||||||
|
}
|
||||||
|
} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* 回复200 OK
|
* 回复200 OK
|
||||||
|
|
|
@ -50,7 +50,6 @@ public class RegisterResponseProcessor implements ISIPResponseProcessor {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
|
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) {
|
||||||
// TODO Auto-generated method stub
|
|
||||||
Response response = evt.getResponse();
|
Response response = evt.getResponse();
|
||||||
CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
|
CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
|
||||||
String callId = callIdHeader.getCallId();
|
String callId = callIdHeader.getCallId();
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.vmanager.play;
|
||||||
|
|
||||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||||
import com.genersoft.iot.vmp.conf.MediaServerConfig;
|
import com.genersoft.iot.vmp.conf.MediaServerConfig;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
|
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
||||||
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
||||||
|
@ -27,6 +28,8 @@ import org.springframework.web.context.request.async.DeferredResult;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.sip.message.Response;
|
||||||
|
|
||||||
@CrossOrigin
|
@CrossOrigin
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api")
|
@RequestMapping("/api")
|
||||||
|
@ -204,5 +207,47 @@ public class PlayController {
|
||||||
}
|
}
|
||||||
return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
|
return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音广播命令API接口
|
||||||
|
*
|
||||||
|
* @param deviceId
|
||||||
|
*/
|
||||||
|
@GetMapping("/broadcast/{deviceId}")
|
||||||
|
@PostMapping("/broadcast/{deviceId}")
|
||||||
|
public DeferredResult<ResponseEntity<String>> broadcastApi(@PathVariable String deviceId) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("语音广播API调用");
|
||||||
|
}
|
||||||
|
Device device = storager.queryVideoDevice(deviceId);
|
||||||
|
cmder.audioBroadcastCmd(device, event -> {
|
||||||
|
Response response = event.getResponse();
|
||||||
|
RequestMessage msg = new RequestMessage();
|
||||||
|
msg.setId(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId);
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("DeviceID", deviceId);
|
||||||
|
json.put("CmdType", "Broadcast");
|
||||||
|
json.put("Result", "Failed");
|
||||||
|
json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
|
||||||
|
msg.setData(json);
|
||||||
|
resultHolder.invokeResult(msg);
|
||||||
|
});
|
||||||
|
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
|
||||||
|
result.onTimeout(() -> {
|
||||||
|
logger.warn(String.format("语音广播操作超时, 设备未返回应答指令"));
|
||||||
|
RequestMessage msg = new RequestMessage();
|
||||||
|
msg.setId(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId);
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.put("DeviceID", deviceId);
|
||||||
|
json.put("CmdType", "Broadcast");
|
||||||
|
json.put("Result", "Failed");
|
||||||
|
json.put("Error", "Timeout. Device did not response to broadcast command.");
|
||||||
|
msg.setData(json);
|
||||||
|
resultHolder.invokeResult(msg);
|
||||||
|
});
|
||||||
|
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue