Compare commits
108 Commits
Author | SHA1 | Date | |
---|---|---|---|
10724561be | |||
99887f715d | |||
05c00db058 | |||
14d9a80759 | |||
c993a15711 | |||
0e8d4f0cc1 | |||
1852e7b3d5 | |||
443ed7fc2c | |||
99ce7afc73 | |||
b712ec2421 | |||
648821525b | |||
4baba22224 | |||
974e34641b | |||
c5a1ae8866 | |||
31f1ed957a | |||
bf5be55ec8 | |||
70153f192c | |||
47f6edc667 | |||
405e29c15b | |||
e8f5bfd1b0 | |||
dfba129768 | |||
5bda3a2d51 | |||
46359028ff | |||
51c4a73a76 | |||
0ec87388b0 | |||
f02d18e1e3 | |||
72c11e4625 | |||
cc9f7a0e73 | |||
aa15ffd1a7 | |||
9a1d2a53b5 | |||
de4726ffba | |||
e89656122d | |||
61cdec8a74 | |||
5b8db7322d | |||
dc3cf8f5b5 | |||
4a69cbd8ac | |||
60ea2fc372 | |||
d3bf7b4056 | |||
d9ab07f1ef | |||
369919ca29 | |||
6202acb560 | |||
7b3f2a57a2 | |||
1c280ef64f | |||
e1b8db65a0 | |||
3db949ed26 | |||
2ab7e9dff5 | |||
cc4577cc4e | |||
bbe513d72d | |||
61c989afaf | |||
7dec62b229 | |||
67c3875ffb | |||
8a8dd66b8b | |||
90d5661305 | |||
fde9332d9b | |||
563fb03ea3 | |||
f47fe2b10a | |||
84cd2110f4 | |||
63b2a7d3d0 | |||
7446bf1647 | |||
ba6fa8f921 | |||
1d44613e88 | |||
89225fa9c3 | |||
bb0bdcb397 | |||
2bf19edb4e | |||
7eee14510e | |||
3a00d027e4 | |||
a87fedc28b | |||
f44440c42a | |||
1911877d96 | |||
d820cb7a9c | |||
87d4fc2a38 | |||
2d38c7d6ec | |||
ad72b02371 | |||
74cd2d2048 | |||
296747ec31 | |||
d875a90fd6 | |||
41c8480ae0 | |||
e559446d08 | |||
b40494f3a7 | |||
fd87512758 | |||
b1d3d2d235 | |||
84cab3441f | |||
f0857df86e | |||
d810236d62 | |||
e9fb357b95 | |||
57b04fdcf3 | |||
309062faa1 | |||
568c03fb67 | |||
32ca6698fb | |||
d7848cf694 | |||
b072ef7c58 | |||
76233345b5 | |||
f92c9a70f0 | |||
6efb97875f | |||
b443e720b3 | |||
3f6b28350a | |||
d9805afe88 | |||
63367ab05f | |||
08cb9d58f6 | |||
4e62e90c0c | |||
2550500fb9 | |||
3ab46488b0 | |||
e6f1da2018 | |||
a3d6573bbb | |||
49ea15db92 | |||
a15f00cd33 | |||
f3ae4156d1 | |||
dc030c6844 |
57
README.md
57
README.md
@ -26,8 +26,61 @@ 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 -DaltDeploymentRepository=amleixun-mvn-reop::default::file:H:/Repository/skcks.cn/gb28181-docking-platform-mvn-repo
|
||||
mvn deploy -s settings.xml -DaltDeploymentRepository=local-repo::default::file:H:/Repository/skcks.cn/gb28181-docking-platform-mvn-repo
|
||||
```
|
||||
git push 推送即可
|
||||
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
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.skcks.docking</groupId>
|
||||
<artifactId>gb28181</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.1.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>cn.skcks.docking.gb28181</groupId>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.skcks.docking</groupId>
|
||||
<artifactId>gb28181</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.1.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>cn.skcks.docking.gb28181</groupId>
|
||||
|
@ -12,6 +12,7 @@ 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;
|
||||
|
||||
@ -72,6 +73,12 @@ 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();
|
||||
|
@ -0,0 +1,39 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
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");
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package cn.skcks.docking.gb28181.api.record;
|
||||
package cn.skcks.docking.gb28181.api.gb28181.record;
|
||||
|
||||
import cn.skcks.docking.gb28181.annotation.web.JsonMapping;
|
||||
import cn.skcks.docking.gb28181.annotation.web.methods.GetJson;
|
||||
import cn.skcks.docking.gb28181.api.record.dto.GetInfoDTO;
|
||||
import cn.skcks.docking.gb28181.api.gb28181.record.dto.GetRecordInfoDTO;
|
||||
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/device/record")
|
||||
@JsonMapping("/api/gb28181/record")
|
||||
@RequiredArgsConstructor
|
||||
public class RecordController {
|
||||
private final RecordService recordService;
|
||||
@ -30,8 +30,8 @@ public class RecordController {
|
||||
return SwaggerConfig.api("Record", "/api/device/record");
|
||||
}
|
||||
|
||||
@GetJson("/getInfoList")
|
||||
public DeferredResult<JsonResponse<List<RecordInfoItemVO>>> getInfo(@ParameterObject @Validated GetInfoDTO dto){
|
||||
return recordService.requestRecordInfo(dto.getDeviceId(), dto.getTimeout(), dto.getDate());
|
||||
@GetJson("/list")
|
||||
public DeferredResult<JsonResponse<List<RecordInfoItemVO>>> getInfo(@ParameterObject @Validated GetRecordInfoDTO dto){
|
||||
return recordService.requestRecordInfo(dto.getGbDeviceId(), dto.getGbDeviceChannelId(), dto.getTimeout(), dto.getDate());
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cn.skcks.docking.gb28181.api.record.dto;
|
||||
package cn.skcks.docking.gb28181.api.gb28181.record.dto;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@ -11,10 +11,14 @@ import java.util.Date;
|
||||
|
||||
@Schema(title = "查询历史录像")
|
||||
@Data
|
||||
public class GetInfoDTO {
|
||||
public class GetRecordInfoDTO {
|
||||
@NotBlank
|
||||
@Schema(description = "设备id", example = "44050100001180000001")
|
||||
private String deviceId;
|
||||
@Schema(description = "设备id", example = "44050100002000000006")
|
||||
private String gbDeviceId;
|
||||
|
||||
@NotBlank
|
||||
@Schema(description = "通道id", example = "34020000001310000001")
|
||||
private String gbDeviceChannelId;
|
||||
|
||||
@Min(30)
|
||||
@Schema(description = "超时时间(秒)", example = "30")
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.skcks.docking</groupId>
|
||||
<artifactId>gb28181</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.1.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>cn.skcks.docking.gb28181</groupId>
|
||||
|
@ -1,5 +1,7 @@
|
||||
package cn.skcks.docking.gb28181.common.json;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
public class JsonException extends Exception{
|
||||
public JsonException(String message){
|
||||
super(message);
|
||||
|
@ -28,7 +28,7 @@ public class XmlUtils {
|
||||
// 大驼峰 (首字母大写)
|
||||
mapper.setPropertyNamingStrategy(new PropertyNamingStrategies.UpperCamelCaseStrategy());
|
||||
// 添加 xml 头部声明
|
||||
mapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true);
|
||||
mapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, false);
|
||||
}
|
||||
|
||||
public static String toXml(Object obj) {
|
||||
|
@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.skcks.docking</groupId>
|
||||
<artifactId>gb28181</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.1.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>cn.skcks.docking.gb28181</groupId>
|
||||
@ -22,19 +22,21 @@
|
||||
<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>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.skcks.docking.gb28181</groupId>
|
||||
<artifactId>gb28181-sip</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.skcks.docking.gb28181</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -49,13 +51,6 @@
|
||||
<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>
|
||||
|
@ -1,65 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
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;
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
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;
|
||||
@ -21,7 +23,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SipListenerImpl implements SipListener {
|
||||
private final SipSubscribe sipSubscribe;
|
||||
private final SipSubscribe subscribe;
|
||||
private final ConcurrentMap<String, MessageProcessor> requestProcessor = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<String, MessageProcessor> responseProcessor = new ConcurrentHashMap<>();
|
||||
|
||||
@ -65,6 +67,11 @@ public class SipListenerImpl implements SipListener {
|
||||
// 增加其它无需回复的响应,如101、180等
|
||||
} 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();
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
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) {
|
||||
}
|
||||
}
|
@ -2,28 +2,35 @@ 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.core.sip.gb28181.constant.CmdType;
|
||||
import cn.skcks.docking.gb28181.config.sip.SipConfig;
|
||||
import cn.skcks.docking.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.header.CallIdHeader;
|
||||
import javax.sip.address.SipURI;
|
||||
import javax.sip.message.Response;
|
||||
import java.util.EventObject;
|
||||
import java.util.Optional;
|
||||
@ -36,6 +43,9 @@ 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
|
||||
@ -48,10 +58,10 @@ public class MessageRequestProcessor implements MessageProcessor {
|
||||
RequestEvent requestEvent = (RequestEvent) eventObject;
|
||||
SIPRequest request = (SIPRequest)requestEvent.getRequest();
|
||||
String deviceId = SipUtil.getUserIdFromFromHeader(request);
|
||||
CallIdHeader callIdHeader = request.getCallIdHeader();
|
||||
String callId = request.getCallIdHeader().getCallId();
|
||||
|
||||
byte[] content = request.getRawContent();
|
||||
MessageDTO messageDto = XmlUtils.parse(content, MessageDTO.class, GB28181Constant.CHARSET);
|
||||
MessageDTO messageDto = MANSCDPUtils.parse(content, MessageDTO.class);
|
||||
log.debug("接收到的消息 => {}", messageDto);
|
||||
|
||||
DockingDevice device = deviceService.getDevice(deviceId);
|
||||
@ -70,13 +80,28 @@ 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.getRecordInfoSubscribe().getPublisher(key))
|
||||
.ifPresentOrElse(publisher-> publisher.submit(dto),
|
||||
()-> log.warn("对应订阅 {} 已结束, 异常数据 => {}",key, dto));
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
response = response(request, Response.NOT_IMPLEMENTED, ResponseStatus.NOT_IMPLEMENTED.getMessage());
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
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.core.sip.utils.SipUtil;
|
||||
import cn.skcks.docking.gb28181.sdp.GB28181Description;
|
||||
import cn.skcks.docking.gb28181.sdp.parser.GB28181DescriptionParser;
|
||||
import gov.nist.javax.sip.ResponseEventExt;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@ -16,7 +17,6 @@ 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.getInviteSubscribe().getPublisher(subscribeKey).submit(response);
|
||||
subscribe.getSipResponseSubscribe().getPublisher(subscribeKey).submit(response);
|
||||
return;
|
||||
}
|
||||
// 成功响应
|
||||
// 下发ack
|
||||
if (statusCode == Response.OK) {
|
||||
Optional.ofNullable(subscribe.getInviteSubscribe().getPublisher(subscribeKey))
|
||||
Optional.ofNullable(subscribe.getSipResponseSubscribe().getPublisher(subscribeKey))
|
||||
.ifPresentOrElse(publisher-> publisher.submit(response),
|
||||
()-> log.warn("对应订阅 {} 已结束",callId.getCallId()));
|
||||
|
||||
ResponseEventExt event = (ResponseEventExt) requestEvent;
|
||||
String contentString = new String(response.getRawContent());
|
||||
Gb28181Sdp gb28181Sdp = SipUtil.parseSDP(contentString);
|
||||
SessionDescription sdp = gb28181Sdp.getBaseSdb();
|
||||
GB28181DescriptionParser gb28181DescriptionParser = new GB28181DescriptionParser(contentString);
|
||||
GB28181Description sdp = gb28181DescriptionParser.parse();
|
||||
SipURI requestUri = SipFactory.getInstance().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
|
||||
Request reqAck = SipRequestBuilder.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);
|
||||
|
||||
|
@ -7,7 +7,9 @@ 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.core.sip.message.auth.DigestServerAuthenticationHelper;
|
||||
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.processor.MessageProcessor;
|
||||
import cn.skcks.docking.gb28181.core.sip.message.sender.SipMessageSender;
|
||||
import cn.skcks.docking.gb28181.core.sip.utils.SipUtil;
|
||||
@ -49,7 +51,7 @@ public class RegisterRequestProcessor implements MessageProcessor {
|
||||
@PostConstruct
|
||||
@Override
|
||||
public void init(){
|
||||
sipListener.addRequestProcessor(Method.REGISTER,this);
|
||||
sipListener.addRequestProcessor(Request.REGISTER,this);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@ -81,18 +83,19 @@ 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 = getMessageFactory().createResponse(Response.UNAUTHORIZED, request);
|
||||
DigestServerAuthenticationHelper.generateChallenge(getHeaderFactory(),response,sipConfig.getDomain());
|
||||
Response response = registerResponseBuilder.createAuthorzatioinResponse(request,sipConfig.getDomain(), password);
|
||||
sender.send(senderIp,response);
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("认证信息 => {}", authorization);
|
||||
boolean authPass = StringUtils.isBlank(password) ||
|
||||
DigestServerAuthenticationHelper.doAuthenticatePlainTextPassword(request,password);
|
||||
DigestAuthenticationHelper.doAuthenticatePlainTextPassword(request,password);
|
||||
if(!authPass){
|
||||
Response response = getMessageFactory().createResponse(Response.FORBIDDEN, request);
|
||||
response.setReasonPhrase("认证失败");
|
||||
@ -130,6 +133,7 @@ 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;
|
||||
@ -137,14 +141,14 @@ public class RegisterRequestProcessor implements MessageProcessor {
|
||||
|
||||
if (device == null) {
|
||||
device = new DockingDevice();
|
||||
device.setStreamMode(ListeningPoint.UDP);
|
||||
device.setStreamMode(MediaStreamMode.UDP.getMode());
|
||||
device.setCharset(GB28181Constant.CHARSET);
|
||||
device.setGeoCoordSys(GB28181Constant.GEO_COORD_SYS);
|
||||
device.setDeviceId(deviceId);
|
||||
device.setOnLine(false);
|
||||
} else {
|
||||
if (ObjectUtils.isEmpty(device.getStreamMode())) {
|
||||
device.setStreamMode(ListeningPoint.UDP);
|
||||
device.setStreamMode(MediaStreamMode.UDP.getMode());
|
||||
}
|
||||
if (ObjectUtils.isEmpty(device.getCharset())) {
|
||||
device.setCharset(GB28181Constant.CHARSET);
|
||||
|
@ -4,6 +4,7 @@ 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;
|
||||
@ -18,6 +19,8 @@ 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){
|
||||
@ -31,7 +34,7 @@ public interface GenericSubscribe<T> {
|
||||
|
||||
public static <T> void delPublisher(Map<String, SubmissionPublisher<T>> publishers, String key){
|
||||
SubmissionPublisher<T> publisher = publishers.remove(key);
|
||||
publisher.close();
|
||||
Optional.ofNullable(publisher).ifPresent(SubmissionPublisher::close);
|
||||
}
|
||||
|
||||
public static <T> void addPublisher(Executor executor, Map<String, SubmissionPublisher<T>> publishers, String key){
|
||||
|
@ -0,0 +1,16 @@
|
||||
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);
|
||||
}
|
@ -30,6 +30,10 @@ 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);
|
||||
|
@ -30,6 +30,10 @@ 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);
|
||||
|
@ -0,0 +1,69 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ 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;
|
||||
@ -12,6 +13,8 @@ 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
|
||||
@ -20,18 +23,20 @@ import java.util.concurrent.Executor;
|
||||
public class SipSubscribe {
|
||||
@Qualifier(DefaultSipExecutor.EXECUTOR_BEAN_NAME)
|
||||
private final Executor executor;
|
||||
private GenericSubscribe<RecordInfoResponseDTO> recordInfoSubscribe;
|
||||
private GenericSubscribe<SIPResponse> inviteSubscribe;
|
||||
private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
private GenericTimeoutSubscribe<SIPResponse> sipResponseSubscribe;
|
||||
private GenericTimeoutSubscribe<SIPRequest> sipRequestSubscribe;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
recordInfoSubscribe = new RecordInfoSubscribe(executor);
|
||||
inviteSubscribe = new InviteSubscribe(executor);
|
||||
// 通用订阅器
|
||||
sipResponseSubscribe = new SipResponseSubscribe(executor, scheduledExecutorService);
|
||||
sipRequestSubscribe = new SipRequestSubscribe(executor, scheduledExecutorService);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void destroy() {
|
||||
inviteSubscribe.close();
|
||||
recordInfoSubscribe.close();
|
||||
sipResponseSubscribe.close();
|
||||
sipRequestSubscribe.close();
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,11 @@ 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
|
||||
@ -67,6 +70,61 @@ 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"));
|
||||
@ -98,7 +156,7 @@ public class SipServiceImpl implements SipService {
|
||||
}
|
||||
} catch (Exception e){
|
||||
log.error("[sip] {}:{} 监听失败, 请检查端口是否被占用, 错误信息 => {}",ip,port, e.getMessage());
|
||||
Runtime.getRuntime().exit(-1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ 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;
|
||||
@ -19,9 +18,6 @@ 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;
|
||||
@ -166,37 +162,6 @@ 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=字段, 移除以解析。
|
||||
|
@ -24,5 +24,6 @@ public class DockingOrmInitService {
|
||||
public void init() {
|
||||
log.info("[orm] 自动建表");
|
||||
mapper.createDeviceTable();
|
||||
mapper.createDeviceChannelTable();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,76 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ 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;
|
||||
|
||||
@ -48,6 +49,7 @@ 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());
|
||||
|
@ -0,0 +1,66 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,19 @@
|
||||
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;
|
||||
@ -23,8 +22,15 @@ 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;
|
||||
@ -41,6 +47,7 @@ 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;
|
||||
|
||||
@ -55,6 +62,7 @@ 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");
|
||||
@ -92,7 +100,7 @@ public class PlayService {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private JsonResponse<Void> closeStream(String streamId, MediaSdpHelper.Action action, DockingDevice device, String channelId) {
|
||||
private JsonResponse<Void> closeStream(String streamId, GB28181SDPBuilder.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);
|
||||
@ -123,8 +131,8 @@ public class PlayService {
|
||||
return result;
|
||||
}
|
||||
|
||||
String streamId = MediaSdpHelper.getStreamId(deviceId, channelId);
|
||||
String key = CacheUtil.getKey(MediaSdpHelper.Action.PLAY.getAction(), deviceId, channelId);
|
||||
String streamId = GB28181SDPBuilder.getStreamId(deviceId, channelId);
|
||||
String key = CacheUtil.getKey(GB28181SDPBuilder.Action.PLAY.getAction(), deviceId, channelId);
|
||||
if (RedisUtil.KeyOps.hasKey(key)) {
|
||||
result.setResult(JsonResponse.success(videoUrl(streamId)));
|
||||
return result;
|
||||
@ -139,22 +147,30 @@ 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 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);
|
||||
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);
|
||||
Flow.Subscriber<SIPResponse> subscriber = new Flow.Subscriber<>() {
|
||||
private Flow.Subscription subscription;
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Flow.Subscription subscription) {
|
||||
this.subscription = subscription;
|
||||
log.info("订阅 {} {}", MessageProcessor.Method.INVITE, subscribeKey);
|
||||
log.info("订阅 {} {}", Request.INVITE, subscribeKey);
|
||||
subscription.request(1);
|
||||
}
|
||||
|
||||
@ -163,15 +179,15 @@ public class PlayService {
|
||||
int statusCode = item.getStatusCode();
|
||||
log.debug("{} 收到订阅消息 {}", subscribeKey, item);
|
||||
if (statusCode == Response.TRYING) {
|
||||
log.info("订阅 {} {} 尝试连接流媒体服务", MessageProcessor.Method.INVITE, subscribeKey);
|
||||
log.info("订阅 {} {} 尝试连接流媒体服务", Request.INVITE, subscribeKey);
|
||||
subscription.request(1);
|
||||
} else if (statusCode >= Response.OK && statusCode < Response.MULTIPLE_CHOICES) {
|
||||
log.info("订阅 {} {} 流媒体服务连接成功, 开始推送视频流", MessageProcessor.Method.INVITE, subscribeKey);
|
||||
log.info("订阅 {} {} 流媒体服务连接成功, 开始推送视频流", Request.INVITE, subscribeKey);
|
||||
RedisUtil.StringOps.set(key, JsonUtils.toCompressJson(new SipTransactionInfo(item, ssrc)));
|
||||
result.setResult(JsonResponse.success(videoUrl(streamId)));
|
||||
onComplete();
|
||||
} else {
|
||||
log.info("订阅 {} {} 连接流媒体服务时出现异常, 终止订阅", MessageProcessor.Method.INVITE, subscribeKey);
|
||||
log.info("订阅 {} {} 连接流媒体服务时出现异常, 终止订阅", Request.INVITE, subscribeKey);
|
||||
RedisUtil.KeyOps.delete(key);
|
||||
result.setResult(JsonResponse.error("连接流媒体服务失败"));
|
||||
ssrcService.releaseSsrc(zlmMediaConfig.getId(), ssrc);
|
||||
@ -186,13 +202,17 @@ public class PlayService {
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
subscribe.getInviteSubscribe().delPublisher(subscribeKey);
|
||||
subscribe.getSipResponseSubscribe().delPublisher(subscribeKey);
|
||||
}
|
||||
};
|
||||
subscribe.getInviteSubscribe().addSubscribe(subscribeKey, subscriber);
|
||||
// 1小时自动关闭
|
||||
byeSubscribe(inviteRequestBuilder,provider,callId,3600,()->{
|
||||
RedisUtil.KeyOps.delete(key);
|
||||
});
|
||||
subscribe.getSipResponseSubscribe().addSubscribe(subscribeKey, subscriber);
|
||||
sender.send(senderIp, request);
|
||||
result.onTimeout(() -> {
|
||||
subscribe.getInviteSubscribe().delPublisher(subscribeKey);
|
||||
subscribe.getSipResponseSubscribe().delPublisher(subscribeKey);
|
||||
result.setResult(JsonResponse.error("点播超时"));
|
||||
});
|
||||
return result;
|
||||
@ -206,8 +226,8 @@ public class PlayService {
|
||||
return JsonResponse.error(null, "未找到设备");
|
||||
}
|
||||
|
||||
String streamId = MediaSdpHelper.getStreamId(deviceId, channelId);
|
||||
return closeStream(streamId, MediaSdpHelper.Action.PLAY, device, channelId);
|
||||
String streamId = GB28181SDPBuilder.getStreamId(deviceId, channelId);
|
||||
return closeStream(streamId, GB28181SDPBuilder.Action.PLAY, device, channelId);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@ -215,13 +235,13 @@ public class PlayService {
|
||||
DockingDevice device = deviceService.getDevice(deviceId);
|
||||
long start = startTime.toInstant().getEpochSecond();
|
||||
long end = endTime.toInstant().getEpochSecond();
|
||||
String streamId = MediaSdpHelper.getStreamId(deviceId, channelId, String.valueOf(start), String.valueOf(end));
|
||||
String streamId = GB28181SDPBuilder.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(MediaSdpHelper.Action.PLAY_BACK.getAction(), deviceId, channelId);
|
||||
String key = CacheUtil.getKey(GB28181SDPBuilder.Action.PLAY_BACK.getAction(), deviceId, channelId);
|
||||
if (RedisUtil.KeyOps.hasKey(key)) {
|
||||
result.setResult(JsonResponse.success(videoUrl(streamId)));
|
||||
return result;
|
||||
@ -235,24 +255,32 @@ public class PlayService {
|
||||
return result;
|
||||
}
|
||||
|
||||
String ssrc = ssrcService.getPlaySsrc();
|
||||
GB28181Description description = MediaSdpHelper.playback(deviceId, channelId, Connection.IP4, ip, port, ssrc, StreamMode.of(device.getStreamMode()), startTime, endTime);
|
||||
|
||||
String ssrc = ssrcService.getPlayBackSsrc();
|
||||
String transport = device.getTransport();
|
||||
String senderIp = device.getLocalIp();
|
||||
SipProvider provider = sipService.getProvider(transport, senderIp);
|
||||
CallIdHeader callId = provider.getNewCallId();
|
||||
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);
|
||||
|
||||
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("订阅 {} {}", MessageProcessor.Method.INVITE, subscribeKey);
|
||||
log.info("订阅 {} {}", Request.INVITE, subscribeKey);
|
||||
subscription.request(1);
|
||||
}
|
||||
|
||||
@ -261,15 +289,16 @@ public class PlayService {
|
||||
int statusCode = item.getStatusCode();
|
||||
log.debug("{} 收到订阅消息 {}", subscribeKey, item);
|
||||
if (statusCode == Response.TRYING) {
|
||||
log.info("订阅 {} {} 尝试连接流媒体服务", MessageProcessor.Method.INVITE, subscribeKey);
|
||||
log.info("订阅 {} {} 尝试连接流媒体服务", Request.INVITE, subscribeKey);
|
||||
subscription.request(1);
|
||||
} else if (statusCode >= Response.OK && statusCode < Response.MULTIPLE_CHOICES) {
|
||||
log.info("订阅 {} {} 流媒体服务连接成功, 开始推送视频流", MessageProcessor.Method.INVITE, subscribeKey);
|
||||
log.info("订阅 {} {} 流媒体服务连接成功, 开始推送视频流", Request.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("订阅 {} {} 连接流媒体服务时出现异常, 终止订阅", MessageProcessor.Method.INVITE, subscribeKey);
|
||||
log.info("订阅 {} {} 连接流媒体服务时出现异常, 终止订阅", Request.INVITE, subscribeKey);
|
||||
RedisUtil.KeyOps.delete(key);
|
||||
result.setResult(JsonResponse.error("连接流媒体服务失败"));
|
||||
ssrcService.releaseSsrc(zlmMediaConfig.getId(), ssrc);
|
||||
@ -284,18 +313,62 @@ public class PlayService {
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
subscribe.getRecordInfoSubscribe().delPublisher(subscribeKey);
|
||||
subscribe.getSipResponseSubscribe().delPublisher(subscribeKey);
|
||||
}
|
||||
};
|
||||
subscribe.getInviteSubscribe().addSubscribe(subscribeKey, subscriber);
|
||||
byeSubscribe(inviteRequestBuilder,provider,callId,DateUtil.between(startTime,endTime,DateUnit.SECOND),()->{
|
||||
RedisUtil.KeyOps.delete(key);
|
||||
});
|
||||
subscribe.getSipResponseSubscribe().addSubscribe(subscribeKey, subscriber);
|
||||
sender.send(senderIp, request);
|
||||
result.onTimeout(() -> {
|
||||
subscribe.getInviteSubscribe().delPublisher(subscribeKey);
|
||||
subscribe.getSipResponseSubscribe().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);
|
||||
@ -306,8 +379,8 @@ public class PlayService {
|
||||
|
||||
long start = startTime.toInstant().getEpochSecond();
|
||||
long end = endTime.toInstant().getEpochSecond();
|
||||
String streamId = MediaSdpHelper.getStreamId(deviceId, channelId, String.valueOf(start), String.valueOf(end));
|
||||
return closeStream(streamId, MediaSdpHelper.Action.PLAY_BACK, device, channelId);
|
||||
String streamId = GB28181SDPBuilder.getStreamId(deviceId, channelId, String.valueOf(start), String.valueOf(end));
|
||||
return closeStream(streamId, GB28181SDPBuilder.Action.PLAY_BACK, device, channelId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,21 +3,26 @@ 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.common.xml.XmlUtils;
|
||||
import cn.skcks.docking.gb28181.config.sip.SipConfig;
|
||||
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;
|
||||
@ -25,11 +30,8 @@ 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.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
@ -38,11 +40,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
|
||||
@ -50,99 +52,55 @@ public class RecordService {
|
||||
* @param date 查询日期
|
||||
*/
|
||||
@SneakyThrows
|
||||
public DeferredResult<JsonResponse<List<RecordInfoItemVO>>> requestRecordInfo(String deviceId, long timeout, Date date) {
|
||||
public DeferredResult<JsonResponse<List<RecordInfoItemVO>>> requestRecordInfo(String deviceId, String channelId, 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(null, "未找到设备"));
|
||||
result.setResult(JsonResponse.error("未找到设备"));
|
||||
return result;
|
||||
}
|
||||
Optional<DockingDeviceChannel> deviceChannel = deviceChannelService.getDeviceChannel(deviceId, channelId);
|
||||
if(deviceChannel.isEmpty()){
|
||||
log.info("未能找到 设备编码为 => {}, 通道 => {} 的信息", deviceId, channelId);
|
||||
result.setResult(JsonResponse.error("未找到通道信息"));
|
||||
return result;
|
||||
}
|
||||
|
||||
String transport = device.getTransport();
|
||||
String senderIp = device.getLocalIp();
|
||||
SipProvider provider = sipService.getProvider(transport, senderIp);
|
||||
CallIdHeader callId = provider.getNewCallId();
|
||||
String localIp = device.getLocalIp();
|
||||
SipProvider provider = sipService.getProvider(transport, localIp);
|
||||
String callId = provider.getNewCallId().getCallId();
|
||||
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(deviceId)
|
||||
.deviceId(channelId)
|
||||
.startTime(DateUtil.beginOfDay(date))
|
||||
.endTime(DateUtil.endOfDay(date))
|
||||
.sn(sn)
|
||||
.build();
|
||||
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);
|
||||
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));
|
||||
result.onTimeout(() -> {
|
||||
result.setResult(JsonResponse.build(ResponseStatus.PARTIAL_CONTENT,
|
||||
RecordConvertor.INSTANCE.dto2Vo(sortedRecordList(list)),
|
||||
RecordConvertor.INSTANCE.dto2Vo(Collections.emptyList()),
|
||||
"查询超时, 结果可能不完整"));
|
||||
subscribe.getRecordInfoSubscribe().delPublisher(key);
|
||||
subscribe.getSipRequestSubscribe().delPublisher(key);
|
||||
});
|
||||
|
||||
provider.sendRequest(request);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,81 @@
|
||||
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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -81,7 +81,7 @@ public class SsrcService {
|
||||
/**
|
||||
* 获取录像回放的SSRC值,第一位固定为1
|
||||
*/
|
||||
public String getPlayBackSsrc(String mediaServerI) {
|
||||
public String getPlayBackSsrc() {
|
||||
return "1" + getSN();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,23 +1,24 @@
|
||||
package cn.skcks.docking.gb28181.core.sip.message.event;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
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 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 gov.nist.core.Separators;
|
||||
import gov.nist.javax.sdp.MediaDescriptionImpl;
|
||||
import gov.nist.javax.sdp.SessionDescriptionImpl;
|
||||
import gov.nist.javax.sdp.fields.*;
|
||||
import gov.nist.javax.sdp.fields.AttributeField;
|
||||
import gov.nist.javax.sdp.fields.ConnectionField;
|
||||
import gov.nist.javax.sdp.fields.URIField;
|
||||
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.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@ -124,7 +125,8 @@ public class SipEventTest {
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void sdpTest() {
|
||||
GB28181Description description = GB28181Description.Convertor.convert((SessionDescriptionImpl) SdpFactory.getInstance().createSessionDescription("Play"));
|
||||
|
||||
GB28181Description description = new GB28181Description(SdpFactory.getInstance().createSessionDescription("Play"));
|
||||
|
||||
Version version = SdpFactory.getInstance().createVersion(0);
|
||||
description.setVersion(version);
|
||||
@ -132,10 +134,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, MediaSdpHelper.RTPMAP.keySet().toArray(new String[0]));
|
||||
MediaDescription mediaDescription = SdpFactory.getInstance().createMediaDescription("video", 6666, 0, SdpConstants.RTP_AVP, GB28181SDPBuilder.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)->{
|
||||
GB28181SDPBuilder.RTPMAP.forEach((k, v)->{
|
||||
Optional.ofNullable(GB28181SDPBuilder.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)));
|
||||
@ -181,7 +183,7 @@ public class SipEventTest {
|
||||
int rtpPort = 5080;
|
||||
String rtpIp = "10.10.10.20";
|
||||
long ssrc = RandomUtil.randomLong(10000000,100000000);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
38
gb28181-sip/.gitignore
vendored
Normal file
38
gb28181-sip/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
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
|
87
gb28181-sip/pom.xml
Normal file
87
gb28181-sip/pom.xml
Normal file
@ -0,0 +1,87 @@
|
||||
<?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>
|
@ -0,0 +1,18 @@
|
||||
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";
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cn.skcks.docking.gb28181.core.sip.gb28181.sdp;
|
||||
package cn.skcks.docking.gb28181.sdp.field.ssrc;
|
||||
|
||||
import gov.nist.core.Separators;
|
||||
import gov.nist.javax.sdp.fields.SDPField;
|
||||
@ -10,7 +10,8 @@ import lombok.EqualsAndHashCode;
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class SsrcField extends SDPField {
|
||||
private static final String SSRC_FIELD = "y=";
|
||||
public static final String SSRC_FIELD_NAME = "y";
|
||||
private static final String SSRC_FIELD = SSRC_FIELD_NAME + "=";
|
||||
public SsrcField() {
|
||||
super(SSRC_FIELD);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
package cn.skcks.docking.gb28181.core.sip.gb28181.sdp;
|
||||
|
||||
package cn.skcks.docking.gb28181.sdp.media;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
@ -7,7 +8,7 @@ import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum StreamMode {
|
||||
public enum MediaStreamMode {
|
||||
UDP("UDP"),
|
||||
TCP_ACTIVE("TCP-ACTIVE"),
|
||||
TCP_PASSIVE("TCP-PASSIVE");
|
||||
@ -16,8 +17,8 @@ public enum StreamMode {
|
||||
private final String mode;
|
||||
|
||||
@JsonCreator
|
||||
public static StreamMode of(String mode) {
|
||||
for (StreamMode m : values()) {
|
||||
public static MediaStreamMode of(String mode) {
|
||||
for (MediaStreamMode m : values()) {
|
||||
if (m.getMode().equalsIgnoreCase(mode)) {
|
||||
return m;
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
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");
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
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";
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
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<>();
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
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";
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
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";
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
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";
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
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";
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
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<>();
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
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());
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package cn.skcks.docking.gb28181.sip.method.invite;
|
||||
|
||||
import javax.sip.message.Request;
|
||||
|
||||
|
||||
public interface InviteBuilder {
|
||||
String METHOD = Request.INVITE;
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package cn.skcks.docking.gb28181.sip.method.message;
|
||||
|
||||
import javax.sip.message.Request;
|
||||
|
||||
public interface MessageBuilder {
|
||||
String METHOD = Request.MESSAGE;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package cn.skcks.docking.gb28181.sip.method.notify;
|
||||
|
||||
import javax.sip.message.Request;
|
||||
|
||||
public interface NotifyBuilder {
|
||||
String METHOD = Request.NOTIFY;
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
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"));
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package cn.skcks.docking.gb28181.sip.method.register;
|
||||
|
||||
import javax.sip.message.Request;
|
||||
|
||||
|
||||
public interface RegisterBuilder {
|
||||
String METHOD = Request.REGISTER;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
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()));
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package cn.skcks.docking.gb28181.sip.method.subscribe;
|
||||
|
||||
import javax.sip.message.Request;
|
||||
|
||||
|
||||
public interface SubscribeBuilder {
|
||||
String METHOD = Request.SUBSCRIBE;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
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));
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,436 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
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
Loading…
Reference in New Issue
Block a user