gb28181 sdp 消息构造与解析

This commit is contained in:
shikong 2023-09-24 03:54:34 +08:00
parent 3ab46488b0
commit 2550500fb9
7 changed files with 319 additions and 7 deletions

View File

@ -19,6 +19,24 @@
</properties>
<dependencies>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -3,6 +3,7 @@ package cn.skcks.docking.gb28181.sdp;
import cn.skcks.docking.gb28181.sdp.field.ssrc.SsrcField;
import gov.nist.javax.sdp.SessionDescriptionImpl;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@ -21,6 +22,12 @@ public class GB28181Description extends SessionDescriptionImpl implements Sessio
super();
}
@SuppressWarnings("CopyConstructorMissesField")
public GB28181Description(GB28181Description gb28181Description) throws SdpException {
super(gb28181Description);
ssrcField = gb28181Description.getSsrcField();
}
public GB28181Description(SessionDescription sessionDescription) throws SdpException {
super(sessionDescription);
}

View File

@ -1,18 +1,35 @@
package cn.skcks.docking.gb28181.sdp;
import cn.skcks.docking.gb28181.sdp.field.ssrc.SsrcField;
import cn.skcks.docking.gb28181.sdp.media.MediaStreamMode;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import gov.nist.core.Separators;
import gov.nist.javax.sdp.MediaDescriptionImpl;
import gov.nist.javax.sdp.fields.AttributeField;
import gov.nist.javax.sdp.fields.ConnectionField;
import gov.nist.javax.sdp.fields.TimeField;
import gov.nist.javax.sdp.fields.URIField;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
import javax.sdp.*;
import java.util.*;
@SuppressWarnings("unused")
@Slf4j
public class GB28181SDPBuilder {
public final static String SEPARATOR = "_";
public static String getStreamId(String prefix,String... ids){
return StringUtils.joinWith(SEPARATOR, (Object[]) ArrayUtils.addFirst(ids,prefix));
public static String getStreamId(String prefix, String... ids) {
return StringUtils.joinWith(SEPARATOR, (Object[]) ArrayUtils.addFirst(ids, prefix));
}
public static final Map<String, String> RTPMAP = new HashMap<>() {{
public static Map<String, String> RTPMAP = new HashMap<>() {{
put("96", "PS/90000");
put("126", "H264/90000");
put("125", "H264S/90000");
@ -20,8 +37,218 @@ public class GB28181SDPBuilder {
put("98", "H264/90000");
put("97", "MPEG4/90000");
}};
public static final Map<String, String> FMTP = new HashMap<>() {{
public static Map<String, String> FMTP = new HashMap<>() {{
put("126", "profile-level-id=42e01e");
put("125", "profile-level-id=42e01e");
}};
@AllArgsConstructor
@Getter
public enum Protocol {
RTP_AVP(SdpConstants.RTP_AVP),
RTP_AVP_TCP(SdpConstants.RTP_AVP + "/" + "TCP"),
// 个别会使用这种格式
TCP_RTP_AVP("TCP" + "/" + SdpConstants.RTP_AVP);
@JsonValue
private final String protocol;
@JsonCreator
public static Protocol fromProtocol(String protocol) {
for (Protocol a : values()) {
if (a.getProtocol().equalsIgnoreCase(protocol)) {
return a;
}
}
return null;
}
@SneakyThrows
public static Protocol fromProtocol(MediaDescription mediaDescription) {
String protocol = mediaDescription.getMedia().getProtocol();
for (Protocol a : values()) {
if (a.getProtocol().equalsIgnoreCase(protocol)) {
return a;
}
}
return null;
}
}
@AllArgsConstructor
@Getter
public enum Action {
PLAY("Play"), PLAY_BACK("Playback"), DOWNLOAD("Download");
@JsonValue
private final String action;
@JsonCreator
public static Action fromCode(String action) {
for (Action a : values()) {
if (a.getAction().equalsIgnoreCase(action)) {
return a;
}
}
return null;
}
}
@SuppressWarnings("Duplicates")
@SneakyThrows
public static GB28181Description build(Action action, String deviceId, String channelId,
String netType, String rtpIp, int rtpPort, String ssrc,
MediaStreamMode streamMode, TimeDescription timeDescription,
boolean isRecv, Map<String,String> rtpMap, Map<String,String> fmtpMap) {
log.debug("{} {} {} {} {} {} {} {} {}", action, deviceId, channelId, netType, rtpIp, rtpPort, ssrc, streamMode, timeDescription);
GB28181Description description = new GB28181Description();
description.setSessionName(SdpFactory.getInstance().createSessionName(action.getAction()));
Version version = SdpFactory.getInstance().createVersion(0);
description.setVersion(version);
Connection connectionField = SdpFactory.getInstance().createConnection(ConnectionField.IN, netType, rtpIp);
description.setConnection(connectionField);
MediaDescription mediaDescription;
if(MediaStreamMode.UDP.equals(streamMode)){
mediaDescription = SdpFactory.getInstance().createMediaDescription("video", rtpPort, 0, Protocol.RTP_AVP.getProtocol(), rtpMap.keySet().toArray(new String[0]));
} else {
mediaDescription = SdpFactory.getInstance().createMediaDescription("video", rtpPort, 0, Protocol.RTP_AVP_TCP.getProtocol(), rtpMap.keySet().toArray(new String[0]));
}
if (isRecv) {
mediaDescription.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute("recvonly", null));
} else {
mediaDescription.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute("sendonly", null));
}
rtpMap.forEach((k, v) -> {
if(fmtpMap != null){
Optional.ofNullable(fmtpMap.get(k)).ifPresent((f) -> {
mediaDescription.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute(SdpConstants.FMTP.toLowerCase(), StringUtils.joinWith(Separators.SP, k, f)));
});
}
mediaDescription.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute(SdpConstants.RTPMAP, StringUtils.joinWith(Separators.SP, k, v)));
});
if (streamMode == MediaStreamMode.TCP_PASSIVE) {
// TCP-PASSIVE
mediaDescription.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute("setup", "passive"));
mediaDescription.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute("connection", "new"));
} else if (streamMode == MediaStreamMode.TCP_ACTIVE) {
// TCP-ACTIVE
mediaDescription.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute("setup", "active"));
mediaDescription.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute("connection", "new"));
}
description.setMediaDescriptions(new Vector<>() {{
add(mediaDescription);
}});
description.setTimeDescriptions(new Vector<>() {{
add(timeDescription);
}});
Origin origin = SdpFactory.getInstance().createOrigin(channelId, 0, 0, ConnectionField.IN, netType, rtpIp);
description.setOrigin(origin);
description.setSsrcField(new SsrcField(ssrc));
return description;
}
public static class Receiver {
@SneakyThrows
public static GB28181Description build(Action action, String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, MediaStreamMode streamMode, TimeDescription timeDescription) {
return GB28181SDPBuilder.build(action, deviceId, channelId, netType, rtpIp, rtpPort, ssrc, streamMode, timeDescription, true, GB28181SDPBuilder.RTPMAP, GB28181SDPBuilder.FMTP);
}
@SneakyThrows
public static GB28181Description play(String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, MediaStreamMode streamMode) {
TimeDescription timeDescription = SdpFactory.getInstance().createTimeDescription();
return build(Action.PLAY, deviceId, channelId, netType, rtpIp, rtpPort, ssrc, streamMode, timeDescription);
}
@SneakyThrows
public static GB28181Description playback(String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, MediaStreamMode streamMode, Date start, Date end) {
TimeField timeField = new TimeField();
timeField.setStartTime(start.toInstant().getEpochSecond());
timeField.setStopTime(end.toInstant().getEpochSecond());
TimeDescription timeDescription = SdpFactory.getInstance().createTimeDescription(timeField);
GB28181Description description = build(Action.PLAY_BACK, deviceId, channelId, netType, rtpIp, rtpPort, ssrc, streamMode, timeDescription);
URIField uriField = new URIField();
uriField.setURI(StringUtils.joinWith(":", channelId, "0"));
description.setURI(uriField);
return description;
}
@SneakyThrows
public static GB28181Description download(String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, MediaStreamMode streamMode, Date start, Date end, Double downloadSpeed) {
TimeField timeField = new TimeField();
timeField.setStartTime(start.toInstant().getEpochSecond());
timeField.setStopTime(end.toInstant().getEpochSecond());
TimeDescription timeDescription = SdpFactory.getInstance().createTimeDescription(timeField);
GB28181Description description = build(Action.DOWNLOAD, deviceId, channelId, netType, rtpIp, rtpPort, ssrc, streamMode, timeDescription);
MediaDescriptionImpl media = (MediaDescriptionImpl) description.getMediaDescriptions(false).get(0);
media.setAttribute("downloadspeed", String.valueOf(downloadSpeed));
URIField uriField = new URIField();
uriField.setURI(StringUtils.joinWith(":", channelId, "0"));
description.setURI(uriField);
return description;
}
}
public static class Sender {
@SneakyThrows
public static GB28181Description build(Action action, String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, MediaStreamMode streamMode, TimeDescription timeDescription, Map<String,String> rtpMap, Map<String,String> fmtpMap) {
return GB28181SDPBuilder.build(action, deviceId, channelId, netType, rtpIp, rtpPort, ssrc, streamMode, timeDescription, false, GB28181SDPBuilder.RTPMAP, GB28181SDPBuilder.FMTP);
}
@SneakyThrows
public static GB28181Description build(Action action, String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, MediaStreamMode streamMode, TimeDescription timeDescription) {
return build(action, deviceId, channelId, netType, rtpIp, rtpPort, ssrc, streamMode, timeDescription, GB28181SDPBuilder.RTPMAP, GB28181SDPBuilder.FMTP);
}
@SuppressWarnings({"unchecked","Duplicates"})
@SneakyThrows
public static GB28181Description build(GB28181Description receive, String rtpIp, int rtpPort, Map<String,String> rtpMap, Map<String,String> fmtpMap){
GB28181Description description = new GB28181Description(receive);
MediaDescriptionImpl media = (MediaDescriptionImpl) description.getMediaDescriptions(true).get(0);
Vector<String> formats = media.getMedia().getMediaFormats(true);
formats.clear();
formats.addAll(rtpMap.keySet());
Vector<String> attributes = media.getAttributes(true);
attributes.clear();
rtpMap.forEach((k, v) -> {
if (fmtpMap != null) {
Optional.ofNullable(fmtpMap.get(k)).ifPresent((f) -> {
media.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute(SdpConstants.FMTP.toLowerCase(), StringUtils.joinWith(Separators.SP, k, f)));
});
}
media.addAttribute((AttributeField) SdpFactory.getInstance().createAttribute(SdpConstants.RTPMAP, StringUtils.joinWith(Separators.SP, k, v)));
});
media.setAttribute("sendonly",null);
Connection connection = description.getConnection();
connection.setAddress(rtpIp);
media.getMedia().setMediaPort(rtpPort);
return description;
}
@SneakyThrows
public static GB28181Description build(GB28181Description receive, String rtpIp, int rtpPort){
GB28181Description description = new GB28181Description(receive);
MediaDescriptionImpl media = (MediaDescriptionImpl) description.getMediaDescriptions(true).get(0);
return build(description, rtpIp, rtpPort, GB28181SDPBuilder.RTPMAP, GB28181SDPBuilder.FMTP);
}
}
}

View File

@ -0,0 +1,28 @@
package cn.skcks.docking.gb28181.sdp.media;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum MediaStreamMode {
UDP("UDP"),
TCP_ACTIVE("TCP-ACTIVE"),
TCP_PASSIVE("TCP-PASSIVE");
@JsonValue
private final String mode;
@JsonCreator
public static MediaStreamMode of(String mode) {
for (MediaStreamMode m : values()) {
if (m.getMode().equalsIgnoreCase(mode)) {
return m;
}
}
return null;
}
}

View File

@ -140,6 +140,11 @@ public class SipBuilder {
return getHeaderFactory().createCallIdHeader(callId);
}
@SneakyThrows
public static ExpiresHeader createExpiresHeader(int expires){
return getHeaderFactory().createExpiresHeader(expires);
}
@SneakyThrows
public static XGBVerHeader createXGBVerHeader(int m,int n){
return new XGBVerHeaderImpl(m,n);

View File

@ -1,6 +1,7 @@
package cn.skcks.docking.gb28181.sdp;
import cn.skcks.docking.gb28181.sdp.field.ssrc.SsrcField;
import cn.skcks.docking.gb28181.sdp.media.MediaStreamMode;
import cn.skcks.docking.gb28181.sdp.parser.GB28181DescriptionParser;
import gov.nist.javax.sdp.SessionDescriptionImpl;
import gov.nist.javax.sdp.fields.TimeField;
@ -8,8 +9,12 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import javax.sdp.Connection;
import javax.sdp.SdpFactory;
import javax.sdp.TimeDescription;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
@Slf4j
@ -25,7 +30,8 @@ public class SdpTest {
gb28181Description.setTimeDescriptions(new Vector<>() {{
add(timeDescription);
}});
gb28181Description.setSsrcField(new SsrcField(String.format("%s%04d", domain.substring(3, 8), 1)));
String ssrc = String.format("%s%04d", domain.substring(3, 8), 1);
gb28181Description.setSsrcField(new SsrcField(ssrc));
log.info("gb28181 sdp \n{}", gb28181Description);
GB28181DescriptionParser gb28181DescriptionParser = new GB28181DescriptionParser(gb28181Description.toString());
@ -44,5 +50,24 @@ public class SdpTest {
gb28181DescriptionParser = new GB28181DescriptionParser(gb28181Description.toString());
parse = gb28181DescriptionParser.parse();
log.info("从 SessionDescriptionImpl 转换为 gb28181 sdp 解析\n{}",parse);
String deviceId = "00000000000000000002";
String channelId = "00000000000000000002";
String rtpNetType = Connection.IP4;
String rtpIp = "10.10.10.200";
int rtpPort = 5080;
String sendRtpIp = "127.0.0.1";
int sendRtpPort = 5080;
GB28181Description play = GB28181SDPBuilder.Receiver.play(deviceId, channelId, rtpNetType, rtpIp, rtpPort, ssrc, MediaStreamMode.TCP_ACTIVE);
log.info("sdp play 请求\n{}",play);
final String psType = "96";
Map<String,String> respRtpMap = new HashMap<>(){{
put(psType, GB28181SDPBuilder.RTPMAP.get(psType));
}};
GB28181Description resp = GB28181SDPBuilder.Sender.build(play, sendRtpIp, sendRtpPort,respRtpMap,null);
log.info("sdp 响应\n{}", resp);
}
}

View File

@ -52,10 +52,12 @@ public class SipTest {
CallIdHeader callIdHeader = SipBuilder.createCallIdHeader(MessageFormat.format("{0}@{1}", "123456", localIp));
List<ViaHeader> viaHeaders = SipBuilder.createViaHeaders(localIp, localPort, GB28181Constant.TransPort.TCP, "z9hG4bK" + "0000000001");
MaxForwardsHeader maxForwardsHeader = SipBuilder.createMaxForwardsHeader(70);
ExpiresHeader expiresHeader = SipBuilder.createExpiresHeader(3600);
// 创建请求
Request request = SipRequestBuilder.createRequest(remoteSipUri, method, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwardsHeader);
request.addHeader(contactHeader);
request.addHeader(expiresHeader);
log.info("构造请求\n{}", request);