Compare commits

..

1 Commits

Author SHA1 Message Date
d5c2d86146 feign 忽略 ssl 证书 2023-10-17 16:26:45 +08:00
143 changed files with 474 additions and 6991 deletions

View File

@ -26,61 +26,8 @@ docker run --name gb28181 --rm \
skcks.cn/gb28181-docking-platform:0.0.1-SNAPSHOT
```
### 清理私仓
切换到私仓目录
例: cd H:/Repository/skcks.cn/gb28181-docking-platform-mvn-repo
```shell
rm -rf ./*/*/*/*/0.1.0-SNAPSHOT
rm -rf ./*/*/*/*/*/0.1.0-SNAPSHOT
```
### 打包到本地私仓
```shell
mvn deploy -s settings.xml -DaltDeploymentRepository=local-repo::default::file:H:/Repository/skcks.cn/gb28181-docking-platform-mvn-repo
mvn deploy -DaltDeploymentRepository=amleixun-mvn-reop::default::file:H:/Repository/skcks.cn/gb28181-docking-platform-mvn-repo
```
git push 推送即可
### 公共仓库
https://central.sonatype.com
https://central.sonatype.org/publish/requirements/gpg/#signing-a-file
gpg签名
https://central.sonatype.org/publish/requirements/gpg/
https://www.gpg4win.org/thanks-for-download.html
gpg服务器
- keyserver.ubuntu.com
- keys.openpgp.org
- pgp.mit.edu
settings.xml
```xml
<settings>
<servers>
<server>
<id>ossrh</id>
<username><!-- your token username --></username>
<password><!-- your token password --></password>
</server>
</servers>
<profiles>
<profile>
<id>ossrh</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.executable>gpg</gpg.executable>
<gpg.keyname>GPG KEY NAME</gpg.keyname>
<gpg.passphrase>GPG KEY PASSWORD</gpg.passphrase>
</properties>
</profile>
</profiles>
</settings>
```
#### 解决idea 控制台乱码
- 设置 > 构建/运行/部署 > 构建工具 > Maven > 运行程序 > VM options
- 添加 -Dfile.encoding=GBK
git push 推送即可

View File

@ -6,7 +6,7 @@
<parent>
<groupId>cn.skcks.docking</groupId>
<artifactId>gb28181</artifactId>
<version>0.1.0</version>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.skcks.docking.gb28181</groupId>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>cn.skcks.docking</groupId>
<artifactId>gb28181</artifactId>
<version>0.1.0</version>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.skcks.docking.gb28181</groupId>

View File

@ -12,7 +12,6 @@ import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import java.util.Objects;
@ -73,12 +72,6 @@ public class ExceptionAdvice {
return JsonResponse.error("参数异常");
}
@ExceptionHandler(AsyncRequestTimeoutException.class)
public JsonResponse<String> exception(AsyncRequestTimeoutException e) {
e.printStackTrace();
return JsonResponse.error("请求超时");
}
@ExceptionHandler(Exception.class)
public JsonResponse<String> exception(Exception e) {
e.printStackTrace();

View File

@ -1,39 +0,0 @@
package cn.skcks.docking.gb28181.api.device;
import cn.skcks.docking.gb28181.annotation.web.JsonMapping;
import cn.skcks.docking.gb28181.annotation.web.methods.GetJson;
import cn.skcks.docking.gb28181.api.device.dto.DeviceChannelPageDTO;
import cn.skcks.docking.gb28181.common.json.JsonResponse;
import cn.skcks.docking.gb28181.common.page.PageWrapper;
import cn.skcks.docking.gb28181.config.SwaggerConfig;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDeviceChannel;
import cn.skcks.docking.gb28181.service.device.DeviceChannelService;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "设备通道")
@JsonMapping("/api/device/channel")
@RestController
@RequiredArgsConstructor
public class DeviceChannelController {
private final DeviceChannelService deviceChannelService;
@Bean
public GroupedOpenApi deviceChannelApi() {
return SwaggerConfig.api("DeviceChannel", "/api/device/channel");
}
@Operation(summary = "分页查询 设备通道信息")
@GetJson("/page")
public JsonResponse<PageWrapper<DockingDeviceChannel>> getWithPage(@ParameterObject @Validated DeviceChannelPageDTO dto){
PageInfo<DockingDeviceChannel> withPage = deviceChannelService.getWithPage(dto.getPage(), dto.getSize());
return JsonResponse.success(PageWrapper.of(withPage));
}
}

View File

@ -1,17 +0,0 @@
package cn.skcks.docking.gb28181.api.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class DeviceChannelPageDTO {
@Schema(description = "页数", example = "1")
@Min(value = 1, message = "page 必须为正整数")
Integer page;
@Schema(description = "每页条数", example = "10")
@Min(value = 1, message = "size 必须为正整数")
Integer size;
}

View File

@ -1,16 +0,0 @@
package cn.skcks.docking.gb28181.api.gb28181;
import cn.skcks.docking.gb28181.annotation.web.JsonMapping;
import cn.skcks.docking.gb28181.config.SwaggerConfig;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RestController;
@JsonMapping("/api/gb28181")
@RestController
public class GB28181Controller {
@Bean
public GroupedOpenApi gb28181Api() {
return SwaggerConfig.api("GB28181", "/api/gb28181");
}
}

View File

@ -1,31 +0,0 @@
package cn.skcks.docking.gb28181.api.gb28181.catalog;
import cn.skcks.docking.gb28181.annotation.web.JsonMapping;
import cn.skcks.docking.gb28181.annotation.web.methods.GetJson;
import cn.skcks.docking.gb28181.common.json.JsonResponse;
import cn.skcks.docking.gb28181.service.catalog.CatalogService;
import cn.skcks.docking.gb28181.sip.manscdp.catalog.response.CatalogItemDTO;
import cn.skcks.docking.gb28181.utils.FutureDeferredResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Tag(name="获取设备目录信息")
@RestController
@JsonMapping("/api/gb28181/catalog")
@RequiredArgsConstructor
public class CatalogController {
private final CatalogService catalogService;
@SneakyThrows
@GetJson
public DeferredResult<JsonResponse<List<CatalogItemDTO>>> catalog(String gbDeviceId){
CompletableFuture<List<CatalogItemDTO>> catalog = catalogService.catalog(gbDeviceId);
return FutureDeferredResult.toDeferredResultWithJson(catalog);
}
}

View File

@ -1,8 +1,8 @@
package cn.skcks.docking.gb28181.api.gb28181.record;
package cn.skcks.docking.gb28181.api.record;
import cn.skcks.docking.gb28181.annotation.web.JsonMapping;
import cn.skcks.docking.gb28181.annotation.web.methods.GetJson;
import cn.skcks.docking.gb28181.api.gb28181.record.dto.GetRecordInfoDTO;
import cn.skcks.docking.gb28181.api.record.dto.GetInfoDTO;
import cn.skcks.docking.gb28181.common.json.JsonResponse;
import cn.skcks.docking.gb28181.config.SwaggerConfig;
import cn.skcks.docking.gb28181.service.record.RecordService;
@ -20,7 +20,7 @@ import java.util.List;
@Tag(name="历史录像")
@RestController
@JsonMapping("/api/gb28181/record")
@JsonMapping("/api/device/record")
@RequiredArgsConstructor
public class RecordController {
private final RecordService recordService;
@ -30,8 +30,8 @@ public class RecordController {
return SwaggerConfig.api("Record", "/api/device/record");
}
@GetJson("/list")
public DeferredResult<JsonResponse<List<RecordInfoItemVO>>> getInfo(@ParameterObject @Validated GetRecordInfoDTO dto){
return recordService.requestRecordInfo(dto.getGbDeviceId(), dto.getGbDeviceChannelId(), dto.getTimeout(), dto.getDate());
@GetJson("/getInfoList")
public DeferredResult<JsonResponse<List<RecordInfoItemVO>>> getInfo(@ParameterObject @Validated GetInfoDTO dto){
return recordService.requestRecordInfo(dto.getDeviceId(), dto.getTimeout(), dto.getDate());
}
}

View File

@ -1,4 +1,4 @@
package cn.skcks.docking.gb28181.api.gb28181.record.dto;
package cn.skcks.docking.gb28181.api.record.dto;
import cn.hutool.core.date.DatePattern;
import io.swagger.v3.oas.annotations.media.Schema;
@ -11,14 +11,10 @@ import java.util.Date;
@Schema(title = "查询历史录像")
@Data
public class GetRecordInfoDTO {
public class GetInfoDTO {
@NotBlank
@Schema(description = "设备id", example = "44050100002000000006")
private String gbDeviceId;
@NotBlank
@Schema(description = "通道id", example = "34020000001310000001")
private String gbDeviceChannelId;
@Schema(description = "设备id", example = "44050100001180000001")
private String deviceId;
@Min(30)
@Schema(description = "超时时间(秒)", example = "30")

View File

@ -6,7 +6,7 @@
<parent>
<groupId>cn.skcks.docking</groupId>
<artifactId>gb28181</artifactId>
<version>0.1.0</version>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.skcks.docking.gb28181</groupId>

View File

@ -1,7 +1,5 @@
package cn.skcks.docking.gb28181.common.json;
import lombok.Builder;
public class JsonException extends Exception{
public JsonException(String message){
super(message);

View File

@ -28,7 +28,7 @@ public class XmlUtils {
// 大驼峰 (首字母大写)
mapper.setPropertyNamingStrategy(new PropertyNamingStrategies.UpperCamelCaseStrategy());
// 添加 xml 头部声明
mapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, false);
mapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true);
}
public static String toXml(Object obj) {

View File

@ -6,7 +6,7 @@
<parent>
<groupId>cn.skcks.docking</groupId>
<artifactId>gb28181</artifactId>
<version>0.1.0</version>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>cn.skcks.docking.gb28181</groupId>
@ -22,21 +22,19 @@
<dependency>
<groupId>cn.skcks.docking</groupId>
<artifactId>zlmediakit-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.skcks.docking.gb28181</groupId>
<artifactId>orm</artifactId>
</dependency>
<dependency>
<groupId>cn.skcks.docking.gb28181</groupId>
<artifactId>gb28181-sip</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.skcks.docking.gb28181</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
@ -51,6 +49,13 @@
<version>1.3.0-91</version>
</dependency>
<!-- xml解析库 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.4</version>
</dependency>
<!--MapStruct-->
<dependency>
<groupId>org.mapstruct</groupId>

View File

@ -0,0 +1,65 @@
package cn.skcks.docking.gb28181.core.sip.gb28181.sdp;
import gov.nist.javax.sdp.SessionDescriptionImpl;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import javax.sdp.*;
import java.util.Optional;
@EqualsAndHashCode(callSuper = false)
@Data
public class GB28181Description extends SessionDescriptionImpl implements SessionDescription {
public static class Convertor {
@SneakyThrows
public static GB28181Description convert(SessionDescriptionImpl sessionDescription){
GB28181Description gb28181Description = new GB28181Description();
SessionName sessionName = sessionDescription.getSessionName();
if(sessionName != null){
gb28181Description.setSessionName(sessionName);
}
gb28181Description.setMediaDescriptions(sessionDescription.getMediaDescriptions(true));
gb28181Description.setBandwidths(sessionDescription.getBandwidths(true));
Connection connection = sessionDescription.getConnection();
if (connection != null){
gb28181Description.setConnection(connection);
}
gb28181Description.setEmails(sessionDescription.getEmails(true));
gb28181Description.setTimeDescriptions(sessionDescription.getTimeDescriptions(true));
Origin origin = sessionDescription.getOrigin();
if(origin != null){
gb28181Description.setOrigin(origin);
}
gb28181Description.setAttributes(sessionDescription.getAttributes(true));
URI uri = sessionDescription.getURI();
if(uri != null){
gb28181Description.setURI(uri);
}
return gb28181Description;
}
}
private SsrcField ssrcField;
GB28181Description(){
super();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(super.toString());
sb.append(getSsrcField() == null ? "" : getSsrcField().toString());
return sb.toString();
}
}

View File

@ -0,0 +1,16 @@
package cn.skcks.docking.gb28181.core.sip.gb28181.sdp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.sdp.SessionDescription;
@Builder
@Data
public class Gb28181Sdp {
private SessionDescription baseSdb;
private String ssrc;
private String mediaDescription;
}

View File

@ -0,0 +1,127 @@
package cn.skcks.docking.gb28181.core.sip.gb28181.sdp;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import gov.nist.core.Separators;
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 javax.sdp.*;
import java.util.*;
@Slf4j
public class MediaSdpHelper {
public final static String SEPARATOR = "_";
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<>() {{
put("96", "PS/90000");
put("126", "H264/90000");
put("125", "H264S/90000");
put("99", "H265/90000");
put("98", "H264/90000");
put("97", "MPEG4/90000");
}};
public static final Map<String, String> FMTP = new HashMap<>() {{
put("126", "profile-level-id=42e01e");
put("125", "profile-level-id=42e01e");
}};
@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;
}
}
@SneakyThrows
public static GB28181Description build(Action action, String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, StreamMode streamMode, TimeDescription timeDescription){
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 = SdpFactory.getInstance().createMediaDescription("video", rtpPort, 0, SdpConstants.RTP_AVP, MediaSdpHelper.RTPMAP.keySet().toArray(new String[0]));
mediaDescription.addAttribute((AttributeField)SdpFactory.getInstance().createAttribute("recvonly",null));
MediaSdpHelper.RTPMAP.forEach((k, v)->{
Optional.ofNullable(MediaSdpHelper.FMTP.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 == StreamMode.TCP_PASSIVE){
// TCP-PASSIVE
mediaDescription.addAttribute((AttributeField)SdpFactory.getInstance().createAttribute("setup","passive"));
mediaDescription.addAttribute((AttributeField)SdpFactory.getInstance().createAttribute("connection","new"));
} else if(streamMode == StreamMode.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;
}
@SneakyThrows
public static GB28181Description play(String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, StreamMode 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, StreamMode 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;
}
}

View File

@ -1,4 +1,4 @@
package cn.skcks.docking.gb28181.sdp.field.ssrc;
package cn.skcks.docking.gb28181.core.sip.gb28181.sdp;
import gov.nist.core.Separators;
import gov.nist.javax.sdp.fields.SDPField;
@ -10,8 +10,7 @@ import lombok.EqualsAndHashCode;
@Data
@AllArgsConstructor
public class SsrcField extends SDPField {
public static final String SSRC_FIELD_NAME = "y";
private static final String SSRC_FIELD = SSRC_FIELD_NAME + "=";
private static final String SSRC_FIELD = "y=";
public SsrcField() {
super(SSRC_FIELD);
}

View File

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

View File

@ -1,10 +1,8 @@
package cn.skcks.docking.gb28181.core.sip.listener;
import cn.skcks.docking.gb28181.core.sip.executor.DefaultSipExecutor;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.processor.MessageProcessor;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
@ -23,7 +21,7 @@ import java.util.concurrent.ConcurrentMap;
@Component
@Slf4j
public class SipListenerImpl implements SipListener {
private final SipSubscribe subscribe;
private final SipSubscribe sipSubscribe;
private final ConcurrentMap<String, MessageProcessor> requestProcessor = new ConcurrentHashMap<>();
private final ConcurrentMap<String, MessageProcessor> responseProcessor = new ConcurrentHashMap<>();
@ -67,11 +65,6 @@ public class SipListenerImpl implements SipListener {
// 增加其它无需回复的响应如101180等
} else {
log.warn("接收到失败的response响应status" + status + ",message:" + response.getReasonPhrase());
SIPResponse sipResponse = (SIPResponse) response;
String callId = sipResponse.getCallIdHeader().getCallId();
Optional.ofNullable(subscribe.getSipResponseSubscribe()
.getPublisher(GenericSubscribe.Helper.getKey(MessageProcessor.Method.INVITE, callId)))
.ifPresent(publisher->publisher.submit(sipResponse));
if (responseEvent.getDialog() != null) {
responseEvent.getDialog().delete();
}

View File

@ -1,59 +0,0 @@
package cn.skcks.docking.gb28181.core.sip.message.processor.bye.request;
import cn.skcks.docking.gb28181.core.sip.listener.SipListener;
import cn.skcks.docking.gb28181.core.sip.message.processor.MessageProcessor;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.core.sip.service.SipService;
import cn.skcks.docking.gb28181.sip.method.invite.response.InviteResponseBuilder;
import gov.nist.javax.sip.message.SIPRequest;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.message.Request;
import java.util.EventObject;
import java.util.Optional;
@Slf4j
@RequiredArgsConstructor
@Component
public class ByeRequestProcessor implements MessageProcessor {
private final SipListener sipListener;
private final SipSubscribe subscribe;
private final SipService sipService;
@PostConstruct
@Override
public void init() {
sipListener.addRequestProcessor(Request.BYE, this);
}
@Override
public void process(EventObject eventObject) {
RequestEvent requestEvent = (RequestEvent) eventObject;
SIPRequest request = (SIPRequest) requestEvent.getRequest();
String callId = request.getCallId().getCallId();
String key = GenericSubscribe.Helper.getKey(Request.BYE, callId);
log.info("key {}", key);
String ip = request.getLocalAddress().getHostAddress();
String transport = request.getTopmostViaHeader().getTransport();
SipProvider provider= sipService.getProvider(transport, ip);
Optional.ofNullable(subscribe.getSipRequestSubscribe().getPublisher(key))
.ifPresentOrElse(
publisher -> publisher.submit(request),
() -> {
try {
provider.sendResponse(InviteResponseBuilder.builder().build().createTryingInviteResponse(request));
} catch (SipException e) {
throw new RuntimeException(e);
}
});
}
}

View File

@ -1,44 +0,0 @@
package cn.skcks.docking.gb28181.core.sip.message.processor.bye.response;
import cn.skcks.docking.gb28181.core.sip.listener.SipListener;
import cn.skcks.docking.gb28181.core.sip.message.processor.MessageProcessor;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.core.sip.service.SipService;
import cn.skcks.docking.gb28181.sip.generic.SipResponseBuilder;
import cn.skcks.docking.gb28181.sip.method.invite.response.InviteResponseBuilder;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.util.EventObject;
import java.util.Optional;
@Slf4j
@RequiredArgsConstructor
@Component
public class ByeResponseProcessor implements MessageProcessor {
private final SipListener sipListener;
private final SipSubscribe subscribe;
private final SipService sipService;
@PostConstruct
@Override
public void init() {
sipListener.addResponseProcessor(Request.BYE, this);
}
@Override
public void process(EventObject eventObject) {
}
}

View File

@ -2,35 +2,28 @@ package cn.skcks.docking.gb28181.core.sip.message.processor.message.request;
import cn.skcks.docking.gb28181.common.json.ResponseStatus;
import cn.skcks.docking.gb28181.common.xml.XmlUtils;
import cn.skcks.docking.gb28181.config.sip.SipConfig;
import cn.skcks.docking.gb28181.constant.CmdType;
import cn.skcks.docking.gb28181.core.sip.gb28181.constant.CmdType;
import cn.skcks.docking.gb28181.core.sip.gb28181.constant.GB28181Constant;
import cn.skcks.docking.gb28181.core.sip.listener.SipListener;
import cn.skcks.docking.gb28181.core.sip.message.processor.MessageProcessor;
import cn.skcks.docking.gb28181.core.sip.message.processor.message.request.dto.MessageDTO;
import cn.skcks.docking.gb28181.core.sip.message.processor.message.types.recordinfo.reponse.dto.RecordInfoResponseDTO;
import cn.skcks.docking.gb28181.core.sip.message.sender.SipMessageSender;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.core.sip.utils.SipUtil;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDevice;
import cn.skcks.docking.gb28181.service.docking.device.DockingDeviceService;
import cn.skcks.docking.gb28181.service.notify.MediaStatusService;
import cn.skcks.docking.gb28181.sip.manscdp.MessageDTO;
import cn.skcks.docking.gb28181.sip.manscdp.catalog.response.CatalogResponseDTO;
import cn.skcks.docking.gb28181.sip.manscdp.mediastatus.notify.MediaStatusRequestDTO;
import cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response.RecordInfoResponseDTO;
import cn.skcks.docking.gb28181.sip.method.invite.request.InviteRequestBuilder;
import cn.skcks.docking.gb28181.sip.utils.MANSCDPUtils;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.sip.RequestEvent;
import javax.sip.address.SipURI;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Response;
import java.util.EventObject;
import java.util.Optional;
@ -43,9 +36,6 @@ public class MessageRequestProcessor implements MessageProcessor {
private final DockingDeviceService deviceService;
private final SipMessageSender sender;
private final SipSubscribe subscribe;
private final SipConfig sipConfig;
private final MediaStatusService mediaStatusService;
@PostConstruct
@Override
@ -58,10 +48,10 @@ public class MessageRequestProcessor implements MessageProcessor {
RequestEvent requestEvent = (RequestEvent) eventObject;
SIPRequest request = (SIPRequest)requestEvent.getRequest();
String deviceId = SipUtil.getUserIdFromFromHeader(request);
String callId = request.getCallIdHeader().getCallId();
CallIdHeader callIdHeader = request.getCallIdHeader();
byte[] content = request.getRawContent();
MessageDTO messageDto = MANSCDPUtils.parse(content, MessageDTO.class);
MessageDTO messageDto = XmlUtils.parse(content, MessageDTO.class, GB28181Constant.CHARSET);
log.debug("接收到的消息 => {}", messageDto);
DockingDevice device = deviceService.getDevice(deviceId);
@ -80,28 +70,13 @@ public class MessageRequestProcessor implements MessageProcessor {
response = ok;
// 更新设备在线状态
deviceService.online(device, response);
} else if(messageDto.getCmdType().equalsIgnoreCase(CmdType.RECORD_INFO)) {
} else if(messageDto.getCmdType().equalsIgnoreCase(CmdType.RECORD_INFO)){
response = ok;
RecordInfoResponseDTO dto = XmlUtils.parse(content, RecordInfoResponseDTO.class, GB28181Constant.CHARSET);
String key = GenericSubscribe.Helper.getKey(CmdType.RECORD_INFO, dto.getDeviceId(), dto.getSn());
Optional.ofNullable(subscribe.getSipRequestSubscribe().getPublisher(key))
.ifPresentOrElse(publisher -> publisher.submit(request),
() -> log.warn("对应订阅 {} 已结束, 异常数据 => {}", key, dto));
}else if(messageDto.getCmdType().equalsIgnoreCase(CmdType.CATALOG)){
CatalogResponseDTO catalogResponseDTO = MANSCDPUtils.parse(content, CatalogResponseDTO.class);
String key = GenericSubscribe.Helper.getKey(CmdType.CATALOG,catalogResponseDTO.getDeviceId(), catalogResponseDTO.getSn());
Optional.ofNullable(subscribe.getSipRequestSubscribe().getPublisher(key)).ifPresent(publisher->{
publisher.submit(request);
});
response = ok;
} else if(messageDto.getCmdType().equalsIgnoreCase(CmdType.MEDIA_STATUS)){
response = ok;
sender.send(senderIp, response);
MediaStatusRequestDTO mediaStatusRequestDTO = MANSCDPUtils.parse(content, MediaStatusRequestDTO.class);
if(StringUtils.equalsIgnoreCase(mediaStatusRequestDTO.getNotifyType(),"121")){
mediaStatusService.process(request, mediaStatusRequestDTO);
return;
}
Optional.ofNullable(subscribe.getRecordInfoSubscribe().getPublisher(key))
.ifPresentOrElse(publisher-> publisher.submit(dto),
()-> log.warn("对应订阅 {} 已结束, 异常数据 => {}",key, dto));
} else {
response = response(request, Response.NOT_IMPLEMENTED, ResponseStatus.NOT_IMPLEMENTED.getMessage());
}

View File

@ -1,14 +1,13 @@
package cn.skcks.docking.gb28181.core.sip.message.processor.message.response;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.Gb28181Sdp;
import cn.skcks.docking.gb28181.core.sip.listener.SipListener;
import cn.skcks.docking.gb28181.core.sip.message.processor.MessageProcessor;
import cn.skcks.docking.gb28181.core.sip.message.request.SipRequestBuilder;
import cn.skcks.docking.gb28181.core.sip.message.sender.SipMessageSender;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.sdp.parser.GB28181DescriptionParser;
import cn.skcks.docking.gb28181.core.sip.utils.SipUtil;
import gov.nist.javax.sip.ResponseEventExt;
import gov.nist.javax.sip.message.SIPResponse;
import jakarta.annotation.PostConstruct;
@ -17,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.sdp.SdpParseException;
import javax.sdp.SessionDescription;
import javax.sip.InvalidArgumentException;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
@ -53,20 +53,20 @@ public class InviteResponseProcessor implements MessageProcessor {
// trying不会回复
if (statusCode == Response.TRYING) {
subscribe.getSipResponseSubscribe().getPublisher(subscribeKey).submit(response);
subscribe.getInviteSubscribe().getPublisher(subscribeKey).submit(response);
return;
}
// 成功响应
// 下发ack
if (statusCode == Response.OK) {
Optional.ofNullable(subscribe.getSipResponseSubscribe().getPublisher(subscribeKey))
Optional.ofNullable(subscribe.getInviteSubscribe().getPublisher(subscribeKey))
.ifPresentOrElse(publisher-> publisher.submit(response),
()-> log.warn("对应订阅 {} 已结束",callId.getCallId()));
ResponseEventExt event = (ResponseEventExt) requestEvent;
String contentString = new String(response.getRawContent());
GB28181DescriptionParser gb28181DescriptionParser = new GB28181DescriptionParser(contentString);
GB28181Description sdp = gb28181DescriptionParser.parse();
Gb28181Sdp gb28181Sdp = SipUtil.parseSDP(contentString);
SessionDescription sdp = gb28181Sdp.getBaseSdb();
SipURI requestUri = SipFactory.getInstance().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
Request reqAck = SipRequestBuilder.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);

View File

@ -7,9 +7,7 @@ import cn.skcks.docking.gb28181.core.sip.dto.SipTransactionInfo;
import cn.skcks.docking.gb28181.core.sip.gb28181.constant.GB28181Constant;
import cn.skcks.docking.gb28181.core.sip.gb28181.sip.GbSipDate;
import cn.skcks.docking.gb28181.core.sip.listener.SipListener;
import cn.skcks.docking.gb28181.sdp.media.MediaStreamMode;
import cn.skcks.docking.gb28181.sip.method.register.response.RegisterResponseBuilder;
import cn.skcks.docking.gb28181.sip.utils.DigestAuthenticationHelper;
import cn.skcks.docking.gb28181.core.sip.message.auth.DigestServerAuthenticationHelper;
import cn.skcks.docking.gb28181.core.sip.message.processor.MessageProcessor;
import cn.skcks.docking.gb28181.core.sip.message.sender.SipMessageSender;
import cn.skcks.docking.gb28181.core.sip.utils.SipUtil;
@ -51,7 +49,7 @@ public class RegisterRequestProcessor implements MessageProcessor {
@PostConstruct
@Override
public void init(){
sipListener.addRequestProcessor(Request.REGISTER,this);
sipListener.addRequestProcessor(Method.REGISTER,this);
}
@SneakyThrows
@ -83,19 +81,18 @@ public class RegisterRequestProcessor implements MessageProcessor {
}
}
RegisterResponseBuilder registerResponseBuilder = RegisterResponseBuilder.builder().build();
String password = sipConfig.getPassword();
Authorization authorization = request.getAuthorization();
if(authorization == null && StringUtils.isNotBlank(password)){
Response response = registerResponseBuilder.createAuthorzatioinResponse(request,sipConfig.getDomain(), password);
Response response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
DigestServerAuthenticationHelper.generateChallenge(getHeaderFactory(),response,sipConfig.getDomain());
sender.send(senderIp,response);
return;
}
log.debug("认证信息 => {}", authorization);
boolean authPass = StringUtils.isBlank(password) ||
DigestAuthenticationHelper.doAuthenticatePlainTextPassword(request,password);
DigestServerAuthenticationHelper.doAuthenticatePlainTextPassword(request,password);
if(!authPass){
Response response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
response.setReasonPhrase("认证失败");
@ -133,7 +130,6 @@ public class RegisterRequestProcessor implements MessageProcessor {
private void registerDevice(String deviceId, DockingDevice device, SIPRequest request, String senderIp, RemoteInfo remoteInfo) {
Response response = generateRegisterResponse(request);
log.debug("response.getStatusCode {}", response.getStatusCode());
if(response.getStatusCode() != Response.OK){
sender.send(senderIp, response);
return;
@ -141,14 +137,14 @@ public class RegisterRequestProcessor implements MessageProcessor {
if (device == null) {
device = new DockingDevice();
device.setStreamMode(MediaStreamMode.UDP.getMode());
device.setStreamMode(ListeningPoint.UDP);
device.setCharset(GB28181Constant.CHARSET);
device.setGeoCoordSys(GB28181Constant.GEO_COORD_SYS);
device.setDeviceId(deviceId);
device.setOnLine(false);
} else {
if (ObjectUtils.isEmpty(device.getStreamMode())) {
device.setStreamMode(MediaStreamMode.UDP.getMode());
device.setStreamMode(ListeningPoint.UDP);
}
if (ObjectUtils.isEmpty(device.getCharset())) {
device.setCharset(GB28181Constant.CHARSET);

View File

@ -4,7 +4,6 @@ import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
@ -19,8 +18,6 @@ public interface GenericSubscribe<T> {
void addSubscribe(String key,Flow.Subscriber<T> subscribe);
void delPublisher(String key);
void complete(String key);
class Helper {
public final static String SEPARATOR = ":";
public static String getKey(String prefix,String... ids){
@ -34,7 +31,7 @@ public interface GenericSubscribe<T> {
public static <T> void delPublisher(Map<String, SubmissionPublisher<T>> publishers, String key){
SubmissionPublisher<T> publisher = publishers.remove(key);
Optional.ofNullable(publisher).ifPresent(SubmissionPublisher::close);
publisher.close();
}
public static <T> void addPublisher(Executor executor, Map<String, SubmissionPublisher<T>> publishers, String key){

View File

@ -1,16 +0,0 @@
package cn.skcks.docking.gb28181.core.sip.message.subscribe;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.TimeUnit;
public interface GenericTimeoutSubscribe<T> extends GenericSubscribe<T>{
void addPublisher(String key, long time, TimeUnit timeUnit);
void refreshPublisher(String key, long time, TimeUnit timeUnit);
}

View File

@ -30,10 +30,6 @@ public class InviteSubscribe implements GenericSubscribe<SIPResponse> {
Helper.addSubscribe(publishers, key, subscribe);
}
public void complete(String key){
delPublisher(key);
}
@Override
public void delPublisher(String key) {
Helper.delPublisher(publishers, key);

View File

@ -30,10 +30,6 @@ public class RecordInfoSubscribe implements GenericSubscribe<RecordInfoResponseD
Helper.addSubscribe(publishers, key, subscribe);
}
public void complete(String key){
delPublisher(key);
}
@Override
public void delPublisher(String key) {
Helper.delPublisher(publishers, key);

View File

@ -1,69 +0,0 @@
package cn.skcks.docking.gb28181.core.sip.message.subscribe;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.Closeable;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.*;
@Slf4j
@RequiredArgsConstructor
public class SipRequestSubscribe implements GenericTimeoutSubscribe<SIPRequest>, Closeable {
private final Executor executor;
private final ScheduledExecutorService scheduledExecutorService;
private final ConcurrentMap<String, ScheduledFuture<?>> scheduledFutureManager = new ConcurrentHashMap<>(0);
private static final Map<String, SubmissionPublisher<SIPRequest>> publishers = new ConcurrentHashMap<>();
public void close() {
Helper.close(publishers);
}
public void addPublisher(String key) {
Helper.addPublisher(executor, publishers, key);
}
public SubmissionPublisher<SIPRequest> getPublisher(String key) {
return Helper.getPublisher(publishers, key);
}
public void addSubscribe(String key, Flow.Subscriber<SIPRequest> subscribe) {
Helper.addSubscribe(publishers, key, subscribe);
}
public void complete(String key){
delPublisher(key);
}
@Override
public void delPublisher(String key) {
ScheduledFuture<?> schedule = scheduledFutureManager.remove(key);
Optional.ofNullable(schedule).ifPresent(scheduledFuture->scheduledFuture.cancel(true));
Helper.delPublisher(publishers, key);
}
@Override
public void addPublisher(String key, long time, TimeUnit timeUnit) {
addPublisher(key);
ScheduledFuture<?> schedule = scheduledExecutorService.schedule(() -> {
scheduledFutureManager.remove(key);
delPublisher(key);
log.debug("清理超时 请求 订阅器 {}", key);
}, time, timeUnit);
scheduledFutureManager.put(key,schedule);
}
@Override
public void refreshPublisher(String key, long time, TimeUnit timeUnit) {
ScheduledFuture<?> schedule = scheduledFutureManager.remove(key);
Optional.ofNullable(schedule).ifPresent(scheduledFuture->scheduledFuture.cancel(true));
schedule = scheduledExecutorService.schedule(() -> {
scheduledFutureManager.remove(key);
delPublisher(key);
log.debug("清理超时 请求 订阅器 {}", key);
}, time, timeUnit);
scheduledFutureManager.put(key,schedule);
}
}

View File

@ -1,69 +0,0 @@
package cn.skcks.docking.gb28181.core.sip.message.subscribe;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.Closeable;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.*;
@Slf4j
@RequiredArgsConstructor
public class SipResponseSubscribe implements GenericTimeoutSubscribe<SIPResponse>, Closeable {
private final Executor executor;
private final ScheduledExecutorService scheduledExecutorService;
private final ConcurrentMap<String, ScheduledFuture<?>> scheduledFutureManager = new ConcurrentHashMap<>(0);
private static final Map<String, SubmissionPublisher<SIPResponse>> publishers = new ConcurrentHashMap<>();
public void close() {
Helper.close(publishers);
}
public void addPublisher(String key) {
Helper.addPublisher(executor, publishers, key);
}
public SubmissionPublisher<SIPResponse> getPublisher(String key) {
return Helper.getPublisher(publishers, key);
}
public void addSubscribe(String key, Flow.Subscriber<SIPResponse> subscribe) {
Helper.addSubscribe(publishers, key, subscribe);
}
public void complete(String key){
delPublisher(key);
}
@Override
public void delPublisher(String key) {
ScheduledFuture<?> schedule = scheduledFutureManager.remove(key);
Optional.ofNullable(schedule).ifPresent(scheduledFuture->scheduledFuture.cancel(true));
Helper.delPublisher(publishers, key);
}
@Override
public void addPublisher(String key, long time, TimeUnit timeUnit) {
addPublisher(key);
ScheduledFuture<?> schedule = scheduledExecutorService.schedule(() -> {
scheduledFutureManager.remove(key);
delPublisher(key);
log.debug("清理超时 响应 订阅器 {}", key);
}, time, timeUnit);
scheduledFutureManager.put(key,schedule);
}
@Override
public void refreshPublisher(String key, long time, TimeUnit timeUnit) {
ScheduledFuture<?> schedule = scheduledFutureManager.remove(key);
Optional.ofNullable(schedule).ifPresent(scheduledFuture->scheduledFuture.cancel(true));
schedule = scheduledExecutorService.schedule(() -> {
scheduledFutureManager.remove(key);
delPublisher(key);
log.debug("清理超时 响应 订阅器 {}", key);
}, time, timeUnit);
scheduledFutureManager.put(key,schedule);
}
}

View File

@ -2,7 +2,6 @@ package cn.skcks.docking.gb28181.core.sip.message.subscribe;
import cn.skcks.docking.gb28181.core.sip.executor.DefaultSipExecutor;
import cn.skcks.docking.gb28181.core.sip.message.processor.message.types.recordinfo.reponse.dto.RecordInfoResponseDTO;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@ -13,8 +12,6 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@Slf4j
@Data
@ -23,20 +20,18 @@ import java.util.concurrent.ScheduledExecutorService;
public class SipSubscribe {
@Qualifier(DefaultSipExecutor.EXECUTOR_BEAN_NAME)
private final Executor executor;
private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
private GenericTimeoutSubscribe<SIPResponse> sipResponseSubscribe;
private GenericTimeoutSubscribe<SIPRequest> sipRequestSubscribe;
private GenericSubscribe<RecordInfoResponseDTO> recordInfoSubscribe;
private GenericSubscribe<SIPResponse> inviteSubscribe;
@PostConstruct
private void init() {
// 通用订阅器
sipResponseSubscribe = new SipResponseSubscribe(executor, scheduledExecutorService);
sipRequestSubscribe = new SipRequestSubscribe(executor, scheduledExecutorService);
recordInfoSubscribe = new RecordInfoSubscribe(executor);
inviteSubscribe = new InviteSubscribe(executor);
}
@PreDestroy
private void destroy() {
sipResponseSubscribe.close();
sipRequestSubscribe.close();
inviteSubscribe.close();
recordInfoSubscribe.close();
}
}

View File

@ -13,11 +13,8 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import javax.sip.*;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Slf4j
@RequiredArgsConstructor
@ -70,61 +67,6 @@ public class SipServiceImpl implements SipService {
}).findFirst().orElse(null);
}
public List<SipProvider> getProviders(String transport) {
return sipConfig.getIp().stream().map(item -> getProvider(transport, item))
.filter(Objects::nonNull)
.toList();
}
public void sendResponse(SipProvider sipProvider, SendResponse response) {
log.info("{}", sipProvider);
ListeningPoint[] listeningPoints = sipProvider.getListeningPoints();
if (listeningPoints == null || listeningPoints.length == 0) {
log.error("发送响应失败, 未找到有效的监听地址");
return;
}
ListeningPoint listeningPoint = listeningPoints[0];
String ip = listeningPoint.getIPAddress();
int port = listeningPoint.getPort();
try {
sipProvider.sendResponse(response.build(sipProvider, ip, port));
} catch (SipException e) {
log.error("向{} {}:{} 发送响应失败, 异常: {}", ip, listeningPoint.getPort(), listeningPoint.getTransport(), e.getMessage());
}
}
public void sendResponse(String senderIp,String transport, SendResponse response) {
SipProvider sipProvider = getProvider(transport, senderIp);
sendResponse(sipProvider, response);
}
public void sendRequest(String transport, SendRequest request) {
getProviders(transport).parallelStream().forEach(sipProvider -> {
log.info("{}", sipProvider);
ListeningPoint[] listeningPoints = sipProvider.getListeningPoints();
if (listeningPoints == null || listeningPoints.length == 0) {
log.error("发送请求失败, 未找到有效的监听地址");
return;
}
ListeningPoint listeningPoint = listeningPoints[0];
String ip = listeningPoint.getIPAddress();
int port = listeningPoint.getPort();
try {
sipProvider.sendRequest(request.build(sipProvider, ip, port));
} catch (SipException e) {
log.error("向{} {}:{} 发送请求失败, 异常: {}", ip, listeningPoint.getPort(), listeningPoint.getTransport(), e.getMessage());
}
});
}
public interface SendRequest {
Request build(SipProvider provider, String ip, int port);
}
public interface SendResponse {
Response build(SipProvider provider, String ip, int port);
}
public void listen(String ip, int port){
try{
sipStack = (SipStackImpl)sipFactory.createSipStack(DefaultProperties.getProperties("GB28181_SIP"));
@ -156,7 +98,7 @@ public class SipServiceImpl implements SipService {
}
} catch (Exception e){
log.error("[sip] {}:{} 监听失败, 请检查端口是否被占用, 错误信息 => {}",ip,port, e.getMessage());
Runtime.getRuntime().exit(-1);
}
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.skcks.docking.gb28181.common.config.ProjectConfig;
import cn.skcks.docking.gb28181.core.sip.dto.RemoteInfo;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.Gb28181Sdp;
import gov.nist.javax.sip.address.AddressImpl;
import gov.nist.javax.sip.address.SipUri;
import gov.nist.javax.sip.header.Subject;
@ -18,6 +19,9 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import javax.sdp.SdpFactory;
import javax.sdp.SdpParseException;
import javax.sdp.SessionDescription;
import javax.sip.PeerUnavailableException;
import javax.sip.SipFactory;
import javax.sip.header.FromHeader;
@ -162,6 +166,37 @@ public class SipUtil implements ApplicationContextAware {
return builder.toString();
}
public static Gb28181Sdp parseSDP(String sdpStr) throws SdpParseException {
// jainSip不支持y= f=字段 移除以解析
int ssrcIndex = sdpStr.indexOf("y=");
int mediaDescriptionIndex = sdpStr.indexOf("f=");
// 检查是否有y字段
SessionDescription sdp;
String ssrc = null;
String mediaDescription = null;
if (mediaDescriptionIndex == 0 && ssrcIndex == 0) {
sdp = SdpFactory.getInstance().createSessionDescription(sdpStr);
}else {
String[] lines = sdpStr.split("\\r?\\n");
StringBuilder sdpBuffer = new StringBuilder();
for (String line : lines) {
if (line.trim().startsWith("y=")) {
ssrc = line.substring(2);
}else if (line.trim().startsWith("f=")) {
mediaDescription = line.substring(2);
}else {
sdpBuffer.append(line.trim()).append("\r\n");
}
}
sdp = SdpFactory.getInstance().createSessionDescription(sdpBuffer.toString());
}
return Gb28181Sdp.builder()
.baseSdb(sdp)
.ssrc(ssrc)
.mediaDescription(mediaDescription)
.build();
}
public static String getSsrcFromSdp(String sdpStr) {
// jainSip不支持y= f=字段 移除以解析

View File

@ -24,6 +24,5 @@ public class DockingOrmInitService {
public void init() {
log.info("[orm] 自动建表");
mapper.createDeviceTable();
mapper.createDeviceChannelTable();
}
}

View File

@ -1,79 +0,0 @@
package cn.skcks.docking.gb28181.service.catalog;
import cn.skcks.docking.gb28181.common.json.JsonException;
import cn.skcks.docking.gb28181.common.json.JsonResponse;
import cn.skcks.docking.gb28181.config.sip.SipConfig;
import cn.skcks.docking.gb28181.constant.CmdType;
import cn.skcks.docking.gb28181.core.sip.message.request.SipRequestBuilder;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.core.sip.service.SipService;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDevice;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDeviceChannel;
import cn.skcks.docking.gb28181.service.device.DeviceChannelService;
import cn.skcks.docking.gb28181.service.docking.device.cache.DockingDeviceCacheService;
import cn.skcks.docking.gb28181.sip.manscdp.catalog.query.CatalogQueryDTO;
import cn.skcks.docking.gb28181.sip.manscdp.catalog.response.CatalogItemDTO;
import cn.skcks.docking.gb28181.sip.method.message.request.MessageRequestBuilder;
import cn.skcks.docking.gb28181.sip.utils.MANSCDPUtils;
import cn.skcks.docking.gb28181.sip.utils.SipUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.sip.SipProvider;
import javax.sip.message.Request;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
@RequiredArgsConstructor
public class CatalogService {
private final SipService sipService;
private final DockingDeviceCacheService deviceCacheService;
private final SipConfig sipConfig;
private final SipSubscribe subscribe;
private final DeviceChannelService deviceChannelService;
@SneakyThrows
public CompletableFuture<List<CatalogItemDTO>> catalog(String gbDeviceId){
CompletableFuture<List<CatalogItemDTO>> result = new CompletableFuture<>();
result.completeOnTimeout(Collections.emptyList(), 60, TimeUnit.SECONDS);
DockingDevice device = deviceCacheService.getDevice(gbDeviceId);
if (device == null) {
log.info("未能找到 编码为 => {} 的设备", gbDeviceId);
result.completeExceptionally(new JsonException("未找到设备 " + gbDeviceId));
return result;
}
SipProvider provider = sipService.getProvider(device.getTransport(), device.getLocalIp());
MessageRequestBuilder requestBuilder = MessageRequestBuilder.builder()
.localIp(device.getLocalIp())
.localId(sipConfig.getId())
.localPort(sipConfig.getPort())
.transport(device.getTransport())
.targetId(device.getDeviceId())
.targetIp(device.getIp())
.targetPort(device.getPort())
.build();
String callId = provider.getNewCallId().getCallId();
long cSeq = SipRequestBuilder.getCSeq();
String sn = SipUtil.generateSn();
CatalogQueryDTO catalogQueryDTO = CatalogQueryDTO.builder()
.deviceId(gbDeviceId)
.sn(sn)
.build();
Request request = requestBuilder.createMessageRequest(callId, cSeq, MANSCDPUtils.toByteXml(catalogQueryDTO, device.getCharset()));
String key = GenericSubscribe.Helper.getKey(CmdType.CATALOG, gbDeviceId, sn);
subscribe.getSipRequestSubscribe().addPublisher(key, 60, TimeUnit.SECONDS);
subscribe.getSipRequestSubscribe().addSubscribe(key, new CatalogSubscriber(subscribe, key, result, device.getDeviceId(), deviceChannelService::add));
provider.sendRequest(request);
return result;
}
}

View File

@ -1,76 +0,0 @@
package cn.skcks.docking.gb28181.service.catalog;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDeviceChannel;
import cn.skcks.docking.gb28181.sip.manscdp.catalog.response.CatalogItemDTO;
import cn.skcks.docking.gb28181.sip.manscdp.catalog.response.CatalogResponseDTO;
import cn.skcks.docking.gb28181.sip.utils.MANSCDPUtils;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Flow;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
@Slf4j
@RequiredArgsConstructor
public class CatalogSubscriber implements Flow.Subscriber<SIPRequest>{
private final SipSubscribe subscribe;
private final String key;
private final CompletableFuture<List<CatalogItemDTO>> result;
private final String deviceId;
private final Consumer<? super DockingDeviceChannel> addDeviceChannelFunc;
private Flow.Subscription subscription;
private final AtomicLong num = new AtomicLong(0);
private long sumNum = 0;
private final List<CatalogItemDTO> data = new ArrayList<>();
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(SIPRequest item) {
CatalogResponseDTO catalogResponseDTO = MANSCDPUtils.parse(item.getRawContent(), CatalogResponseDTO.class);
sumNum = Math.max(sumNum,catalogResponseDTO.getSumNum());
long curNum = num.addAndGet(catalogResponseDTO.getDeviceList().getNum());
log.debug("当前获取数量: {}/{}", curNum, sumNum);
data.addAll(catalogResponseDTO.getDeviceList().getDeviceList());
if(curNum >= sumNum){
log.info("获取完成 {}", key);
subscribe.getSipRequestSubscribe().complete(key);
} else {
subscription.request(1);
}
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
onComplete();
}
@Override
public void onComplete() {
log.info("{} 返回结果 {}", key, result.complete(data));
data.stream().map(item->{
DockingDeviceChannel model = new DockingDeviceChannel();
model.setGbDeviceId(deviceId);
model.setGbDeviceChannelId(item.getDeviceId());
model.setName(item.getName());
model.setAddress(item.getAddress());
return model;
}).forEach(addDeviceChannelFunc);
subscribe.getSipRequestSubscribe().delPublisher(key);
}
}

View File

@ -1,112 +0,0 @@
package cn.skcks.docking.gb28181.service.device;
import cn.skcks.docking.gb28181.common.json.JsonException;
import cn.skcks.docking.gb28181.common.json.JsonUtils;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.mapper.DockingDeviceChannelDynamicSqlSupport;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.mapper.DockingDeviceChannelMapper;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDeviceChannel;
import com.github.pagehelper.ISelect;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.AndOrCriteriaGroup;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.mybatis.dynamic.sql.where.WhereDSL;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceChannelService {
private final DockingDeviceChannelMapper deviceChannelMapper;
public Optional<DockingDeviceChannel> getDeviceChannelById(Long id){
return deviceChannelMapper.selectOne(s->s.where(DockingDeviceChannelDynamicSqlSupport.id, isEqualTo(id)));
}
public List<DockingDeviceChannel> getDeviceByGbDeviceId(String gbDeviceId){
return deviceChannelMapper.select(s->
s.where(DockingDeviceChannelDynamicSqlSupport.gbDeviceId,isEqualTo(gbDeviceId)));
}
public Optional<DockingDeviceChannel> getDeviceChannel(String gbDeviceId,String gbDeviceChannelId){
return deviceChannelMapper.selectOne(s->
s.where()
.and(DockingDeviceChannelDynamicSqlSupport.gbDeviceId, isEqualTo(gbDeviceId))
.and(DockingDeviceChannelDynamicSqlSupport.gbDeviceChannelId, isEqualTo(gbDeviceChannelId))
.limit(1));
}
/**
* 分页查询
* @param page 页数
* @param size 数量
* @param select 查询语句
* @return 分页设备
*/
public PageInfo<DockingDeviceChannel> getWithPage(int page, int size, ISelect select){
PageInfo<DockingDeviceChannel> pageInfo;
try (Page<DockingDeviceChannel> startPage = PageHelper.startPage(page, size)) {
pageInfo = startPage.doSelectPageInfo(select);
}
return pageInfo;
}
public PageInfo<DockingDeviceChannel> getWithPage(int page, int size){
ISelect select = () -> deviceChannelMapper.select(u -> u.orderBy(DockingDeviceChannelDynamicSqlSupport.id.descending()));
return getWithPage(page, size, select);
}
@SneakyThrows
@Transactional
public boolean add(DockingDeviceChannel model){
if(model == null){
return false;
}
String gbDeviceId = model.getGbDeviceId();
String gbDeviceChannelId = model.getGbDeviceChannelId();
if(StringUtils.isAnyBlank(gbDeviceChannelId,gbDeviceId)){
throw new JsonException("国标id 或 通道id 不能为空");
}
Optional<DockingDeviceChannel> deviceChannel = getDeviceChannel(gbDeviceId, gbDeviceChannelId);
log.info("{}", JsonUtils.toJson(deviceChannel.orElse(null)));
return deviceChannelMapper.insert(model) > 0;
}
@SneakyThrows
@Transactional
public boolean del(DockingDeviceChannel model){
if(model == null){
return false;
}
Long id = model.getId();
String gbDeviceId = model.getGbDeviceId();
String gbDeviceChannelId = model.getGbDeviceChannelId();
if(id != null){
return deviceChannelMapper.deleteByPrimaryKey(id) > 0;
}
if(StringUtils.isBlank(gbDeviceId)){
throw new JsonException("国标id 不能为空");
}
if(StringUtils.isBlank(gbDeviceChannelId)){
return deviceChannelMapper.delete(d->d.where(DockingDeviceChannelDynamicSqlSupport.gbDeviceId, isEqualTo(gbDeviceId))) > 0;
}
return deviceChannelMapper.delete(d->d.where(DockingDeviceChannelDynamicSqlSupport.gbDeviceId, isEqualTo(gbDeviceId))) > 0;
}
}

View File

@ -14,7 +14,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.sip.message.Response;
@ -49,7 +48,6 @@ public class DockingDeviceService {
return onlineCacheService.isOnline(deviceId);
}
@Transactional
public void online(DockingDevice device, Response response) {
String deviceId = device.getDeviceId();
log.info("[设备上线] deviceId => {}, {}://{}:{}", deviceId, device.getTransport(), device.getIp(), device.getPort());

View File

@ -1,66 +0,0 @@
package cn.skcks.docking.gb28181.service.notify;
import cn.hutool.core.collection.CollectionUtil;
import cn.skcks.docking.gb28181.common.json.JsonUtils;
import cn.skcks.docking.gb28181.common.redis.RedisUtil;
import cn.skcks.docking.gb28181.config.sip.SipConfig;
import cn.skcks.docking.gb28181.core.sip.dto.SipTransactionInfo;
import cn.skcks.docking.gb28181.core.sip.gb28181.cache.CacheUtil;
import cn.skcks.docking.gb28181.core.sip.message.sender.SipMessageSender;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.sdp.GB28181SDPBuilder;
import cn.skcks.docking.gb28181.sip.manscdp.mediastatus.notify.MediaStatusRequestDTO;
import cn.skcks.docking.gb28181.sip.method.invite.request.InviteRequestBuilder;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.sip.address.SipURI;
import javax.sip.message.Request;
import java.util.Set;
@RequiredArgsConstructor
@Slf4j
@Service
public class MediaStatusService {
private final SipConfig sipConfig;
private final SipMessageSender sender;
private final SipSubscribe subscribe;
public void process(SIPRequest request, MediaStatusRequestDTO dto){
String senderIp = request.getLocalAddress().getHostAddress();
String deviceId = ((SipURI)request.getFromHeader().getAddress().getURI()).getUser();
if(StringUtils.equalsIgnoreCase(dto.getNotifyType(),"121")){
InviteRequestBuilder inviteRequestBuilder = InviteRequestBuilder.builder()
.localIp(request.getLocalAddress().getHostAddress())
.localPort(sipConfig.getPort())
.localId(((SipURI)request.getToHeader().getAddress().getURI()).getUser())
.targetIp(request.getRemoteAddress().getHostAddress())
.targetPort(request.getRemotePort())
.targetId(((SipURI)request.getFromHeader().getAddress().getURI()).getUser())
.transport(request.getTopmostViaHeader().getTransport())
.build();
String keyPattern = CacheUtil.getKey(GB28181SDPBuilder.Action.PLAY_BACK.getAction(), deviceId,"*");
Set<String> keys = RedisUtil.KeyOps.keys(keyPattern);
if (CollectionUtil.isEmpty(keys)){
// 实在找不到就原样发回去 ()
sender.send(senderIp, inviteRequestBuilder.createByeRequest(request.getCallId().getCallId(), request.getCSeq().getSeqNumber() + 1));
} else {
keys.forEach(key -> {
String json = RedisUtil.StringOps.get(key);
if(StringUtils.isNotBlank(json)){
log.debug("{} {}",key,json);
SipTransactionInfo transactionInfo = JsonUtils.parse(json, SipTransactionInfo.class);
String callId = transactionInfo.getCallId();
String subscribeKey = GenericSubscribe.Helper.getKey(Request.BYE, callId);
log.debug("{} {}",callId,subscribeKey);
subscribe.getSipRequestSubscribe().getPublisher(subscribeKey).submit(request);
}
});
}
}
}
}

View File

@ -1,19 +1,20 @@
package cn.skcks.docking.gb28181.service.play;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.skcks.docking.gb28181.common.json.JsonResponse;
import cn.skcks.docking.gb28181.common.json.JsonUtils;
import cn.skcks.docking.gb28181.common.redis.RedisUtil;
import cn.skcks.docking.gb28181.config.sip.SipConfig;
import cn.skcks.docking.gb28181.core.sip.dto.SipTransactionInfo;
import cn.skcks.docking.gb28181.core.sip.gb28181.cache.CacheUtil;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.MediaSdpHelper;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.StreamMode;
import cn.skcks.docking.gb28181.core.sip.message.processor.MessageProcessor;
import cn.skcks.docking.gb28181.core.sip.message.request.SipRequestBuilder;
import cn.skcks.docking.gb28181.core.sip.message.sender.SipMessageSender;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericTimeoutSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.core.sip.service.SipService;
import cn.skcks.docking.gb28181.core.sip.utils.SipUtil;
import cn.skcks.docking.gb28181.media.config.ZlmMediaConfig;
import cn.skcks.docking.gb28181.media.dto.rtp.CloseRtpServer;
import cn.skcks.docking.gb28181.media.dto.rtp.GetRtpInfoResp;
@ -22,15 +23,8 @@ import cn.skcks.docking.gb28181.media.dto.rtp.OpenRtpServerResp;
import cn.skcks.docking.gb28181.media.dto.status.ResponseStatus;
import cn.skcks.docking.gb28181.media.proxy.ZlmMediaService;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDevice;
import cn.skcks.docking.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.sdp.GB28181SDPBuilder;
import cn.skcks.docking.gb28181.sdp.media.MediaStreamMode;
import cn.skcks.docking.gb28181.service.docking.device.DockingDeviceService;
import cn.skcks.docking.gb28181.service.ssrc.SsrcService;
import cn.skcks.docking.gb28181.sip.method.invite.request.InviteRequestBuilder;
import cn.skcks.docking.gb28181.sip.method.invite.response.InviteResponseBuilder;
import cn.skcks.docking.gb28181.sip.utils.SipUtil;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@ -47,7 +41,6 @@ import javax.sip.message.Request;
import javax.sip.message.Response;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.Flow;
import java.util.concurrent.TimeUnit;
@ -62,7 +55,6 @@ public class PlayService {
private final SipService sipService;
private final SipMessageSender sender;
private final SipSubscribe subscribe;
private final SipConfig sipConfig;
private String videoUrl(String streamId) {
return StringUtils.joinWith("/", zlmMediaConfig.getUrl(), "rtp", streamId + ".live.flv");
@ -100,7 +92,7 @@ public class PlayService {
}
@SneakyThrows
private JsonResponse<Void> closeStream(String streamId, GB28181SDPBuilder.Action action, DockingDevice device, String channelId) {
private JsonResponse<Void> closeStream(String streamId, MediaSdpHelper.Action action, DockingDevice device, String channelId) {
zlmMediaService.closeRtpServer(new CloseRtpServer(streamId));
String key = CacheUtil.getKey(action.getAction(), device.getDeviceId(), channelId);
SipTransactionInfo transactionInfo = JsonUtils.parse(RedisUtil.StringOps.get(key), SipTransactionInfo.class);
@ -131,8 +123,8 @@ public class PlayService {
return result;
}
String streamId = GB28181SDPBuilder.getStreamId(deviceId, channelId);
String key = CacheUtil.getKey(GB28181SDPBuilder.Action.PLAY.getAction(), deviceId, channelId);
String streamId = MediaSdpHelper.getStreamId(deviceId, channelId);
String key = CacheUtil.getKey(MediaSdpHelper.Action.PLAY.getAction(), deviceId, channelId);
if (RedisUtil.KeyOps.hasKey(key)) {
result.setResult(JsonResponse.success(videoUrl(streamId)));
return result;
@ -147,30 +139,22 @@ public class PlayService {
}
String ssrc = ssrcService.getPlaySsrc();
GB28181Description description = MediaSdpHelper.play(deviceId, channelId, Connection.IP4, ip, port, ssrc, StreamMode.of(device.getStreamMode()));
String transport = device.getTransport();
String senderIp = device.getLocalIp();
SipProvider provider = sipService.getProvider(transport, senderIp);
CallIdHeader callIdHeader = provider.getNewCallId();
String callId = callIdHeader.getCallId();
InviteRequestBuilder inviteRequestBuilder = InviteRequestBuilder.builder()
.localId(sipConfig.getId())
.localPort(sipConfig.getPort())
.localIp(device.getLocalIp())
.targetId(deviceId)
.targetIp(device.getIp())
.targetPort(device.getPort())
.transport(device.getTransport())
.build();
Request request = inviteRequestBuilder.createPlayInviteRequest(callId, SipRequestBuilder.getCSeq(),channelId,ip,port,ssrc,MediaStreamMode.of(device.getStreamMode()));
String subscribeKey = GenericSubscribe.Helper.getKey(Request.INVITE, callIdHeader.getCallId());
subscribe.getSipResponseSubscribe().addPublisher(subscribeKey);
CallIdHeader callId = provider.getNewCallId();
Request request = SipRequestBuilder.createInviteRequest(device, channelId, description.toString(), SipUtil.generateViaTag(), SipUtil.generateFromTag(), null, ssrc, callId);
String subscribeKey = GenericSubscribe.Helper.getKey(MessageProcessor.Method.INVITE, callId.getCallId());
subscribe.getInviteSubscribe().addPublisher(subscribeKey);
Flow.Subscriber<SIPResponse> subscriber = new Flow.Subscriber<>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
log.info("订阅 {} {}", Request.INVITE, subscribeKey);
log.info("订阅 {} {}", MessageProcessor.Method.INVITE, subscribeKey);
subscription.request(1);
}
@ -179,15 +163,15 @@ public class PlayService {
int statusCode = item.getStatusCode();
log.debug("{} 收到订阅消息 {}", subscribeKey, item);
if (statusCode == Response.TRYING) {
log.info("订阅 {} {} 尝试连接流媒体服务", Request.INVITE, subscribeKey);
log.info("订阅 {} {} 尝试连接流媒体服务", MessageProcessor.Method.INVITE, subscribeKey);
subscription.request(1);
} else if (statusCode >= Response.OK && statusCode < Response.MULTIPLE_CHOICES) {
log.info("订阅 {} {} 流媒体服务连接成功, 开始推送视频流", Request.INVITE, subscribeKey);
log.info("订阅 {} {} 流媒体服务连接成功, 开始推送视频流", MessageProcessor.Method.INVITE, subscribeKey);
RedisUtil.StringOps.set(key, JsonUtils.toCompressJson(new SipTransactionInfo(item, ssrc)));
result.setResult(JsonResponse.success(videoUrl(streamId)));
onComplete();
} else {
log.info("订阅 {} {} 连接流媒体服务时出现异常, 终止订阅", Request.INVITE, subscribeKey);
log.info("订阅 {} {} 连接流媒体服务时出现异常, 终止订阅", MessageProcessor.Method.INVITE, subscribeKey);
RedisUtil.KeyOps.delete(key);
result.setResult(JsonResponse.error("连接流媒体服务失败"));
ssrcService.releaseSsrc(zlmMediaConfig.getId(), ssrc);
@ -202,17 +186,13 @@ public class PlayService {
@Override
public void onComplete() {
subscribe.getSipResponseSubscribe().delPublisher(subscribeKey);
subscribe.getInviteSubscribe().delPublisher(subscribeKey);
}
};
// 1小时自动关闭
byeSubscribe(inviteRequestBuilder,provider,callId,3600,()->{
RedisUtil.KeyOps.delete(key);
});
subscribe.getSipResponseSubscribe().addSubscribe(subscribeKey, subscriber);
subscribe.getInviteSubscribe().addSubscribe(subscribeKey, subscriber);
sender.send(senderIp, request);
result.onTimeout(() -> {
subscribe.getSipResponseSubscribe().delPublisher(subscribeKey);
subscribe.getInviteSubscribe().delPublisher(subscribeKey);
result.setResult(JsonResponse.error("点播超时"));
});
return result;
@ -226,8 +206,8 @@ public class PlayService {
return JsonResponse.error(null, "未找到设备");
}
String streamId = GB28181SDPBuilder.getStreamId(deviceId, channelId);
return closeStream(streamId, GB28181SDPBuilder.Action.PLAY, device, channelId);
String streamId = MediaSdpHelper.getStreamId(deviceId, channelId);
return closeStream(streamId, MediaSdpHelper.Action.PLAY, device, channelId);
}
@SneakyThrows
@ -235,13 +215,13 @@ public class PlayService {
DockingDevice device = deviceService.getDevice(deviceId);
long start = startTime.toInstant().getEpochSecond();
long end = endTime.toInstant().getEpochSecond();
String streamId = GB28181SDPBuilder.getStreamId(deviceId, channelId, String.valueOf(start), String.valueOf(end));
String streamId = MediaSdpHelper.getStreamId(deviceId, channelId, String.valueOf(start), String.valueOf(end));
DeferredResult<JsonResponse<String>> result = makeResult(deviceId, channelId, timeout, device);
if (result.hasResult()) {
return result;
}
String key = CacheUtil.getKey(GB28181SDPBuilder.Action.PLAY_BACK.getAction(), deviceId, channelId);
String key = CacheUtil.getKey(MediaSdpHelper.Action.PLAY_BACK.getAction(), deviceId, channelId);
if (RedisUtil.KeyOps.hasKey(key)) {
result.setResult(JsonResponse.success(videoUrl(streamId)));
return result;
@ -255,32 +235,24 @@ public class PlayService {
return result;
}
String ssrc = ssrcService.getPlayBackSsrc();
String ssrc = ssrcService.getPlaySsrc();
GB28181Description description = MediaSdpHelper.playback(deviceId, channelId, Connection.IP4, ip, port, ssrc, StreamMode.of(device.getStreamMode()), startTime, endTime);
String transport = device.getTransport();
String senderIp = device.getLocalIp();
SipProvider provider = sipService.getProvider(transport, senderIp);
CallIdHeader callIdHeader = provider.getNewCallId();
String callId = callIdHeader.getCallId();
InviteRequestBuilder inviteRequestBuilder = InviteRequestBuilder.builder()
.localId(sipConfig.getId())
.localPort(sipConfig.getPort())
.localIp(device.getLocalIp())
.targetId(deviceId)
.targetIp(device.getIp())
.targetPort(device.getPort())
.transport(device.getTransport())
.build();
Request request = inviteRequestBuilder.createPlaybackInviteRequest(callId, SipRequestBuilder.getCSeq(),channelId,ip,port,ssrc,MediaStreamMode.of(device.getStreamMode()),startTime,endTime);
String subscribeKey = GenericSubscribe.Helper.getKey(Request.INVITE, callIdHeader.getCallId());
subscribe.getSipResponseSubscribe().addPublisher(subscribeKey);
CallIdHeader callId = provider.getNewCallId();
Request request = SipRequestBuilder.createInviteRequest(device, channelId, description.toString(), SipUtil.generateViaTag(), SipUtil.generateFromTag(), null, ssrc, callId);
String subscribeKey = GenericSubscribe.Helper.getKey(MessageProcessor.Method.INVITE, callId.getCallId());
subscribe.getInviteSubscribe().addPublisher(subscribeKey);
Flow.Subscriber<SIPResponse> subscriber = new Flow.Subscriber<>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
log.info("订阅 {} {}", Request.INVITE, subscribeKey);
log.info("订阅 {} {}", MessageProcessor.Method.INVITE, subscribeKey);
subscription.request(1);
}
@ -289,16 +261,15 @@ public class PlayService {
int statusCode = item.getStatusCode();
log.debug("{} 收到订阅消息 {}", subscribeKey, item);
if (statusCode == Response.TRYING) {
log.info("订阅 {} {} 尝试连接流媒体服务", Request.INVITE, subscribeKey);
log.info("订阅 {} {} 尝试连接流媒体服务", MessageProcessor.Method.INVITE, subscribeKey);
subscription.request(1);
} else if (statusCode >= Response.OK && statusCode < Response.MULTIPLE_CHOICES) {
log.info("订阅 {} {} 流媒体服务连接成功, 开始推送视频流", Request.INVITE, subscribeKey);
log.info("订阅 {} {} 流媒体服务连接成功, 开始推送视频流", MessageProcessor.Method.INVITE, subscribeKey);
RedisUtil.StringOps.set(key, JsonUtils.toCompressJson(new SipTransactionInfo(item, ssrc)));
RedisUtil.KeyOps.expire(key, DateUtil.between(startTime, endTime, DateUnit.SECOND) + 30, TimeUnit.SECONDS);
result.setResult(JsonResponse.success(videoUrl(streamId)));
onComplete();
} else {
log.info("订阅 {} {} 连接流媒体服务时出现异常, 终止订阅", Request.INVITE, subscribeKey);
log.info("订阅 {} {} 连接流媒体服务时出现异常, 终止订阅", MessageProcessor.Method.INVITE, subscribeKey);
RedisUtil.KeyOps.delete(key);
result.setResult(JsonResponse.error("连接流媒体服务失败"));
ssrcService.releaseSsrc(zlmMediaConfig.getId(), ssrc);
@ -313,62 +284,18 @@ public class PlayService {
@Override
public void onComplete() {
subscribe.getSipResponseSubscribe().delPublisher(subscribeKey);
subscribe.getRecordInfoSubscribe().delPublisher(subscribeKey);
}
};
byeSubscribe(inviteRequestBuilder,provider,callId,DateUtil.between(startTime,endTime,DateUnit.SECOND),()->{
RedisUtil.KeyOps.delete(key);
});
subscribe.getSipResponseSubscribe().addSubscribe(subscribeKey, subscriber);
subscribe.getInviteSubscribe().addSubscribe(subscribeKey, subscriber);
sender.send(senderIp, request);
result.onTimeout(() -> {
subscribe.getSipResponseSubscribe().delPublisher(subscribeKey);
subscribe.getInviteSubscribe().delPublisher(subscribeKey);
result.setResult(JsonResponse.error("点播超时"));
});
return result;
}
public void byeSubscribe(InviteRequestBuilder inviteRequestBuilder,SipProvider provider, String callId, long seconds, Runnable cb){
GenericTimeoutSubscribe<SIPRequest> sipRequestSubscribe = subscribe.getSipRequestSubscribe();
String subscribeKey = GenericSubscribe.Helper.getKey(Request.BYE, callId);
sipRequestSubscribe.addPublisher(subscribeKey,seconds + 30,TimeUnit.SECONDS);
Flow.Subscriber<SIPRequest> subscriber = new Flow.Subscriber<>(){
SIPRequest request;
@Override
public void onSubscribe(Flow.Subscription subscription) {
subscription.request(1);
}
@Override
@SneakyThrows
public void onNext(SIPRequest item) {
sipRequestSubscribe.delPublisher(GenericSubscribe.Helper.getKey(Request.INVITE, callId));
request = item;
sipRequestSubscribe.complete(subscribeKey);
}
@Override
public void onError(Throwable throwable) {
}
@SneakyThrows
@Override
public void onComplete() {
if(request != null && Objects.equals(request.getMethod(), Request.BYE)){
Response byeResponse = InviteResponseBuilder.builder().build().createByeResponse(request, SipUtil.nanoId());
provider.sendResponse(byeResponse);
} else {
Request byeRequest = inviteRequestBuilder.createByeRequest(callId, SipRequestBuilder.getCSeq());
provider.sendRequest(byeRequest);
}
cb.run();
sipRequestSubscribe.delPublisher(subscribeKey);
}
};
sipRequestSubscribe.addSubscribe(subscribeKey,subscriber);
}
@SneakyThrows
public JsonResponse<Void> recordStop(String deviceId, String channelId, Date startTime, Date endTime) {
DockingDevice device = deviceService.getDevice(deviceId);
@ -379,8 +306,8 @@ public class PlayService {
long start = startTime.toInstant().getEpochSecond();
long end = endTime.toInstant().getEpochSecond();
String streamId = GB28181SDPBuilder.getStreamId(deviceId, channelId, String.valueOf(start), String.valueOf(end));
return closeStream(streamId, GB28181SDPBuilder.Action.PLAY_BACK, device, channelId);
String streamId = MediaSdpHelper.getStreamId(deviceId, channelId, String.valueOf(start), String.valueOf(end));
return closeStream(streamId, MediaSdpHelper.Action.PLAY_BACK, device, channelId);
}
}

View File

@ -3,26 +3,21 @@ package cn.skcks.docking.gb28181.service.record;
import cn.hutool.core.date.DateUtil;
import cn.skcks.docking.gb28181.common.json.JsonResponse;
import cn.skcks.docking.gb28181.common.json.ResponseStatus;
import cn.skcks.docking.gb28181.config.sip.SipConfig;
import cn.skcks.docking.gb28181.common.xml.XmlUtils;
import cn.skcks.docking.gb28181.core.sip.gb28181.constant.CmdType;
import cn.skcks.docking.gb28181.core.sip.message.processor.message.types.recordinfo.query.dto.RecordInfoRequestDTO;
import cn.skcks.docking.gb28181.core.sip.message.processor.message.types.recordinfo.reponse.dto.RecordInfoItemDTO;
import cn.skcks.docking.gb28181.core.sip.message.processor.message.types.recordinfo.reponse.dto.RecordInfoResponseDTO;
import cn.skcks.docking.gb28181.core.sip.message.request.SipRequestBuilder;
import cn.skcks.docking.gb28181.core.sip.message.sender.SipMessageSender;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.GenericSubscribe;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.core.sip.service.SipService;
import cn.skcks.docking.gb28181.core.sip.utils.SipUtil;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDevice;
import cn.skcks.docking.gb28181.orm.mybatis.dynamic.model.DockingDeviceChannel;
import cn.skcks.docking.gb28181.service.device.DeviceChannelService;
import cn.skcks.docking.gb28181.service.docking.device.DockingDeviceService;
import cn.skcks.docking.gb28181.service.record.convertor.RecordConvertor;
import cn.skcks.docking.gb28181.service.record.vo.RecordInfoItemVO;
import cn.skcks.docking.gb28181.sip.manscdp.recordinfo.request.RecordInfoRequestDTO;
import cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response.RecordInfoItemDTO;
import cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response.RecordInfoResponseDTO;
import cn.skcks.docking.gb28181.sip.method.message.request.MessageRequestBuilder;
import cn.skcks.docking.gb28181.sip.utils.MANSCDPUtils;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@ -30,8 +25,11 @@ import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.SipProvider;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Request;
import java.util.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
@ -40,11 +38,11 @@ import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class RecordService {
private final SipConfig sipConfig;
private final DockingDeviceService deviceService;
private final DeviceChannelService deviceChannelService;
private final SipService sipService;
private final SipMessageSender sender;
private final SipSubscribe subscribe;
/**
*
* @param deviceId 设备id
@ -52,55 +50,99 @@ public class RecordService {
* @param date 查询日期
*/
@SneakyThrows
public DeferredResult<JsonResponse<List<RecordInfoItemVO>>> requestRecordInfo(String deviceId, String channelId, long timeout, Date date) {
public DeferredResult<JsonResponse<List<RecordInfoItemVO>>> requestRecordInfo(String deviceId, long timeout, Date date) {
log.info("查询 设备 => {} {} 的历史媒体记录, 超时时间 {} 秒", deviceId, DateUtil.formatDate(date), timeout);
DeferredResult<JsonResponse<List<RecordInfoItemVO>>> result = new DeferredResult<>(TimeUnit.SECONDS.toMillis(timeout));
DockingDevice device = deviceService.getDevice(deviceId);
if (device == null) {
log.info("未能找到 编码为 => {} 的设备", deviceId);
result.setResult(JsonResponse.error("未找到设备"));
return result;
}
Optional<DockingDeviceChannel> deviceChannel = deviceChannelService.getDeviceChannel(deviceId, channelId);
if(deviceChannel.isEmpty()){
log.info("未能找到 设备编码为 => {}, 通道 => {} 的信息", deviceId, channelId);
result.setResult(JsonResponse.error("未找到通道信息"));
result.setResult(JsonResponse.error(null, "未找到设备"));
return result;
}
String transport = device.getTransport();
String localIp = device.getLocalIp();
SipProvider provider = sipService.getProvider(transport, localIp);
String callId = provider.getNewCallId().getCallId();
String senderIp = device.getLocalIp();
SipProvider provider = sipService.getProvider(transport, senderIp);
CallIdHeader callId = provider.getNewCallId();
String sn = String.valueOf((int) (Math.random() * 9 + 1) * 100000);
MessageRequestBuilder requestBuilder = MessageRequestBuilder.builder()
.targetIp(device.getIp())
.targetPort(device.getPort())
.targetId(deviceId)
.localId(sipConfig.getId())
.localIp(localIp)
.localPort(sipConfig.getPort())
.transport(transport)
.build();
RecordInfoRequestDTO dto = RecordInfoRequestDTO.builder()
.deviceId(channelId)
.deviceId(deviceId)
.startTime(DateUtil.beginOfDay(date))
.endTime(DateUtil.endOfDay(date))
.sn(sn)
.build();
Request request = requestBuilder.createMessageRequest(callId,SipRequestBuilder.getCSeq(), MANSCDPUtils.toByteXml(dto, device.getCharset()));
String key = GenericSubscribe.Helper.getKey(CmdType.RECORD_INFO, channelId, sn);
subscribe.getSipRequestSubscribe().addPublisher(key);
subscribe.getSipRequestSubscribe().addSubscribe(key, new RecordSubscriber(subscribe, key, result, deviceId));
Request request = SipRequestBuilder.createMessageRequest(device,
XmlUtils.toXml(dto),
SipUtil.generateViaTag(),
SipUtil.generateFromTag(),
null,
callId);
String key = GenericSubscribe.Helper.getKey(CmdType.RECORD_INFO, deviceId, sn);
subscribe.getRecordInfoSubscribe().addPublisher(key);
List<RecordInfoItemDTO> list = new ArrayList<>();
AtomicLong atomicSum = new AtomicLong(0);
AtomicLong atomicNum = new AtomicLong(0);
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
final ScheduledFuture<?>[] schedule = new ScheduledFuture<?>[1];
Flow.Subscriber<RecordInfoResponseDTO> subscriber = new Flow.Subscriber<>() {
Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
log.debug("建立订阅 => {}", key);
subscription.request(1);
}
@Override
public void onNext(RecordInfoResponseDTO item) {
atomicSum.set(item.getSumNum());
atomicNum.getAndAdd(item.getRecordList().size());
list.addAll(item.getRecordList());
long num = atomicNum.get();
long sum = atomicSum.get();
if(num > sum){
log.warn("检测到 设备 => {}, 未按规范实现, 订阅 => {}, 期望总数为 => {}, 已接收数量 => {}", deviceId, key, atomicSum.get(), atomicNum.get());
} else {
log.info("获取订阅 => {}, {}/{}", key, atomicNum.get(), atomicSum.get());
}
if (num >= sum) {
// 针对某些不按规范的设备
// 如果已获取数量 >= 约定的总数
// 就执行定时任务, 500ms 内未收到新的数据视为已结束
if(schedule[0] != null){
schedule[0].cancel(true);
}
schedule[0] = scheduledExecutorService.schedule(this::onComplete, 500, TimeUnit.MILLISECONDS);
}
subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
schedule[0].cancel(true);
subscribe.getRecordInfoSubscribe().delPublisher(key);
result.setResult(JsonResponse.success(RecordConvertor.INSTANCE.dto2Vo(sortedRecordList(list))));
log.debug("订阅结束 => {}", key);
}
};
subscribe.getRecordInfoSubscribe().addSubscribe(key, subscriber);
sender.send(senderIp, request);
result.onTimeout(() -> {
result.setResult(JsonResponse.build(ResponseStatus.PARTIAL_CONTENT,
RecordConvertor.INSTANCE.dto2Vo(Collections.emptyList()),
RecordConvertor.INSTANCE.dto2Vo(sortedRecordList(list)),
"查询超时, 结果可能不完整"));
subscribe.getSipRequestSubscribe().delPublisher(key);
subscribe.getRecordInfoSubscribe().delPublisher(key);
});
provider.sendRequest(request);
return result;
}

View File

@ -1,81 +0,0 @@
package cn.skcks.docking.gb28181.service.record;
import cn.hutool.core.date.DateUtil;
import cn.skcks.docking.gb28181.common.json.JsonResponse;
import cn.skcks.docking.gb28181.core.sip.message.subscribe.SipSubscribe;
import cn.skcks.docking.gb28181.service.record.convertor.RecordConvertor;
import cn.skcks.docking.gb28181.service.record.vo.RecordInfoItemVO;
import cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response.RecordInfoItemDTO;
import cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response.RecordInfoResponseDTO;
import cn.skcks.docking.gb28181.sip.utils.MANSCDPUtils;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Flow;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
public class RecordSubscriber implements Flow.Subscriber<SIPRequest>{
private final SipSubscribe subscribe;
private final String key;
private final DeferredResult<JsonResponse<List<RecordInfoItemVO>>> result;
private final String deviceId;
private final List<RecordInfoItemDTO> list = new ArrayList<>();
private final AtomicLong atomicSum = new AtomicLong(0);
private final AtomicLong atomicNum = new AtomicLong(0);
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
log.debug("建立订阅 => {}", key);
subscription.request(1);
}
@Override
public void onNext(SIPRequest item) {
RecordInfoResponseDTO data = MANSCDPUtils.parse(item.getRawContent(), RecordInfoResponseDTO.class);
atomicSum.set(Math.max(data.getSumNum(), atomicNum.get()));
atomicNum.addAndGet(data.getRecordList().getNum());
list.addAll(data.getRecordList().getRecordList());
long num = atomicNum.get();
long sum = atomicSum.get();
if(num > sum){
log.warn("检测到 设备 => {}, 未按规范实现, 订阅 => {}, 期望总数为 => {}, 已接收数量 => {}", deviceId, key, atomicSum.get(), atomicNum.get());
} else {
log.info("获取订阅 => {}, {}/{}", key, atomicNum.get(), atomicSum.get());
}
if (num >= sum) {
// 针对某些不按规范的设备
// 如果已获取数量 >= 约定的总数
// 就执行定时任务, 500ms 内未收到新的数据视为已结束
subscribe.getSipRequestSubscribe().refreshPublisher(key,500, TimeUnit.MILLISECONDS);
}
subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onComplete() {
result.setResult(JsonResponse.success(RecordConvertor.INSTANCE.dto2Vo(sortedRecordList(list))));
log.debug("订阅结束 => {}", key);
subscribe.getSipRequestSubscribe().delPublisher(key);
}
private List<RecordInfoItemDTO> sortedRecordList(List<RecordInfoItemDTO> list){
return list.stream().sorted((a,b)-> DateUtil.compare(a.getStartTime(),b.getStartTime())).collect(Collectors.toList());
}
}

View File

@ -1,7 +1,7 @@
package cn.skcks.docking.gb28181.service.record.convertor;
import cn.skcks.docking.gb28181.core.sip.message.processor.message.types.recordinfo.reponse.dto.RecordInfoItemDTO;
import cn.skcks.docking.gb28181.service.record.vo.RecordInfoItemVO;
import cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response.RecordInfoItemDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

View File

@ -81,7 +81,7 @@ public class SsrcService {
/**
* 获取录像回放的SSRC值,第一位固定为1
*/
public String getPlayBackSsrc() {
public String getPlayBackSsrc(String mediaServerI) {
return "1" + getSN();
}

View File

@ -1,51 +0,0 @@
package cn.skcks.docking.gb28181.utils;
import cn.skcks.docking.gb28181.common.json.JsonResponse;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class FutureDeferredResult {
public static <T> DeferredResult<JsonResponse<T>> toDeferredResultWithJson(CompletableFuture<T> future){
DeferredResult<JsonResponse<T>> result = new DeferredResult<>();
future.whenComplete((data,throwable)->{
if(throwable!= null){
result.setResult(JsonResponse.error(throwable.getMessage()));
return;
}
result.setResult(JsonResponse.success(data));
});
return result;
}
public static <T> DeferredResult<JsonResponse<T>> toDeferredResultWithJsonAndTimeout(CompletableFuture<T> future, long time, TimeUnit timeUnit){
DeferredResult<JsonResponse<T>> result = new DeferredResult<>(timeUnit.toMillis(time));
result.onTimeout(()-> result.setResult(JsonResponse.error("请求超时")));
future.whenComplete((data,throwable)->{
if(throwable!= null){
result.setResult(JsonResponse.error(throwable.getMessage()));
return;
}
result.setResult(JsonResponse.success(data));
});
return result;
}
public static <T> DeferredResult<T> toDeferredResult(CompletableFuture<T> future){
DeferredResult<T> result = new DeferredResult<>();
future.whenComplete((data,throwable)->{
result.setResult(data);
});
return result;
}
public static <T> DeferredResult<T> toDeferredResultWithTimeout(CompletableFuture<T> future, T timeoutResult,long time, TimeUnit timeUnit){
DeferredResult<T> result = new DeferredResult<>(timeUnit.toMillis(time), timeoutResult);
future.completeOnTimeout(timeoutResult,time,timeUnit);
future.whenComplete((data, throwable) -> {
result.setResult(data);
});
return result;
}
}

View File

@ -1,24 +1,23 @@
package cn.skcks.docking.gb28181.core.sip.message.event;
import cn.hutool.core.util.RandomUtil;
import cn.skcks.docking.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.sdp.GB28181SDPBuilder;
import cn.skcks.docking.gb28181.sdp.field.ssrc.SsrcField;
import cn.skcks.docking.gb28181.sdp.media.MediaStreamMode;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.MediaSdpHelper;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.SsrcField;
import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.StreamMode;
import gov.nist.core.NameValue;
import gov.nist.core.Separators;
import gov.nist.javax.sdp.fields.AttributeField;
import gov.nist.javax.sdp.fields.ConnectionField;
import gov.nist.javax.sdp.fields.URIField;
import gov.nist.javax.sdp.MediaDescriptionImpl;
import gov.nist.javax.sdp.SessionDescriptionImpl;
import gov.nist.javax.sdp.fields.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import javax.sdp.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Vector;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@ -125,8 +124,7 @@ public class SipEventTest {
@Test
@SneakyThrows
public void sdpTest() {
GB28181Description description = new GB28181Description(SdpFactory.getInstance().createSessionDescription("Play"));
GB28181Description description = GB28181Description.Convertor.convert((SessionDescriptionImpl) SdpFactory.getInstance().createSessionDescription("Play"));
Version version = SdpFactory.getInstance().createVersion(0);
description.setVersion(version);
@ -134,10 +132,10 @@ public class SipEventTest {
Connection connectionField = SdpFactory.getInstance().createConnection(ConnectionField.IN, Connection.IP4, "10.10.10.20");
description.setConnection(connectionField);
MediaDescription mediaDescription = SdpFactory.getInstance().createMediaDescription("video", 6666, 0, SdpConstants.RTP_AVP, GB28181SDPBuilder.RTPMAP.keySet().toArray(new String[0]));
MediaDescription mediaDescription = SdpFactory.getInstance().createMediaDescription("video", 6666, 0, SdpConstants.RTP_AVP, MediaSdpHelper.RTPMAP.keySet().toArray(new String[0]));
mediaDescription.addAttribute((AttributeField)SdpFactory.getInstance().createAttribute("recvonly",null));
GB28181SDPBuilder.RTPMAP.forEach((k, v)->{
Optional.ofNullable(GB28181SDPBuilder.FMTP.get(k)).ifPresent((f)->{
MediaSdpHelper.RTPMAP.forEach((k, v)->{
Optional.ofNullable(MediaSdpHelper.FMTP.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)));
@ -183,7 +181,7 @@ public class SipEventTest {
int rtpPort = 5080;
String rtpIp = "10.10.10.20";
long ssrc = RandomUtil.randomLong(10000000,100000000);
GB28181Description gb28181Description = GB28181SDPBuilder.Sender.build(GB28181SDPBuilder.Action.PLAY, deviceId, channel, Connection.IP4, rtpIp, rtpPort, String.valueOf(ssrc), MediaStreamMode.UDP, SdpFactory.getInstance().createTimeDescription());
log.info("\n{}", gb28181Description);
GB28181Description description = MediaSdpHelper.build(MediaSdpHelper.Action.PLAY, deviceId, channel, Connection.IP4, rtpIp, rtpPort, String.valueOf(ssrc), StreamMode.UDP, SdpFactory.getInstance().createTimeDescription());
log.info("\n{}", description);
}
}

View File

@ -1,38 +0,0 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.skcks.docking</groupId>
<artifactId>gb28181</artifactId>
<version>0.1.0</version>
</parent>
<groupId>cn.skcks.docking.gb28181</groupId>
<artifactId>gb28181-sip</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- 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>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- sip协议栈 -->
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-ri</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,18 +0,0 @@
package cn.skcks.docking.gb28181.constant;
@SuppressWarnings("unused")
public class CmdType {
public static final String KEEPALIVE = "Keepalive";
public static final String DEVICE_CONFIG = "DeviceConfig";
public static final String DEVICE_CONTROL = "DeviceControl";
public static final String DEVICE_STATUS = "DeviceStatus";
public static final String CATALOG = "Catalog";
public static final String ALARM = "Alarm";
public static final String MOBILE_POSITION = "MobilePosition";
public static final String BROADCAST = "Broadcast";
public static final String DEVICE_INFO = "DeviceInfo";
public static final String RECORD_INFO = "RecordInfo";
public static final String MEDIA_STATUS = "MediaStatus";
public static final String CONFIG_DOWNLOAD = "ConfigDownload";
public static final String PRESET_QUERY = "PresetQuery";
}

View File

@ -1,16 +0,0 @@
package cn.skcks.docking.gb28181.constant;
import javax.sip.ListeningPoint;
public class GB28181Constant {
public static final String TIME_ZONE = "Asia/Shanghai";
public static final String CHARSET = "GB2312";
public static final String GEO_COORD_SYS = "WGS84";
public static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
public static class TransPort {
public static final String UDP = ListeningPoint.UDP;
public static final String TCP = ListeningPoint.TCP;
}
}

View File

@ -1,49 +0,0 @@
package cn.skcks.docking.gb28181.sdp;
import cn.skcks.docking.gb28181.sdp.field.ssrc.FormatField;
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;
import javax.sdp.SdpException;
import javax.sdp.SessionDescription;
@Slf4j
@Setter
@Getter
public class GB28181Description extends SessionDescriptionImpl implements SessionDescription {
private SsrcField ssrcField;
private FormatField formatField = new FormatField();
private SessionDescriptionImpl sessionDescription;
public GB28181Description(){
super();
}
@SuppressWarnings("CopyConstructorMissesField")
public GB28181Description(GB28181Description gb28181Description) throws SdpException {
super(gb28181Description);
ssrcField = gb28181Description.getSsrcField();
}
public GB28181Description(SessionDescription sessionDescription) throws SdpException {
super(sessionDescription);
}
@SneakyThrows
public static GB28181Description build(SessionDescription sessionDescription){
return new GB28181Description(sessionDescription);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(super.toString());
sb.append(getSsrcField() == null ? "" : getSsrcField().toString());
sb.append(getFormatField() == null ? "" : getFormatField().toString());
return sb.toString();
}
}

View File

@ -1,322 +0,0 @@
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.Builder;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
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 Map<String, String> RTPMAP = new HashMap<>() {{
put("96", "PS/90000");
put("126", "H264/90000");
put("125", "H264S/90000");
put("99", "H265/90000");
put("98", "H264/90000");
put("97", "MPEG4/90000");
}};
public static Map<String, String> FMTP = new HashMap<>() {{
put("126", "profile-level-id=42e01e");
put("125", "profile-level-id=42e01e");
}};
public static class StreamType {
public interface Attribute<T> {
AttributeField stream();
}
public static AttributeField getAttribute(Attribute<?> attribute) {
return attribute.stream();
}
@AllArgsConstructor
public static class TPLink implements Attribute<String>{
private String stream;
public static final TPLink MAIN = new TPLink("main");
public static final TPLink SUB = new TPLink("sub");
public AttributeField stream() {
return (AttributeField) SdpFactory.getInstance().createAttribute("streamMode", stream);
}
}
@AllArgsConstructor
public static class StreamProfile implements Attribute<Integer> {
private Integer stream;
public static final StreamProfile MAIN = new StreamProfile(0);
public static final StreamProfile SUB = new StreamProfile(1);
@Override
public AttributeField stream() {
return (AttributeField) SdpFactory.getInstance().createAttribute("streamprofile", String.valueOf(stream));
}
}
@AllArgsConstructor
public static class GB2022 implements Attribute<Integer> {
private Integer stream;
public static final GB2022 MAIN = new GB2022(0);
public static final GB2022 SUB = new GB2022(1);
public AttributeField stream(){
return (AttributeField) SdpFactory.getInstance().createAttribute("streamnumber", String.valueOf(stream));
}
}
public static final GB2022 DEFAULT_GB_2022 = GB2022.MAIN;
public static final StreamProfile DEFAULT = StreamProfile.MAIN;
}
@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);
}
/**
*
* @param deviceId 设备id
* @param channelId 通道id
* @param netType 网络类型
* @param rtpIp rtp服务器ip
* @param rtpPort rtp端口
* @param ssrc ssrc
* @param streamMode 网络类型
* @param streamType 流类型 (/子码流)
* @return GB28181Description sdp
*/
@SneakyThrows
public static GB28181Description play(String deviceId, String channelId, String netType, String rtpIp, int rtpPort, String ssrc, MediaStreamMode streamMode, StreamType.Attribute<?> streamType) {
GB28181Description play = play(deviceId, channelId, netType, rtpIp, rtpPort, ssrc, streamMode);
MediaDescription m = (MediaDescription)play.getMediaDescriptions(false).get(0);
m.addAttribute(StreamType.getAttribute(streamType));
return play;
}
@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

@ -1,72 +0,0 @@
package cn.skcks.docking.gb28181.sdp.field.ssrc;
import gov.nist.core.Separators;
import gov.nist.javax.sdp.fields.SDPField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
public class FormatField extends SDPField {
public static final String FORMAT_FIELD_NAME = "f";
private static final String FORMAT_FIELD = FORMAT_FIELD_NAME + "=";
public FormatField() {
super(FORMAT_FIELD);
}
/**
* 视频编码格式
*/
private String videoFormat = "";
/**
* 视频分辨率
*/
private String videoRatio = "";
/**
* 视频帧率
*/
private String videoFrame = "";
/**
* 视频码率类型
*/
private String videoRateType = "";
/**
* 视频码率大小
*/
private String videoRateNum = "";
/**
* 音频编码格式
*/
private String audioFormat = "";
/**
* 音频码率大小
*/
private String audioRateNum = "";
/**
* 音频采样率
*/
private String audioSampling = "";
@SneakyThrows
@Override
public String encode() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(FORMAT_FIELD);
if(!StringUtils.isAllBlank(videoFormat, videoRatio, videoFrame, videoRateType, videoRateNum,audioFormat, audioRateNum, audioSampling)){
String video = StringUtils.joinWith("/", "v", videoFormat, videoRatio, videoFrame, videoRateType, videoRateNum);
String audio = StringUtils.joinWith("/", "a", audioFormat, audioRateNum, audioSampling);
stringBuilder.append(StringUtils.joinWith("/",video,audio));
}
stringBuilder.append(Separators.NEWLINE);
return stringBuilder.toString();
}
public String toString(){
return encode();
}
}

View File

@ -1,78 +0,0 @@
package cn.skcks.docking.gb28181.sdp.field.ssrc.parser;
import cn.skcks.docking.gb28181.sdp.field.ssrc.FormatField;
import cn.skcks.docking.gb28181.sdp.field.ssrc.SsrcField;
import gov.nist.javax.sdp.fields.SDPField;
import gov.nist.javax.sdp.parser.Lexer;
import gov.nist.javax.sdp.parser.SDPParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.text.ParseException;
@Slf4j
public class FormatFieldParser extends SDPParser {
public FormatFieldParser(String ssrcField) {
this.lexer = new Lexer("charLexer", ssrcField);
}
public FormatField formatField() throws ParseException {
try {
this.lexer.match('f');
this.lexer.SPorHT();
this.lexer.match('=');
this.lexer.SPorHT();
FormatField formatField = new FormatField();
String rest = lexer.getRest().trim();
String[] split = StringUtils.split(rest, 'a');
if(split.length == 0){
return formatField;
}
log.info("{}", (Object) split);
String video = split[0];
String[] videoParams = StringUtils.split(video,"/");
log.info("videoParams {}", (Object) videoParams);
if(videoParams.length > 1){
formatField.setVideoFormat(videoParams[1]);
}
if(videoParams.length > 2){
formatField.setVideoRatio(videoParams[2]);
}
if(videoParams.length > 3){
formatField.setVideoFrame(videoParams[3]);
}
if(videoParams.length > 4){
formatField.setVideoRateType(videoParams[4]);
}
if(videoParams.length > 5){
formatField.setVideoRateNum(videoParams[5]);
}
if(split.length < 2){
return formatField;
}
String audio = split[1];
String[] audioParams = audio.split("/");
log.info("audioParams {}", (Object) audioParams);
if(audioParams.length > 0){
formatField.setAudioFormat(audioParams[0]);
}
if(audioParams.length > 1){
formatField.setAudioRateNum(audioParams[1]);
}
if(audioParams.length > 2){
formatField.setAudioSampling(audioParams[2]);
}
return formatField;
} catch (Exception e) {
e.printStackTrace();
throw lexer.createParseException();
}
}
public SDPField parse() throws ParseException {
return this.formatField();
}
}

View File

@ -1,34 +0,0 @@
package cn.skcks.docking.gb28181.sdp.field.ssrc.parser;
import cn.skcks.docking.gb28181.sdp.field.ssrc.SsrcField;
import gov.nist.javax.sdp.fields.SDPField;
import gov.nist.javax.sdp.parser.Lexer;
import gov.nist.javax.sdp.parser.SDPParser;
import java.text.ParseException;
public class SsrcFieldParser extends SDPParser {
public SsrcFieldParser(String ssrcField) {
this.lexer = new Lexer("charLexer", ssrcField);
}
public SsrcField ssrcField() throws ParseException {
try {
this.lexer.match('y');
this.lexer.SPorHT();
this.lexer.match('=');
this.lexer.SPorHT();
SsrcField ssrcField = new SsrcField();
String rest = lexer.getRest().trim();
ssrcField.setSsrc(rest);
return ssrcField;
} catch (Exception e) {
throw lexer.createParseException();
}
}
public SDPField parse() throws ParseException {
return this.ssrcField();
}
}

View File

@ -1,89 +0,0 @@
package cn.skcks.docking.gb28181.sdp.parser;
import cn.skcks.docking.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.sdp.field.ssrc.SsrcField;
import gov.nist.core.ParserCore;
import gov.nist.javax.sdp.fields.SDPField;
import gov.nist.javax.sdp.parser.Lexer;
import gov.nist.javax.sdp.parser.SDPParser;
import java.text.ParseException;
import java.util.Vector;
@SuppressWarnings("all")
public class GB28181DescriptionParser extends ParserCore {
protected Lexer lexer;
protected Vector sdpMessage;
public GB28181DescriptionParser(Vector sdpMessage) {
this.sdpMessage = sdpMessage;
}
public GB28181DescriptionParser(String message) {
int start = 0;
String line = null;
// Return trivially if there is no sdp announce message
// to be parsed. Bruno Konik noticed this bug.
if (message == null) return;
sdpMessage = new Vector();
// Strip off leading and trailing junk.
String sdpAnnounce = message.trim() + "\r\n";
// Bug fix by Andreas Bystrom.
while (start < sdpAnnounce.length()) {
// Major re-write by Ricardo Borba.
int lfPos = sdpAnnounce.indexOf("\n", start);
int crPos = sdpAnnounce.indexOf("\r", start);
if (lfPos >= 0 && crPos < 0) {
// there are only "\n" separators
line = sdpAnnounce.substring(start, lfPos);
start = lfPos + 1;
} else if (lfPos < 0 && crPos >= 0) {
//bug fix: there are only "\r" separators
line = sdpAnnounce.substring(start, crPos);
start = crPos + 1;
} else if (lfPos >= 0 && crPos >= 0) {
// there are "\r\n" or "\n\r" (if exists) separators
if (lfPos > crPos) {
// assume "\r\n" for now
line = sdpAnnounce.substring(start, crPos);
// Check if the "\r" and "\n" are close together
if (lfPos == crPos + 1) {
start = lfPos + 1; // "\r\n"
} else {
start = crPos + 1; // "\r" followed by the next record and a "\n" further away
}
} else {
// assume "\n\r" for now
line = sdpAnnounce.substring(start, lfPos);
// Check if the "\n" and "\r" are close together
if (crPos == lfPos + 1) {
start = crPos + 1; // "\n\r"
} else {
start = lfPos + 1; // "\n" followed by the next record and a "\r" further away
}
}
} else if (lfPos < 0 && crPos < 0) { // end
break;
}
sdpMessage.addElement(line);
}
}
public GB28181Description parse() throws ParseException {
GB28181Description retval = new GB28181Description();
for (int i = 0; i < sdpMessage.size(); i++) {
String field = (String) sdpMessage.elementAt(i);
SDPParser sdpParser = GB28181DescriptionParserFactory.createParser(field);
SDPField sdpField = null;
if (sdpParser != null) {
sdpField = sdpParser.parse();
}
retval.addField(sdpField);
if (sdpField instanceof SsrcField ssrc) {
retval.setSsrcField(ssrc);
}
}
return retval;
}
}

View File

@ -1,24 +0,0 @@
package cn.skcks.docking.gb28181.sdp.parser;
import cn.skcks.docking.gb28181.sdp.field.ssrc.FormatField;
import cn.skcks.docking.gb28181.sdp.field.ssrc.SsrcField;
import cn.skcks.docking.gb28181.sdp.field.ssrc.parser.FormatFieldParser;
import cn.skcks.docking.gb28181.sdp.field.ssrc.parser.SsrcFieldParser;
import gov.nist.javax.sdp.parser.Lexer;
import gov.nist.javax.sdp.parser.ParserFactory;
import gov.nist.javax.sdp.parser.SDPParser;
import java.text.ParseException;
public class GB28181DescriptionParserFactory {
public static SDPParser createParser(String field) throws ParseException {
String fieldName = Lexer.getFieldName(field);
if(fieldName.equalsIgnoreCase(SsrcField.SSRC_FIELD_NAME)){
return new SsrcFieldParser(field);
}
if(fieldName.equalsIgnoreCase(FormatField.FORMAT_FIELD_NAME)){
return new FormatFieldParser(field);
}
return ParserFactory.createParser(field);
}
}

View File

@ -1,197 +0,0 @@
package cn.skcks.docking.gb28181.sip.generic;
import cn.skcks.docking.gb28181.constant.GB28181Constant;
import cn.skcks.docking.gb28181.sip.header.XGBVerHeader;
import cn.skcks.docking.gb28181.sip.header.impl.XGBVerHeaderImpl;
import cn.skcks.docking.gb28181.sip.utils.SipUtil;
import gov.nist.javax.sip.message.MessageFactoryImpl;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.sip.SipFactory;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
@Slf4j
public class SipBuilder {
@Setter
public static String DEFAULT_CHARSET = GB28181Constant.CHARSET;
@Setter
public static XGBVerHeader GB_VERSION = XGBVerHeaderImpl.GB28181_2016;
public static SipFactory getSipFactory(){
return SipFactory.getInstance();
}
public static UserAgentHeader userAgentHeader = SipUtil.getUserAgentHeader();
@SneakyThrows
public static MessageFactory getMessageFactory(){
MessageFactoryImpl messageFactory = (MessageFactoryImpl)getSipFactory().createMessageFactory();
messageFactory.setDefaultContentEncodingCharset(DEFAULT_CHARSET);
messageFactory.setDefaultUserAgentHeader(userAgentHeader);
log.debug("将使用 {} 编码 sip 消息", DEFAULT_CHARSET);
return messageFactory;
}
@SneakyThrows
public static MessageFactory getMessageFactory(String charset){
MessageFactoryImpl messageFactory = (MessageFactoryImpl)getSipFactory().createMessageFactory();
messageFactory.setDefaultContentEncodingCharset(charset);
messageFactory.setDefaultUserAgentHeader(userAgentHeader);
log.debug("将使用 {} 编码 sip 消息", charset);
return messageFactory;
}
@SneakyThrows
public static AddressFactory getAddressFactory() {
return getSipFactory().createAddressFactory();
}
@SneakyThrows
public static HeaderFactory getHeaderFactory() {
return getSipFactory().createHeaderFactory();
}
@SneakyThrows
public static String createHostAddress(String ip, int port) {
return StringUtils.joinWith(":", ip, port);
}
@SneakyThrows
public static SipURI createSipURI(String id, String address) {
return getAddressFactory().createSipURI(id, address);
}
@SneakyThrows
public static Address createAddress(SipURI uri) {
return getAddressFactory().createAddress(uri);
}
@SneakyThrows
public static ToHeader createToHeader(Address toAddress, String toTag) {
return getHeaderFactory().createToHeader(toAddress, toTag);
}
@SneakyThrows
public static ToHeader createToHeader(Address toAddress) {
return createToHeader(toAddress, null);
}
@SneakyThrows
public static FromHeader createFromHeader(Address fromAddress, String fromTag) {
return getHeaderFactory().createFromHeader(fromAddress, fromTag);
}
@SneakyThrows
public static FromHeader createFromHeader(Address fromAddress) {
return createFromHeader(fromAddress, null);
}
@SneakyThrows
public static CSeqHeader createCSeqHeader(long cSeq, String method){
return getHeaderFactory().createCSeqHeader(cSeq, method);
}
@SneakyThrows
public static MaxForwardsHeader createMaxForwardsHeader(int maxForwards) {
return getHeaderFactory().createMaxForwardsHeader(maxForwards);
}
@SneakyThrows
public static ViaHeader createViaHeader(String ip, int port, String transport, String viaTag){
ViaHeader viaHeader = getHeaderFactory().createViaHeader(ip, port, transport, viaTag);
viaHeader.setRPort();
return viaHeader;
}
@SneakyThrows
public static List<ViaHeader> createViaHeaders(String ip, int port, String transport, String viaTag) {
return Collections.singletonList(createViaHeader(ip, port, transport, viaTag));
}
@SneakyThrows
public static ContactHeader createContactHeader(Address address){
return getHeaderFactory().createContactHeader(address);
};
@SneakyThrows
public static ContentTypeHeader createContentTypeHeader(String contentType, String subType){
return getHeaderFactory().createContentTypeHeader(contentType, subType);
}
@SneakyThrows
public static SubjectHeader createSubjectHeader(String subject){
return getHeaderFactory().createSubjectHeader(subject);
}
@SneakyThrows
public static ContentTypeHeader createSDPContentTypeHeader(){
return getHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
}
public static UserAgentHeader createUserAgentHeader(String userAgent){
return createUserAgentHeader(Arrays.stream(StringUtils.split(userAgent, StringUtils.SPACE)).toList());
}
@SneakyThrows
public static UserAgentHeader createUserAgentHeader(List<String> product){
return getHeaderFactory().createUserAgentHeader(product);
}
@SneakyThrows
public static CallIdHeader createCallIdHeader(String callId){
return getHeaderFactory().createCallIdHeader(callId);
}
@SneakyThrows
public static ExpiresHeader createExpiresHeader(int expires){
return getHeaderFactory().createExpiresHeader(expires);
}
public static DateHeader createDateHeader(Calendar date){
return getHeaderFactory().createDateHeader(date);
}
@SneakyThrows
public static EventHeader createEventHeader(String event){
return getHeaderFactory().createEventHeader(event);
}
@SneakyThrows
public static SubscriptionStateHeader createSubscriptionStateHeader(String state){
return getHeaderFactory().createSubscriptionStateHeader(state);
}
@SneakyThrows
public static XGBVerHeader createXGBVerHeader(int m,int n){
return new XGBVerHeaderImpl(m,n);
}
public static Response addHeaders(Response response,Header ...headers){
for (Header header : headers) {
response.addHeader(header);
}
return response;
}
public static Request addHeaders(Request request, Header ...headers){
for (Header header : headers) {
request.addHeader(header);
}
return request;
}
}

View File

@ -1,9 +0,0 @@
package cn.skcks.docking.gb28181.sip.generic;
import javax.sip.header.ContentTypeHeader;
public class SipContentType {
public static final ContentTypeHeader XML = SipBuilder.createContentTypeHeader("Application", "MANSCDP+xml");
public static final ContentTypeHeader SDP = SipBuilder.createContentTypeHeader("Application", "SDP");
public static final ContentTypeHeader RTSP = SipBuilder.createContentTypeHeader("Application", "MANSRTSP");
}

View File

@ -1,45 +0,0 @@
package cn.skcks.docking.gb28181.sip.generic;
import lombok.SneakyThrows;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.Request;
import java.util.List;
import static cn.skcks.docking.gb28181.sip.generic.SipBuilder.GB_VERSION;
public class SipRequestBuilder {
@SneakyThrows
public static Request createRequest(SipURI requestURI, String method, CallIdHeader callId, CSeqHeader cSeq,
FromHeader from, ToHeader to, List<ViaHeader> via, MaxForwardsHeader maxForwards) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory().createRequest(requestURI, method, callId, cSeq, from, to, via, maxForwards),
GB_VERSION);
}
@SneakyThrows
public static <T> Request createRequest(SipURI requestURI, String method, CallIdHeader callId, CSeqHeader cSeq,
FromHeader from, ToHeader to, List<ViaHeader> via, MaxForwardsHeader maxForwards,
ContentTypeHeader contentType, T content) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory().createRequest(requestURI, method, callId, cSeq, from, to, via, maxForwards, contentType, content),
GB_VERSION);
}
@SneakyThrows
public static <T> Request createXmlRequest(SipURI requestURI, String method, CallIdHeader callId, CSeqHeader cSeq,
FromHeader from, ToHeader to, List<ViaHeader> via, MaxForwardsHeader maxForwards, T content) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory().createRequest(requestURI, method, callId, cSeq, from, to, via, maxForwards, SipContentType.XML, content),
GB_VERSION);
}
@SneakyThrows
public static <T> Request createXmlRequest(SipURI requestURI, String method, CallIdHeader callId, CSeqHeader cSeq,
FromHeader from, ToHeader to, List<ViaHeader> via, MaxForwardsHeader maxForwards, T content, String charset) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory(charset).createRequest(requestURI, method, callId, cSeq, from, to, via, maxForwards, SipContentType.XML, content),
GB_VERSION);
}
}

View File

@ -1,56 +0,0 @@
package cn.skcks.docking.gb28181.sip.generic;
import lombok.SneakyThrows;
import javax.sip.header.*;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.util.List;
import static cn.skcks.docking.gb28181.sip.generic.SipBuilder.GB_VERSION;
public class SipResponseBuilder {
@SneakyThrows
public static Response createResponse(int statusCode, Request request) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory().createResponse(statusCode, request),
SipBuilder.userAgentHeader,
GB_VERSION);
}
@SneakyThrows
public static Response createResponse(int statusCode, CallIdHeader callId, CSeqHeader cSeq,
FromHeader from, ToHeader to, List<ViaHeader> via, MaxForwardsHeader maxForwards) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory().createResponse(statusCode, callId, cSeq, from, to, via, maxForwards),
SipBuilder.userAgentHeader,
GB_VERSION);
}
@SneakyThrows
public static <T> Response createResponse(int statusCode, Request request, ContentTypeHeader contentType, T content) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory().createResponse(statusCode, request, contentType, content),
SipBuilder.userAgentHeader,
GB_VERSION
);
}
@SneakyThrows
public static <T> Response createXmlResponse(int statusCode, Request request, T content) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory().createResponse(statusCode, request, SipContentType.XML, content),
SipBuilder.userAgentHeader,
GB_VERSION
);
}
@SneakyThrows
public static <T> Response createXmlResponse(int statusCode, Request request, T content, String charset) {
return SipBuilder.addHeaders(
SipBuilder.getMessageFactory(charset).createResponse(statusCode, request, SipContentType.XML, content),
SipBuilder.userAgentHeader,
GB_VERSION);
}
}

View File

@ -1,32 +0,0 @@
package cn.skcks.docking.gb28181.sip.header;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import gov.nist.javax.sip.header.SIPHeader;
import lombok.*;
import javax.sip.header.Header;
import java.util.Calendar;
@EqualsAndHashCode(callSuper = true)
public class GBDateHeader extends SIPHeader implements Header{
public static final String NAME = "Date";
@Getter
@Setter
private Calendar date;
public GBDateHeader() {
super(NAME);
}
public GBDateHeader(Calendar date) {
super(NAME);
this.date = date;
}
@Override
protected StringBuilder encodeBody(StringBuilder buffer) {
return buffer.append(DateUtil.format(date.getTime(), DatePattern.UTC_SIMPLE_MS_FORMAT));
}
}

View File

@ -1,11 +0,0 @@
package cn.skcks.docking.gb28181.sip.header;
import javax.sip.header.Header;
public interface XGBVerHeader extends Header {
public void setVersion(int m, int n);
public String getVersion();
public final static String NAME = "X-GB-Ver";
}

View File

@ -1,51 +0,0 @@
package cn.skcks.docking.gb28181.sip.header.impl;
import cn.skcks.docking.gb28181.sip.header.XGBVerHeader;
import gov.nist.javax.sip.header.SIPHeader;
public class XGBVerHeaderImpl extends SIPHeader implements XGBVerHeader {
/**
* GB/T 28181-2011
*/
public static XGBVerHeaderImpl GB28181_2011 = new XGBVerHeaderImpl(1, 0);
/**
* GB/T 28181-2011 补充文件
*/
public static XGBVerHeaderImpl GB28181_2011_V2 = new XGBVerHeaderImpl(1, 1);
/**
* GB/T 28181-2016
*/
public static XGBVerHeaderImpl GB28181_2016 = new XGBVerHeaderImpl(2, 0);
/**
* GB/T 28181-2022
*/
public static XGBVerHeaderImpl GB28181_2022 = new XGBVerHeaderImpl(3, 0);
private String version;
@Override
public void setVersion(int m, int n) {
this.version = String.format("%d.%d", m, n);
}
@Override
public String getVersion() {
return version;
}
public XGBVerHeaderImpl(int m, int n) {
super(NAME);
setVersion(m, n);
}
public XGBVerHeaderImpl() {
super(NAME);
}
protected StringBuilder encodeBody(StringBuilder buffer) {
return buffer.append(version);
}
}

View File

@ -1,79 +0,0 @@
package cn.skcks.docking.gb28181.sip.logger;
import gov.nist.core.CommonLogger;
import gov.nist.core.ServerLogger;
import gov.nist.core.StackLogger;
import gov.nist.javax.sip.message.SIPMessage;
import gov.nist.javax.sip.stack.SIPTransactionStack;
import javax.sip.SipStack;
import java.util.Properties;
@SuppressWarnings("unused")
public class ServerLoggerImpl implements ServerLogger {
private boolean showLog = true;
protected StackLogger stackLogger;
@Override
public void closeLogFile() {
}
@Override
public void logMessage(SIPMessage message, String from, String to, boolean sender, long time) {
if (!showLog) {
return;
}
String log = (sender ? "发送: 目标 => " + to : "接收: 来自 => " + from) + "\r\n" + message;
this.stackLogger.logInfo(log);
}
@Override
public void logMessage(SIPMessage message, String from, String to, String status, boolean sender, long time) {
if (!showLog) {
return;
}
String log = (sender ? "发送: 目标 => " + to : "接收: 来自 => " + from) + "\r\n" + message;
this.stackLogger.logInfo(log);
}
@Override
public void logMessage(SIPMessage message, String from, String to, String status, boolean sender) {
if (!showLog) {
return;
}
String log = (sender ? "发送: 目标 => " + to : "接收: 来自 => " + from) + "\r\n" + message;
this.stackLogger.logInfo(log);
}
@Override
public void logException(Exception ex) {
if (!showLog) {
return;
}
this.stackLogger.logException(ex);
}
@Override
public void setStackProperties(Properties stackProperties) {
if (!showLog) {
return;
}
String TRACE_LEVEL = stackProperties.getProperty("gov.nist.javax.sip.TRACE_LEVEL");
if (TRACE_LEVEL != null) {
showLog = true;
}
}
@Override
public void setSipStack(SipStack sipStack) {
if (!showLog) {
return;
}
if(sipStack instanceof SIPTransactionStack st) {
this.stackLogger = CommonLogger.getLogger(st.getClass());
}
}
}

View File

@ -1,99 +0,0 @@
package cn.skcks.docking.gb28181.sip.logger;
import gov.nist.core.StackLogger;
import lombok.extern.slf4j.Slf4j;
import java.util.Properties;
@Slf4j
@SuppressWarnings("unused")
public class StackLoggerImpl implements StackLogger {
@Override
public void logStackTrace() {}
@Override
public void logStackTrace(int traceLevel) {
log.info("traceLevel: " + traceLevel);
}
@Override
public int getLineCount() {
return 0;
}
@Override
public void logException(Throwable ex) {
}
@Override
public void logDebug(String message) {
}
@Override
public void logDebug(String message, Exception ex) {
}
@Override
public void logTrace(String message) {
log.trace(message);
}
@Override
public void logFatalError(String message) {
}
@Override
public void logError(String message) {
}
@Override
public boolean isLoggingEnabled() {
return true;
}
@Override
public boolean isLoggingEnabled(int logLevel) {
return true;
}
@Override
public void logError(String message, Exception ex) {
log.error(message);
}
@Override
public void logWarning(String message) {
log.warn(message);
}
@Override
public void logInfo(String message) {
log.info(message);
}
@Override
public void disableLogging() {
}
@Override
public void enableLogging() {
}
@Override
public void setBuildTimeStamp(String buildTimeStamp) {
}
@Override
public void setStackProperties(Properties stackProperties) {
}
@Override
public String getLoggerName() {
return null;
}
}

View File

@ -1,21 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MessageDTO {
private String cmdType;
@JacksonXmlProperty(localName = "SN")
private String sn;
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
}

View File

@ -1,30 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.catalog.query;
import cn.skcks.docking.gb28181.constant.CmdType;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@JacksonXmlRootElement(localName = "Query")
@JsonRootName("Query")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CatalogQueryDTO {
@Builder.Default
private String cmdType = CmdType.CATALOG;
@JacksonXmlProperty(localName = "SN")
private String sn;
/**
* 目标设备的设备编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
}

View File

@ -1,28 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.catalog.response;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@JacksonXmlRootElement(localName = "DeviceList")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CatalogDeviceListDTO {
@Builder.Default
@JacksonXmlProperty(isAttribute = true)
private Integer num = 0;
@Builder.Default
@JacksonXmlProperty(localName = "Item")
@JacksonXmlElementWrapper(useWrapping = false)
private List<CatalogItemDTO> deviceList = new ArrayList<>();
}

View File

@ -1,150 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.catalog.response;
import cn.skcks.docking.gb28181.constant.GB28181Constant;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@JacksonXmlRootElement(localName = "Item")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CatalogItemDTO {
/**
* 设备/区域/系统编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
/**
* 设备/区域/系统名称(必选)
*/
private String name;
/**
* 当为设备时,设备厂商(必选)
*/
private String manufacturer;
/**
* 当为设备时,设备型号(必选)
*/
private String model;
/**
* 当为设备时,设备归属(必选)
*/
private String owner;
/**
* 行政区域(必选)
*/
@JacksonXmlProperty(localName = "CivilCode")
private String civilCode;
/**
* 警区(可选)
*/
private String block;
/**
* 当为设备时,安装地址(必选)
*/
private String address;
/**
* 当为设备时,是否有子设备(必选)1有, 0没有
*/
@Builder.Default
private Integer parental = 0;
/**
* 父设备/区域/系统ID(必选)
*/
@JacksonXmlProperty(localName = "ParentID")
private String parentId;
/**
* 信令安全模式(可选)缺省为0; 0:不采用;2:S/MIME 签名方式;3:S/ MIME加密签名同时采用方式;4:数字摘要方式
*/
@Builder.Default
private Integer safetyWay = 0;
/**
* 注册方式(必选)缺省为1;1:符合IETF RFC3261标准的认证注册模 ;2:基于口令的双向认证注册模式;3:基于数字证书的双向认证注册模式
*/
@Builder.Default
private Integer registerWay = 1;
/**
* 证书序列号(有证书的设备必选)
*/
private String certNum;
/**
* 证书有效标识(有证书的设备必选)缺省为0;证书有效标识:0:无效 1: 有效
*/
@Builder.Default
private Integer certifiable = 0;
/**
* 无效原因码(有证书且证书无效的设备必选)
*/
@Builder.Default
private Integer errCode = 0;
/**
* 证书终止有效期(有证书的设备必选)
*/
@JsonFormat(pattern = GB28181Constant.DATETIME_FORMAT, timezone = GB28181Constant.TIME_ZONE)
private Date endTime;
/**
* 保密属性(必选)缺省为0;0:不涉密,1:涉密
*/
@Builder.Default
private Integer secrecy = 0;
/**
* 设备/区域/系统IP地址(可选)
*/
@JacksonXmlProperty(localName = "IPAddress")
private String ipAddress;
/**
* 设备/区域/系统端口(可选)
*/
private Integer port;
/**
* 设备口令(可选)
*/
private String password;
/**
* 设备状态(必选)
*/
@Builder.Default
private String status = "ON";
/**
* 经度(可选)
*/
@Builder.Default
private String longitude = "0.0";
/**
* 纬度(可选)
*/
@Builder.Default
private String latitude = "0.0";
}

View File

@ -1,31 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.catalog.response;
import cn.skcks.docking.gb28181.constant.CmdType;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@JacksonXmlRootElement(localName = "Response")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class CatalogResponseDTO {
@Builder.Default
private String cmdType = CmdType.CATALOG;
@JacksonXmlProperty(localName = "SN")
private String sn;
/**
* 目标设备的设备编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
private Long sumNum;
private CatalogDeviceListDTO deviceList;
}

View File

@ -1,33 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.catalog.response;
import cn.skcks.docking.gb28181.constant.CmdType;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@JacksonXmlRootElement(localName = "Response")
@JsonRootName("Response")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CatalogSubscribeResponseDTO {
@Builder.Default
private String cmdType = CmdType.CATALOG;
@JacksonXmlProperty(localName = "SN")
private String sn;
/**
* 目标设备的设备编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
@Builder.Default
private String result = "OK";
}

View File

@ -1,77 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.control;
import cn.skcks.docking.gb28181.constant.CmdType;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@JacksonXmlRootElement(localName = "Control")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class DeviceControlRequestDTO {
@Builder.Default
private String cmdType = CmdType.DEVICE_CONTROL;
@JacksonXmlProperty(localName = "SN")
private String sn;
/**
* 目标设备的设备编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
/**
* 录像控制命令
*/
private String recordCmd;
/**
* 云台控制命令
*/
@JacksonXmlProperty(localName = "PTZCmd")
private String ptzCmd;
/**
* 远程启动
*/
private String teleBoot;
/**
* 布防撤防
*/
private String guardCmd;
/**
* 告警控制
*/
private String alarmCmd;
/**
* 强制关键帧
*/
@JacksonXmlProperty(localName = "IFameCmd")
private String iFameCmd;
/**
* 拉框放大
*/
private String dragZoomIn;
/**
* 拉框缩小
*/
private String dragZoomOut;
/**
* 看守位
*/
private String homePosition;
}

View File

@ -1,24 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.deviceinfo.request;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@JacksonXmlRootElement(localName = "Query")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DeviceInfoRequestDTO {
@Builder.Default
private String cmdType = "DeviceInfo";
@JacksonXmlProperty(localName = "SN")
private String sn;
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
}

View File

@ -1,48 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.deviceinfo.response;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@JacksonXmlRootElement(localName = "Response")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class DeviceInfoResponseDTO {
@Builder.Default
private String cmdType = "DeviceInfo";
@JacksonXmlProperty(localName = "SN")
private String sn;
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
private String deviceName;
@Builder.Default
private String result = "OK";
/**
* 设备生产商
*/
private String manufacturer;
/**
* 设备型号(可选)
*/
private String model;
/**
* 设备固件版本(可选)
*/
private String firmware;
/**
* 视频输入通道数(可选)
*/
private Integer channel;
}

View File

@ -1,28 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.keepalive.notify;
import cn.skcks.docking.gb28181.constant.CmdType;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@JacksonXmlRootElement(localName = "Notify")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class KeepaliveNotifyDTO {
@Builder.Default
private String cmdType = CmdType.KEEPALIVE;
@JacksonXmlProperty(localName = "SN")
private String sn;
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
@Builder.Default
private String status = "OK";
}

View File

@ -1,30 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.mediastatus.notify;
import cn.skcks.docking.gb28181.constant.CmdType;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@JacksonXmlRootElement(localName = "Notify")
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class MediaStatusRequestDTO {
@Builder.Default
private String cmdType = CmdType.MEDIA_STATUS;
@JacksonXmlProperty(localName = "SN")
private String sn;
/**
* 目标设备的设备编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
@JacksonXmlProperty(localName = "NotifyType")
private String notifyType = "121";
}

View File

@ -1,67 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.recordinfo.request;
import cn.skcks.docking.gb28181.constant.CmdType;
import cn.skcks.docking.gb28181.constant.GB28181Constant;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@JacksonXmlRootElement(localName = "Query")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RecordInfoRequestDTO {
/**
* 命令类型:设备录像查询(必选)
*/
@Builder.Default
private String cmdType = CmdType.RECORD_INFO;
/**
* 命令序列号(必选)
*/
@JacksonXmlProperty(localName = "SN")
private String sn;
/**
* 目标设备的设备编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
@JsonFormat(pattern = GB28181Constant.DATETIME_FORMAT, timezone = GB28181Constant.TIME_ZONE)
private Date startTime;
@JsonFormat(pattern = GB28181Constant.DATETIME_FORMAT, timezone = GB28181Constant.TIME_ZONE)
private Date endTime;
private String filePath;
private String address;
@Builder.Default
private Integer secrecy = 0;
@Builder.Default
private String type = "all";
@JacksonXmlProperty(localName = "RecorderID")
private String recorderId;
/**
* 录像模糊查询属性(可选) 缺省为 0; <p/>
* 0: 不进行模糊查询,此时根据SIP消息中To头域
* URI 中的ID值确定查询录像位置, ID 值为本域系统 ID 则进行中心历史记录检索
* 若为前端设备 ID 则进行前端设备历史记录检索; <p/>
*
* 1: 进行模糊查询,此时设备所在域应同时进行中心检索和前端检索并将结果统一返回
*/
private Integer indistinctQuery;
}

View File

@ -1,43 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response;
import cn.skcks.docking.gb28181.constant.GB28181Constant;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@JacksonXmlRootElement(localName = "Item")
public class RecordInfoItemDTO {
/**
* 目标设备的设备编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
private String name;
private String address;
@JsonFormat(pattern = GB28181Constant.DATETIME_FORMAT, timezone = GB28181Constant.TIME_ZONE)
private Date startTime;
@JsonFormat(pattern = GB28181Constant.DATETIME_FORMAT, timezone = GB28181Constant.TIME_ZONE)
private Date endTime;
@Builder.Default
private Integer secrecy = 0;
@Builder.Default
private String type = "all";
private Long fileSize;
}

View File

@ -1,40 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response;
import cn.skcks.docking.gb28181.constant.CmdType;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@JacksonXmlRootElement(localName = "Response")
public class RecordInfoResponseDTO {
/**
* 命令类型:设备信息查询(必选)
*/
@Builder.Default
private String cmdType = CmdType.RECORD_INFO;
/**
* 命令序列号(必选)
*/
@JacksonXmlProperty(localName = "SN")
private String sn;
/**
* 目标设备的设备编码(必选)
*/
@JacksonXmlProperty(localName = "DeviceID")
private String deviceId;
private String name;
private Long sumNum;
private RecordListDTO recordList;
}

View File

@ -1,28 +0,0 @@
package cn.skcks.docking.gb28181.sip.manscdp.recordinfo.response;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@JacksonXmlRootElement(localName = "RecordList")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class RecordListDTO {
@Builder.Default
@JacksonXmlProperty(isAttribute = true)
private Integer num = 0;
@Builder.Default
@JacksonXmlProperty(localName = "Item")
@JacksonXmlElementWrapper(useWrapping = false)
private List<RecordInfoItemDTO> recordList = new ArrayList<>();
}

View File

@ -1,77 +0,0 @@
package cn.skcks.docking.gb28181.sip.method;
import cn.skcks.docking.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.generic.SipContentType;
import cn.skcks.docking.gb28181.sip.generic.SipRequestBuilder;
import cn.skcks.docking.gb28181.sip.utils.SipUtil;
import lombok.Data;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.message.Request;
@SuppressWarnings("Duplicates")
@Data
@SuperBuilder
public class RequestBuilder {
@Setter
public static int DEFAULT_MAX_FORWARD = 70;
private String localIp;
private int localPort;
private String localId;
private String targetIp;
private int targetPort;
private String targetId;
private String transport;
public Request createRequest(String method, String callId, long cSeq) {
String local = SipBuilder.createHostAddress(getLocalIp(), getLocalPort());
Address localAddress = SipBuilder.createAddress(SipBuilder.createSipURI(getLocalId(), local));
String target = SipBuilder.createHostAddress(getTargetIp(), getTargetPort());
SipURI targetUri = SipBuilder.createSipURI(getTargetId(), target);
Address targetAddress = SipBuilder.createAddress(targetUri);
return SipRequestBuilder.createRequest(targetUri, method,
SipBuilder.createCallIdHeader(callId),
SipBuilder.createCSeqHeader(cSeq, method),
SipBuilder.createFromHeader(localAddress, SipUtil.generateFromTag()),
SipBuilder.createToHeader(targetAddress),
SipBuilder.createViaHeaders(getTargetIp(), getTargetPort(), getTransport(), SipUtil.generateViaTag()),
SipBuilder.createMaxForwardsHeader(DEFAULT_MAX_FORWARD));
}
public Request createRequest(String method, String callId, long cSeq, byte[] content) {
String local = SipBuilder.createHostAddress(getLocalIp(), getLocalPort());
Address localAddress = SipBuilder.createAddress(SipBuilder.createSipURI(getLocalId(), local));
String target = SipBuilder.createHostAddress(getTargetIp(), getTargetPort());
SipURI targetUri = SipBuilder.createSipURI(getTargetId(), target);
Address targetAddress = SipBuilder.createAddress(targetUri);
return SipRequestBuilder.createRequest(targetUri, method,
SipBuilder.createCallIdHeader(callId),
SipBuilder.createCSeqHeader(cSeq, method),
SipBuilder.createFromHeader(localAddress, SipUtil.generateFromTag()),
SipBuilder.createToHeader(targetAddress),
SipBuilder.createViaHeaders(getTargetIp(), getTargetPort(), getTransport(), SipUtil.generateViaTag()),
SipBuilder.createMaxForwardsHeader(DEFAULT_MAX_FORWARD), SipContentType.XML, content);
}
public Request createRequest(String method, String callId, long cSeq, GB28181Description description) {
String local = SipBuilder.createHostAddress(getLocalIp(), getLocalPort());
Address localAddress = SipBuilder.createAddress(SipBuilder.createSipURI(getLocalId(), local));
String target = SipBuilder.createHostAddress(getTargetIp(), getTargetPort());
SipURI targetUri = SipBuilder.createSipURI(getTargetId(), target);
Address targetAddress = SipBuilder.createAddress(targetUri);
return SipRequestBuilder.createRequest(targetUri, method,
SipBuilder.createCallIdHeader(callId),
SipBuilder.createCSeqHeader(cSeq, method),
SipBuilder.createFromHeader(localAddress, SipUtil.generateFromTag()),
SipBuilder.createToHeader(targetAddress),
SipBuilder.createViaHeaders(getTargetIp(), getTargetPort(), getTransport(), SipUtil.generateViaTag()),
SipBuilder.createMaxForwardsHeader(DEFAULT_MAX_FORWARD), SipContentType.SDP, description.toString());
}
}

View File

@ -1,9 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.invite;
import javax.sip.message.Request;
public interface InviteBuilder {
String METHOD = Request.INVITE;
}

View File

@ -1,84 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.invite.request;
import cn.skcks.docking.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.sdp.GB28181SDPBuilder;
import cn.skcks.docking.gb28181.sdp.media.MediaStreamMode;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.method.RequestBuilder;
import cn.skcks.docking.gb28181.sip.method.invite.InviteBuilder;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.StringUtils;
import javax.sdp.Connection;
import javax.sip.address.Address;
import javax.sip.header.SubjectHeader;
import javax.sip.message.Request;
import java.util.Date;
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class InviteRequestBuilder extends RequestBuilder implements InviteBuilder {
private SubjectHeader createSubject(String senderId, String senderStreamId, String receiveId, String receiveStreamId) {
String subject = StringUtils.joinWith(",",
// 发送者 channelId:流序号
StringUtils.joinWith(":", senderId, senderStreamId),
// 接收者 id:流序号号
StringUtils.joinWith(":", receiveId, receiveStreamId));
return SipBuilder.createSubjectHeader(subject);
}
public Request createPlayInviteRequest(String callId, long cSeq, String channelId, String rtpIp, int rtpPort, String ssrc, MediaStreamMode mediaStreamMode, String receiveId) {
GB28181Description description = GB28181SDPBuilder.Receiver.play(getTargetId(), channelId, Connection.IP4, rtpIp, rtpPort, ssrc, mediaStreamMode);
SIPRequest request = (SIPRequest) createRequest(METHOD, callId, cSeq, description);
Address address = request.getFrom().getAddress();
return SipBuilder.addHeaders(request,
SipBuilder.createContactHeader(address),
createSubject(channelId, ssrc, getLocalId(), receiveId));
}
public Request createPlayInviteRequest(String callId, long cSeq, String channelId, String rtpIp, int rtpPort, String ssrc, MediaStreamMode mediaStreamMode) {
return createPlayInviteRequest(callId, cSeq, channelId, rtpIp, rtpPort, ssrc, mediaStreamMode, String.valueOf(0));
}
public Request createPlaybackInviteRequest(String callId, long cSeq, String channelId, String rtpIp, int rtpPort, String ssrc, MediaStreamMode mediaStreamMode, String receiveId, Date startTime, Date endTime) {
GB28181Description description = GB28181SDPBuilder.Receiver.playback(getTargetId(), channelId, Connection.IP4, rtpIp, rtpPort, ssrc, mediaStreamMode, startTime, endTime);
SIPRequest request = (SIPRequest) createRequest(METHOD, callId, cSeq, description);
Address address = request.getFrom().getAddress();
return SipBuilder.addHeaders(request,
SipBuilder.createContactHeader(address),
createSubject(channelId, ssrc, getLocalId(), receiveId));
}
public Request createPlaybackInviteRequest(String callId, long cSeq, String channelId, String rtpIp, int rtpPort, String ssrc, MediaStreamMode mediaStreamMode, Date startTime, Date endTime) {
return createPlaybackInviteRequest(callId, cSeq, channelId, rtpIp, rtpPort, ssrc, mediaStreamMode, String.valueOf(0), startTime, endTime);
}
public Request createDownloadInviteRequest(String callId, long cSeq, String channelId, String rtpIp, int rtpPort, String ssrc, MediaStreamMode mediaStreamMode, String receiveId, Date startTime, Date endTime, Double downloadSpeed) {
GB28181Description description = GB28181SDPBuilder.Receiver.download(getTargetId(), channelId, Connection.IP4, rtpIp, rtpPort, ssrc, mediaStreamMode, startTime, endTime, downloadSpeed);
SIPRequest request = (SIPRequest) createRequest(METHOD, callId, cSeq, description);
Address address = request.getFrom().getAddress();
return SipBuilder.addHeaders(request,
SipBuilder.createContactHeader(address),
createSubject(channelId, ssrc, getLocalId(), receiveId));
}
public Request createDownloadInviteRequest(String callId, long cSeq, String channelId, String rtpIp, int rtpPort, String ssrc, MediaStreamMode mediaStreamMode, Date startTime, Date endTime, Double downloadSpeed) {
return createDownloadInviteRequest(callId, cSeq, channelId, rtpIp, rtpPort, ssrc, mediaStreamMode, String.valueOf(0), startTime, endTime, downloadSpeed);
}
public Request createByeRequest(String callId, long cSeq) {
return createRequest(Request.BYE, callId, cSeq);
}
}

View File

@ -1,69 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.invite.response;
import cn.skcks.docking.gb28181.sdp.GB28181Description;
import cn.skcks.docking.gb28181.sdp.GB28181SDPBuilder;
import cn.skcks.docking.gb28181.sdp.parser.GB28181DescriptionParser;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.generic.SipContentType;
import cn.skcks.docking.gb28181.sip.generic.SipResponseBuilder;
import cn.skcks.docking.gb28181.sip.method.invite.InviteBuilder;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.sip.message.Request;
import javax.sip.message.Response;
@Slf4j
@Data
@SuperBuilder
@ToString(callSuper = true)
public class InviteResponseBuilder implements InviteBuilder {
@SneakyThrows
public Response createInviteResponse(Request request, GB28181Description gb28181Description, String toTag){
Response response = SipResponseBuilder.createResponse(Response.OK, request, SipContentType.SDP, gb28181Description);
return addHeader(response, toTag);
}
@SneakyThrows
public Response createInviteResponse(Request request, String rtpIp, int rtpPort){
return createInviteResponse(request, rtpIp, rtpPort, "");
}
@SneakyThrows
public Response createInviteResponse(Request request, String rtpIp, int rtpPort, String toTag){
SIPRequest sipRequest = (SIPRequest) request;
GB28181DescriptionParser receive = new GB28181DescriptionParser(new String(sipRequest.getRawContent()));
GB28181Description gb28181Description = GB28181SDPBuilder.Sender.build(receive.parse(), rtpIp, rtpPort);
Response response = SipResponseBuilder.createResponse(Response.OK, request, SipContentType.SDP, gb28181Description);
return addHeader(response, toTag);
}
@SneakyThrows
private Response addHeader(Response response, String toTag){
SIPResponse sipResponse = (SIPResponse) response;
if (StringUtils.isNotBlank(toTag)) {
sipResponse.getToHeader().setTag(toTag);
}
return SipBuilder.addHeaders(
response,
SipBuilder.createContactHeader(sipResponse.getFromHeader().getAddress()));
}
public Response createTryingInviteResponse(Request request){
return SipResponseBuilder.createResponse(Response.TRYING,request);
}
public Response createByeResponse(Request request){
return createByeResponse(request, "");
}
public Response createByeResponse(Request request, String toTag){
return addHeader(SipResponseBuilder.createResponse(Response.OK,request), toTag);
}
}

View File

@ -1,7 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.message;
import javax.sip.message.Request;
public interface MessageBuilder {
String METHOD = Request.MESSAGE;
}

View File

@ -1,33 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.message.request;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.method.RequestBuilder;
import cn.skcks.docking.gb28181.sip.method.message.MessageBuilder;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.StringUtils;
import javax.sip.message.Request;
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MessageRequestBuilder extends RequestBuilder implements MessageBuilder {
public Request createMessageRequest(String callId, long cSeq, byte[] content) {
return createMessageRequest(callId, cSeq, content, "");
}
@SneakyThrows
public Request createMessageRequest(String callId, long cSeq, byte[] content, String toTag) {
SIPRequest request = (SIPRequest) createRequest(METHOD, callId, cSeq, content);
if (StringUtils.isNotBlank(toTag)) {
request.getToHeader().setTag(toTag);
}
return SipBuilder.addHeaders(request);
}
}

View File

@ -1,37 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.message.response;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.generic.SipContentType;
import cn.skcks.docking.gb28181.sip.generic.SipResponseBuilder;
import cn.skcks.docking.gb28181.sip.method.message.MessageBuilder;
import gov.nist.javax.sip.message.SIPResponse;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.sip.message.Request;
import javax.sip.message.Response;
@Slf4j
@Data
@SuperBuilder
@ToString(callSuper = true)
public class MessageResponseBuilder implements MessageBuilder {
@SneakyThrows
public Response createMessageResponse(Request request,byte[] content){
return createMessageResponse(request, content, null);
}
@SneakyThrows
public Response createMessageResponse(Request request,byte[] content, String toTag) {
Response response = SipResponseBuilder.createResponse(Response.OK, request, SipContentType.XML, content);
SIPResponse sipResponse = (SIPResponse) response;
if (StringUtils.isNotBlank(toTag)) {
sipResponse.getToHeader().setTag(toTag);
}
return SipBuilder.addHeaders(response);
}
}

View File

@ -1,7 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.notify;
import javax.sip.message.Request;
public interface NotifyBuilder {
String METHOD = Request.NOTIFY;
}

View File

@ -1,52 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.notify.request;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.method.RequestBuilder;
import cn.skcks.docking.gb28181.sip.method.notify.NotifyBuilder;
import gov.nist.javax.sip.header.SubscriptionState;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.StringUtils;
import javax.sip.message.Request;
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class NotifyRequestBuilder extends RequestBuilder implements NotifyBuilder {
@SneakyThrows
public Request createNotifyRequest(String callId, long cSeq, String event, byte[] content, Request request){
SIPRequest sipRequest = (SIPRequest) request;
String toTag = sipRequest.getToTag();
int expires = sipRequest.getExpires().getExpires();
return createNotifyRequest(callId, cSeq, event, content, toTag, expires);
}
@SneakyThrows
public Request createNotifyRequest(String callId, long cSeq, String event, byte[] content, String toTag, int expire) {
SIPRequest notifyRequest = (SIPRequest) createNotifyRequest(callId, cSeq, event, content, toTag);
SubscriptionState subscriptionState = (SubscriptionState) notifyRequest.getHeader(SubscriptionState.NAME);
subscriptionState.setExpires(expire);
return notifyRequest;
}
public Request createNotifyRequest(String callId, long cSeq, String event, byte[] content) {
return createNotifyRequest(callId, cSeq, event, content, "");
}
@SneakyThrows
public Request createNotifyRequest(String callId, long cSeq, String event, byte[] content, String toTag) {
SIPRequest request = (SIPRequest) createRequest(METHOD, callId, cSeq, content);
if (StringUtils.isNotBlank(toTag)) {
request.getToHeader().setTag(toTag);
}
return SipBuilder.addHeaders(request,
SipBuilder.createEventHeader(event),
SipBuilder.createSubscriptionStateHeader("active"));
}
}

View File

@ -1,8 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.register;
import javax.sip.message.Request;
public interface RegisterBuilder {
String METHOD = Request.REGISTER;
}

View File

@ -1,45 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.register.request;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.method.RequestBuilder;
import cn.skcks.docking.gb28181.sip.method.register.RegisterBuilder;
import cn.skcks.docking.gb28181.sip.utils.DigestAuthenticationHelper;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import javax.sip.address.Address;
import javax.sip.header.AuthorizationHeader;
import javax.sip.header.WWWAuthenticateHeader;
import javax.sip.message.Request;
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class RegisterRequestBuilder extends RequestBuilder implements RegisterBuilder {
public Request createNoAuthorizationRequest(String callId, int expires) {
String local = SipBuilder.createHostAddress(getLocalIp(), getLocalPort());
Address localAddress = SipBuilder.createAddress(SipBuilder.createSipURI(getLocalId(), local));
SIPRequest request = (SIPRequest) createRequest(METHOD, callId, 1);
Address address = request.getFromHeader().getAddress();
request.getToHeader().setAddress(address);
return SipBuilder.addHeaders(request,
SipBuilder.createExpiresHeader(expires),
SipBuilder.createContactHeader(localAddress));
}
@SneakyThrows
public Request createAuthorizationRequest(String callId, int expires, String id, String passwd, long cSeq, WWWAuthenticateHeader wwwAuthenticateHeader) {
SIPRequest request = (SIPRequest) createNoAuthorizationRequest(callId, expires);
request.getCSeq().setSeqNumber(cSeq);
AuthorizationHeader authorization = DigestAuthenticationHelper.createAuthorization(METHOD, getTargetIp(), getTargetPort(), getTargetId(), id, passwd, (int) cSeq, wwwAuthenticateHeader);
return SipBuilder.addHeaders(request, authorization);
}
}

View File

@ -1,65 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.register.response;
import cn.hutool.core.date.DateUtil;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.generic.SipResponseBuilder;
import cn.skcks.docking.gb28181.sip.header.GBDateHeader;
import cn.skcks.docking.gb28181.sip.method.register.RegisterBuilder;
import cn.skcks.docking.gb28181.sip.utils.DigestAuthenticationHelper;
import gov.nist.javax.sip.header.Authorization;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.*;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.sip.header.WWWAuthenticateHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
@Slf4j
@Data
@SuperBuilder
@ToString(callSuper = true)
public class RegisterResponseBuilder implements RegisterBuilder {
/**
* 不做任何校验 即无密码时 使用的认证响应 (如果请求的method错误 依然返回 401 认证失败)
*
* @param request 请求
*/
public Response createPassedAuthorzatioinResponse(Request request) {
SIPRequest sipRequest = (SIPRequest) request;
if (!StringUtils.equalsIgnoreCase(sipRequest.getMethod(), METHOD)) {
return SipBuilder.addHeaders(
SipResponseBuilder.createResponse(Response.UNAUTHORIZED, request),
sipRequest.getContactHeader());
}
return SipBuilder.addHeaders(
SipResponseBuilder.createResponse(Response.OK, request),
sipRequest.getContactHeader(),
sipRequest.getExpires(),
new GBDateHeader(DateUtil.calendar()));
}
public Response createAuthorzatioinResponse(Request request, String domain, String password) {
SIPRequest sipRequest = (SIPRequest) request;
Authorization authorization = sipRequest.getAuthorization();
if (authorization == null) {
WWWAuthenticateHeader wwwAuthenticateHeader = DigestAuthenticationHelper.generateChallenge(domain);
return SipBuilder.addHeaders(
SipResponseBuilder.createResponse(Response.UNAUTHORIZED, request),
sipRequest.getContactHeader(),
wwwAuthenticateHeader);
}
boolean passed = DigestAuthenticationHelper.doAuthenticatePlainTextPassword(request, password);
if (!passed) {
sipRequest.removeHeader(Authorization.NAME);
return createAuthorzatioinResponse(request, domain, password);
}
return SipBuilder.addHeaders(
SipResponseBuilder.createResponse(Response.OK, request),
sipRequest.getContactHeader(),
sipRequest.getExpires(),
new GBDateHeader(DateUtil.calendar()));
}
}

View File

@ -1,8 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.subscribe;
import javax.sip.message.Request;
public interface SubscribeBuilder {
String METHOD = Request.SUBSCRIBE;
}

View File

@ -1,27 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.subscribe.request;
import cn.skcks.docking.gb28181.sip.generic.SipBuilder;
import cn.skcks.docking.gb28181.sip.method.RequestBuilder;
import cn.skcks.docking.gb28181.sip.method.subscribe.SubscribeBuilder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import javax.sip.message.Request;
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SubscribeRequestBuilder extends RequestBuilder implements SubscribeBuilder {
public Request createSubscribeRequest(String callId,long cSeq,String event,byte[] content){
return SipBuilder.addHeaders(createRequest(METHOD,callId,cSeq,content),
SipBuilder.createEventHeader(event));
}
public Request createSubscribeRequest(String callId,long cSeq,String event,byte[] content, int expire){
return SipBuilder.addHeaders(createSubscribeRequest(callId,cSeq,event,content),
SipBuilder.createExpiresHeader(expire));
}
}

View File

@ -1,20 +0,0 @@
package cn.skcks.docking.gb28181.sip.method.subscribe.response;
import cn.skcks.docking.gb28181.sip.generic.SipResponseBuilder;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;
import javax.sip.message.Request;
import javax.sip.message.Response;
@Slf4j
@Data
@SuperBuilder
@ToString(callSuper = true)
public class SubscribeResponseBuilder {
public Response createSubscribeResponse(Request request, byte[] content) {
return SipResponseBuilder.createXmlResponse(Response.OK, request, content);
}
}

View File

@ -1,436 +0,0 @@
package cn.skcks.docking.gb28181.sip.parser;
import gov.nist.core.Host;
import gov.nist.core.HostNameParser;
import gov.nist.javax.sip.SIPConstants;
import gov.nist.javax.sip.address.AddressImpl;
import gov.nist.javax.sip.address.GenericURI;
import gov.nist.javax.sip.address.SipUri;
import gov.nist.javax.sip.address.TelephoneNumber;
import gov.nist.javax.sip.header.*;
import gov.nist.javax.sip.message.SIPMessage;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import gov.nist.javax.sip.parser.*;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
@SuppressWarnings({"unused"})
public class GBStringMsgParser implements MessageParser {
protected static boolean computeContentLengthFromMessage = false;
/**
* @since v0.9
*/
public GBStringMsgParser() {
super();
}
/**
* Parse a buffer containing a single SIP Message where the body is an array
* of un-interpreted bytes. This is intended for parsing the message from a
* memory buffer when the buffer. Incorporates a bug fix for a bug that was
* noted by Will Sullin of Callcast
*
* @param msgBuffer
* a byte buffer containing the messages to be parsed. This can
* consist of multiple SIP Messages concatenated together.
* @return a SIPMessage[] structure (request or response) containing the
* parsed SIP message.
* @exception ParseException
* is thrown when an illegal message has been encountered
* (and the rest of the buffer is discarded).
* @see ParseExceptionListener
*/
public SIPMessage parseSIPMessage(byte[] msgBuffer, boolean readBody, boolean strict, ParseExceptionListener parseExceptionListener) throws ParseException {
if (msgBuffer == null || msgBuffer.length == 0)
return null;
int i = 0;
// Squeeze out any leading control character.
try {
while (msgBuffer[i] < 0x20)
i++;
}
catch (ArrayIndexOutOfBoundsException e) {
// Array contains only control char, return null.
return null;
}
// Iterate thru the request/status line and headers.
String currentLine = null;
String currentHeader = null;
boolean isFirstLine = true;
SIPMessage message = null;
do
{
int lineStart = i;
// Find the length of the line.
try {
while (msgBuffer[i] != '\r' && msgBuffer[i] != '\n')
i++;
}
catch (ArrayIndexOutOfBoundsException e) {
// End of the message.
break;
}
int lineLength = i - lineStart;
// Make it a String.
currentLine = new String(msgBuffer, lineStart, lineLength, StandardCharsets.UTF_8);
currentLine = trimEndOfLine(currentLine);
if (currentLine.length() == 0) {
// Last header line, process the previous buffered header.
if (currentHeader != null && message != null) {
processHeader(currentHeader, message, parseExceptionListener, msgBuffer);
}
}
else {
if (isFirstLine) {
message = processFirstLine(currentLine, parseExceptionListener, msgBuffer);
} else {
char firstChar = currentLine.charAt(0);
if (firstChar == '\t' || firstChar == ' ') {
if (currentHeader == null)
throw new ParseException("Bad header continuation.", 0);
// This is a continuation, append it to the previous line.
currentHeader += currentLine.substring(1);
}
else {
if (currentHeader != null && message != null) {
processHeader(currentHeader, message, parseExceptionListener, msgBuffer);
}
currentHeader = currentLine;
}
}
}
if (msgBuffer[i] == '\r' && msgBuffer.length > i+1 && msgBuffer[i+1] == '\n')
i++;
i++;
isFirstLine = false;
} while (currentLine.length() > 0); // End do - while
if (message == null) throw new ParseException("Bad message", 0);
message.setSize(i);
// Check for content legth header
if (readBody && message.getContentLength() != null ) {
if ( message.getContentLength().getContentLength() != 0) {
int bodyLength = msgBuffer.length - i;
byte[] body = new byte[bodyLength];
System.arraycopy(msgBuffer, i, body, 0, bodyLength);
message.setMessageContent(body,!strict,computeContentLengthFromMessage,message.getContentLength().getContentLength());
} else if (message.getCSeqHeader().getMethod().equalsIgnoreCase("MESSAGE")) {
int bodyLength = msgBuffer.length - i;
byte[] body = new byte[bodyLength];
System.arraycopy(msgBuffer, i, body, 0, bodyLength);
message.setMessageContent(body,!strict,computeContentLengthFromMessage,bodyLength);
}else if (!computeContentLengthFromMessage && strict) {
String last4Chars = new String(msgBuffer, msgBuffer.length - 4, 4);
if(!"\r\n\r\n".equals(last4Chars)) {
throw new ParseException("Extraneous characters at the end of the message ",i);
}
}
}
return message;
}
protected static String trimEndOfLine(String line) {
if (line == null)
return null;
int i = line.length() - 1;
while (i >= 0 && line.charAt(i) <= 0x20)
i--;
if (i == line.length() - 1)
return line;
if (i == -1)
return "";
return line.substring(0, i+1);
}
protected SIPMessage processFirstLine(String firstLine, ParseExceptionListener parseExceptionListener, byte[] msgBuffer) throws ParseException {
SIPMessage message;
if (!firstLine.startsWith(SIPConstants.SIP_VERSION_STRING)) {
message = new SIPRequest();
try {
RequestLine requestLine = new RequestLineParser(firstLine + "\n")
.parse();
((SIPRequest) message).setRequestLine(requestLine);
} catch (ParseException ex) {
if (parseExceptionListener != null) {
parseExceptionListener.handleException(ex, message,
RequestLine.class, firstLine, new String(msgBuffer, StandardCharsets.UTF_8));
}
else
throw ex;
}
} else {
message = new SIPResponse();
try {
StatusLine sl = new StatusLineParser(firstLine + "\n").parse();
((SIPResponse) message).setStatusLine(sl);
} catch (ParseException ex) {
if (parseExceptionListener != null) {
parseExceptionListener.handleException(ex, message,
StatusLine.class, firstLine, new String(msgBuffer, StandardCharsets.UTF_8));
} else
throw ex;
}
}
return message;
}
protected void processHeader(String header, SIPMessage message, ParseExceptionListener parseExceptionListener, byte[] rawMessage) throws ParseException {
if (header == null || header.length() == 0)
return;
HeaderParser headerParser = null;
try {
headerParser = ParserFactory.createParser(header + "\n");
} catch (ParseException ex) {
// https://java.net/jira/browse/JSIP-456
if (parseExceptionListener != null) {
parseExceptionListener.handleException(ex, message, null,
header, null);
return;
} else {
throw ex;
}
}
try {
SIPHeader sipHeader = headerParser.parse();
message.attachHeader(sipHeader, false);
} catch (ParseException ex) {
if (parseExceptionListener != null) {
String headerName = Lexer.getHeaderName(header);
Class<?> headerClass = NameMap.getClassFromName(headerName);
if (headerClass == null) {
headerClass = ExtensionHeaderImpl.class;
}
parseExceptionListener.handleException(ex, message,
headerClass, header, new String(rawMessage, StandardCharsets.UTF_8));
}
}
}
/**
* Parse an address (nameaddr or address spec) and return and address
* structure.
*
* @param address
* is a String containing the address to be parsed.
* @return a parsed address structure.
* @since v1.0
* @exception ParseException
* when the address is badly formatted.
*/
public AddressImpl parseAddress(String address) throws ParseException {
AddressParser addressParser = new AddressParser(address);
return addressParser.address(true);
}
/**
* Parse a host:port and return a parsed structure.
*
* @param hostport
* is a String containing the host:port to be parsed
* @return a parsed address structure.
* @since v1.0
* @exception throws
* a ParseException when the address is badly formatted.
*
public HostPort parseHostPort(String hostport) throws ParseException {
Lexer lexer = new Lexer("charLexer", hostport);
return new HostNameParser(lexer).hostPort();
}
*/
/**
* Parse a host name and return a parsed structure.
*
* @param host
* is a String containing the host name to be parsed
* @return a parsed address structure.
* @since v1.0
* @exception ParseException
* a ParseException when the hostname is badly formatted.
*/
public Host parseHost(String host) throws ParseException {
Lexer lexer = new Lexer("charLexer", host);
return new HostNameParser(lexer).host();
}
/**
* Parse a telephone number return a parsed structure.
*
* @param telephone_number
* is a String containing the telephone # to be parsed
* @return a parsed address structure.
* @since v1.0
* @exception ParseException
* a ParseException when the address is badly formatted.
*/
public TelephoneNumber parseTelephoneNumber(String telephone_number)
throws ParseException {
// Bug fix contributed by Will Scullin
return new URLParser(telephone_number).parseTelephoneNumber(true);
}
/**
* Parse a SIP url from a string and return a URI structure for it.
*
* @param url
* a String containing the URI structure to be parsed.
* @return A parsed URI structure
* @exception ParseException
* if there was an error parsing the message.
*/
public SipUri parseSIPUrl(String url) throws ParseException {
try {
return new URLParser(url).sipURL(true);
} catch (ClassCastException ex) {
throw new ParseException(url + " Not a SIP URL ", 0);
}
}
/**
* Parse a uri from a string and return a URI structure for it.
*
* @param url
* a String containing the URI structure to be parsed.
* @return A parsed URI structure
* @exception ParseException
* if there was an error parsing the message.
*/
public GenericURI parseUrl(String url) throws ParseException {
return new URLParser(url).parse();
}
/**
* Parse an individual SIP message header from a string.
*
* @param header
* String containing the SIP header.
* @return a SIPHeader structure.
* @exception ParseException
* if there was an error parsing the message.
*/
public static SIPHeader parseSIPHeader(String header) throws ParseException {
int start = 0;
int end = header.length() - 1;
try {
// Squeeze out any leading control character.
while (header.charAt(start) <= 0x20)
start++;
// Squeeze out any trailing control character.
while (header.charAt(end) <= 0x20)
end--;
}
catch (ArrayIndexOutOfBoundsException e) {
// Array contains only control char.
throw new ParseException("Empty header.", 0);
}
StringBuilder buffer = new StringBuilder(end + 1);
int i = start;
int lineStart = start;
boolean endOfLine = false;
while (i <= end) {
char c = header.charAt(i);
if (c == '\r' || c == '\n') {
if (!endOfLine) {
buffer.append(header, lineStart, i);
endOfLine = true;
}
}
else {
if (endOfLine) {
endOfLine = false;
if (c == ' ' || c == '\t') {
buffer.append(' ');
lineStart = i + 1;
}
else {
lineStart = i;
}
}
}
i++;
}
buffer.append(header, lineStart, i);
buffer.append('\n');
HeaderParser hp = ParserFactory.createParser(buffer.toString());
if (hp == null)
throw new ParseException("could not create parser", 0);
return hp.parse();
}
/**
* Parse the SIP Request Line
*
* @param requestLine
* a String containing the request line to be parsed.
* @return a RequestLine structure that has the parsed RequestLine
* @exception ParseException
* if there was an error parsing the requestLine.
*/
public RequestLine parseSIPRequestLine(String requestLine)
throws ParseException {
requestLine += "\n";
return new RequestLineParser(requestLine).parse();
}
/**
* Parse the SIP Response message status line
*
* @param statusLine
* a String containing the Status line to be parsed.
* @return StatusLine class corresponding to message
* @exception ParseException
* if there was an error parsing
* @see StatusLine
*/
public StatusLine parseSIPStatusLine(String statusLine)
throws ParseException {
statusLine += "\n";
return new StatusLineParser(statusLine).parse();
}
public static void setComputeContentLengthFromMessage(
boolean computeContentLengthFromMessage) {
GBStringMsgParser.computeContentLengthFromMessage = computeContentLengthFromMessage;
}
}

View File

@ -1,21 +0,0 @@
package cn.skcks.docking.gb28181.sip.parser;
import gov.nist.javax.sip.parser.MessageParser;
import gov.nist.javax.sip.parser.MessageParserFactory;
import gov.nist.javax.sip.stack.SIPTransactionStack;
public class GbStringMsgParserFactory implements MessageParserFactory {
/**
* msg parser is completely stateless, reuse instance for the whole stack
* fixes https://github.com/RestComm/jain-sip/issues/92
*/
private static final GBStringMsgParser msgParser = new GBStringMsgParser();
/*
* (non-Javadoc)
* @see gov.nist.javax.sip.parser.MessageParserFactory#createMessageParser(gov.nist.javax.sip.stack.SIPTransactionStack)
*/
public MessageParser createMessageParser(SIPTransactionStack stack) {
return msgParser;
}
}

View File

@ -1,52 +0,0 @@
package cn.skcks.docking.gb28181.sip.property;
import java.util.Properties;
/**
* 获取sip默认配置
*/
public class DefaultProperties {
public static Properties getProperties(String name,String stackLoggerFQN,String serverLoggerFQN) {
Properties properties = new Properties();
properties.setProperty("javax.sip.STACK_NAME", name);
// properties.setProperty("javax.sip.IP_ADDRESS", ip);
// 关闭自动会话
properties.setProperty("javax.sip.AUTOMATIC_DIALOG_SUPPORT", "off");
/**
* 完整配置参考 gov.nist.javax.sip.SipStackImpl需要下载源码
* gov/nist/javax/sip/SipStackImpl.class
* sip消息的解析在 gov.nist.javax.sip.stack.UDPMessageChannel的processIncomingDataPacket方法
*/
// * gov/nist/javax/sip/SipStackImpl.class
// 接收所有notify请求即使没有订阅
properties.setProperty("gov.nist.javax.sip.DELIVER_UNSOLICITED_NOTIFY", "true");
properties.setProperty("gov.nist.javax.sip.AUTOMATIC_DIALOG_ERROR_HANDLING", "false");
properties.setProperty("gov.nist.javax.sip.CANCEL_CLIENT_TRANSACTION_CHECKED", "true");
// 为_NULL _对话框传递_终止的_事件
properties.setProperty("gov.nist.javax.sip.DELIVER_TERMINATED_EVENT_FOR_NULL_DIALOG", "true");
// 是否自动计算content length的实际长度默认不计算
properties.setProperty("gov.nist.javax.sip.COMPUTE_CONTENT_LENGTH_FROM_MESSAGE_BODY", "true");
// 会话清理策略
properties.setProperty("gov.nist.javax.sip.RELEASE_REFERENCES_STRATEGY", "Normal");
// 处理由该服务器处理的基于底层TCP的保持生存超时
properties.setProperty("gov.nist.javax.sip.RELIABLE_CONNECTION_KEEP_ALIVE_TIMEOUT", "60");
// 获取实际内容长度不使用header中的长度信息
properties.setProperty("gov.nist.javax.sip.COMPUTE_CONTENT_LENGTH_FROM_MESSAGE_BODY", "true");
// 线程可重入
properties.setProperty("gov.nist.javax.sip.REENTRANT_LISTENER", "true");
// 定义应用程序打算多久审计一次 SIP 堆栈了解其内部线程的健康状况该属性指定连续审计之间的时间以毫秒为单位
properties.setProperty("gov.nist.javax.sip.THREAD_AUDIT_INTERVAL_IN_MILLISECS", "30000");
// properties.setProperty("gov.nist.javax.sip.MESSAGE_PROCESSOR_FACTORY", "gov.nist.javax.sip.stack.NioMessageProcessorFactory");
/**
* sip_server_log.log sip_debug_log.log ERROR, INFO, WARNING, OFF, DEBUG, TRACE
*/
properties.setProperty("gov.nist.javax.sip.STACK_LOGGER", stackLoggerFQN);
properties.setProperty("gov.nist.javax.sip.SERVER_LOGGER", serverLoggerFQN);
properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "true");
return properties;
}
}

Some files were not shown because too many files have changed in this diff Show More