Compare commits

...

246 Commits

Author SHA1 Message Date
muxiang
f00a5b11e9 兼容海康设备推流,处理流为tcp 协议时将sdp中y放到最后 2024-12-13 15:40:24 +08:00
langhuihui
5690be3b64 feat: update gosip lib 2024-08-19 16:37:01 +08:00
dexter
92c9876d82 Merge pull request #117 from bigbeer1/v4
:解决端口没有回收的问题,明确错误提示
2024-08-05 15:49:37 +08:00
dabenxiong
e9353651aa Merge remote-tracking branch 'origin/v4' into v4 2024-08-05 15:44:29 +08:00
dabenxiong
26f452fad0 fix:解决端口没有回收的问题,明确错误提示 2024-08-05 15:43:55 +08:00
dexter
ec32f5db39 Merge pull request #116 from BleethNie/v4
feat:预置位操作接口和列表查询
2024-08-02 17:39:14 +08:00
bleeth
87f3849fe7 feat:预置位操作接口和列表查询 2024-08-02 17:15:18 +08:00
dexter
78e16b4746 Merge pull request #114 from bigbeer1/v4
feat:增加配置文件支持多端口多路复用
2024-07-30 10:07:29 +08:00
dabenxiong
6ab73cd8dd feat:增加配置文件支持多端口多路复用 2024-07-30 10:00:03 +08:00
langhuihui
edffd7d470 fix: invite 失败时回收端口 2024-07-23 15:15:07 +08:00
langhuihui
70c06ebf84 fix: add ErrNoAvailablePorts 2024-07-09 19:47:12 +08:00
dexter
d35b18f1a9 Merge pull request #107 from duiniuluantanqin/fix-bug
Fix incorrect comparison
2024-04-03 10:15:12 +08:00
Haibo Chen
210f67b047 Fix incorrect comparison 2024-04-03 10:11:21 +08:00
langhuihui
bee261a670 支持通道号为空的情况 2023-12-12 19:18:48 +08:00
langhuihui
ce49463689 默认限制 invite 的通道类型 2023-12-08 19:04:43 +08:00
langhuihui
cbcaab61d4 开放更多属性 2023-12-08 09:07:13 +08:00
langhuihui
f80916dcf9 开放一些设备属性 2023-12-06 09:57:12 +08:00
langhuihui
35e916dd1c 配置中增加额外信息 2023-12-05 19:17:17 +08:00
langhuihui
26311753c2 doc: update readme 2023-10-18 14:19:49 +08:00
langhuihui
8f6ac6a40c 录像流无人观看则自动关闭 2023-09-29 09:39:15 +08:00
langhuihui
90901dc1ad 进一步加强对 from 的判断 2023-09-25 14:22:59 +08:00
langhuihui
f065353bd9 加点日志 2023-09-22 10:02:33 +08:00
langhuihui
879a7fc14c 升级 aurora 2023-09-20 19:12:31 +08:00
langhuihui
a2e47c727d 增加一些From 判空 2023-09-18 17:15:09 +08:00
langhuihui
f967679763 序列化 channel 时增加一些字段 2023-09-06 14:17:49 +08:00
langhuihui
db8ce0d94f 适配引擎升级 2023-08-13 14:49:38 +08:00
langhuihui
538e96a5c2 规避注册包里面from.Address为空导致的panic 2023-08-06 14:26:41 +08:00
langhuihui
c2003d53e8 更新readme 2023-07-14 15:55:48 +08:00
langhuihui
64ac75905f 默认打开范围端口 2023-07-11 19:38:30 +08:00
langhuihui
d2cc62ff9e 修改一个变量名拼写错误 2023-07-07 14:50:09 +08:00
dexter
2e8aa47bc5 Merge pull request #99 from kingecg/v4
修复:按需拉流支持回放流
2023-07-04 18:42:09 +08:00
程广
585d5949d3 fix query record 2023-07-04 18:17:11 +08:00
kingecg
0285236cce 修复:按需拉流支持回放流 2023-06-30 22:31:51 +08:00
dexter
12895fa2cc Merge pull request #98 from kingecg/patch-1
Update channel.go to support device not use tcp
2023-06-30 21:24:07 +08:00
kingecg
c66303e7e8 Update channel.go to support device not use tcp
when media set to tcp and device not support, fallback to udp
2023-06-30 19:13:47 +08:00
ogofly
5435c2ef1c Merge pull request #95 from rufftio/v4
设备状态变更处理
2023-06-20 14:38:59 +08:00
ogofly
d8c6ad30dd 合并三处定时任务到一个协程 2023-06-19 15:50:31 +08:00
ogofly
86fa7cc7e6 Merge branch 'Monibuca:v4' into v4 2023-06-19 14:33:50 +08:00
liuyancong
692ec21877 定时删除注册超时设备,定时设置心跳超时设备为离线, 规范设备和通道状态为枚举量 2023-06-19 14:33:18 +08:00
liuyancong
71f2b36d2d update: 默认注册有效期配置为 3600s 2023-06-19 13:20:02 +08:00
dexter
b068bd9e5b Merge pull request #94 from rufftio/v4
add:处理 Register 消息的注销情况,将设备从列表中清除
2023-06-19 12:53:10 +08:00
liuyancong
78ac89e7af add:处理 Register 消息的注销情况,将设备从列表中清除 2023-06-19 11:56:17 +08:00
dexter
1cec5301c3 Merge pull request #92 from rufftio/v4
使用 engine 的 stream pasue 和 resume 替代 neverTimeout
2023-06-07 11:09:54 +08:00
ogofly
319f7fc636 Merge branch 'Monibuca:v4' into v4 2023-06-07 11:08:22 +08:00
liuyancong
7a75810203 使用 engine 的 stream pasue 和 resume 替代 neverTimeout 2023-06-07 10:46:42 +08:00
dexter
bbc7b09835 Merge pull request #91 from rufftio/v4
fix: ptz api 参数违规返回问题
2023-06-06 19:03:00 +08:00
liuyancong
1dbdff1fe5 add: 录像播放的暂停、恢复、快进、跳转到制定时间接口 2023-06-06 18:54:27 +08:00
ogofly
952c8f0ff8 Merge branch 'Monibuca:v4' into v4 2023-06-05 22:55:29 +08:00
liuyancong
730f3014f8 fix: ptz api 参数违规返回问题 2023-06-05 19:02:14 +08:00
dexter
682aec656b Merge pull request #90 from rufftio/v4
ptz 控制接口,采用更易理解和使用的参数
2023-06-05 18:40:32 +08:00
ogofly
fbd8683f5b Merge branch 'Monibuca:v4' into v4 2023-06-05 18:18:50 +08:00
liuyancong
b15e4ee89c add: ptz 控制接口,采用更易理解和使用的参数 2023-06-05 18:17:46 +08:00
langhuihui
3c7b3a042d fix: list接口为空时返回[] 而不是null 2023-05-25 14:12:57 +08:00
dexter
858df1377e Merge pull request #88 from ogofly/v4
录像查询重构为在当前查询的http响应中返回
2023-05-24 11:36:14 +08:00
liuyancong
60021d3cd9 录像查询重构为在当前查询的http响应中返回 2023-05-24 11:30:50 +08:00
langhuihui
d8061cd7c3 channel结构体反转 2023-05-23 20:56:24 +08:00
langhuihui
ed397063c4 chroe: update log format 2023-05-21 22:12:09 +08:00
langhuihui
5853120d30 update readme 2023-05-17 09:07:12 +08:00
langhuihui
4c47df0695 fix: update dep ps version to 4.0.1 2023-05-16 23:11:26 +08:00
langhuihui
37fd121d11 feat: change to use ps plugin 2023-05-14 11:12:25 +08:00
langhuihui
05fd8c38f7 feat: add a new way to config port 2023-05-04 09:57:20 +08:00
dexter
2d85e46a8b ChannelEx 2023-04-09 11:08:23 +08:00
charlestamz
4a90d7bf91 修复Recover导致断流的问题 2023-04-05 22:25:53 +08:00
dexter
a020f3ea81 适配引擎修改 2023-04-04 20:20:27 +08:00
dexter
c68862160f sip监听网卡地址可配,dump 可同时多路,修复 tcp 方式,媒体默认使用 tcp 2023-03-28 19:42:37 +08:00
dexter
9db29a9280 rtp引用采用v1版 2023-03-13 13:12:17 +08:00
charlestamz
5d5dae8939 增加了是否排序的功能 2023-03-09 05:57:40 +08:00
charlestamz
a0f16d1184 修复id小于9位时报错
增加一些更易排错的信息
2023-03-04 23:03:03 +08:00
charlestamz
53ddc0cb63 修复catalog报文parentid的处理 2023-03-01 17:15:04 +08:00
charlestamz
d156974f73 国标向上级联的一些改动 2023-03-01 17:06:34 +08:00
dexter
f3046bcde3 将ps发布逻辑移入引擎中 2023-02-28 20:13:19 +08:00
charlestamz
9c970ad282 注释暂不处理级联目录信息 2023-02-27 14:25:41 +08:00
charlestamz
c4de92e9f6 修复MediaPortMin无法使用的问题 2023-02-22 23:44:44 +08:00
charlestamz
cf5a803971 优化Invite,代码优化 2023-02-22 23:13:08 +08:00
dexter
f487be5fdb invite恢复成channelID 2023-02-21 21:28:23 +08:00
dexter
bd70d24a16 Merge pull request #81 from WXC9102/v4
修复对平台等非摄像头通道进行invite的问题
2023-02-21 14:02:02 +08:00
weixuechao
708cd042df 增加配置项inviteids,修复对平台等非摄像头通道进行invite的问题 2023-02-21 13:42:32 +08:00
charlestamz
a69b739e5e 修复Invite报文 2023-02-20 16:41:55 +08:00
dexter
4e96efa9ff fix: 丢包逻辑判断第一个包会丢掉 2023-02-20 16:20:28 +08:00
dexter
3a704b68cc 采用引擎处理ps 2023-02-20 00:20:20 +08:00
charlestamz
c8f51a7ec5 修复:gosip不支持go标准的监听格式 2023-02-19 23:57:48 +08:00
charlestamz
b7bad99292 优化,消除一些低级错误 2023-02-19 23:45:36 +08:00
charlestamz
7b6b827899 修复多次发送Invite和Catalog得问题 2023-02-19 15:25:18 +08:00
charlestamz
d121927c96 优化GB插件,修复一些问题 2023-02-17 23:19:02 +08:00
dexter
9a3ad6a51c 音频加入时钟频率 2023-02-15 19:32:37 +08:00
dexter
e0c6fbefcd 适配引擎 2023-02-14 21:22:54 +08:00
dexter
b924977085 Merge pull request #80 from yangchao2015/v4
基于新版本修改时间判断,调整AutosubPosition 默认为false
2023-02-03 16:14:47 +08:00
yangchao
521ee36769 基于新版本修改时间判断,调整AutosubPosition 默认为false 2023-02-03 16:02:09 +08:00
dexter
583754ea82 对时间配置统一改成time.Duration类型 2023-02-02 12:03:44 +08:00
charlestamz
58b6a818bd Merge pull request #79 from yangchao2015/v4
调整和更新GB轨迹订阅和获取
2023-02-01 16:59:20 +08:00
yangchao
8b1b176f51 调整和更新GB轨迹订阅和获取 2023-02-01 16:07:45 +08:00
dexter
68d6cbaab9 g711初始化代码收于引擎中 2023-01-18 21:03:08 +08:00
dexter
f88d4d264e tcp使用范围端口时不再判断ssrc 2023-01-10 09:24:27 +08:00
dexter
55aa20e868 fix: 时间戳设置错误 2022-12-31 21:30:28 +08:00
dexter
9f4ad83da7 增加对aac的预测 2022-12-18 14:28:21 +08:00
charlestamz
68ff4dba5b Merge remote-tracking branch 'origin/v4' into v4 2022-11-29 12:38:00 +08:00
charlestamz
e9f576e3f4 解决GB设备兼容的问题。
修复发送invite指令中非同域To的地址问题
2022-11-29 12:37:46 +08:00
charlestamz
b271cb8e50 merge remote changes 2022-11-29 12:32:09 +08:00
dexter
2b82a0ffc4 📦 NEW: 设置丢帧标志 2022-11-22 11:18:21 +08:00
dexter
940d7c5e59 🐛 FIX: 优化PS解析 2022-11-17 23:47:20 +08:00
dexter
2142a474a3 🐛 FIX: 大PES分包机制 2022-11-17 11:53:36 +08:00
dexter
86e9bccb85 🐛 FIX: 增加对AAC的支持以及多slice合并发送 2022-11-13 23:30:45 +08:00
dexter
bb3a679a60 音频时间戳转换 2022-10-25 21:38:06 +08:00
dexter
ecd97c8439 🐛 FIX: tcp解析 2022-10-25 15:35:00 +08:00
dexter
bfd71a72d8 Merge pull request #74 from hongri8488/v4
添加28181 bye接口调用时将流关闭
2022-10-25 14:44:39 +08:00
dexter
c6bef8ccd8 🐛 FIX: udp范围端口增加超时功能 2022-10-25 14:44:03 +08:00
hongri8488
20c0ac52cb 添加28181 bye接口调用时将流关闭 2022-10-25 13:51:31 +08:00
dexter
34f5b7da79 🐛 FIX: 推测h264的时候取的下标错误 2022-10-24 10:54:03 +08:00
dexter
0d3a795dc2 👌 IMPROVE: 细粒度拼凑视频帧 2022-10-23 11:39:04 +08:00
dexter
b52d457990 🐛 FIX: 解决udp丢包引起的ps包组包错乱问题 2022-10-22 09:22:46 +08:00
dexter
4a214cebeb 🐛 FIX: PS解析 2022-10-21 16:16:44 +08:00
dexter
8f78f992ca Merge pull request #73 from hongri8488/v4
修改tcp请求部分设备不能拉流的问题
2022-10-21 11:52:36 +08:00
zhangdongfang
228d7b0cd2 修改tcp请求部分设备不能拉流的问题 2022-10-21 10:49:31 +08:00
dexter
e99150b0be Merge pull request #72 from hongri8488/v4
添加tcp多端口端口回收
2022-10-20 15:07:21 +08:00
zhangdongfang
31112e0052 添加tcp多端口端口回收 2022-10-20 15:03:18 +08:00
dexter
8663b8e171 Merge pull request #71 from hongri8488/v4
添加gb28181 tcp多端口
2022-10-20 14:55:32 +08:00
zhangdongfang
5960f07fc3 添加gb28181 tcp多端口 2022-10-20 14:49:31 +08:00
dexter
eb6004d6ef 🐛 FIX: 防止dts自动生成 2022-10-17 11:37:30 +08:00
dexter
cce5f67ab9 🐛 FIX: initRoutes索引越界 2022-10-13 16:23:32 +08:00
dexter
fdfb462d46 Merge pull request #70 from WXC9102/v4
更新通道panic
2022-09-26 18:52:23 +08:00
weixuechao
c05adce562 1.更新通道时会偶发panic, 2.新增/删除通道信息没有更新 2022-09-26 16:54:55 +08:00
dexter
aa3727f582 🐛 FIX: unlock时的判空 2022-09-19 00:21:48 +08:00
dexter
6e8709176e 🐛 FIX: sdp中t值序列化问题 2022-09-16 18:06:15 +08:00
dexter
3e6c43f6ff 📦 NEW: UDP多端口支持 2022-09-16 16:23:58 +08:00
dexter
4a7aa94bd2 🐛 FIX: PTZCmd报文传递DeviceID为通道ID 2022-09-16 13:29:11 +08:00
dexter
fd13c6d9ab 🐛 FIX: InviteOptions.String方法格式化类型不匹配 2022-09-14 17:44:09 +08:00
dexter
085d413d2b 📦 NEW: 增加设备信息写盘重启快速恢复连接 2022-09-12 22:56:04 +08:00
dexter
4eba0e23f9 👌 IMPROVE: 增加网页显示dump文件的详细数据展示 2022-09-11 15:10:21 +08:00
dexter
f0324c4283 📦 NEW: 添加dump功能 2022-09-10 00:14:53 +08:00
dexter
f6b5f15b83 🐛 FIX: 查询录像接口文档错误 2022-09-06 19:15:08 +08:00
dexter
a6de68496b 🐛 FIX: 修复PS解析漏洞 2022-09-04 21:35:37 +08:00
dexter
709988cf09 增加sdp后的空行 2022-08-23 18:50:27 +08:00
dexter
9cf8c1acc1 消除多余的引用 2022-08-21 19:11:25 +08:00
dexter
03bd17bd24 隐藏一个日志打印 2022-08-21 14:53:19 +08:00
dexter
ed0b751e89 1、修复SIPIP的获取,2、修复多次自动invite,3、修复时间戳pts获取bug 2022-08-20 23:02:40 +08:00
dexter
b8a2812c40 微调mediaIP计算逻辑 2022-08-19 09:55:45 +08:00
dexter
abd7b6ba77 补充position接口文档 2022-08-14 11:10:34 +08:00
dexter
a09080641d 增加日志级别配置 2022-08-13 12:13:33 +08:00
charlestamz
cf761e09d7 fixed ssrc in invite 2022-07-17 10:41:55 +08:00
charlestamz
e4569c1dd7 fixed ssrc in invite 2022-07-15 15:23:32 +08:00
dexter
50bfd5b390 Merge pull request #61 from husanpao/v4
1.修复海康摄像头获取不到ip,崩溃的问题。
2022-07-12 10:33:42 +08:00
uma-pc001
e937325058 1.修复海康摄像头获取不到ip,崩溃的问题。 2022-07-12 10:30:53 +08:00
dexter
158c317511 Merge pull request #60 from husanpao/v4
修复自动获取serverip支持公网
2022-07-11 16:38:30 +08:00
uma-pc001
e1a2587b48 1.修复deviceip获取方式
2.添加公网ip获取
2022-07-11 16:01:07 +08:00
dexter
8f2a90e8ac 代码优化 2022-07-10 15:48:49 +08:00
dexter
179280b67a Merge pull request #59 from dwdcth/v4
用zap替换logger
2022-07-10 09:22:48 +08:00
banshan
35c8d5171f 用zap替换logger 2022-07-10 07:15:53 +08:00
dexter
75342e5bb3 增加错误判断 2022-07-09 23:12:44 +08:00
dexter
c742f226e9 代码重构 2022-07-09 15:33:59 +08:00
dexter
11b9da4c9d Merge pull request #57 from husanpao/v4
update auto media ip.
2022-07-09 10:12:11 +08:00
tianmiao
490c85b46a fix: 解决bye时publisher空指针的问题 2022-07-08 15:26:17 +08:00
uma-pc001
5d7d81a46b update auto media ip. 2022-07-07 14:05:25 +08:00
uma-pc001
eb53a9594a update auto media ip. 2022-07-07 14:02:20 +08:00
uma-pc001
37b4774e9a update auto media ip 2022-07-07 12:30:01 +08:00
差沙
64861c37b1 Merge pull request #54 from jmszl/develop-v4
fix: 注册回复时,header增加Date
2022-07-04 10:38:57 +08:00
tbz
16b5fa375c fix: 注册回复时,header增加Date 2022-07-01 19:25:25 +08:00
dexter
ee419570a2 删除无效engine依赖 2022-06-17 10:11:21 +08:00
dexter
e110acfb56 序列化json错误 2022-06-07 21:17:24 +08:00
dexter
07c4dd0c21 list增加json返回功能 2022-06-07 19:18:55 +08:00
dexter
0299a9ef7e Merge branch 'v4' of github.com:Monibuca/plugin-gb28181 into v4 2022-06-05 21:30:26 +08:00
dexter
bbeae2bc9c feat: mediaIP默认不配置后跟随sipIP 2022-06-05 21:23:51 +08:00
charlestamz
b0a23ac3d2 修复gb28181插件NVR多通道接入问题
修改为goland兼容
2022-05-31 09:08:38 +08:00
dexter
7f69c61eaf 修改readme中配置大小写 2022-05-20 16:59:36 +08:00
dexter
d795e7c80a update go.mod 2022-05-15 19:05:46 +08:00
tianmiao
2033bdf04f fix: 写入dts 2022-05-09 20:35:44 +08:00
tianmiao
79538986e5 升级rtp包 2022-04-23 12:35:54 +08:00
tianmiao
438b0d2bd0 调整pts,dts 2022-04-22 19:14:59 +08:00
charlestamz
47c916e131 Merge pull request #49 from EngineerZhong/v4
Sip获取Catalog信令,MessageHeader,From缺失tag导致的通信超时问题。
2022-04-18 16:26:39 +08:00
钟文彬
748c4117c6 Sip获取Catalog信令,MessageHeader,From缺失tag导致的通信超时问题。 2022-04-18 14:40:15 +08:00
tianmiao
11444a6398 修复绑定serverip问题 2022-04-18 11:46:42 +08:00
tianmiao
ae6cb14536 更新README 2022-04-17 18:22:16 +08:00
tianmiao
957bdee6a4 更新README,添加API 2022-04-17 17:40:53 +08:00
差沙
9cf4607478 Merge pull request #48 from airiotdev/v4
V4
2022-04-17 17:18:25 +08:00
tianmiao
74241c20ab Merge remote-tracking branch 'monibuca/v4' into v4 2022-04-17 17:16:43 +08:00
tianmiao
9d7b5feaf0 删除老的sip实现 2022-04-17 14:29:07 +08:00
tianmiao
53551f3514 完成gosip移植,完成视频流显示 2022-04-17 14:09:54 +08:00
tianmiao
6e2bd0c146 Merge branch 'v4' of https://github.com/airiotdev/plugin-gb28181 into v4 2022-04-15 19:48:51 +08:00
tianmiao
60f9a18aca 更新使用gosip 2022-04-15 19:13:15 +08:00
tianmiao
4bfd3103cc 使用gosip 2022-04-14 23:36:56 +08:00
charlestamz
98bf9c6b32 fix 2022-04-14 22:13:41 +08:00
charlestamz
cfed8a9d2f fix 2022-04-14 22:12:06 +08:00
charlestamz
6cd4d8e51a 1. location protocol.
2. more fix
2022-04-14 22:07:05 +08:00
tianmiao
dd5617737a 使用gosip 2022-04-14 21:45:41 +08:00
dexter
616df4c2b1 Merge pull request #46 from airiotdev/v4
V4版本适配
2022-04-11 11:09:15 +08:00
tianmiao
b8fcfb0213 实现v4版本推流逻辑 2022-04-07 21:31:45 +08:00
tianmiao
01c36d591c Publisher逻辑升级完成 2022-04-07 00:30:28 +08:00
tianmiao
2143cc4911 初步升级到v4,完成设备注册等工作。 2022-04-06 19:12:45 +08:00
charlie
931f8d888d 1. added LogVerbose parameter in config
2. fixed  problems that public IP address communicates to private networking devices
****attention:
language spec was updated to 1.18
2022-04-05 05:19:18 +08:00
dexter
c9709d0e48 Merge pull request #42 from GeekChengZ/v3
Update channel.go
2022-03-08 13:54:05 +08:00
GeekCheng
61441cc372 Update channel.go
查询所有录像产生类型
2022-03-08 11:11:15 +08:00
charlie
803543fbdf 处理invite发送过快导致callid随机字符串一致的问题 2022-02-28 14:10:33 +08:00
charlie
03a2aeec2c tested on more cameras 2022-02-16 14:26:15 +08:00
charlie
8bb92f87f1 fix self-channel becoming children 2022-02-11 11:35:23 +08:00
charlie
3b9f9abc4a dependency fix 2022-02-10 14:54:10 +08:00
charlie
813447b355 完善gb28181协议 2022-02-10 11:28:30 +08:00
charlie
81827cb8c9 thread safe improvement 2022-02-09 00:50:02 +08:00
charlie
4cbffb84b8 refactor 2022-01-27 18:48:00 +08:00
charlie
5b4e520be7 first working code 2022-01-27 14:26:10 +08:00
charlie
cb1bafc269 first compiled code 2022-01-26 18:26:31 +08:00
dexter
1a013b5ebc Merge pull request #38 from charlestamz/v3
fixed memory leak
2022-01-21 08:53:44 +08:00
charlie
63eba38094 fixed memory leak 2022-01-21 00:14:27 +08:00
dexter
3f03146f5c 修改sip请求等待时间 2022-01-19 13:42:15 +08:00
dexter
c5ac90cdff 适配引擎升级 2022-01-10 09:09:15 +08:00
dexter
9c818244cb 补充越界判断 2021-12-22 09:54:56 +08:00
dexter
bf5f2b804d 修复音频时间戳问题 2021-12-01 21:17:06 +08:00
dexter
a2bfeec948 Merge pull request #36 from dwdcth/v3
修复排序清空缓存的bug
2021-11-29 11:23:09 +08:00
banshan
3a618fc0ce Merge branch 'Monibuca:v3' into v3 2021-11-28 19:54:54 +08:00
banshan
75f3601326 修复empty后序号断裂 2021-11-28 19:54:20 +08:00
dexter
c07309f14f Merge pull request #35 from dwdcth/v3
udp包排序
2021-11-22 12:32:24 +08:00
banshan
aefd8e72f5 rtp sort 2021-11-22 12:06:38 +08:00
banshan
0db5da87a8 sort rtp 2021-11-21 22:31:49 +08:00
dexter
50da427ab7 修改自动关流的配置字段,改为延时大小 2021-11-16 20:22:36 +08:00
dexter
35c9142d0e 增加可以监听多个TCP媒体端口的功能 2021-11-13 11:44:41 +08:00
dexter
de4a9668cd 状态机nict完成时没有及时终止事务导致bye卡住5秒 2021-11-10 21:18:13 +08:00
dexter
fc7758e1da tcp断开清理 2021-11-10 20:11:53 +08:00
dexter
4cae096425 修复低级错误 2021-11-09 13:52:31 +08:00
dexter
cad56c207d 添加TCP拉流功能 2021-11-08 20:57:34 +08:00
dexter
75b1890e20 Merge branch 'v3' of github.com:Monibuca/plugin-gb28181 into v3 2021-10-31 11:00:04 +08:00
dexter
83f7d3711a 加上对消息头中的from判空 2021-10-31 10:59:58 +08:00
dexter
b2d65700bc Merge pull request #34 from dwdcth/v3
修复账号密码注册bug
2021-10-19 10:46:41 +08:00
banshan
eb7e47e98a 修复账号密码注册bug 2021-10-19 10:42:14 +08:00
banshan
57bce22209 注修复册密码判断bug 2021-10-19 10:12:51 +08:00
dexter
97d65b9710 优化设备更新机制 2021-10-09 09:59:54 +08:00
dexter
416818ff53 Query改成循环尝试,防止内存泄露 2021-10-09 09:30:52 +08:00
dexter
fd7ee7f80c 增加子码流定时判断 2021-10-08 09:20:38 +08:00
dexter
b251f8471a 对bye增加参数区分录像和直播 2021-10-07 20:21:43 +08:00
dexter
a2647a1564 增加自动重连机制 2021-10-07 17:48:16 +08:00
dexter
8e6544766e 漏了第一个timer 2021-10-07 10:33:40 +08:00
dexter
86f7effd47 更新device时关闭定时器 2021-10-07 08:53:44 +08:00
dexter
65c1864d5b 更新设备时移植channel数据 2021-10-07 08:38:07 +08:00
dexter
d474a500c9 加入encoding 2021-10-07 08:28:12 +08:00
dexter
ba14fdafa7 fix自动拉流 2021-10-06 22:06:19 +08:00
dexter
a7c866d453 回复消息中加入Date字段 2021-10-06 21:50:16 +08:00
dexter
ae6400cfc0 尝试\r\n 2021-10-06 21:21:56 +08:00
dexter
bd7f5eba92 给200加上OK 2021-10-06 21:00:46 +08:00
dexter
195c0ac124 加入Event头域 2021-10-06 20:50:50 +08:00
dexter
6fd2d3f5a7 注册回包增加contact 2021-10-06 18:35:41 +08:00
dexter
523d1e8e5f fix bug 2021-10-06 16:50:31 +08:00
dexter
0c4a7356a1 catalog直到成功 2021-10-06 10:24:02 +08:00
dexter
ad0402fcbf 增加subscribe信令定时发送 2021-10-06 08:52:42 +08:00
dexter
fd2e614d74 fix:alive=true 2021-10-05 09:56:55 +08:00
dexter
8704c54137 对于多批次目录查询结果的删除机制优化 2021-10-02 18:55:55 +08:00
dexter
9c99033b3c 去掉via中无branch时painc的逻辑 2021-10-02 15:19:57 +08:00
dexter
00aa30e428 Merge pull request #33 from dwdcth/v3
增加国标注册时的用户名和密码
2021-09-25 17:29:07 +08:00
banshan
51583d3885 增加国标注册时的用户名和密码 2021-09-25 16:39:50 +08:00
dexter
837b6b287d 更新channelMap中的device引用 2021-09-15 19:04:06 +08:00
50 changed files with 3538 additions and 4902 deletions

162
README.md
View File

@@ -7,33 +7,42 @@
github.com/Monibuca/plugin-gb28181
## 插件引入
```go
import (
_ "github.com/Monibuca/plugin-gb28181"
_ "m7s.live/plugin/gb28181/v4"
)
```
## 默认插件配置
```toml
[GB28181]
Serial = "34020000002000000001"
Realm = "3402000000"
Expires = 3600
ListenAddr = "127.0.0.1:5060"
AutoUnPublish = true
AutoInvite = false
MediaPort = 58200
CatalogInterval = 30
```yaml
gb28181:
invitemode: 1 #0、手动invite 1、表示自动发起invite当ServerSIP接收到设备信息时立即向设备发送invite命令获取流,2、按需拉流既等待订阅者触发
position:
autosubposition: false #是否自动订阅定位
expires: 3600s #订阅周期(单位:秒)默认3600
interval: 6s #订阅间隔单位默认6
sipip: "" #sip服务器地址 默认 自动适配设备网段
serial: "34020000002000000001"
realm: "3402000000"
username: ""
password: ""
registervalidity: 60s #注册有效期
mediaip: "" #媒体服务器地址 默认 自动适配设备网段
port:
sip: udp:5060 #sip服务器端口
media: tcp:58200-59200 #媒体服务器端口,用于接收设备的流
fdm: false #端口复用,单端口默认多路复用,多端口多路复用根据这个
removebaninterval: 10m #定时移除注册失败的设备黑名单单位秒默认10分钟600秒
loglevel: info
```
- `ListenAddr`是监听的地址这里需要注意的是必须要带上Server的IP地址这个IP地址是向设备发送信息的时候需要带上的。
- `Serial` ServerSIP的编号
- `Realm` ServerSIP的域
- `AutoUnPublish` 如果设置为true则当某个流最后一个订阅者取消订阅时会自动发送bye节省流量。如果为了响应及时可以设置成false保持流的连接
- `AutoInvite` 表示自动发起invite当ServerSIP接收到设备信息时立即向设备发送invite命令获取流
- `MediaPort` 表示用于接收设备流的端口号
- `CatalogInterval` 定时获取设备目录的间隔,单位秒
**如果配置了端口范围(默认为范围端口),将采用范围端口机制,每一个流对应一个端口
**注意某些摄像机没有设置用户名的地方摄像机会以自身的国标id作为用户名这个时候m7s会忽略使用摄像机的用户名忽略配置的用户名**
如果设备配置了错误的用户名和密码连续三次上报错误后m7s会记录设备id并在10分钟内禁止设备注册
## 插件功能
@@ -44,6 +53,7 @@ CatalogInterval = 30
- 发送RecordInfo命令查询设备对录像数据
- 发送Invite命令获取设备的实时视频或者录像视频
- 发送PTZ命令来控制摄像头云台
- 自动同步设备位置
### 作为GB28281的流媒体服务器接受设备的媒体流
@@ -53,66 +63,100 @@ CatalogInterval = 30
## 接口API
### 罗列所有的gb28181协议的设备
`/api/gb28181/list`
`/gb28181/api/list`
设备的结构体如下
```go
type Device struct {
*transaction.Core `json:"-"`
ID string
RegisterTime time.Time
UpdateTime time.Time
Status string
Channels []*Channel
queryChannel bool
sn int
from *sip.Contact
to *sip.Contact
Addr string
SipIP string //暴露的IP
channelMap map[string]*Channel
channelMutex sync.RWMutex
ID string
Name string
Manufacturer string
Model string
Owner string
RegisterTime time.Time
UpdateTime time.Time
LastKeepaliveAt time.Time
Status string
Channels []*Channel
NetAddr string
}
```
> 根据golang的规则小写字母开头的变量不会被序列化
### 从设备拉取视频流
`/api/gb28181/invite`
参数名 | 必传 | 含义
|----|---|---
id|是 | 设备ID
channel|是|通道编号
startTime|否|开始时间纯数字Unix时间戳
endTime|否|结束时间纯数字Unix时间戳
`/gb28181/api/invite`
返回200代表成功
| 参数名 | 必传 | 含义 |
| --------- | ---- | ---------------------------- |
| id | 是 | 设备ID |
| channel | 是 | 通道编号 |
| startTime | 否 | 开始时间纯数字Unix时间戳 |
| endTime | 否 | 结束时间纯数字Unix时间戳 |
返回200代表成功, 304代表已经在拉取中不能重复拉仅仅针对直播流
### 停止从设备拉流
`/api/gb28181/bye`
`/gb28181/api/bye`
参数名 | 必传 | 含义
|----|---|---
id|是 | 设备ID
channel|是|通道编号
| 参数名 | 必传 | 含义 |
| ------- | ---- | -------- |
| id | 是 | 设备ID |
| channel | 是 | 通道编号 |
http 200 表示成功404流不存在
### 发送控制命令
`/api/gb28181/control`
`/gb28181/api/control`
参数名 | 必传 | 含义
|----|---|---
id|是 | 设备ID
channel|是|通道编号
ptzcmd|是|PTZ控制指令
| 参数名 | 必传 | 含义 |
| ------- | ---- | ----------- |
| id | 是 | 设备ID |
| channel | 是 | 通道编号 |
| ptzcmd | 是 | PTZ控制指令 |
### 查询录像
`/api/gb28181/query/records`
`/gb28181/api/records`
参数名 | 必传 | 含义
|----|---|---
id|是 | 设备ID
channel|是|通道编号
startTime|否|开始时间字符串格式2021-7-23T12:00:00
endTime|否|结束时间(字符串格式同上)
| 参数名 | 必传 | 含义 |
| --------- | ---- | -------------------------------------------- |
| id | 是 | 设备ID |
| channel | 是 | 通道编号 |
| startTime | 否 | 开始时间Unix时间戳 |
| endTime | 否 | 结束时间Unix时间戳 |
### 移动位置订阅
`/gb28181/api/position`
| 参数名 | 必传 | 含义 |
| -------- | ---- | -------------- |
| id | 是 | 设备ID |
| expires | 是 | 订阅周期(秒) |
| interval | 是 | 订阅间隔(秒) |
### 预置位列表查询
`/gb28181/api/preset/list`
| 参数名 | 必传 | 含义 |
| -------- | ---- | -------------- |
| id | 是 | 设备ID |
| channel | 是 | 通道编号 |
### 预置位操作
`/gb28181/api/preset/control`
| 参数名 | 必传 | 含义 |
| -------- | ---- |---------------------|
| id | 是 | 设备ID |
| channel | 是 | 通道编号 |
| cmd | 是 | 操作指令 0=新增,1=删除,2=调用 |
| point | 是 | 预置点位1-255 |

View File

@@ -2,36 +2,158 @@ package gb28181
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/Monibuca/engine/v3"
"github.com/Monibuca/plugin-gb28181/v3/sip"
"github.com/Monibuca/plugin-gb28181/v3/transaction"
"github.com/Monibuca/plugin-gb28181/v3/utils"
"sync/atomic"
"github.com/ghettovoice/gosip/sip"
"github.com/goccy/go-json"
"go.uber.org/zap"
. "m7s.live/engine/v4"
"m7s.live/engine/v4/log"
"m7s.live/plugin/gb28181/v4/utils"
"m7s.live/plugin/ps/v4"
)
type ChannelEx struct {
device *Device
inviteRes *sip.Message
recordInviteRes *sip.Message
RecordPublisher *Publisher
LivePublisher *Publisher
LiveSubSP string //实时子码流
Records []*Record
RecordStartTime string
RecordEndTime string
recordStartTime time.Time
recordEndTime time.Time
state int32
var QUERY_RECORD_TIMEOUT = time.Second * 5
type PullStream struct {
opt *InviteOptions
channel *Channel
inviteRes sip.Response
}
func (p *PullStream) CreateRequest(method sip.RequestMethod) (req sip.Request) {
res := p.inviteRes
req = p.channel.CreateRequst(method)
from, _ := res.From()
to, _ := res.To()
callId, _ := res.CallID()
req.ReplaceHeaders(from.Name(), []sip.Header{from})
req.ReplaceHeaders(to.Name(), []sip.Header{to})
req.ReplaceHeaders(callId.Name(), []sip.Header{callId})
return
}
func (p *PullStream) Bye() int {
req := p.CreateRequest(sip.BYE)
resp, err := p.channel.Device.SipRequestForResponse(req)
if p.opt.IsLive() {
p.channel.State.Store(0)
}
if p.opt.recyclePort != nil {
p.opt.recyclePort(p.opt.MediaPort)
}
if err != nil {
return http.StatusInternalServerError
}
return int(resp.StatusCode())
}
func (p *PullStream) info(body string) int {
d := p.channel.Device
req := p.CreateRequest(sip.INFO)
contentType := sip.ContentType("Application/MANSRTSP")
req.AppendHeader(&contentType)
req.SetBody(body, true)
resp, err := d.SipRequestForResponse(req)
if err != nil {
log.Warnf("Send info to stream error: %v, stream=%s, body=%s", err, p.opt.StreamPath, body)
return getSipRespErrorCode(err)
}
return int(resp.StatusCode())
}
// 暂停播放
func (p *PullStream) Pause() int {
body := fmt.Sprintf(`PAUSE RTSP/1.0
CSeq: %d
PauseTime: now
`, p.channel.Device.SN)
return p.info(body)
}
// 恢复播放
func (p *PullStream) Resume() int {
d := p.channel.Device
body := fmt.Sprintf(`PLAY RTSP/1.0
CSeq: %d
Range: npt=now-
`, d.SN)
return p.info(body)
}
// 跳转到播放时间
// second: 相对于起始点调整到第 sec 秒播放
func (p *PullStream) PlayAt(second uint) int {
d := p.channel.Device
body := fmt.Sprintf(`PLAY RTSP/1.0
CSeq: %d
Range: npt=%d-
`, d.SN, second)
return p.info(body)
}
// 快进/快退播放
// speed 取值: 0.25 0.5 1 2 4 或者其对应的负数表示倒放
func (p *PullStream) PlayForward(speed float32) int {
d := p.channel.Device
body := fmt.Sprintf(`PLAY RTSP/1.0
CSeq: %d
Scale: %0.6f
`, d.SN, speed)
return p.info(body)
}
type Channel struct {
Device *Device `json:"-" yaml:"-"` // 所属设备
State atomic.Int32 `json:"-" yaml:"-"` // 通道状态,0:空闲,1:正在invite,2:正在播放/对讲
LiveSubSP string // 实时子码流通过rtsp
GpsTime time.Time // gps时间
Longitude string // 经度
Latitude string // 纬度
*log.Logger `json:"-" yaml:"-"`
ChannelInfo
}
type PresetInfo struct {
PresetID int `json:"-" yaml:"-"` //
PresetName string `json:"-" yaml:"-"` //
}
func (c *Channel) MarshalJSON() ([]byte, error) {
m := map[string]any{
"DeviceID": c.DeviceID,
"ParentID": c.ParentID,
"Name": c.Name,
"Manufacturer": c.Manufacturer,
"Model": c.Model,
"Owner": c.Owner,
"CivilCode": c.CivilCode,
"Address": c.Address,
"Port": c.Port,
"Parental": c.Parental,
"SafetyWay": c.SafetyWay,
"RegisterWay": c.RegisterWay,
"Secrecy": c.Secrecy,
"Status": c.Status,
"Longitude": c.Longitude,
"Latitude": c.Latitude,
"GpsTime": c.GpsTime,
"LiveSubSP": c.LiveSubSP,
"LiveStatus": c.State.Load(),
}
return json.Marshal(m)
}
// Channel 通道
type Channel struct {
DeviceID string
type ChannelInfo struct {
DeviceID string // 通道ID
ParentID string
Name string
Manufacturer string
@@ -39,65 +161,173 @@ type Channel struct {
Owner string
CivilCode string
Address string
Port int
Parental int
SafetyWay int
RegisterWay int
Secrecy int
Status string
Children []*Channel
*ChannelEx //自定义属性
Status ChannelStatus
}
func (c *Channel) CreateMessage(Method sip.Method) (requestMsg *sip.Message) {
requestMsg = c.device.CreateMessage(Method)
requestMsg.StartLine.Uri = sip.NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain())
requestMsg.To = &sip.Contact{
Uri: requestMsg.StartLine.Uri,
type ChannelStatus string
const (
ChannelOnStatus = "ON"
ChannelOffStatus = "OFF"
)
func (channel *Channel) CreateRequst(Method sip.RequestMethod) (req sip.Request) {
d := channel.Device
d.SN++
callId := sip.CallID(utils.RandNumString(10))
userAgent := sip.UserAgentHeader("Monibuca")
maxForwards := sip.MaxForwards(70) //增加max-forwards为默认值 70
cseq := sip.CSeq{
SeqNo: uint32(d.SN),
MethodName: Method,
}
requestMsg.From = &sip.Contact{
Uri: sip.NewURI(config.Serial + "@" + config.Realm),
Params: map[string]string{"tag": utils.RandNumString(9)},
port := sip.Port(conf.SipPort)
serverAddr := sip.Address{
//DisplayName: sip.String{Str: d.serverConfig.Serial},
Uri: &sip.SipUri{
FUser: sip.String{Str: conf.Serial},
FHost: d.SipIP,
FPort: &port,
},
Params: sip.NewParams().Add("tag", sip.String{Str: utils.RandNumString(9)}),
}
return
//非同一域的目标地址需要使用@host
host := conf.Realm
if channel.DeviceID[0:10] != host {
if channel.Port != 0 {
deviceIp := d.NetAddr
deviceIp = deviceIp[0:strings.LastIndex(deviceIp, ":")]
host = fmt.Sprintf("%s:%d", deviceIp, channel.Port)
} else {
host = d.NetAddr
}
}
channelAddr := sip.Address{
//DisplayName: sip.String{Str: d.serverConfig.Serial},
Uri: &sip.SipUri{FUser: sip.String{Str: channel.DeviceID}, FHost: host},
}
req = sip.NewRequest(
"",
Method,
channelAddr.Uri,
"SIP/2.0",
[]sip.Header{
serverAddr.AsFromHeader(),
channelAddr.AsToHeader(),
&callId,
&userAgent,
&cseq,
&maxForwards,
serverAddr.AsContactHeader(),
},
"",
nil,
)
req.SetTransport(conf.SipNetwork)
req.SetDestination(d.NetAddr)
return req
}
func (channel *Channel) QueryRecord(startTime, endTime string) int {
d := channel.device
channel.RecordStartTime = startTime
channel.RecordEndTime = endTime
channel.recordStartTime, _ = time.Parse(TIME_LAYOUT, startTime)
channel.recordEndTime, _ = time.Parse(TIME_LAYOUT, endTime)
channel.Records = nil
requestMsg := channel.CreateMessage(sip.MESSAGE)
requestMsg.ContentType = "Application/MANSCDP+xml"
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
<Query>
<CmdType>RecordInfo</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
<StartTime>%s</StartTime>
<EndTime>%s</EndTime>
<Secrecy>0</Secrecy>
<Type>time</Type>
</Query>`, d.sn, requestMsg.To.Uri.UserInfo(), startTime, endTime)
requestMsg.ContentLength = len(requestMsg.Body)
return d.SendMessage(requestMsg).Code
func (channel *Channel) QueryRecord(startTime, endTime string) ([]*Record, error) {
d := channel.Device
request := d.CreateRequest(sip.MESSAGE)
contentType := sip.ContentType("Application/MANSCDP+xml")
request.AppendHeader(&contentType)
// body := fmt.Sprintf(`<?xml version="1.0"?>
// <Query>
// <CmdType>RecordInfo</CmdType>
// <SN>%d</SN>
// <DeviceID>%s</DeviceID>
// <StartTime>%s</StartTime>
// <EndTime>%s</EndTime>
// <Secrecy>0</Secrecy>
// <Type>all</Type>
// </Query>`, d.sn, channel.DeviceID, startTime, endTime)
start, _ := strconv.ParseInt(startTime, 10, 0)
end, _ := strconv.ParseInt(endTime, 10, 0)
body := BuildRecordInfoXML(d.SN, channel.DeviceID, start, end)
request.SetBody(body, true)
resultCh := RecordQueryLink.WaitResult(d.ID, channel.DeviceID, d.SN, QUERY_RECORD_TIMEOUT)
resp, err := d.SipRequestForResponse(request)
if err != nil {
return nil, fmt.Errorf("query error: %s", err)
}
if resp.StatusCode() != http.StatusOK {
return nil, fmt.Errorf("query error, status=%d", resp.StatusCode())
}
// RecordQueryLink 中加了超时机制,该结果一定会返回
// 所以此处不用再增加超时等保护机制
r := <-resultCh
return r.list, r.err
}
func (channel *Channel) QueryPresetList() (sip.Response, error) {
d := channel.Device
request := d.CreateRequest(sip.MESSAGE)
contentType := sip.ContentType("Application/MANSCDP+xml")
request.AppendHeader(&contentType)
body := BuildPresetListXML(100, channel.DeviceID)
request.SetBody(body, true)
resp, err := d.SipRequestForResponse(request)
if err != nil {
return nil, fmt.Errorf("query error: %s", err)
}
if resp.StatusCode() != http.StatusOK {
return nil, fmt.Errorf("query error, status=%d", resp.StatusCode())
}
return resp, nil
}
func (channel *Channel) PresetControl(ptzCode int, point byte) int {
cmd := byte(PresetSet)
switch ptzCode {
case PresetAddPoint:
cmd = PresetSet
case PresetDelPoint:
cmd = PresetDel
case PresetCallPoint:
cmd = PresetCall
default:
}
PTZCmd := Pack(cmd, point)
return channel.Control(PTZCmd)
}
func (channel *Channel) Control(PTZCmd string) int {
d := channel.device
requestMsg := channel.CreateMessage(sip.MESSAGE)
requestMsg.ContentType = "Application/MANSCDP+xml"
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
<Control>
<CmdType>DeviceControl</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
<PTZCmd>%s</PTZCmd>
</Control>`, d.sn, requestMsg.To.Uri.UserInfo(), PTZCmd)
requestMsg.ContentLength = len(requestMsg.Body)
return d.SendMessage(requestMsg).Code
d := channel.Device
request := d.CreateRequest(sip.MESSAGE)
contentType := sip.ContentType("Application/MANSCDP+xml")
request.AppendHeader(&contentType)
body := fmt.Sprintf(`<?xml version="1.0"?>
<Control>
<CmdType>DeviceControl</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
<PTZCmd>%s</PTZCmd>
</Control>`, d.SN, channel.DeviceID, PTZCmd)
request.SetBody(body, true)
resp, err := d.SipRequestForResponse(request)
if err != nil {
return http.StatusRequestTimeout
}
return int(resp.StatusCode())
}
/*
// Invite 发送Invite报文 invites a channel to play
// 注意里面的锁保证不同时发送invite报文该锁由channel持有
/***
f字段 f = v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
各项具体含义:
v后续参数为视频的参数各参数间以 “/”分割;
@@ -141,163 +371,265 @@ f = v/a/编码格式/码率大小/采样率
f字段中视、音频参数段之间不需空格分割。
可使用f字段中的分辨率参数标识同一设备不同分辨率的码流。
*/
func (channel *Channel) Invite(start, end string) (code int) {
if start == "" {
if !atomic.CompareAndSwapInt32(&channel.state, 0, 1) {
return 304
func (channel *Channel) Invite(opt *InviteOptions) (code int, err error) {
if opt.IsLive() {
if !channel.State.CompareAndSwap(0, 1) {
return 304, nil
}
defer func() {
if code != 200 {
atomic.StoreInt32(&channel.state, 0)
if err != nil {
GB28181Plugin.Error("InviteRetryInit", zap.Error(err))
channel.State.Store(0)
if conf.InviteMode == 1 {
// 5秒后重试
time.AfterFunc(time.Second*5, func() {
channel.Invite(opt)
})
}
} else {
channel.State.Store(2)
}
}()
}
channel.Bye()
sint, err1 := strconv.ParseInt(start, 10, 0)
eint, err2 := strconv.ParseInt(end, 10, 0)
d := channel.device
d := channel.Device
streamPath := fmt.Sprintf("%s/%s", d.ID, channel.DeviceID)
s := "Play"
ssrc := make([]byte, 10)
if start != "" {
if err1 != nil || err2 != nil {
return 400
}
opt.CreateSSRC()
if opt.Record() {
s = "Playback"
ssrc[0] = '1'
streamPath = fmt.Sprintf("%s/%s/%s-%s", d.ID, channel.DeviceID, start, end)
streamPath = fmt.Sprintf("%s/%s/%d-%d", d.ID, channel.DeviceID, opt.Start, opt.End)
}
if opt.StreamPath != "" {
streamPath = opt.StreamPath
} else if channel.DeviceID == "" {
streamPath = "gb28181/" + d.ID
} else {
ssrc[0] = '0'
opt.StreamPath = streamPath
}
if opt.dump == "" {
opt.dump = conf.DumpPath
}
protocol := ""
networkType := "udp"
// 根据配置文件判断是否多路复用
reusePort := conf.Port.Fdm
if conf.IsMediaNetworkTCP() {
networkType = "tcp"
protocol = "TCP/"
if conf.tcpPorts.Valid {
opt.MediaPort, err = conf.tcpPorts.GetPort()
opt.recyclePort = conf.tcpPorts.Recycle
}
} else {
if conf.udpPorts.Valid {
opt.MediaPort, err = conf.udpPorts.GetPort()
opt.recyclePort = conf.udpPorts.Recycle
}
}
if err != nil {
return http.StatusInternalServerError, err
}
if opt.MediaPort == 0 {
opt.MediaPort = conf.MediaPort
// 单端口默认多路复用
reusePort = true
}
// size := 1
// fps := 15
// bitrate := 200
// fmt.Sprintf("f=v/2/%d/%d/1/%da///", size, fps, bitrate)
copy(ssrc[1:6], []byte(config.Serial[3:8]))
randNum := rand.Intn(10000)
copy(ssrc[6:], []byte(strconv.Itoa(randNum)))
sdpInfo := []string{
"v=0",
fmt.Sprintf("o=%s 0 0 IN IP4 %s", d.Serial, d.SipIP),
fmt.Sprintf("o=%s 0 0 IN IP4 %s", channel.DeviceID, d.MediaIP),
"s=" + s,
"u=" + channel.DeviceID + ":0",
"c=IN IP4 " + d.SipIP,
fmt.Sprintf("t=%d %d", sint, eint),
fmt.Sprintf("m=video %d RTP/AVP 96 97 98", config.MediaPort),
"c=IN IP4 " + d.MediaIP,
opt.String(),
fmt.Sprintf("m=video %d %sRTP/AVP 96", opt.MediaPort, protocol),
"a=recvonly",
"a=rtpmap:96 PS/90000",
"a=rtpmap:97 MPEG4/90000",
"a=rtpmap:98 H264/90000",
"y=" + string(ssrc),
}
invite := channel.CreateMessage(sip.INVITE)
invite.ContentType = "application/sdp"
invite.Contact = &sip.Contact{
Uri: sip.NewURI(fmt.Sprintf("%s@%s:%d", d.Serial, d.SipIP, d.SipPort)),
if conf.IsMediaNetworkTCP() {
sdpInfo = append(sdpInfo, "a=setup:passive", "a=connection:new")
}
invite.Body = strings.Join(sdpInfo, "\r\n") + "\r\n"
invite.ContentLength = len(invite.Body)
invite.Subject = fmt.Sprintf("%s:%s,%s:0", channel.DeviceID, ssrc, config.Serial)
response := d.SendMessage(invite)
fmt.Printf("invite response statuscode: %d\n", response.Code)
if response.Code == 200 {
ds := strings.Split(response.Data.Body, "\r\n")
_SSRC, _ := strconv.ParseInt(string(ssrc), 10, 0)
SSRC := uint32(_SSRC)
sdpInfo = append(sdpInfo, "y="+opt.ssrc)
invite := channel.CreateRequst(sip.INVITE)
contentType := sip.ContentType("application/sdp")
invite.AppendHeader(&contentType)
invite.SetBody(strings.Join(sdpInfo, "\r\n")+"\r\n", true)
subject := sip.GenericHeader{
HeaderName: "Subject", Contents: fmt.Sprintf("%s:%s,%s:0", channel.DeviceID, opt.ssrc, conf.Serial),
}
invite.AppendHeader(&subject)
inviteRes, err := d.SipRequestForResponse(invite)
if err != nil {
if opt.recyclePort != nil {
opt.recyclePort(opt.MediaPort)
}
channel.Error("inviteRequestError", zap.Error(err), zap.String("msg", invite.String()))
return http.StatusInternalServerError, err
}
code = int(inviteRes.StatusCode())
channel.Info("invite response", zap.Int("status code", code))
if code == http.StatusOK {
ds := strings.Split(inviteRes.Body(), "\r\n")
for _, l := range ds {
if ls := strings.Split(l, "="); len(ls) > 1 {
if ls[0] == "y" && len(ls[1]) > 0 {
_SSRC, _ = strconv.ParseInt(ls[1], 10, 0)
SSRC = uint32(_SSRC)
break
if _ssrc, err := strconv.ParseInt(ls[1], 10, 0); err == nil {
opt.SSRC = uint32(_ssrc)
} else {
channel.Error("read invite response y ", zap.Error(err))
}
// break
}
if ls[0] == "m" && len(ls[1]) > 0 {
netinfo := strings.Split(ls[1], " ")
if strings.ToUpper(netinfo[2]) == "TCP/RTP/AVP" {
channel.Debug("Device support tcp")
} else {
channel.Debug("Device not support tcp")
networkType = "udp"
}
}
}
}
publisher := &Publisher{
Stream: &engine.Stream{
StreamPath: streamPath,
AutoUnPublish: config.AutoUnPublish,
},
}
if start == "" {
publisher.Type = "GB18181 Live"
publisher.OnClose = func() {
publishers.Remove(SSRC)
channel.LivePublisher = nil
channel.ByeBye(channel.inviteRes)
channel.inviteRes = nil
atomic.StoreInt32(&channel.state, 0)
if config.AutoInvite {
go channel.Invite("", "")
}
var psPuber ps.PSPublisher
err = psPuber.Receive(streamPath, opt.dump, fmt.Sprintf("%s:%d", networkType, opt.MediaPort), opt.SSRC, reusePort)
if err != nil {
if opt.recyclePort != nil {
opt.recyclePort(opt.MediaPort)
}
} else {
publisher.Type = "GB18181 Record"
publisher.OnClose = func() {
publishers.Remove(SSRC)
channel.RecordPublisher = nil
channel.ByeBye(channel.recordInviteRes)
channel.recordInviteRes = nil
channel.Error("inviteTcpCreateError", zap.Error(err))
return http.StatusInternalServerError, err
}
if !opt.IsLive() {
// 10秒无数据关闭
if psPuber.Stream.DelayCloseTimeout == 0 {
psPuber.Stream.DelayCloseTimeout = time.Second * 10
}
if psPuber.Stream.IdleTimeout == 0 {
psPuber.Stream.IdleTimeout = time.Second * 10
}
}
if !publisher.Publish() {
return 403
PullStreams.Store(streamPath, &PullStream{
opt: opt,
channel: channel,
inviteRes: inviteRes,
})
err = srv.Send(sip.NewAckRequest("", invite, inviteRes, "", nil))
} else {
if opt.recyclePort != nil {
opt.recyclePort(opt.MediaPort)
}
publishers.Add(SSRC, publisher)
if start == "" {
channel.inviteRes = response.Data
channel.LivePublisher = publisher
} else {
channel.RecordPublisher = publisher
channel.recordInviteRes = response.Data
}
return
}
func (channel *Channel) Bye(streamPath string) int {
d := channel.Device
if streamPath == "" {
streamPath = fmt.Sprintf("%s/%s", d.ID, channel.DeviceID)
}
if s, loaded := PullStreams.LoadAndDelete(streamPath); loaded {
s.(*PullStream).Bye()
if s := Streams.Get(streamPath); s != nil {
s.Close()
}
ack := d.CreateMessage(sip.ACK)
ack.StartLine = &sip.StartLine{
Uri: sip.NewURI(channel.DeviceID + "@" + d.to.Uri.Domain()),
Method: sip.ACK,
return http.StatusOK
}
return http.StatusNotFound
}
func (channel *Channel) Pause(streamPath string) int {
if s, loaded := PullStreams.Load(streamPath); loaded {
r := s.(*PullStream).Pause()
if s := Streams.Get(streamPath); s != nil {
s.Pause()
}
ack.From = response.Data.From
ack.To = response.Data.To
ack.CallID = response.Data.CallID
ack.CSeq.ID = invite.CSeq.ID
go d.Send(ack)
return r
}
return http.StatusNotFound
}
func (channel *Channel) Resume(streamPath string) int {
if s, loaded := PullStreams.Load(streamPath); loaded {
r := s.(*PullStream).Resume()
if s := Streams.Get(streamPath); s != nil {
s.Resume()
}
return r
}
return http.StatusNotFound
}
func (channel *Channel) PlayAt(streamPath string, second uint) int {
if s, loaded := PullStreams.Load(streamPath); loaded {
r := s.(*PullStream).PlayAt(second)
if s := Streams.Get(streamPath); s != nil {
s.Resume()
}
return r
}
return http.StatusNotFound
}
func (channel *Channel) PlayForward(streamPath string, speed float32) int {
if s, loaded := PullStreams.Load(streamPath); loaded {
return s.(*PullStream).PlayForward(speed)
}
if s := Streams.Get(streamPath); s != nil {
s.Resume()
}
return http.StatusNotFound
}
func (channel *Channel) TryAutoInvite(opt *InviteOptions) {
condition := !opt.IsLive() || channel.CanInvite()
channel.Debug("TryAutoInvite", zap.Any("opt", opt), zap.Bool("condition", condition))
if condition {
go channel.Invite(opt)
}
}
func (channel *Channel) CanInvite() bool {
if channel.State.Load() != 0 || len(channel.DeviceID) != 20 || channel.Status == ChannelOffStatus {
return false
}
return response.Code
}
func (channel *Channel) Bye() int {
if channel.inviteRes != nil {
defer func() {
channel.inviteRes = nil
if channel.LivePublisher != nil {
channel.LivePublisher.Close()
if conf.InviteIDs == "" {
return true
}
// 1113位是设备类型编码
typeID := channel.DeviceID[10:13]
// format: start-end,type1,type2
tokens := strings.Split(conf.InviteIDs, ",")
for _, tok := range tokens {
if first, second, ok := strings.Cut(tok, "-"); ok {
if typeID >= first && typeID <= second {
return true
}
}()
return channel.ByeBye(channel.inviteRes).Code
}
if channel.recordInviteRes != nil {
defer func() {
channel.recordInviteRes = nil
if channel.RecordPublisher != nil {
channel.RecordPublisher.Close()
} else {
if typeID == first {
return true
}
}()
return channel.ByeBye(channel.recordInviteRes).Code
}
}
return 404
return false
}
func (c *Channel) ByeBye(res *sip.Message) *transaction.Response {
if res == nil {
return nil
func getSipRespErrorCode(err error) int {
if re, ok := err.(*sip.RequestError); ok {
return int(re.Code)
} else {
return http.StatusInternalServerError
}
bye := c.device.CreateMessage(sip.BYE)
bye.StartLine = &sip.StartLine{
Uri: sip.NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain()),
Method: sip.BYE,
}
bye.From = res.From
bye.To = res.To
bye.CallID = res.CallID
return c.device.SendMessage(bye)
}

View File

@@ -1,27 +1,18 @@
package sip
package gb28181
import "fmt"
//transaction sip error
var errorMap = map[int]string{
//1xx
var reasons = map[int]string{
100: "Trying",
180: "Ringing",
181: "Call Is Being Forwarded",
182: "Queued",
183: "Session Progress",
199: "Early Dialog Terminated",
//2xx
200: "OK",
202: "Accepted",
204: "No Notification",
//3xx
300: "Multiple Choices",
301: "Moved Permanently",
302: "Moved Temporarily",
305: "Use Proxy",
380: "Alternative Service",
//4xx
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
@@ -31,33 +22,16 @@ var errorMap = map[int]string{
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Conditional Request Failed",
413: "Request Entity Too Large",
414: "Request-URI Too Long",
415: "Unsupported Media Type",
416: "Unsupported URI Scheme",
417: "Unknown Resource-Priority",
420: "Bad Extension",
421: "Extension Required",
422: "Session Interval Too Small",
423: "Interval Too Brief",
424: "Bad Location Information",
428: "Use Identity Header",
429: "Provide Referrer Identity",
430: "Flow Failed",
433: "Anonymity Disallowed",
436: "Bad Identity Info",
437: "Unsupported Credential",
438: "Invalid Identity Header",
439: "First Hop Lacks Outbound Support",
440: "Max-Breadth Exceeded",
469: "Bad Info Package",
470: "Consent Needed",
480: "Temporarily Unavailable",
481: "Call/Transaction Does Not Exist",
481: "Call transaction Does Not Exist",
482: "Loop Detected",
483: "Too Many Hops",
484: "Address Incomplete",
@@ -68,28 +42,25 @@ var errorMap = map[int]string{
489: "Bad Event",
491: "Request Pending",
493: "Undecipherable",
494: "Security Agreement Required",
//5xx
500: "Server Internal Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Server Time-out",
504: "Server Tim",
505: "Version Not Supported",
513: "Message Too Large",
580: "Precondition Failure",
//6xx
513: "message Too Large",
600: "Busy Everywhere",
603: "Decline",
604: "Does Not Exist Anywhere",
606: "Not Acceptable",
607: "Unwanted",
687: "Dialog Terminated",
606: "SESSION NOT ACCEPTABLE",
}
func DumpError(code int) string {
if code == 0 {
return "invalid status reason for request"
}
return fmt.Sprintf("%d %s", code, errorMap[code])
func Explain(statusCode int) string {
return reasons[statusCode]
}
const (
INVIDE_MODE_MANUAL = iota
INVIDE_MODE_AUTO
INVIDE_MODE_ONSUBSCRIBE
)

612
device.go
View File

@@ -1,24 +1,28 @@
package gb28181
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/Monibuca/engine/v3"
"github.com/Monibuca/plugin-gb28181/v3/sip"
"github.com/Monibuca/plugin-gb28181/v3/transaction"
"github.com/Monibuca/plugin-gb28181/v3/utils"
// . "github.com/Monibuca/utils/v3"
// . "github.com/logrusorgru/aurora"
"go.uber.org/zap"
"m7s.live/engine/v4"
"m7s.live/engine/v4/log"
"m7s.live/plugin/gb28181/v4/utils"
"github.com/ghettovoice/gosip/sip"
myip "github.com/husanpao/ip"
)
const TIME_LAYOUT = "2006-01-02T15:04:05"
// Record 录像
type Record struct {
//channel *Channel
DeviceID string
Name string
FilePath string
@@ -33,128 +37,504 @@ func (r *Record) GetPublishStreamPath() string {
return fmt.Sprintf("%s/%s", r.DeviceID, r.StartTime)
}
var (
Devices sync.Map
DeviceNonce sync.Map //保存nonce防止设备伪造
DeviceRegisterCount sync.Map //设备注册次数
)
type DeviceStatus string
const (
DeviceRegisterStatus = "REGISTER"
DeviceRecoverStatus = "RECOVER"
DeviceOnlineStatus = "ONLINE"
DeviceOfflineStatus = "OFFLINE"
DeviceAlarmedStatus = "ALARMED"
)
type Device struct {
*transaction.Core `json:"-"`
ID string
RegisterTime time.Time
UpdateTime time.Time
Status string
Channels []*Channel
queryChannel bool
sn int
from *sip.Contact
to *sip.Contact
Addr string
SipIP string //暴露的IP
channelMap map[string]*Channel
channelMutex sync.RWMutex
ID string
Name string
Manufacturer string
Model string
Owner string
RegisterTime time.Time
UpdateTime time.Time
LastKeepaliveAt time.Time
Status DeviceStatus
SN int
Addr sip.Address `json:"-" yaml:"-"`
SipIP string //设备对应网卡的服务器ip
MediaIP string //设备对应网卡的服务器ip
NetAddr string
channelMap sync.Map
subscriber struct {
CallID string
Timeout time.Time
}
lastSyncTime time.Time
GpsTime time.Time //gps时间
Longitude string //经度
Latitude string //纬度
*log.Logger `json:"-" yaml:"-"`
}
func (d *Device) addChannel(channel *Channel) {
for _, c := range d.Channels {
if c.DeviceID == channel.DeviceID {
return
}
func (d *Device) MarshalJSON() ([]byte, error) {
type Alias Device
data := &struct {
Channels []*Channel
*Alias
}{
Alias: (*Alias)(d),
}
d.Channels = append(d.Channels, channel)
d.channelMap.Range(func(key, value interface{}) bool {
data.Channels = append(data.Channels, value.(*Channel))
return true
})
return json.Marshal(data)
}
func (d *Device) UpdateChannels(list []*Channel) {
d.channelMutex.Lock()
defer d.channelMutex.Unlock()
if d.queryChannel {
d.Channels = nil
d.queryChannel = false
func (c *GB28181Config) RecoverDevice(d *Device, req sip.Request) {
from, _ := req.From()
d.Addr = sip.Address{
DisplayName: from.DisplayName,
Uri: from.Address,
}
for _, c := range list {
if _, ok := Ignores[c.DeviceID]; ok {
continue
deviceIp := req.Source()
servIp := req.Recipient().Host()
//根据网卡ip获取对应的公网ip
sipIP := c.routes[servIp]
//如果相等,则服务器是内网通道.海康摄像头不支持...自动获取
if strings.LastIndex(deviceIp, ".") != -1 && strings.LastIndex(servIp, ".") != -1 {
if servIp[0:strings.LastIndex(servIp, ".")] == deviceIp[0:strings.LastIndex(deviceIp, ".")] || sipIP == "" {
sipIP = servIp
}
if c.ParentID != "" {
path := strings.Split(c.ParentID, "/")
parentId := path[len(path)-1]
if parent, ok := d.channelMap[parentId]; ok {
parent.Children = append(parent.Children, c)
} else {
d.addChannel(c)
}
} else {
d.addChannel(c)
}
if old, ok := d.channelMap[c.DeviceID]; ok {
c.ChannelEx = old.ChannelEx
if len(old.Children) == 0 {
if config.PreFetchRecord {
n := time.Now()
n = time.Date(n.Year(), n.Month(), n.Day(), 0, 0, 0, 0, time.Local)
if len(c.Records) == 0 || (n.Format(TIME_LAYOUT) == c.RecordStartTime && n.Add(time.Hour*24-time.Second).Format(TIME_LAYOUT) == c.RecordEndTime) {
go c.QueryRecord(n.Format(TIME_LAYOUT), n.Add(time.Hour*24-time.Second).Format(TIME_LAYOUT))
}
}
if config.AutoInvite && c.LivePublisher == nil {
go c.Invite("", "")
}
}
} else {
c.ChannelEx = &ChannelEx{
device: d,
}
}
if s := engine.FindStream("sub/" + c.DeviceID); s != nil {
c.LiveSubSP = s.StreamPath
} else {
c.LiveSubSP = ""
}
d.channelMap[c.DeviceID] = c
}
}
func (d *Device) UpdateRecord(channelId string, list []*Record) {
d.channelMutex.RLock()
if c, ok := d.channelMap[channelId]; ok {
c.Records = append(c.Records, list...)
//如果用户配置过则使用配置的
if c.SipIP != "" {
sipIP = c.SipIP
} else if sipIP == "" {
sipIP = myip.InternalIPv4()
}
d.channelMutex.RUnlock()
mediaIp := sipIP
if c.MediaIP != "" {
mediaIp = c.MediaIP
}
d.Info("RecoverDevice", zap.String("deviceIp", deviceIp), zap.String("servIp", servIp), zap.String("sipIP", sipIP), zap.String("mediaIp", mediaIp))
d.Status = DeviceRegisterStatus
d.SipIP = sipIP
d.MediaIP = mediaIp
d.NetAddr = deviceIp
d.UpdateTime = time.Now()
}
func (d *Device) CreateMessage(Method sip.Method) (requestMsg *sip.Message) {
d.sn++
requestMsg = &sip.Message{
Mode: sip.SIP_MESSAGE_REQUEST,
MaxForwards: 70,
UserAgent: "Monibuca",
StartLine: &sip.StartLine{
Method: Method,
Uri: d.to.Uri,
}, Via: &sip.Via{
Transport: "UDP",
Host: d.Core.SipIP,
Port: fmt.Sprintf("%d", d.SipPort),
Params: map[string]string{
"branch": fmt.Sprintf("z9hG4bK%s", utils.RandNumString(8)),
"rport": "-1", //only key,no-value
},
}, From: &sip.Contact{Uri: d.from.Uri, Params: map[string]string{"tag": utils.RandNumString(9)}},
To: d.to, CSeq: &sip.CSeq{
ID: uint32(d.sn),
Method: Method,
}, CallID: utils.RandNumString(10),
Addr: d.Addr,
func (c *GB28181Config) StoreDevice(id string, req sip.Request) (d *Device) {
from, _ := req.From()
deviceAddr := sip.Address{
DisplayName: from.DisplayName,
Uri: from.Address,
}
deviceIp := req.Source()
if _d, loaded := Devices.Load(id); loaded {
d = _d.(*Device)
d.UpdateTime = time.Now()
d.NetAddr = deviceIp
d.Addr = deviceAddr
d.Debug("UpdateDevice", zap.String("netaddr", d.NetAddr))
} else {
servIp := req.Recipient().Host()
//根据网卡ip获取对应的公网ip
sipIP := c.routes[servIp]
//如果相等,则服务器是内网通道.海康摄像头不支持...自动获取
if strings.LastIndex(deviceIp, ".") != -1 && strings.LastIndex(servIp, ".") != -1 {
if servIp[0:strings.LastIndex(servIp, ".")] == deviceIp[0:strings.LastIndex(deviceIp, ".")] || sipIP == "" {
sipIP = servIp
}
}
//如果用户配置过则使用配置的
if c.SipIP != "" {
sipIP = c.SipIP
} else if sipIP == "" {
sipIP = myip.InternalIPv4()
}
mediaIp := sipIP
if c.MediaIP != "" {
mediaIp = c.MediaIP
}
d = &Device{
ID: id,
RegisterTime: time.Now(),
UpdateTime: time.Now(),
Status: DeviceRegisterStatus,
Addr: deviceAddr,
SipIP: sipIP,
MediaIP: mediaIp,
NetAddr: deviceIp,
Logger: GB28181Plugin.With(zap.String("id", id)),
}
d.Info("StoreDevice", zap.String("deviceIp", deviceIp), zap.String("servIp", servIp), zap.String("sipIP", sipIP), zap.String("mediaIp", mediaIp))
Devices.Store(id, d)
c.SaveDevices()
}
return
}
func (d *Device) Query() int {
d.queryChannel = true
requestMsg := d.CreateMessage(sip.MESSAGE)
requestMsg.ContentType = "Application/MANSCDP+xml"
requestMsg.Body = fmt.Sprintf(`<?xml version="1.0"?>
<Query>
<CmdType>Catalog</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
</Query>`, d.sn, requestMsg.To.Uri.UserInfo())
requestMsg.ContentLength = len(requestMsg.Body)
response := d.SendMessage(requestMsg)
if response.Data != nil && response.Data.Via.Params["received"] != "" {
d.SipIP = response.Data.Via.Params["received"]
func (c *GB28181Config) ReadDevices() {
if f, err := os.OpenFile("devices.json", os.O_RDONLY, 0644); err == nil {
defer f.Close()
var items []*Device
if err = json.NewDecoder(f).Decode(&items); err == nil {
for _, item := range items {
if time.Since(item.UpdateTime) < conf.RegisterValidity {
item.Status = "RECOVER"
item.Logger = GB28181Plugin.With(zap.String("id", item.ID))
Devices.Store(item.ID, item)
}
}
}
}
}
func (c *GB28181Config) SaveDevices() {
var item []any
Devices.Range(func(key, value any) bool {
item = append(item, value)
return true
})
if f, err := os.OpenFile("devices.json", os.O_WRONLY|os.O_CREATE, 0644); err == nil {
defer f.Close()
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
encoder.Encode(item)
}
}
func (d *Device) addOrUpdateChannel(info ChannelInfo) (c *Channel) {
if old, ok := d.channelMap.Load(info.DeviceID); ok {
c = old.(*Channel)
c.ChannelInfo = info
} else {
c = &Channel{
Device: d,
ChannelInfo: info,
Logger: d.Logger.With(zap.String("channel", info.DeviceID)),
}
if s := engine.Streams.Get(fmt.Sprintf("%s/%s/rtsp", c.Device.ID, c.DeviceID)); s != nil {
c.LiveSubSP = s.Path
} else {
c.LiveSubSP = ""
}
d.channelMap.Store(info.DeviceID, c)
}
return
}
func (d *Device) deleteChannel(DeviceID string) {
d.channelMap.Delete(DeviceID)
}
func (d *Device) UpdateChannels(list ...ChannelInfo) {
for _, c := range list {
if _, ok := conf.ignores[c.DeviceID]; ok {
continue
}
//当父设备非空且存在时、父设备节点增加通道
if c.ParentID != "" {
path := strings.Split(c.ParentID, "/")
parentId := path[len(path)-1]
//如果父ID并非本身所属设备一般情况下这是因为下级设备上传了目录信息该信息通常不需要处理。
// 暂时不考虑级联目录的实现
if d.ID != parentId {
if v, ok := Devices.Load(parentId); ok {
parent := v.(*Device)
parent.addOrUpdateChannel(c)
continue
} else {
c.Model = "Directory " + c.Model
c.Status = "NoParent"
}
}
}
//本设备增加通道
channel := d.addOrUpdateChannel(c)
if conf.InviteMode == INVIDE_MODE_AUTO {
channel.TryAutoInvite(&InviteOptions{})
}
if s := engine.Streams.Get("sub/" + c.DeviceID); s != nil {
channel.LiveSubSP = s.Path
} else {
channel.LiveSubSP = ""
}
}
}
func (d *Device) CreateRequest(Method sip.RequestMethod) (req sip.Request) {
d.SN++
callId := sip.CallID(utils.RandNumString(10))
userAgent := sip.UserAgentHeader("Monibuca")
maxForwards := sip.MaxForwards(70) //增加max-forwards为默认值 70
cseq := sip.CSeq{
SeqNo: uint32(d.SN),
MethodName: Method,
}
port := sip.Port(conf.SipPort)
serverAddr := sip.Address{
//DisplayName: sip.String{Str: d.config.Serial},
Uri: &sip.SipUri{
FUser: sip.String{Str: conf.Serial},
FHost: d.SipIP,
FPort: &port,
},
Params: sip.NewParams().Add("tag", sip.String{Str: utils.RandNumString(9)}),
}
req = sip.NewRequest(
"",
Method,
d.Addr.Uri,
"SIP/2.0",
[]sip.Header{
serverAddr.AsFromHeader(),
d.Addr.AsToHeader(),
&callId,
&userAgent,
&cseq,
&maxForwards,
serverAddr.AsContactHeader(),
},
"",
nil,
)
req.SetTransport(conf.SipNetwork)
req.SetDestination(d.NetAddr)
//fmt.Printf("构建请求参数:%s", *&req)
// requestMsg.DestAdd, err2 = d.ResolveAddress(requestMsg)
// if err2 != nil {
// return nil
// }
//intranet ip , let's resolve it with public ip
// var deviceIp, deviceSourceIP net.IP
// switch addr := requestMsg.DestAdd.(type) {
// case *net.UDPAddr:
// deviceIp = addr.IP
// case *net.TCPAddr:
// deviceIp = addr.IP
// }
// switch addr2 := d.SourceAddr.(type) {
// case *net.UDPAddr:
// deviceSourceIP = addr2.IP
// case *net.TCPAddr:
// deviceSourceIP = addr2.IP
// }
// if deviceIp.IsPrivate() && !deviceSourceIP.IsPrivate() {
// requestMsg.DestAdd = d.SourceAddr
// }
return
}
func (d *Device) Subscribe() int {
request := d.CreateRequest(sip.SUBSCRIBE)
if d.subscriber.CallID != "" {
callId := sip.CallID(utils.RandNumString(10))
request.AppendHeader(&callId)
}
expires := sip.Expires(3600)
d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(expires))
contentType := sip.ContentType("Application/MANSCDP+xml")
request.AppendHeader(&contentType)
request.AppendHeader(&expires)
request.SetBody(BuildCatalogXML(d.SN, d.ID), true)
response, err := d.SipRequestForResponse(request)
if err == nil && response != nil {
if response.StatusCode() == http.StatusOK {
callId, _ := request.CallID()
d.subscriber.CallID = string(*callId)
} else {
d.subscriber.CallID = ""
}
return int(response.StatusCode())
}
return http.StatusRequestTimeout
}
func (d *Device) Catalog() int {
//os.Stdout.Write(debug.Stack())
request := d.CreateRequest(sip.MESSAGE)
expires := sip.Expires(3600)
d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(expires))
contentType := sip.ContentType("Application/MANSCDP+xml")
request.AppendHeader(&contentType)
request.AppendHeader(&expires)
request.SetBody(BuildCatalogXML(d.SN, d.ID), true)
// 输出Sip请求设备通道信息信令
GB28181Plugin.Sugar().Debugf("SIP->Catalog:%s", request)
resp, err := d.SipRequestForResponse(request)
if err == nil && resp != nil {
GB28181Plugin.Sugar().Debugf("SIP<-Catalog Response: %s", resp.String())
return int(resp.StatusCode())
} else if err != nil {
GB28181Plugin.Error("SIP<-Catalog error:", zap.Error(err))
}
return http.StatusRequestTimeout
}
func (d *Device) QueryDeviceInfo() {
for i := time.Duration(5); i < 100; i++ {
time.Sleep(time.Second * i)
request := d.CreateRequest(sip.MESSAGE)
contentType := sip.ContentType("Application/MANSCDP+xml")
request.AppendHeader(&contentType)
request.SetBody(BuildDeviceInfoXML(d.SN, d.ID), true)
response, _ := d.SipRequestForResponse(request)
if response != nil {
// via, _ := response.ViaHop()
// if via != nil && via.Params.Has("received") {
// received, _ := via.Params.Get("received")
// d.SipIP = received.String()
// }
d.Info("QueryDeviceInfo", zap.Uint16("status code", uint16(response.StatusCode())))
if response.StatusCode() == http.StatusOK {
break
}
}
}
}
func (d *Device) SipRequestForResponse(request sip.Request) (sip.Response, error) {
return srv.RequestWithContext(context.Background(), request)
}
// MobilePositionSubscribe 移动位置订阅
func (d *Device) MobilePositionSubscribe(id string, expires time.Duration, interval time.Duration) (code int) {
mobilePosition := d.CreateRequest(sip.SUBSCRIBE)
if d.subscriber.CallID != "" {
callId := sip.CallID(utils.RandNumString(10))
mobilePosition.ReplaceHeaders(callId.Name(), []sip.Header{&callId})
}
expiresHeader := sip.Expires(expires / time.Second)
d.subscriber.Timeout = time.Now().Add(expires)
contentType := sip.ContentType("Application/MANSCDP+xml")
mobilePosition.AppendHeader(&contentType)
mobilePosition.AppendHeader(&expiresHeader)
mobilePosition.SetBody(BuildDevicePositionXML(d.SN, id, int(interval/time.Second)), true)
response, err := d.SipRequestForResponse(mobilePosition)
if err == nil && response != nil {
if response.StatusCode() == http.StatusOK {
callId, _ := mobilePosition.CallID()
d.subscriber.CallID = callId.String()
} else {
d.subscriber.CallID = ""
}
return int(response.StatusCode())
}
return http.StatusRequestTimeout
}
// UpdateChannelPosition 更新通道GPS坐标
func (d *Device) UpdateChannelPosition(channelId string, gpsTime string, lng string, lat string) {
if v, ok := d.channelMap.Load(channelId); ok {
c := v.(*Channel)
c.GpsTime = time.Now() //时间取系统收到的时间,避免设备时间和格式问题
c.Longitude = lng
c.Latitude = lat
c.Debug("update channel position success")
} else {
//如果未找到通道,则更新到设备上
d.GpsTime = time.Now() //时间取系统收到的时间,避免设备时间和格式问题
d.Longitude = lng
d.Latitude = lat
d.Debug("update device position success", zap.String("channelId", channelId))
}
}
// UpdateChannelStatus 目录订阅消息处理:新增/移除/更新通道或者更改通道状态
func (d *Device) UpdateChannelStatus(deviceList []*notifyMessage) {
for _, v := range deviceList {
switch v.Event {
case "ON":
d.Debug("receive channel online notify")
d.channelOnline(v.DeviceID)
case "OFF":
d.Debug("receive channel offline notify")
d.channelOffline(v.DeviceID)
case "VLOST":
d.Debug("receive channel video lost notify")
d.channelOffline(v.DeviceID)
case "DEFECT":
d.Debug("receive channel video defect notify")
d.channelOffline(v.DeviceID)
case "ADD":
d.Debug("receive channel add notify")
channel := ChannelInfo{
DeviceID: v.DeviceID,
ParentID: v.ParentID,
Name: v.Name,
Manufacturer: v.Manufacturer,
Model: v.Model,
Owner: v.Owner,
CivilCode: v.CivilCode,
Address: v.Address,
Port: v.Port,
Parental: v.Parental,
SafetyWay: v.SafetyWay,
RegisterWay: v.RegisterWay,
Secrecy: v.Secrecy,
Status: ChannelStatus(v.Status),
}
d.addOrUpdateChannel(channel)
case "DEL":
//删除
d.Debug("receive channel delete notify")
d.deleteChannel(v.DeviceID)
case "UPDATE":
d.Debug("receive channel update notify")
// 更新通道
channel := ChannelInfo{
DeviceID: v.DeviceID,
ParentID: v.ParentID,
Name: v.Name,
Manufacturer: v.Manufacturer,
Model: v.Model,
Owner: v.Owner,
CivilCode: v.CivilCode,
Address: v.Address,
Port: v.Port,
Parental: v.Parental,
SafetyWay: v.SafetyWay,
RegisterWay: v.RegisterWay,
Secrecy: v.Secrecy,
Status: ChannelStatus(v.Status),
}
d.UpdateChannels(channel)
}
}
}
func (d *Device) channelOnline(DeviceID string) {
if v, ok := d.channelMap.Load(DeviceID); ok {
c := v.(*Channel)
c.Status = ChannelOnStatus
c.Debug("channel online", zap.String("channelId", DeviceID))
} else {
d.Debug("update channel status failed, not found", zap.String("channelId", DeviceID))
}
}
func (d *Device) channelOffline(DeviceID string) {
if v, ok := d.channelMap.Load(DeviceID); ok {
c := v.(*Channel)
c.Status = ChannelOffStatus
c.Debug("channel offline", zap.String("channelId", DeviceID))
} else {
d.Debug("update channel status failed, not found", zap.String("channelId", DeviceID))
}
return response.Code
}

70
go.mod
View File

@@ -1,12 +1,66 @@
module github.com/Monibuca/plugin-gb28181/v3
module m7s.live/plugin/gb28181/v4
go 1.13
go 1.19
require (
github.com/Monibuca/engine/v3 v3.2.3
github.com/Monibuca/utils/v3 v3.0.1
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/pion/rtp v1.6.5
golang.org/x/net v0.0.0-20210716203947-853a461950ff
golang.org/x/text v0.3.6
github.com/ghettovoice/gosip v0.0.0-20231227123312-6b80e2d3e6f7
github.com/goccy/go-json v0.10.2
github.com/husanpao/ip v0.0.0-20220711082147-73160bb611a8
github.com/logrusorgru/aurora/v4 v4.0.0
github.com/pion/rtp v1.8.3
go.uber.org/zap v1.26.0
golang.org/x/net v0.19.0
golang.org/x/text v0.14.0
m7s.live/engine/v4 v4.15.2
m7s.live/plugin/ps/v4 v4.1.3
)
require (
github.com/bluenviron/gortsplib/v4 v4.6.2 // indirect
github.com/bluenviron/mediacommon v1.5.1 // indirect
github.com/deepch/vdk v0.0.27 // indirect
github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.3.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mcuadros/go-defaults v1.2.0 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/onsi/ginkgo/v2 v2.12.1 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/webrtc/v3 v3.2.20 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/q191201771/naza v0.30.48 // indirect
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
github.com/quic-go/quic-go v0.38.1 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
github.com/shirou/gopsutil/v3 v3.23.8 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tevino/abool v1.2.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
github.com/yapingcat/gomedia v0.0.0-20230905155010-55b9713fcec1 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

357
go.sum
View File

@@ -1,86 +1,333 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Monibuca/engine/v3 v3.2.3 h1:NHdoBX4bkx4cQ1r/J4tL+QoTN/QS9ohpwuKygk1Zi6I=
github.com/Monibuca/engine/v3 v3.2.3/go.mod h1:odyqD/VTQDN4qgzajsgn7kW7MWDIzTHt+j+BcI8i+4g=
github.com/Monibuca/utils/v3 v3.0.1 h1:t5DBWoLeeRRw6k4E8QgQaklKwn/9gkQR81mAIcx0juY=
github.com/Monibuca/utils/v3 v3.0.1/go.mod h1:RpNS95gapWs6gimwh8Xn2x72FN5tO7Powabj7dTFyvE=
github.com/cnotch/apirouter v0.0.0-20200731232942-89e243a791f3/go.mod h1:5deJPLON/x/s2dLOQfuKS0lenhOIT4xX0pvtN/OEIuY=
github.com/cnotch/ipchub v1.1.0 h1:hH0lh2mU3AZXPiqMwA0pdtqrwo7PFIMRGush9OobMUs=
github.com/cnotch/ipchub v1.1.0/go.mod h1:2PbeBs2q2VxxTVCn1eYCDwpAWuVXbq1+N0FU7GimOH4=
github.com/cnotch/loader v0.0.0-20200405015128-d9d964d09439/go.mod h1:oWpDagHB6p+Kqqq7RoRZKyC4XAXft50hR8pbTxdbYYs=
github.com/cnotch/queue v0.0.0-20200326024423-6e88bdbf2ad4/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg=
github.com/cnotch/queue v0.0.0-20201224060551-4191569ce8f6/go.mod h1:zOssjAlNusOxvtaqT+EMA+Iyi8rrtKr4/XfzN1Fgoeg=
github.com/cnotch/scheduler v0.0.0-20200522024700-1d2da93eefc5/go.mod h1:F4GE3SZkJZ8an1Y0ZCqvSM3jeozNuKzoC67erG1PhIo=
github.com/cnotch/xlog v0.0.0-20201208005456-cfda439cd3a0/go.mod h1:RW9oHsR79ffl3sR3yMGgxYupMn2btzdtJUwoxFPUE5E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/bluenviron/gortsplib/v4 v4.6.2 h1:CGIsxpnUFvSlIxnSFS0oFSSfwsHMmBCmYcrGAtIcwXc=
github.com/bluenviron/gortsplib/v4 v4.6.2/go.mod h1:dN1YjyPNMfy/NwC17Ga6MiIMiUoQfg5GL7LGsVHa0Jo=
github.com/bluenviron/mediacommon v1.5.1 h1:yYVF+ebqZOJh8yH+EeuPcAtTmWR66BqbJGmStxkScoI=
github.com/bluenviron/mediacommon v1.5.1/go.mod h1:Ij/kE1LEucSjryNBVTyPL/gBI0d6/Css3f5PyrM957w=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emitter-io/address v1.0.0/go.mod h1:GfZb5+S/o8694B1GMGK2imUYQyn2skszMvGNA5D84Ug=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deepch/vdk v0.0.27 h1:j/SHaTiZhA47wRpaue8NRp7P9xwOOO/lunxrDJBwcao=
github.com/deepch/vdk v0.0.27/go.mod h1:JlgGyR2ld6+xOIHa7XAxJh+stSDBAkdNvIPkUIdIywk=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca h1:cTTdXpkQ1aVbOOmHwdwtYuwUZcQtcMrleD1UXLWhAq8=
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca/go.mod h1:W+3LQaEkN8qAwwcw0KC546sUEnX86GIT8CcMLZC4mG0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0=
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo=
github.com/funny/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg=
github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghettovoice/gosip v0.0.0-20231227123312-6b80e2d3e6f7 h1:2ENInMa9XHho+1mUM8RZMZomAnfz+qR5v6AS0+TrM8w=
github.com/ghettovoice/gosip v0.0.0-20231227123312-6b80e2d3e6f7/go.mod h1:rlD1yLOErWYohWTryG/2bTTpmzB79p52ntLA/uIFXeI=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.1.0-rc.1/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 h1:gpptm606MZYGaMHMsB4Srmb6EbW/IVHnt04rcMXnkBQ=
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kelindar/process v0.0.0-20170730150328-69a29e249ec3/go.mod h1:+lTCLnZFXOkqwD8sLPl6u4erAc0cP8wFegQHfipz7KE=
github.com/kelindar/rate v1.0.0/go.mod h1:AjT4G+hTItNwt30lucEGZIz8y7Uk5zPho6vurIZ+1Es=
github.com/kelindar/tcp v1.0.0/go.mod h1:JB5hj1cshLU60XrLij2BBxW3JQ4hOye8vqbyvuKb52k=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/husanpao/ip v0.0.0-20220711082147-73160bb611a8 h1:4Jk58quTZmzJcTrLlbB5L1Q6qXu49EIjCReWxcBFWKo=
github.com/husanpao/ip v0.0.0-20220711082147-73160bb611a8/go.mod h1:medl9/CfYoQlqAXtAARmMW5dAX2UOdwwkhaszYPk0AM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.5/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA=
github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/ice/v2 v2.3.11/go.mod h1:hPcLC3kxMa+JGRzMHqQzjoSj3xtE9F+eoncmXLlCL4E=
github.com/pion/interceptor v0.1.18/go.mod h1:tpvvF4cPM6NGxFA1DUMbhabzQBxdWMATDGEUYOR9x6I=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.8/go.mod h1:hYE72WX8WDveIhg7fmXgMKivD3Puklk0Ymzog0lSyaI=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.6.5 h1:o2cZf8OascA5HF/b0PAbTxRKvOWxTQxWYt7SlToxFGI=
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pixelbender/go-sdp v1.1.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtp v1.8.1/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sctp v1.8.8/go.mod h1:igF9nZBrjh5AtmKc7U30jXltsFHicFCXSmWA2GWRaWs=
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/srtp/v2 v2.0.17/go.mod h1:y5WSHcJY4YfNB/5r7ca5YjHeIr1H3LM1rKArGGs8jMc=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.2/go.mod h1:OJg3ojoBJopjEeECq2yJdXH9YVrUJ1uQ++NjXLOUorc=
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/webrtc/v3 v3.2.20 h1:BQJiXQsJq9LgLp3op7rLy1y8d2WD+LtiS9cpY0uQ22A=
github.com/pion/webrtc/v3 v3.2.20/go.mod h1:vVURQTBOG5BpWKOJz3nlr23NfTDeyKVmubRNqzQp+Tg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/q191201771/naza v0.19.1 h1:4KLcxT2CHztO+7miPRtBG3FFgadSQYQw1gPPPKN7rnY=
github.com/q191201771/naza v0.19.1/go.mod h1:5LeGupZZFtYP1g/S203n9vXoUNVdlRnPIfM6rExjqt0=
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/q191201771/naza v0.30.48 h1:lbYUaa7A15kJKYwOiU4AbFS1Zo8oQwppl2tLEbJTqnw=
github.com/q191201771/naza v0.30.48/go.mod h1:n+dpJjQSh90PxBwxBNuifOwQttywvSIN5TkWSSYCeBk=
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE=
github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/yapingcat/gomedia v0.0.0-20230905155010-55b9713fcec1 h1:ilNIuDBR+UKA3qefiyWRoFufIFn3E4tgEXbBM4ILH28=
github.com/yapingcat/gomedia v0.0.0-20230905155010-55b9713fcec1/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
m7s.live/engine/v4 v4.15.2 h1:Uws658Ict2B8JojBG7fNmd2G2i63MlomsQ4npgNzF3g=
m7s.live/engine/v4 v4.15.2/go.mod h1:uKxjmsjU1WARUNowEkP83BSrJMUjGwkJrX5nPi6DGmE=
m7s.live/plugin/ps/v4 v4.1.3 h1:Lbvu3ZlX/s3w9lcOwF0SCOCvxtxongPexCIn6x4yukw=
m7s.live/plugin/ps/v4 v4.1.3/go.mod h1:RAb507iNmPG43I5kUA6ewF1fTRHDRsKbIVkIdLdKeeI=

372
handle.go Normal file
View File

@@ -0,0 +1,372 @@
package gb28181
import (
"bytes"
"crypto/md5"
"encoding/xml"
"fmt"
"strconv"
"github.com/ghettovoice/gosip/sip"
"go.uber.org/zap"
. "m7s.live/engine/v4"
"m7s.live/plugin/gb28181/v4/utils"
"net/http"
"time"
"golang.org/x/net/html/charset"
)
type Authorization struct {
*sip.Authorization
}
func (a *Authorization) Verify(username, passwd, realm, nonce string) bool {
//1、将 username,realm,password 依次组合获取 1 个字符串,并用算法加密的到密文 r1
s1 := fmt.Sprintf("%s:%s:%s", username, realm, passwd)
r1 := a.getDigest(s1)
//2、将 method即REGISTER ,uri 依次组合获取 1 个字符串,并对这个字符串使用算法 加密得到密文 r2
s2 := fmt.Sprintf("REGISTER:%s", a.Uri())
r2 := a.getDigest(s2)
if r1 == "" || r2 == "" {
GB28181Plugin.Error("Authorization algorithm wrong")
return false
}
//3、将密文 1nonce 和密文 2 依次组合获取 1 个字符串,并对这个字符串使用算法加密,获得密文 r3即Response
s3 := fmt.Sprintf("%s:%s:%s", r1, nonce, r2)
r3 := a.getDigest(s3)
//4、计算服务端和客户端上报的是否相等
return r3 == a.Response()
}
func (a *Authorization) getDigest(raw string) string {
switch a.Algorithm() {
case "MD5":
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
default: //如果没有算法默认使用MD5
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
}
}
func (c *GB28181Config) OnRegister(req sip.Request, tx sip.ServerTransaction) {
from, ok := req.From()
if !ok || from.Address == nil || from.Address.User() == nil {
GB28181Plugin.Error("OnRegister", zap.String("error", "no id"))
return
}
id := from.Address.User().String()
GB28181Plugin.Debug("SIP<-OnMessage", zap.String("id", id), zap.String("source", req.Source()), zap.String("req", req.String()))
isUnregister := false
if exps := req.GetHeaders("Expires"); len(exps) > 0 {
exp := exps[0]
expSec, err := strconv.ParseInt(exp.Value(), 10, 32)
if err != nil {
GB28181Plugin.Info("OnRegister",
zap.String("error", fmt.Sprintf("wrong expire header value %q", exp)),
zap.String("id", id),
zap.String("source", req.Source()),
zap.String("destination", req.Destination()))
return
}
if expSec == 0 {
isUnregister = true
}
} else {
GB28181Plugin.Info("OnRegister",
zap.String("error", "has no expire header"),
zap.String("id", id),
zap.String("source", req.Source()),
zap.String("destination", req.Destination()))
return
}
GB28181Plugin.Info("OnRegister",
zap.Bool("isUnregister", isUnregister),
zap.String("id", id),
zap.String("source", req.Source()),
zap.String("destination", req.Destination()))
if len(id) != 20 {
GB28181Plugin.Info("Wrong GB-28181", zap.String("id", id))
return
}
passAuth := false
// 不需要密码情况
if c.Username == "" && c.Password == "" {
passAuth = true
} else {
// 需要密码情况 设备第一次上报返回401和加密算法
if hdrs := req.GetHeaders("Authorization"); len(hdrs) > 0 {
authenticateHeader := hdrs[0].(*sip.GenericHeader)
auth := &Authorization{sip.AuthFromValue(authenticateHeader.Contents)}
// 有些摄像头没有配置用户名的地方用户名就是摄像头自己的国标id
var username string
if auth.Username() == id {
username = id
} else {
username = c.Username
}
if dc, ok := DeviceRegisterCount.LoadOrStore(id, 1); ok && dc.(int) > MaxRegisterCount {
response := sip.NewResponseFromRequest("", req, http.StatusForbidden, "Forbidden", "")
tx.Respond(response)
return
} else {
// 设备第二次上报,校验
_nonce, loaded := DeviceNonce.Load(id)
if loaded && auth.Verify(username, c.Password, c.Realm, _nonce.(string)) {
passAuth = true
} else {
DeviceRegisterCount.Store(id, dc.(int)+1)
}
}
}
}
if passAuth {
var d *Device
if isUnregister {
tmpd, ok := Devices.LoadAndDelete(id)
if ok {
GB28181Plugin.Info("Unregister Device", zap.String("id", id))
d = tmpd.(*Device)
} else {
return
}
} else {
if v, ok := Devices.Load(id); ok {
d = v.(*Device)
c.RecoverDevice(d, req)
} else {
d = c.StoreDevice(id, req)
}
}
DeviceNonce.Delete(id)
DeviceRegisterCount.Delete(id)
resp := sip.NewResponseFromRequest("", req, http.StatusOK, "OK", "")
to, _ := resp.To()
resp.ReplaceHeaders("To", []sip.Header{&sip.ToHeader{Address: to.Address, Params: sip.NewParams().Add("tag", sip.String{Str: utils.RandNumString(9)})}})
resp.RemoveHeader("Allow")
expires := sip.Expires(3600)
resp.AppendHeader(&expires)
resp.AppendHeader(&sip.GenericHeader{
HeaderName: "Date",
Contents: time.Now().Format(TIME_LAYOUT),
})
_ = tx.Respond(resp)
if !isUnregister {
//订阅设备更新
go d.syncChannels()
}
} else {
GB28181Plugin.Info("OnRegister unauthorized", zap.String("id", id), zap.String("source", req.Source()),
zap.String("destination", req.Destination()))
response := sip.NewResponseFromRequest("", req, http.StatusUnauthorized, "Unauthorized", "")
_nonce, _ := DeviceNonce.LoadOrStore(id, utils.RandNumString(32))
auth := fmt.Sprintf(
`Digest realm="%s",algorithm=%s,nonce="%s"`,
c.Realm,
"MD5",
_nonce.(string),
)
response.AppendHeader(&sip.GenericHeader{
HeaderName: "WWW-Authenticate",
Contents: auth,
})
_ = tx.Respond(response)
}
}
// syncChannels
// 同步设备信息、下属通道信息,包括主动查询通道信息,订阅通道变化情况
func (d *Device) syncChannels() {
if time.Since(d.lastSyncTime) > 2*conf.HeartbeatInterval {
d.lastSyncTime = time.Now()
d.Catalog()
d.Subscribe()
d.QueryDeviceInfo()
}
}
type MessageEvent struct {
Type string
Device *Device
}
func (c *GB28181Config) OnMessage(req sip.Request, tx sip.ServerTransaction) {
from, ok := req.From()
if !ok || from.Address == nil || from.Address.User() == nil {
GB28181Plugin.Error("OnMessage", zap.String("error", "no id"))
return
}
id := from.Address.User().String()
GB28181Plugin.Debug("SIP<-OnMessage", zap.String("id", id), zap.String("source", req.Source()), zap.String("req", req.String()))
if v, ok := Devices.Load(id); ok {
d := v.(*Device)
switch d.Status {
case DeviceOfflineStatus, DeviceRecoverStatus:
c.RecoverDevice(d, req)
go d.syncChannels()
case DeviceRegisterStatus:
d.Status = DeviceOnlineStatus
}
d.UpdateTime = time.Now()
temp := &struct {
XMLName xml.Name
CmdType string
SN int // 请求序列号,一般用于对应 request 和 response
DeviceID string
DeviceName string
Manufacturer string
Model string
Channel string
DeviceList []ChannelInfo `xml:"DeviceList>Item"`
RecordList []*Record `xml:"RecordList>Item"`
SumNum int // 录像结果的总数 SumNum录像结果会按照多条消息返回可用于判断是否全部返回
}{}
decoder := xml.NewDecoder(bytes.NewReader([]byte(req.Body())))
decoder.CharsetReader = charset.NewReaderLabel
err := decoder.Decode(temp)
if err != nil {
err = utils.DecodeGbk(temp, []byte(req.Body()))
if err != nil {
GB28181Plugin.Error("decode catelog err", zap.Error(err))
}
}
var body string
switch temp.CmdType {
case "Keepalive":
d.LastKeepaliveAt = time.Now()
//callID !="" 说明是订阅的事件类型信息
if d.lastSyncTime.IsZero() {
go d.syncChannels()
} else {
d.channelMap.Range(func(key, value interface{}) bool {
if conf.InviteMode == INVIDE_MODE_AUTO {
value.(*Channel).TryAutoInvite(&InviteOptions{})
}
return true
})
}
//在KeepLive 进行位置订阅的处理,如果开启了自动订阅位置,则去订阅位置
if c.Position.AutosubPosition && time.Since(d.GpsTime) > c.Position.Interval*2 {
d.MobilePositionSubscribe(d.ID, c.Position.Expires, c.Position.Interval)
GB28181Plugin.Debug("Mobile Position Subscribe", zap.String("deviceID", d.ID))
}
case "Catalog":
d.UpdateChannels(temp.DeviceList...)
case "RecordInfo":
RecordQueryLink.Put(d.ID, temp.DeviceID, temp.SN, temp.SumNum, temp.RecordList)
case "DeviceInfo":
// 主设备信息
d.Name = temp.DeviceName
d.Manufacturer = temp.Manufacturer
d.Model = temp.Model
case "Alarm":
d.Status = DeviceAlarmedStatus
body = BuildAlarmResponseXML(d.ID)
case "Broadcast":
GB28181Plugin.Info("broadcast message", zap.String("body", req.Body()))
case "PresetQuery":
GB28181Plugin.Info("PresetQuery message", zap.String("body", req.Body()))
default:
d.Warn("Not supported CmdType", zap.String("CmdType", temp.CmdType), zap.String("body", req.Body()))
response := sip.NewResponseFromRequest("", req, http.StatusBadRequest, "", "")
tx.Respond(response)
return
}
EmitEvent(MessageEvent{
Type: temp.CmdType,
Device: d,
})
tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, "OK", body))
} else {
GB28181Plugin.Debug("Unauthorized message, device not found", zap.String("id", id))
}
}
func (c *GB28181Config) OnBye(req sip.Request, tx sip.ServerTransaction) {
tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, "OK", ""))
}
type NotifyEvent MessageEvent
// OnNotify 订阅通知处理
func (c *GB28181Config) OnNotify(req sip.Request, tx sip.ServerTransaction) {
from, ok := req.From()
if !ok || from.Address == nil || from.Address.User() == nil {
GB28181Plugin.Error("OnMessage", zap.String("error", "no id"))
return
}
id := from.Address.User().String()
if v, ok := Devices.Load(id); ok {
d := v.(*Device)
d.UpdateTime = time.Now()
temp := &struct {
XMLName xml.Name
CmdType string
DeviceID string
Time string //位置订阅-GPS时间
Longitude string //位置订阅-经度
Latitude string //位置订阅-维度
// Speed string //位置订阅-速度(km/h)(可选)
// Direction string //位置订阅-方向(取值为当前摄像头方向与正北方的顺时针夹角,取值范围0°~360°,单位:°)(可选)
// Altitude string //位置订阅-海拔高度,单位:m(可选)
DeviceList []*notifyMessage `xml:"DeviceList>Item"` //目录订阅
}{}
decoder := xml.NewDecoder(bytes.NewReader([]byte(req.Body())))
decoder.CharsetReader = charset.NewReaderLabel
err := decoder.Decode(temp)
if err != nil {
err = utils.DecodeGbk(temp, []byte(req.Body()))
if err != nil {
GB28181Plugin.Error("decode catelog err", zap.Error(err))
}
}
var body string
switch temp.CmdType {
case "Catalog":
//目录状态
d.UpdateChannelStatus(temp.DeviceList)
case "MobilePosition":
//更新channel的坐标
d.UpdateChannelPosition(temp.DeviceID, temp.Time, temp.Longitude, temp.Latitude)
case "Alarm":
d.Status = DeviceAlarmedStatus
default:
d.Warn("Not supported CmdType", zap.String("CmdType", temp.CmdType), zap.String("body", req.Body()))
response := sip.NewResponseFromRequest("", req, http.StatusBadRequest, "", "")
tx.Respond(response)
return
}
EmitEvent(NotifyEvent{
Type: temp.CmdType,
Device: d})
tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, "OK", body))
}
}
type notifyMessage struct {
DeviceID string
ParentID string
Name string
Manufacturer string
Model string
Owner string
CivilCode string
Address string
Port int
Parental int
SafetyWay int
RegisterWay int
Secrecy int
Status string
//状态改变事件 ON:上线,OFF:离线,VLOST:视频丢失,DEFECT:故障,ADD:增加,DEL:删除,UPDATE:更新(必选)
Event string
}

67
inviteoption.go Normal file
View File

@@ -0,0 +1,67 @@
package gb28181
import (
"errors"
"fmt"
"math/rand"
"strconv"
)
type InviteOptions struct {
Start int
End int
dump string
ssrc string
SSRC uint32
MediaPort uint16
StreamPath string
recyclePort func(p uint16) (err error)
}
func (o InviteOptions) IsLive() bool {
return o.Start == 0 || o.End == 0
}
func (o InviteOptions) Record() bool {
return !o.IsLive()
}
func (o *InviteOptions) Validate(start, end string) error {
if start != "" {
sint, err1 := strconv.ParseInt(start, 10, 0)
if err1 != nil {
return err1
}
o.Start = int(sint)
}
if end != "" {
eint, err2 := strconv.ParseInt(end, 10, 0)
if err2 != nil {
return err2
}
o.End = int(eint)
}
if o.Start >= o.End {
return errors.New("start < end")
}
return nil
}
func (o InviteOptions) String() string {
return fmt.Sprintf("t=%d %d", o.Start, o.End)
}
func (o *InviteOptions) CreateSSRC() {
ssrc := make([]byte, 10)
if o.IsLive() {
ssrc[0] = '0'
} else {
ssrc[0] = '1'
}
copy(ssrc[1:6], conf.Serial[3:8])
randNum := 1000 + rand.Intn(8999)
copy(ssrc[6:], strconv.Itoa(randNum))
o.ssrc = string(ssrc)
_ssrc, _ := strconv.ParseInt(o.ssrc, 10, 0)
o.SSRC = uint32(_ssrc)
}

119
link.go Normal file
View File

@@ -0,0 +1,119 @@
package gb28181
import (
"fmt"
"sync"
"time"
"go.uber.org/zap"
)
// 对于录像查询,通过 queryKey (即 deviceId + channelId + sn) 唯一区分一次请求和响应
// 并将其关联起来,以实现异步响应的目的
// 提供单例实例供调用
var RecordQueryLink = NewRecordQueryLink(time.Second * 60)
type recordQueryLink struct {
pendingResult map[string]recordQueryResult // queryKey 查询结果缓存
pendingResp map[string]recordQueryResp // queryKey 待回复的查询请求
timeout time.Duration // 查询结果的过期时间
sync.RWMutex
}
type recordQueryResult struct {
time time.Time
err error
sum int
finished bool
list []*Record
}
type recordQueryResp struct {
respChan chan<- recordQueryResult
timeout time.Duration
startTime time.Time
}
func NewRecordQueryLink(resultTimeout time.Duration) *recordQueryLink {
c := &recordQueryLink{
timeout: resultTimeout,
pendingResult: make(map[string]recordQueryResult),
pendingResp: make(map[string]recordQueryResp),
}
return c
}
// 唯一区分一次录像查询
func recordQueryKey(deviceId, channelId string, sn int) string {
return fmt.Sprintf("%s-%s-%d", deviceId, channelId, sn)
}
// 定期清理过期的查询结果和请求
func (c *recordQueryLink) cleanTimeout() {
for k, s := range c.pendingResp {
if time.Since(s.startTime) > s.timeout {
if r, ok := c.pendingResult[k]; ok {
c.notify(k, r)
} else {
c.notify(k, recordQueryResult{err: fmt.Errorf("query time out")})
}
}
}
for k, r := range c.pendingResult {
if time.Since(r.time) > c.timeout {
delete(c.pendingResult, k)
}
}
}
func (c *recordQueryLink) Put(deviceId, channelId string, sn int, sum int, record []*Record) {
key, r := c.doPut(deviceId, channelId, sn, sum, record)
if r.finished {
c.notify(key, r)
}
}
func (c *recordQueryLink) doPut(deviceId, channelId string, sn, sum int, record []*Record) (key string, r recordQueryResult) {
c.Lock()
defer c.Unlock()
key = recordQueryKey(deviceId, channelId, sn)
if v, ok := c.pendingResult[key]; ok {
r = v
} else {
r = recordQueryResult{time: time.Now(), sum: sum, list: make([]*Record, 0)}
}
r.list = append(r.list, record...)
if len(r.list) == sum {
r.finished = true
}
c.pendingResult[key] = r
GB28181Plugin.Logger.Debug("put record",
zap.String("key", key),
zap.Int("sum", sum),
zap.Int("count", len(r.list)))
return
}
func (c *recordQueryLink) WaitResult(
deviceId, channelId string, sn int,
timeout time.Duration) (resultCh <-chan recordQueryResult) {
key := recordQueryKey(deviceId, channelId, sn)
c.Lock()
defer c.Unlock()
respCh := make(chan recordQueryResult, 1)
resultCh = respCh
c.pendingResp[key] = recordQueryResp{startTime: time.Now(), timeout: timeout, respChan: respCh}
return
}
func (c *recordQueryLink) notify(key string, r recordQueryResult) {
if s, ok := c.pendingResp[key]; ok {
s.respChan <- r
}
c.Lock()
defer c.Unlock()
delete(c.pendingResp, key)
delete(c.pendingResult, key)
GB28181Plugin.Logger.Debug("record notify", zap.String("key", key))
}

395
main.go
View File

@@ -1,301 +1,150 @@
package gb28181
import (
"bytes"
"encoding/xml"
"log"
"net"
"net/http"
"strconv"
"os"
"strings"
"sync"
"time"
"github.com/Monibuca/engine/v3"
"github.com/Monibuca/plugin-gb28181/v3/sip"
"github.com/Monibuca/plugin-gb28181/v3/transaction"
"github.com/Monibuca/plugin-gb28181/v3/utils"
. "github.com/Monibuca/utils/v3"
. "github.com/logrusorgru/aurora"
"github.com/pion/rtp"
"golang.org/x/net/html/charset"
"github.com/ghettovoice/gosip/sip"
myip "github.com/husanpao/ip"
"go.uber.org/zap"
. "m7s.live/engine/v4"
"m7s.live/engine/v4/util"
)
var (
Devices sync.Map
Ignores = make(map[string]struct{})
publishers Publishers
)
type GB28181PositionConfig struct {
AutosubPosition bool `desc:"是否自动订阅定位"` //是否自动订阅定位
Expires time.Duration `default:"3600s" desc:"订阅周期"` //订阅周期
Interval time.Duration `default:"6s" desc:"订阅间隔"` //订阅间隔
}
func FindChannel(deviceId string, channelId string) (c *Channel) {
if v, ok := Devices.Load(deviceId); ok {
d := v.(*Device)
d.channelMutex.RLock()
c = d.channelMap[channelId]
d.channelMutex.RUnlock()
type GB28181Config struct {
InviteMode int `default:"1" desc:"拉流模式" enum:"0:手动拉流,1:预拉流,2:按需拉流"` //邀请模式0:手动拉流1:预拉流2:按需拉流
InviteIDs string `default:"131,132" desc:"允许邀请的设备类型( 1113位是设备类型编码,逗号分割"` //按照国标gb28181协议允许邀请的设备类型:132 摄像机 NVR
ListenAddr string `default:"0.0.0.0" desc:"监听IP地址"` //监听地址
//sip服务器的配置
SipNetwork string `default:"udp" desc:"废弃,请使用 Port"` //传输协议默认UDP可选TCP
SipIP string `desc:"sip 服务IP地址"` //sip 服务器公网IP
SipPort sip.Port `default:"5060" desc:"废弃,请使用 Port"` //sip 服务器端口,默认 5060
Serial string `default:"34020000002000000001" desc:"sip 服务 id"` //sip 服务器 id, 默认 34020000002000000001
Realm string `default:"3402000000" desc:"sip 服务域"` //sip 服务器域,默认 3402000000
Username string `desc:"sip 服务账号"` //sip 服务器账号
Password string `desc:"sip 服务密码"` //sip 服务器密码
Port struct { // 新配置方式
Sip string `default:"udp:5060" desc:"sip服务端口号"`
Media string `default:"tcp:58200-59200" desc:"媒体服务端口号"`
Fdm bool `default:"false" desc:"多路复用"`
}
return
RegisterValidity time.Duration `default:"3600s" desc:"注册有效期"` //注册有效期,单位秒,默认 3600
HeartbeatInterval time.Duration `default:"60s" desc:"心跳间隔"` //心跳间隔,单位秒,默认 60
//媒体服务器配置
MediaIP string `desc:"媒体服务IP地址"` //媒体服务器地址
MediaPort uint16 `default:"58200" desc:"废弃,请使用 Port"` //媒体服务器端口
MediaNetwork string `default:"tcp" desc:"废弃,请使用 Port"` //媒体传输协议默认UDP可选TCP
MediaPortMin uint16 `default:"58200" desc:"废弃,请使用 Port"`
MediaPortMax uint16 `default:"59200" desc:"废弃,请使用 Port"`
RemoveBanInterval time.Duration `default:"600s" desc:"移除禁止设备间隔"` //移除禁止设备间隔
routes map[string]string
DumpPath string `desc:"dump PS流本地文件路径"` //dump PS流本地文件路径
Ignores []string `desc:"忽略的设备ID"` //忽略的设备ID
ignores map[string]struct{}
tcpPorts PortManager
udpPorts PortManager
Position GB28181PositionConfig //关于定位的配置参数
}
type Publishers struct {
data map[uint32]*Publisher
sync.RWMutex
}
var SipUri *sip.SipUri
func (p *Publishers) Add(key uint32, pp *Publisher) {
p.Lock()
p.data[key] = pp
p.Unlock()
}
func (p *Publishers) Remove(key uint32) {
p.Lock()
delete(p.data, key)
p.Unlock()
}
func (p *Publishers) Get(key uint32) *Publisher {
p.RLock()
defer p.RUnlock()
return p.data[key]
}
var config = struct {
Serial string
Realm string
ListenAddr string
Expires int
MediaPort uint16
AutoInvite bool
AutoUnPublish bool
Ignore []string
CatalogInterval int
PreFetchRecord bool
}{"34020000002000000001", "3402000000", "127.0.0.1:5060", 3600, 58200, false, true, nil, 30, false}
func init() {
engine.InstallPlugin(&engine.PluginConfig{
Name: "GB28181",
Config: &config,
Run: run,
})
publishers.data = make(map[uint32]*Publisher)
}
func run() {
ipAddr, err := net.ResolveUDPAddr("", config.ListenAddr)
if err != nil {
log.Fatal(err)
}
Print(Green("server gb28181 start at"), BrightBlue(config.ListenAddr))
for _, id := range config.Ignore {
Ignores[id] = struct{}{}
}
config := &transaction.Config{
SipIP: ipAddr.IP.String(),
SipPort: uint16(ipAddr.Port),
SipNetwork: "UDP",
Serial: config.Serial,
Realm: config.Realm,
AckTimeout: 10,
MediaIP: ipAddr.IP.String(),
RegisterValidity: config.Expires,
RegisterInterval: 60,
HeartbeatInterval: 60,
HeartbeatRetry: 3,
AudioEnable: true,
WaitKeyFrame: true,
MediaIdleTimeout: 30,
CatalogInterval: config.CatalogInterval,
}
http.HandleFunc("/api/gb28181/query/records", func(w http.ResponseWriter, r *http.Request) {
CORS(w, r)
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
startTime := r.URL.Query().Get("startTime")
endTime := r.URL.Query().Get("endTime")
if c := FindChannel(id, channel); c != nil {
w.WriteHeader(c.QueryRecord(startTime, endTime))
} else {
w.WriteHeader(404)
func (c *GB28181Config) initRoutes() {
c.routes = make(map[string]string)
tempIps := myip.LocalAndInternalIPs()
for k, v := range tempIps {
c.routes[k] = v
if lastdot := strings.LastIndex(k, "."); lastdot >= 0 {
c.routes[k[0:lastdot]] = k
}
})
http.HandleFunc("/api/gb28181/list", func(w http.ResponseWriter, r *http.Request) {
CORS(w, r)
sse := NewSSE(w, r.Context())
for {
var list []*Device
Devices.Range(func(key, value interface{}) bool {
device := value.(*Device)
if time.Since(device.UpdateTime) > time.Duration(config.RegisterValidity)*time.Second {
Devices.Delete(key)
} else {
list = append(list, device)
}
return true
})
sse.WriteJSON(list)
select {
case <-time.After(time.Second * 5):
case <-sse.Done():
return
}
}
GB28181Plugin.Info("LocalAndInternalIPs", zap.Any("routes", c.routes))
}
func (c *GB28181Config) OnEvent(event any) {
switch e := event.(type) {
case FirstConfig:
if c.Port.Sip != "udp:5060" {
protocol, ports := util.Conf2Listener(c.Port.Sip)
c.SipNetwork = protocol
c.SipPort = sip.Port(ports[0])
}
})
http.HandleFunc("/api/gb28181/control", func(w http.ResponseWriter, r *http.Request) {
CORS(w, r)
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
ptzcmd := r.URL.Query().Get("ptzcmd")
if c := FindChannel(id, channel); c != nil {
w.WriteHeader(c.Control(ptzcmd))
} else {
w.WriteHeader(404)
}
})
http.HandleFunc("/api/gb28181/invite", func(w http.ResponseWriter, r *http.Request) {
CORS(w, r)
query := r.URL.Query()
id := query.Get("id")
channel := r.URL.Query().Get("channel")
startTime := query.Get("startTime")
endTime := query.Get("endTime")
if c := FindChannel(id, channel); c != nil {
if startTime == "" && c.LivePublisher != nil {
w.WriteHeader(304) //直播流已存在
if c.Port.Media != "tcp:58200-59200" {
protocol, ports := util.Conf2Listener(c.Port.Media)
c.MediaNetwork = protocol
if len(ports) > 1 {
c.MediaPortMin = ports[0]
c.MediaPortMax = ports[1]
} else {
w.WriteHeader(c.Invite(startTime, endTime))
c.MediaPortMin = 0
c.MediaPortMax = 0
c.MediaPort = ports[0]
}
} else {
w.WriteHeader(404)
}
})
http.HandleFunc("/api/gb28181/bye", func(w http.ResponseWriter, r *http.Request) {
CORS(w, r)
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
if c := FindChannel(id, channel); c != nil {
w.WriteHeader(c.Bye())
} else {
w.WriteHeader(404)
}
})
s := transaction.NewCore(config)
s.OnRegister = func(msg *sip.Message) {
id := msg.From.Uri.UserInfo()
d := &Device{
ID: id,
RegisterTime: time.Now(),
UpdateTime: time.Now(),
Status: string(sip.REGISTER),
Core: s,
from: &sip.Contact{Uri: msg.StartLine.Uri, Params: make(map[string]string)},
to: msg.To,
Addr: msg.Via.GetSendBy(),
SipIP: config.MediaIP,
channelMap: make(map[string]*Channel),
}
if old, ok := Devices.Load(id); !ok {
go d.Query()
} else {
oldD := old.(*Device)
d.RegisterTime = oldD.RegisterTime
d.channelMap = oldD.channelMap
d.Status = oldD.Status
}
Devices.Store(id, d)
}
s.OnMessage = func(msg *sip.Message) bool {
if v, ok := Devices.Load(msg.From.Uri.UserInfo()); ok {
d := v.(*Device)
if d.Status == string(sip.REGISTER) {
d.Status = "ONLINE"
if len(c.Ignores) > 0 {
c.ignores = make(map[string]struct{})
for _, v := range c.Ignores {
c.ignores[v] = util.Null
}
d.UpdateTime = time.Now()
temp := &struct {
XMLName xml.Name
CmdType string
DeviceID string
DeviceList []*Channel `xml:"DeviceList>Item"`
RecordList []*Record `xml:"RecordList>Item"`
}{}
decoder := xml.NewDecoder(bytes.NewReader([]byte(msg.Body)))
decoder.CharsetReader = charset.NewReaderLabel
err := decoder.Decode(temp)
if err != nil {
err = utils.DecodeGbk(temp, []byte(msg.Body))
log.Printf("decode catelog err: %s", err)
}
switch temp.XMLName.Local {
case "Notify":
if d.Channels == nil {
go d.Query()
}
case "Response":
switch temp.CmdType {
case "Catalog":
d.UpdateChannels(temp.DeviceList)
case "RecordInfo":
d.UpdateRecord(temp.DeviceID, temp.RecordList)
}
os.MkdirAll(c.DumpPath, 0766)
c.ReadDevices()
SipUri = &sip.SipUri{
FUser: sip.String{Str: c.Serial},
FHost: c.SipIP,
FPort: &conf.SipPort,
}
go c.initRoutes()
c.startServer()
case InvitePublish:
if c.InviteMode == INVIDE_MODE_ONSUBSCRIBE {
//流可能是回放流stream path是device/channel/start-end形式
streamNames := strings.Split(e.Target, "/")
if channel := FindChannel(streamNames[0], streamNames[1]); channel != nil {
opt := InviteOptions{}
if len(streamNames) > 2 {
last := len(streamNames) - 1
timestr := streamNames[last]
trange := strings.Split(timestr, "-")
if len(trange) == 2 {
startTime := trange[0]
endTime := trange[1]
opt.Validate(startTime, endTime)
}
}
channel.TryAutoInvite(&opt)
}
return true
}
return false
}
//OnStreamClosedHooks.AddHook(func(stream *Stream) {
// Devices.Range(func(key, value interface{}) bool {
// device:=value.(*Device)
// for _,channel := range device.Channels {
// if stream.StreamPath == channel.RecordSP {
//
// }
// }
// })
//})
go listenMedia()
go queryCatalog(config)
s.Start()
}
func listenMedia() {
networkBuffer := 1048576
var rtpPacket rtp.Packet
addr, err := net.ResolveUDPAddr("udp", ":"+strconv.Itoa(int(config.MediaPort)))
if err != nil {
log.Fatalf("udp server ResolveUDPAddr MediaPort:%d error, %v", config.MediaPort, err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatalf("udp server ListenUDP MediaPort:%d error, %v", config.MediaPort, err)
}
if err = conn.SetReadBuffer(networkBuffer); err != nil {
Printf("udp server video conn set read buffer error, %v", err)
}
if err = conn.SetWriteBuffer(networkBuffer); err != nil {
Printf("udp server video conn set write buffer error, %v", err)
}
bufUDP := make([]byte, 1048576)
Printf("udp server start listen video port[%d]", config.MediaPort)
defer Printf("udp server stop listen video port[%d]", config.MediaPort)
for n, _, err := conn.ReadFromUDP(bufUDP); err == nil; n, _, err = conn.ReadFromUDP(bufUDP) {
ps := bufUDP[:n]
if err := rtpPacket.Unmarshal(ps); err != nil {
Println("gb28181 decode rtp error:", err)
case SEpublish:
if channel := FindChannel(e.Target.AppName, strings.TrimSuffix(e.Target.StreamName, "/rtsp")); channel != nil {
channel.LiveSubSP = e.Target.Path
}
if publisher := publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Err() == nil {
publisher.PushPS(&rtpPacket)
case SEclose:
if channel := FindChannel(e.Target.AppName, strings.TrimSuffix(e.Target.StreamName, "/rtsp")); channel != nil {
channel.LiveSubSP = ""
}
if v, ok := PullStreams.LoadAndDelete(e.Target.Path); ok {
go v.(*PullStream).Bye()
}
}
}
func queryCatalog(config *transaction.Config) {
t := time.NewTicker(time.Duration(config.CatalogInterval) * time.Second)
for range t.C {
Devices.Range(func(key, value interface{}) bool {
device := value.(*Device)
if time.Since(device.UpdateTime) > time.Duration(config.RegisterValidity)*time.Second {
Devices.Delete(key)
} else {
go device.Query()
}
return true
})
}
func (c *GB28181Config) IsMediaNetworkTCP() bool {
return strings.ToLower(c.MediaNetwork) == "tcp"
}
var conf GB28181Config
var GB28181Plugin = InstallPlugin(&conf)
var PullStreams sync.Map //拉流

117
manscdp.go Normal file
View File

@@ -0,0 +1,117 @@
package gb28181
import (
"encoding/xml"
"fmt"
"strconv"
"time"
)
var (
// 获取预置位列表
PresentListXML = `
<?xml version="1.0"?>
<Query>
<CmdType>PresetQuery</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
</Query>
`
// CatalogXML 获取设备列表xml样式
CatalogXML = `<?xml version="1.0"?><Query>
<CmdType>Catalog</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
</Query>
`
// RecordInfoXML 获取录像文件列表xml样式
RecordInfoXML = `<?xml version="1.0"?>
<Query>
<CmdType>RecordInfo</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
<StartTime>%s</StartTime>
<EndTime>%s</EndTime>
<Secrecy>0</Secrecy>
<Type>all</Type>
</Query>
`
// DeviceInfoXML 查询设备详情xml样式
DeviceInfoXML = `<?xml version="1.0"?>
<Query>
<CmdType>DeviceInfo</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
</Query>
`
// DevicePositionXML 订阅设备位置
DevicePositionXML = `<?xml version="1.0"?>
<Query>
<CmdType>MobilePosition</CmdType>
<SN>%d</SN>
<DeviceID>%s</DeviceID>
<Interval>%d</Interval>
</Query>`
)
func intTotime(t int64) time.Time {
tstr := strconv.FormatInt(t, 10)
if len(tstr) == 10 {
return time.Unix(t, 0)
}
if len(tstr) == 13 {
return time.UnixMilli(t)
}
return time.Now()
}
// BuildDeviceInfoXML 获取设备详情指令
func BuildDeviceInfoXML(sn int, id string) string {
return fmt.Sprintf(DeviceInfoXML, sn, id)
}
// BuildCatalogXML 获取NVR下设备列表指令
func BuildCatalogXML(sn int, id string) string {
return fmt.Sprintf(CatalogXML, sn, id)
}
func BuildPresetListXML(sn int, id string) string {
return fmt.Sprintf(PresentListXML, sn, id)
}
// BuildRecordInfoXML 获取录像文件列表指令
func BuildRecordInfoXML(sn int, id string, start, end int64) string {
return fmt.Sprintf(RecordInfoXML, sn, id, intTotime(start).Format("2006-01-02T15:04:05"), intTotime(end).Format("2006-01-02T15:04:05"))
}
// BuildDevicePositionXML 订阅设备位置
func BuildDevicePositionXML(sn int, id string, interval int) string {
return fmt.Sprintf(DevicePositionXML, sn, id, interval)
}
// AlarmResponseXML alarm response xml样式
var (
AlarmResponseXML = `<?xml version="1.0"?>
<Response>
<CmdType>Alarm</CmdType>
<SN>17430</SN>
<DeviceID>%s</DeviceID>
</Response>
`
)
// BuildRecordInfoXML 获取录像文件列表指令
func BuildAlarmResponseXML(id string) string {
return fmt.Sprintf(AlarmResponseXML, id)
}
func XmlEncode(v interface{}) (string, error) {
xmlData, err := xml.MarshalIndent(v, "", " ")
if err != nil {
return "", err
}
xml := string(xmlData)
xml = `<?xml version="1.0" ?>` + "\n" + xml + "\n"
return xml, err
}

58
portmanager.go Normal file
View File

@@ -0,0 +1,58 @@
package gb28181
import (
"errors"
)
var ErrNoAvailablePorts = errors.New("no available ports")
type PortManager struct {
recycle chan uint16
start uint16
max uint16
pos uint16
Valid bool
}
func (pm *PortManager) Init(start, end uint16) {
pm.start = start
pm.pos = start - 1
pm.max = end
if pm.pos > 0 && pm.max > pm.pos {
pm.Valid = true
pm.recycle = make(chan uint16, pm.Range())
}
}
func (pm *PortManager) Range() uint16 {
return pm.max - pm.pos
}
func (pm *PortManager) Recycle(p uint16) (err error) {
select {
case pm.recycle <- p:
return nil
default:
return ErrNoAvailablePorts
}
}
func (pm *PortManager) GetPort() (p uint16, err error) {
select {
case p = <-pm.recycle:
return
default:
if pm.Range() > 0 {
pm.pos++
p = pm.pos
return
} else {
if conf.Port.Fdm == false {
return 0, ErrNoAvailablePorts
}
pm.pos = pm.start - 1
p = pm.pos
return
}
}
}

107
ptz.go Normal file
View File

@@ -0,0 +1,107 @@
package gb28181
import (
"encoding/hex"
"encoding/xml"
"fmt"
)
var (
name2code = map[string]uint8{
"stop": 0,
"right": 1,
"left": 2,
"down": 4,
"downright": 5,
"downleft": 6,
"up": 8,
"upright": 9,
"upleft": 10,
"zoomin": 16,
"zoomout": 32,
}
)
type PresetCmd byte
const (
PresetAddPoint = 0
PresetDelPoint = 1
PresetCallPoint = 2
)
const DeviceControl = "DeviceControl"
const PTZFirstByte = 0xA5
const (
PresetSet = 0x81
PresetCall = 0x82
PresetDel = 0x83
)
type MessagePtz struct {
XMLName xml.Name `xml:"Control"`
CmdType string `xml:"CmdType"`
SN int `xml:"SN"`
DeviceID string `xml:"DeviceID"`
PTZCmd string `xml:"PTZCmd"`
}
type Preset struct {
CMD byte
Point byte
}
func toPtzStrByCmdName(cmdName string, horizontalSpeed, verticalSpeed, zoomSpeed uint8) (string, error) {
c, err := toPtzCode(cmdName)
if err != nil {
return "", err
}
return toPtzStr(c, horizontalSpeed, verticalSpeed, zoomSpeed), nil
}
func toPtzStr(cmdCode, horizontalSpeed, verticalSpeed, zoomSpeed uint8) string {
checkCode := uint16(0xA5+0x0F+0x01+cmdCode+horizontalSpeed+verticalSpeed+(zoomSpeed&0xF0)) % 0x100
return fmt.Sprintf("A50F01%02X%02X%02X%01X0%02X",
cmdCode,
horizontalSpeed,
verticalSpeed,
zoomSpeed>>4, // 根据 GB28181 协议zoom 只取 4 bit
checkCode,
)
}
func toPtzCode(cmd string) (uint8, error) {
if code, ok := name2code[cmd]; ok {
return code, nil
} else {
return 0, fmt.Errorf("invalid ptz cmd %q", cmd)
}
}
func getVerificationCode(ptz []byte) {
sum := uint8(0)
for i := 0; i < len(ptz)-1; i++ {
sum += ptz[i]
}
ptz[len(ptz)-1] = sum
}
func getAssembleCode() uint8 {
return (PTZFirstByte>>4 + PTZFirstByte&0xF + 0) % 16
}
func Pack(cmd, point byte) string {
buf := make([]byte, 8)
buf[0] = PTZFirstByte
buf[1] = getAssembleCode()
buf[2] = 0
buf[3] = cmd
buf[4] = 0
buf[5] = point
buf[6] = 0
getVerificationCode(buf)
return hex.EncodeToString(buf)
}

View File

@@ -1,83 +0,0 @@
package gb28181
import (
"github.com/Monibuca/engine/v3"
"github.com/Monibuca/plugin-gb28181/v3/utils"
. "github.com/Monibuca/utils/v3"
"github.com/pion/rtp"
)
type Publisher struct {
*engine.Stream
parser utils.DecPSPackage
pushVideo func(uint32, uint32, []byte)
pushAudio func(uint32, []byte)
lastSeq uint16
}
func (p *Publisher) PushVideo(ts uint32, cts uint32, payload []byte) {
p.pushVideo(ts, cts, payload)
}
func (p *Publisher) PushAudio(ts uint32, payload []byte) {
p.pushAudio(ts, payload)
}
func (p *Publisher) Publish() (result bool) {
if result = p.Stream.Publish(); result {
p.pushVideo = func(ts uint32, cts uint32, payload []byte) {
var vt *engine.VideoTrack
switch p.parser.VideoStreamType {
case utils.StreamTypeH264:
vt = p.Stream.NewVideoTrack(7)
case utils.StreamTypeH265:
vt = p.Stream.NewVideoTrack(12)
default:
return
}
vt.PushAnnexB(ts, cts, payload)
p.pushVideo = vt.PushAnnexB
}
p.pushAudio = func(ts uint32, payload []byte) {
switch p.parser.AudioStreamType {
case utils.G711A:
at := p.Stream.NewAudioTrack(7)
at.SoundRate = 8000
at.SoundSize = 16
at.Channels = 1
at.ExtraData = []byte{(at.CodecID << 4) | (1 << 1)}
at.PushRaw(ts, payload)
p.pushAudio = at.PushRaw
// case utils.G711U:
// at := p.Stream.NewAudioTrack(8)
// at.SoundRate = 8000
// at.SoundSize = 16
// asc := at.CodecID << 4
// asc = asc + 1<<1
// at.ExtraData = []byte{asc}
// at.PushRaw(pack)
// p.pushAudio = at.PushRaw
}
}
}
return
}
func (p *Publisher) PushPS(rtp *rtp.Packet) {
ps := rtp.Payload
if p.lastSeq != 0 {
// rtp序号不连续丢弃PS
if p.lastSeq+1 != rtp.SequenceNumber {
p.parser.Reset()
}
}
p.lastSeq = rtp.SequenceNumber
p.Update()
if len(ps) >= 4 && BigEndian.Uint32(ps) == utils.StartCodePS {
if p.parser.Len() > 0 {
p.parser.Uint32()
p.parser.Read(rtp.Timestamp, p)
p.parser.Reset()
}
p.parser.Write(ps)
} else if p.parser.Len() > 0 {
p.parser.Write(ps)
}
}

303
restful.go Normal file
View File

@@ -0,0 +1,303 @@
package gb28181
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"m7s.live/engine/v4/util"
)
var (
playScaleValues = map[float32]bool{0.25: true, 0.5: true, 1: true, 2: true, 4: true}
)
func (c *GB28181Config) API_list(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
if query.Get("interval") == "" {
query.Set("interval", "5s")
}
util.ReturnFetchValue(func() (list []*Device) {
list = make([]*Device, 0)
Devices.Range(func(key, value interface{}) bool {
list = append(list, value.(*Device))
return true
})
return
}, w, r)
}
func (c *GB28181Config) API_records(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
id := query.Get("id")
channel := query.Get("channel")
startTime := query.Get("startTime")
endTime := query.Get("endTime")
trange := strings.Split(query.Get("range"), "-")
if len(trange) == 2 {
startTime = trange[0]
endTime = trange[1]
}
if c := FindChannel(id, channel); c != nil {
res, err := c.QueryRecord(startTime, endTime)
if err == nil {
util.ReturnValue(res, w, r)
} else {
util.ReturnError(util.APIErrorInternal, err.Error(), w, r)
}
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_control(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
ptzcmd := r.URL.Query().Get("ptzcmd")
if c := FindChannel(id, channel); c != nil {
util.ReturnError(0, fmt.Sprintf("control code:%d", c.Control(ptzcmd)), w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_ptz(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
id := q.Get("id")
channel := q.Get("channel")
cmd := q.Get("cmd") // 命令名称,见 ptz.go name2code 定义
hs := q.Get("hSpeed") // 水平速度
vs := q.Get("vSpeed") // 垂直速度
zs := q.Get("zSpeed") // 缩放速度
hsN, err := strconv.ParseUint(hs, 10, 8)
if err != nil {
util.ReturnError(util.APIErrorQueryParse, "hSpeed parameter is invalid", w, r)
return
}
vsN, err := strconv.ParseUint(vs, 10, 8)
if err != nil {
util.ReturnError(util.APIErrorQueryParse, "vSpeed parameter is invalid", w, r)
return
}
zsN, err := strconv.ParseUint(zs, 10, 8)
if err != nil {
util.ReturnError(util.APIErrorQueryParse, "zSpeed parameter is invalid", w, r)
return
}
ptzcmd, err := toPtzStrByCmdName(cmd, uint8(hsN), uint8(vsN), uint8(zsN))
if err != nil {
util.ReturnError(util.APIErrorQueryParse, err.Error(), w, r)
return
}
if c := FindChannel(id, channel); c != nil {
code := c.Control(ptzcmd)
util.ReturnError(code, "device received", w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_invite(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
id := query.Get("id")
channel := query.Get("channel")
streamPath := query.Get("streamPath")
port, _ := strconv.Atoi(query.Get("mediaPort"))
opt := InviteOptions{
dump: query.Get("dump"),
MediaPort: uint16(port),
StreamPath: streamPath,
}
startTime := query.Get("startTime")
endTime := query.Get("endTime")
trange := strings.Split(query.Get("range"), "-")
if len(trange) == 2 {
startTime = trange[0]
endTime = trange[1]
}
opt.Validate(startTime, endTime)
if c := FindChannel(id, channel); c == nil {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
} else if opt.IsLive() && c.State.Load() > 0 {
util.ReturnError(util.APIErrorQueryParse, "live stream already exists", w, r)
} else if code, err := c.Invite(&opt); err == nil {
if code == 200 {
util.ReturnOK(w, r)
} else {
util.ReturnError(util.APIErrorInternal, fmt.Sprintf("invite return code %d", code), w, r)
}
} else {
util.ReturnError(util.APIErrorInternal, err.Error(), w, r)
}
}
func (c *GB28181Config) API_bye(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
streamPath := r.URL.Query().Get("streamPath")
if c := FindChannel(id, channel); c != nil {
util.ReturnError(0, fmt.Sprintf("bye code:%d", c.Bye(streamPath)), w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_play_pause(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
streamPath := r.URL.Query().Get("streamPath")
if c := FindChannel(id, channel); c != nil {
util.ReturnError(0, fmt.Sprintf("pause code:%d", c.Pause(streamPath)), w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_play_resume(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
streamPath := r.URL.Query().Get("streamPath")
if c := FindChannel(id, channel); c != nil {
util.ReturnError(0, fmt.Sprintf("resume code:%d", c.Resume(streamPath)), w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_play_seek(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
streamPath := r.URL.Query().Get("streamPath")
secStr := r.URL.Query().Get("second")
sec, err := strconv.ParseUint(secStr, 10, 32)
if err != nil {
util.ReturnError(util.APIErrorQueryParse, "second parameter is invalid: "+err.Error(), w, r)
return
}
if c := FindChannel(id, channel); c != nil {
util.ReturnError(0, fmt.Sprintf("play code:%d", c.PlayAt(streamPath, uint(sec))), w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_play_forward(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
channel := r.URL.Query().Get("channel")
streamPath := r.URL.Query().Get("streamPath")
speedStr := r.URL.Query().Get("speed")
speed, err := strconv.ParseFloat(speedStr, 32)
secondErrMsg := "speed parameter is invalid, should be one of 0.25,0.5,1,2,4"
if err != nil || !playScaleValues[float32(speed)] {
util.ReturnError(util.APIErrorQueryParse, secondErrMsg, w, r)
return
}
if c := FindChannel(id, channel); c != nil {
util.ReturnError(0, fmt.Sprintf("playforward code:%d", c.PlayForward(streamPath, float32(speed))), w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_position(w http.ResponseWriter, r *http.Request) {
//CORS(w, r)
query := r.URL.Query()
//设备id
id := query.Get("id")
//订阅周期(单位:秒)
expires := query.Get("expires")
//订阅间隔(单位:秒)
interval := query.Get("interval")
expiresInt, err := time.ParseDuration(expires)
if expires == "" || err != nil {
expiresInt = c.Position.Expires
}
intervalInt, err := time.ParseDuration(interval)
if interval == "" || err != nil {
intervalInt = c.Position.Interval
}
if v, ok := Devices.Load(id); ok {
d := v.(*Device)
util.ReturnError(0, fmt.Sprintf("mobileposition code:%d", d.MobilePositionSubscribe(id, expiresInt, intervalInt)), w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q not found", id), w, r)
}
}
func (c *GB28181Config) API_preset_list(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
//设备id
id := query.Get("id")
//获取通道
channel := query.Get("channel")
if c := FindChannel(id, channel); c != nil {
res, err := c.QueryPresetList()
if err == nil {
util.ReturnValue(res, w, r)
} else {
util.ReturnError(util.APIErrorInternal, err.Error(), w, r)
}
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
func (c *GB28181Config) API_preset_control(w http.ResponseWriter, r *http.Request) {
//CORS(w, r)
query := r.URL.Query()
//设备id
id := query.Get("id")
//获取通道
channel := query.Get("channel")
//获取cmd
ptzCmd := query.Get("cmd")
//获取点
point := query.Get("point")
if c := FindChannel(id, channel); c != nil {
_ptzCmd, _ := strconv.ParseInt(ptzCmd, 10, 16)
_point, _ := strconv.ParseInt(point, 10, 8)
code := c.PresetControl(int(_ptzCmd), byte(_point))
util.ReturnError(code, "device received", w, r)
} else {
util.ReturnError(util.APIErrorNotFound, fmt.Sprintf("device %q channel %q not found", id, channel), w, r)
}
}
type DevicePosition struct {
ID string
GpsTime time.Time //gps时间
Longitude string //经度
Latitude string //纬度
}
func (c *GB28181Config) API_get_position(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
//设备id
id := query.Get("id")
if query.Get("interval") == "" {
query.Set("interval", fmt.Sprintf("%ds", c.Position.Interval.Seconds()))
}
util.ReturnFetchValue(func() (list []*DevicePosition) {
if id == "" {
Devices.Range(func(key, value interface{}) bool {
d := value.(*Device)
if time.Since(d.GpsTime) <= c.Position.Interval {
list = append(list, &DevicePosition{ID: d.ID, GpsTime: d.GpsTime, Longitude: d.Longitude, Latitude: d.Latitude})
}
return true
})
} else if v, ok := Devices.Load(id); ok {
d := v.(*Device)
list = append(list, &DevicePosition{ID: d.ID, GpsTime: d.GpsTime, Longitude: d.Longitude, Latitude: d.Latitude})
}
return
}, w, r)
}

217
server.go Executable file
View File

@@ -0,0 +1,217 @@
package gb28181
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/ghettovoice/gosip"
"github.com/ghettovoice/gosip/log"
"github.com/ghettovoice/gosip/sip"
"github.com/logrusorgru/aurora/v4"
"go.uber.org/zap"
. "m7s.live/engine/v4"
"m7s.live/plugin/gb28181/v4/utils"
)
var srv gosip.Server
const MaxRegisterCount = 3
func FindChannel(deviceId string, channelId string) (c *Channel) {
if v, ok := Devices.Load(deviceId); ok {
d := v.(*Device)
if v, ok := d.channelMap.Load(channelId); ok {
return v.(*Channel)
}
}
return
}
var levelMap = map[string]log.Level{
"trace": log.TraceLevel,
"debug": log.DebugLevel,
"info": log.InfoLevel,
"warn": log.WarnLevel,
"error": log.ErrorLevel,
"fatal": log.FatalLevel,
"panic": log.PanicLevel,
}
func GetSipServer(transport string) gosip.Server {
return srv
}
var sn = 0
func CreateRequest(exposedId string, Method sip.RequestMethod, recipient *sip.Address, netAddr string) (req sip.Request) {
sn++
callId := sip.CallID(utils.RandNumString(10))
userAgent := sip.UserAgentHeader("Monibuca")
cseq := sip.CSeq{
SeqNo: uint32(sn),
MethodName: Method,
}
port := sip.Port(conf.SipPort)
serverAddr := sip.Address{
//DisplayName: sip.String{Str: d.config.Serial},
Uri: &sip.SipUri{
FUser: sip.String{Str: exposedId},
FHost: conf.SipIP,
FPort: &port,
},
Params: sip.NewParams().Add("tag", sip.String{Str: utils.RandNumString(9)}),
}
req = sip.NewRequest(
"",
Method,
recipient.Uri,
"SIP/2.0",
[]sip.Header{
serverAddr.AsFromHeader(),
recipient.AsToHeader(),
&callId,
&userAgent,
&cseq,
serverAddr.AsContactHeader(),
},
"",
nil,
)
req.SetTransport(conf.SipNetwork)
req.SetDestination(netAddr)
//fmt.Printf("构建请求参数:%s", *&req)
// requestMsg.DestAdd, err2 = d.ResolveAddress(requestMsg)
// if err2 != nil {
// return nil
// }
//intranet ip , let's resolve it with public ip
// var deviceIp, deviceSourceIP net.IP
// switch addr := requestMsg.DestAdd.(type) {
// case *net.UDPAddr:
// deviceIp = addr.IP
// case *net.TCPAddr:
// deviceIp = addr.IP
// }
// switch addr2 := d.SourceAddr.(type) {
// case *net.UDPAddr:
// deviceSourceIP = addr2.IP
// case *net.TCPAddr:
// deviceSourceIP = addr2.IP
// }
// if deviceIp.IsPrivate() && !deviceSourceIP.IsPrivate() {
// requestMsg.DestAdd = d.SourceAddr
// }
return
}
func RequestForResponse(transport string, request sip.Request,
options ...gosip.RequestWithContextOption) (sip.Response, error) {
return (GetSipServer(transport)).RequestWithContext(context.Background(), request, options...)
}
func (c *GB28181Config) startServer() {
addr := c.ListenAddr + ":" + strconv.Itoa(int(c.SipPort))
logger := utils.NewZapLogger(GB28181Plugin.Logger, "GB SIP Server", nil)
logger.SetLevel(uint32(levelMap[EngineConfig.LogLevel]))
// logger := log.NewDefaultLogrusLogger().WithPrefix("GB SIP Server")
srvConf := gosip.ServerConfig{}
if c.SipIP != "" {
srvConf.Host = c.SipIP
}
srv = gosip.NewServer(srvConf, nil, nil, logger)
srv.OnRequest(sip.REGISTER, c.OnRegister)
srv.OnRequest(sip.MESSAGE, c.OnMessage)
srv.OnRequest(sip.NOTIFY, c.OnNotify)
srv.OnRequest(sip.BYE, c.OnBye)
err := srv.Listen(strings.ToLower(c.SipNetwork), addr)
if err != nil {
GB28181Plugin.Logger.Error("gb28181 server listen", zap.Error(err))
} else {
GB28181Plugin.Info(fmt.Sprint(aurora.Green("Server gb28181 start at"), aurora.BrightBlue(addr)))
}
if c.MediaNetwork == "tcp" {
c.tcpPorts.Init(c.MediaPortMin, c.MediaPortMax)
} else {
c.udpPorts.Init(c.MediaPortMin, c.MediaPortMax)
}
go c.startJob()
}
// func queryCatalog(config *transaction.Config) {
// t := time.NewTicker(time.Duration(config.CatalogInterval) * time.Second)
// for range t.C {
// Devices.Range(func(key, value interface{}) bool {
// device := value.(*Device)
// if time.Since(device.UpdateTime) > time.Duration(config.RegisterValidity)*time.Second {
// Devices.Delete(key)
// } else if device.Channels != nil {
// go device.Catalog()
// }
// return true
// })
// }
// }
// 定时任务
func (c *GB28181Config) startJob() {
statusTick := time.NewTicker(c.HeartbeatInterval / 2)
banTick := time.NewTicker(c.RemoveBanInterval)
linkTick := time.NewTicker(time.Millisecond * 100)
GB28181Plugin.Debug("start job")
for {
select {
case <-banTick.C:
if c.Username != "" || c.Password != "" {
c.removeBanDevice()
}
case <-statusTick.C:
c.statusCheck()
case <-linkTick.C:
RecordQueryLink.cleanTimeout()
}
}
}
func (c *GB28181Config) removeBanDevice() {
DeviceRegisterCount.Range(func(key, value interface{}) bool {
if value.(int) > MaxRegisterCount {
DeviceRegisterCount.Delete(key)
}
return true
})
}
// statusCheck
// - 当设备超过 3 倍心跳时间未发送过心跳(通过 UpdateTime 判断), 视为离线
// - 当设备超过注册有效期内为发送过消息,则从设备列表中删除
// UpdateTime 在设备发送心跳之外的消息也会被更新,相对于 LastKeepaliveAt 更能体现出设备最会一次活跃的时间
func (c *GB28181Config) statusCheck() {
Devices.Range(func(key, value any) bool {
d := value.(*Device)
if time.Since(d.UpdateTime) > c.RegisterValidity {
Devices.Delete(key)
GB28181Plugin.Info("Device register timeout",
zap.String("id", d.ID),
zap.Time("registerTime", d.RegisterTime),
zap.Time("updateTime", d.UpdateTime),
)
} else if time.Since(d.UpdateTime) > c.HeartbeatInterval*3 {
d.Status = DeviceOfflineStatus
d.channelMap.Range(func(key, value any) bool {
ch := value.(*Channel)
ch.Status = ChannelOffStatus
return true
})
GB28181Plugin.Info("Device offline", zap.String("id", d.ID), zap.Time("updateTime", d.UpdateTime))
}
return true
})
}

View File

@@ -1,120 +0,0 @@
#### SIP消息
```
SIP-message = Request / Response
Request = Request-Line
*( message-header )
CRLF
[ message-body ]
```
#### SIP消息头域
```
message-header = (Accept
/ Accept-Encoding
/ Accept-Language
/ Alert-Info
/ Allow
/ Authentication-Info
/ Authorization
/ Call-ID
/ Call-Info
/ Contact
/ Content-Disposition
/ Content-Encoding
/ Content-Language
/ Content-Length
/ Content-Type
/ CSeq
/ Date
/ Error-Info
/ Expires
/ From
/ In-Reply-To
/ Max-Forwards
/ MIME-Version
/ Min-Expires
/ Organization
/ Priority
/ Proxy-Authenticate
/ Proxy-Authorization
/ Proxy-Require
/ Record-Route
/ Reply-To
/ Require
/ Retry-After
/ Route
/ Server
/ Subject
/ Supported
/ Timestamp
/ To
/ Unsupported
/ User-Agent
/ Via
/ Warning
/ WWW-Authenticate
/ extension-header) CRLF
```
#### SIP 响应状态码
```
Informational = "100" ; Trying
/ "180" ; Ringing
/ "181" ; Call Is Being Forwarded
/ "182" ; Queued
/ "183" ; Session Progress
Success = "200" ; OK
Redirection = "300" ; Multiple Choices
/ "301" ; Moved Permanently
/ "302" ; Moved Temporarily
/ "305" ; Use Proxy
/ "380" ; Alternative Service
Client-Error = "400" ; Bad Request
/ "401" ; Unauthorized
/ "402" ; Payment Required
/ "403" ; Forbidden
/ "404" ; Not Found
/ "405" ; Method Not Allowed
/ "406" ; Not Acceptable
/ "407" ; Proxy Authentication Required
/ "408" ; Request Timeout
/ "410" ; Gone
/ "413" ; Request Entity Too Large
/ "414" ; Request-URI Too Large
/ "415" ; Unsupported Media Type
/ "416" ; Unsupported URI Scheme
/ "420" ; Bad Extension
/ "421" ; Extension Required
/ "423" ; Interval Too Brief
/ "480" ; Temporarily not available
/ "481" ; Call Leg/Transaction Does Not Exist
/ "482" ; Loop Detected
/ "483" ; Too Many Hops
/ "484" ; Address Incomplete
/ "485" ; Ambiguous
/ "486" ; Busy Here
/ "487" ; Request Terminated
/ "488" ; Not Acceptable Here
/ "491" ; Request Pending
/ "493" ; Undecipherable
Server-Error = "500" ; Internal Server Error
/ "501" ; Not Implemented
/ "502" ; Bad Gateway
/ "503" ; Service Unavailable
/ "504" ; Server Time-out
/ "505" ; SIP Version not supported
/ "513" ; Message Too Large
Global-Failure = "600" ; Busy Everywhere
/ "603" ; Decline
/ "604" ; Does not exist anywhere
/ "606" ; Not Acceptable
```

View File

@@ -1,18 +0,0 @@
package sip
//sip message body
//xml解析的字段
const (
MESSAGE_CATALOG = "Catalog"
MESSAGE_DEVICE_INFO = "DeviceInfo"
MESSAGE_BROADCAST = "Broadcast"
MESSAGE_DEVICE_STATUS = "DeviceStatus"
MESSAGE_KEEP_ALIVE = "Keepalive"
MESSAGE_MOBILE_POSITION = "MobilePosition"
MESSAGE_MOBILE_POSITION_INTERVAL = "Interval"
ELEMENT_DEVICE_ID = "DeviceID"
ELEMENT_DEVICE_LIST = "DeviceList"
ELEMENT_NAME = "Name"
ELEMENT_STATUS = "Status"
)

View File

@@ -1,56 +0,0 @@
package sip
import "fmt"
func DemoMessage() {
registerStr := `REGISTER sip:34020000002000000001@3402000000 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.64:5060;rport;branch=z9hG4bK385701375
From: <sip:34020000001320000001@3402000000>;tag=1840661473
To: <sip:34020000001320000001@3402000000>
Call-ID: 418133739
CSeq: 1 REGISTER
Contact: <sip:34020000001320000001@192.168.1.64:5060>
Max-Forwards: 70
User-Agent: IP Camera
Expires: 3600
Content-Length: 0`
fmt.Println("input:")
fmt.Println(registerStr)
msg, err := Decode([]byte(registerStr))
if err != nil {
fmt.Println("decode message failed:", err.Error())
return
}
out, err := Encode(msg)
if err != nil {
fmt.Println("encode message failed:", err.Error())
return
}
fmt.Println("=====================================")
fmt.Println("output:")
fmt.Println(string(out))
}
func DemoVIA() {
str1 := "SIP / 2.0 / UDP first.example.com: 4000;ttl=16 ;maddr=224.2.0.1 ;branch=z9hG4bKa7c6a8dlze.1"
str2 := "SIP/2.0/UDP 192.168.1.64:5060;rport;received=192.168.1.64;branch=z9hG4bK1000615294"
var err error
v1 := &Via{}
err = v1.Parse(str1)
if err != nil {
fmt.Println("error:", err.Error())
return
}
fmt.Printf("result:%v\n", v1.String())
v2 := &Via{}
err = v2.Parse(str2)
if err != nil {
fmt.Println("error:", err.Error())
return
}
fmt.Printf("result:%v\n", v2.String())
}

View File

@@ -1,571 +0,0 @@
package sip
import (
"errors"
"fmt"
"strconv"
"strings"
)
//换行符号:
//linux,unix : \r\n
//windows : \n
//Mac OS : \r
const (
VERSION = "SIP/2.0" // sip version
CRLF = "\r\n" // 0x0D0A
CRLFCRLF = "\r\n\r\n" // 0x0D0A0D0A
//CRLF = "\n" // 0x0D
//CRLFCRLF = "\n\n" // 0x0D0D
)
//SIP消息类型请求or响应
type Mode int
const (
SIP_MESSAGE_REQUEST Mode = 0
SIP_MESSAGE_RESPONSE Mode = 1
)
//sip request method
type Method string
const (
ACK Method = "ACK"
BYE Method = "BYE"
CANCEL Method = "CANCEL"
INVITE Method = "INVITE"
OPTIONS Method = "OPTIONS"
REGISTER Method = "REGISTER"
NOTIFY Method = "NOTIFY"
SUBSCRIBE Method = "SUBSCRIBE"
MESSAGE Method = "MESSAGE"
REFER Method = "REFER"
INFO Method = "INFO"
PRACK Method = "PRACK"
UPDATE Method = "UPDATE"
PUBLISH Method = "PUBLISH"
)
//startline
//MESSAGE sip:34020000001320000001@3402000000 SIP/2.0
//SIP/2.0 200 OK
type StartLine struct {
raw string //原始内容
//request line: method uri version
Method Method
Uri URI //Request-URI:请求的服务地址,不能包含空白字符或者控制字符,并且禁止用”<>”括上。
Version string
//status line: version code phrase
Code int //status code
phrase string
}
func (l *StartLine) String() string {
if l.Version == "" {
l.Version = "SIP/2.0"
}
var result string
if l.Method == "" {
result = fmt.Sprintf("%s %d %s", l.Version, l.Code, l.phrase)
} else {
result = fmt.Sprintf("%s %s %s", l.Method, l.Uri.String(), l.Version)
}
l.raw = result
return l.raw
}
//To From Referto Contact
//From: <sip:34020000001320000001@3402000000>;tag=575945878
//To: <sip:34020000002000000001@3402000000>
//Contact: <sip:34020000001320000001@27.38.49.149:49243>
//Contact: <sip:34020000001320000001@192.168.1.64:5060>;expires=0
type Contact struct {
raw string //原始内容
Nickname string //可以没有
Uri URI //
//header params
Params map[string]string // include tag/q/expires
}
func (c *Contact) String() string {
sb := strings.Builder{}
if c.Nickname != "" {
sb.WriteByte('"')
sb.WriteString(c.Nickname)
sb.WriteByte('"')
sb.WriteByte(' ')
}
urlStr := c.Uri.String()
if strings.ContainsAny(urlStr, ",?:") {
urlStr = fmt.Sprintf("<%s>", urlStr)
}
sb.WriteString(urlStr)
if c.Params != nil {
for k, v := range c.Params {
sb.WriteString(";")
sb.WriteString(k)
sb.WriteString("=")
sb.WriteString(v)
}
}
c.raw = sb.String()
return c.raw
}
func (c *Contact) Parse(str string) (err error) {
c.raw = str
if str == "*" {
c.Uri.host = "*"
return
}
n0 := strings.IndexByte(str, '"')
if n0 != -1 {
str = str[n0+1:]
n1 := strings.IndexByte(str, '"')
if n1 == -1 {
return errors.New("parse nickname failed")
}
c.Nickname = str[:n1]
str = strings.TrimSpace(str[n1+1:])
}
if len(str) == 0 {
return
}
var uriDone = false
if strings.ContainsAny(str, "<>") {
n2 := strings.IndexByte(str, '<')
n3 := strings.IndexByte(str, '>')
if n2 == -1 || n3 == -1 {
err = errors.New("parse contact-uri failed")
return
}
c.Uri, err = parseURI(str[n2+1 : n3])
if err != nil {
return
}
uriDone = true
str = strings.TrimSpace(str[n3+1:])
}
if len(str) == 0 {
return
}
str = strings.Trim(str, ";")
arr1 := strings.Split(str, ";")
for idx, one := range arr1 {
//如果上面没有通过<>解析出来uri则用分号split的第一个元素就是uri字符串
if !uriDone && idx == 0 {
c.Uri, err = parseURI(one)
if err != nil {
return
}
continue
}
if c.Params == nil {
c.Params = make(map[string]string)
}
arr2 := strings.Split(one, "=")
k, v := arr2[0], arr2[1]
c.Params[k] = v
}
return
}
//Via: SIP/2.0/UDP 192.168.1.64:5060;rport=49243;received=27.38.49.149;branch=z9hG4bK879576192
//Params
//Received : IPv4address / IPv6address
//RPort : 0-not found, -1-no-value, other-value
//Branch : branch参数的值必须用magic cookie "z9hG4bK" 作为开头
/*
Via = ( "Via" / "v" ) HCOLON via-parm *(COMMA via-parm)
via-parm = sent-protocol LWS sent-by *( SEMI via-params )
via-params = via-ttl / via-maddr
/ via-received / via-branch
/ via-extension
via-ttl = "ttl" EQUAL ttl
via-maddr = "maddr" EQUAL host
via-received = "received" EQUAL (IPv4address / IPv6address)
via-branch = "branch" EQUAL token
via-extension = generic-param
sent-protocol = protocol-name SLASH protocol-version
SLASH transport
protocol-name = "SIP" / token
protocol-version = token
transport = "UDP" / "TCP" / "TLS" / "SCTP"
/ other-transport
sent-by = host [ COLON port ]
ttl = 1*3DIGIT ; 0 to 255
*/
type Via struct {
raw string // 原始内容
Version string // sip version: default to SIP/2.0
Transport string // UDP,TCP ,TLS , SCTP
Host string // sent-by : host:port
Port string //
//header params
Params map[string]string // include branch/rport/received/ttl/maddr
}
func (v *Via) GetBranch() string {
return v.Params["branch"]
}
func (v *Via) GetSendBy() string {
var host, port string
sb := strings.Builder{}
received := v.Params["received"]
rport := v.Params["rport"]
if received != "" {
host = received
} else {
host = v.Host
}
if rport != "" && rport != "0" && rport != "-1" {
port = rport
} else if v.Port != "" {
port = v.Port
} else {
if strings.ToUpper(v.Transport) == "UDP" {
port = "5060"
} else {
port = "5061"
}
}
sb.WriteString(host)
sb.WriteString(":")
sb.WriteString(port)
return sb.String()
}
func (v *Via) String() string {
sb := strings.Builder{}
if v.Version == "" {
v.Version = "SIP/2.0"
}
if v.Transport == "" {
v.Transport = "UDP"
}
sb.WriteString(v.Version)
sb.WriteString("/")
sb.WriteString(v.Transport)
sb.WriteString(" ")
sb.WriteString(v.Host)
if v.Port != "" {
sb.WriteString(":")
sb.WriteString(v.Port)
}
if v.Params != nil {
for k, v := range v.Params {
sb.WriteString(";")
sb.WriteString(k)
if v == "-1" {
//rport 值为-1的时候没有值
continue
}
sb.WriteString("=")
sb.WriteString(v)
}
}
v.raw = sb.String()
return v.raw
}
//注意via允许以下这种添加空白
//Via: SIP / 2.0 / UDP first.example.com: 4000;ttl=16 ;maddr=224.2.0.1 ;branch=z9hG4bKa7c6a8dlze.1
//Via: SIP/2.0/UDP 192.168.1.64:5060;rport=5060;received=192.168.1.64;branch=z9hG4bK1000615294
func (v *Via) Parse(str string) (err error) {
v.raw = str
str = strings.Trim(str, ";")
arr1 := strings.Split(str, ";")
part1 := strings.TrimSpace(arr1[0]) //SIP / 2.0 / UDP first.example.com: 4000
v.Host, v.Port = "", ""
if n1 := strings.IndexByte(part1, ':'); n1 != -1 {
v.Port = strings.TrimSpace(part1[n1+1:])
part1 = strings.TrimSpace(part1[:n1])
}
n2 := strings.LastIndexByte(part1, ' ')
if n2 == -1 {
v.Host = part1 //error?
} else {
v.Host = strings.TrimSpace(part1[n2+1:])
//解析protocol、version和transportSIP / 2.0 / UDP
part2 := part1[:n2]
arr2 := strings.Split(part2, "/")
if len(arr2) != 3 {
err = errors.New("parse contait part1.1 failed:" + part2)
return
}
v.Version = fmt.Sprintf("%s/%s", strings.TrimSpace(arr2[0]), strings.TrimSpace(arr2[1]))
v.Transport = strings.TrimSpace(arr2[2])
}
//必须有参数
v.Params = make(map[string]string)
for i, one := range arr1 {
if i == 0 {
//arr[0]已经处理
continue
}
one = strings.TrimSpace(one)
arr2 := strings.Split(one, "=")
//rport 这个参数可能没有 value。 -1:no-value, other-value
if len(arr2) == 1 {
if arr2[0] == "rport" {
v.Params["rport"] = "-1"
continue
} else {
fmt.Println("invalid param:", one)
continue
}
}
k, val := arr2[0], arr2[1]
v.Params[k] = val
}
return
}
//CSeq: 101 INVITE
//CSeq: 2 REGISTER
type CSeq struct {
raw string //原始内容
ID uint32
Method Method
}
func (c *CSeq) String() string {
c.raw = fmt.Sprintf("%d %s", c.ID, c.Method)
return c.raw
}
func (c *CSeq) Parse(str string) error {
c.raw = str
arr1 := strings.Split(str, " ")
n, err := strconv.ParseInt(arr1[0], 10, 64)
if err != nil {
fmt.Println("parse cseq faield:", str)
return err
}
c.ID = uint32(n)
c.Method = Method(arr1[1])
return nil
}
//sip:user:password@domain;uri-parameters?headers
/*
RFC3261
SIP-URI = "sip:" [ userinfo ] hostport
uri-parameters [ headers ]
SIPS-URI = "sips:" [ userinfo ] hostport
uri-parameters [ headers ]
userinfo = ( user / telephone-subscriber ) [ ":" password ] "@"
user = 1*( unreserved / escaped / user-unreserved )
user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/"
password = *( unreserved / escaped /
"&" / "=" / "+" / "$" / "," )
hostport = host [ ":" port ]
host = hostname / IPv4address / IPv6reference
hostname = *( domainlabel "." ) toplabel [ "." ]
domainlabel = alphanum
/ alphanum *( alphanum / "-" ) alphanum
toplabel = ALPHA / ALPHA *( alphanum / "-" ) alphanum
IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
IPv6reference = "[" IPv6address "]"
IPv6address = hexpart [ ":" IPv4address ]
hexpart = hexseq / hexseq "::" [ hexseq ] / "::" [ hexseq ]
hexseq = hex4 *( ":" hex4)
hex4 = 1*4HEXDIG
port = 1*DIGIT
uri-parameters = *( ";" uri-parameter)
uri-parameter = transport-param / user-param / method-param
/ ttl-param / maddr-param / lr-param / other-param
transport-param = "transport="
( "udp" / "tcp" / "sctp" / "tls"
/ other-transport)
other-transport = token
user-param = "user=" ( "phone" / "ip" / other-user)
other-user = token
method-param = "method=" Method
ttl-param = "ttl=" ttl
maddr-param = "maddr=" host
lr-param = "lr"
other-param = pname [ "=" pvalue ]
pname = 1*paramchar
pvalue = 1*paramchar
paramchar = param-unreserved / unreserved / escaped
param-unreserved = "[" / "]" / "/" / ":" / "&" / "+" / "$"
headers = "?" header *( "&" header )
header = hname "=" hvalue
hname = 1*( hnv-unreserved / unreserved / escaped )
hvalue = *( hnv-unreserved / unreserved / escaped )
hnv-unreserved = "[" / "]" / "/" / "?" / ":" / "+" / "$"
*/
type URI struct {
scheme string // sip sips
host string // userinfo@domain or userinfo@ip:port
method string // uri和method有关
params map[string]string // include branch/maddr/received/ttl/rport
headers map[string]string // include branch/maddr/received/ttl/rport
}
func (u *URI) Host() string {
return u.host
}
func (u *URI) UserInfo() string {
return strings.Split(u.host, "@")[0]
}
func (u *URI) Domain() string {
return strings.Split(u.host, "@")[1]
}
func (u *URI) IP() string {
t := strings.Split(u.host, "@")
if len(t) == 1 {
return strings.Split(t[0], ":")[0]
}
return strings.Split(t[1], ":")[0]
}
func (u *URI) Port() string {
t := strings.Split(u.host, "@")
if len(t) == 1 {
return strings.Split(t[0], ":")[1]
}
return strings.Split(t[1], ":")[1]
}
func (u *URI) String() string {
if u.scheme == "" {
u.scheme = "sip"
}
sb := strings.Builder{}
sb.WriteString(u.scheme)
sb.WriteString(":")
sb.WriteString(u.host)
if u.params != nil {
for k, v := range u.params {
sb.WriteString(";")
sb.WriteString(k)
sb.WriteString("=")
sb.WriteString(v)
}
}
if u.headers != nil {
sb.WriteString("?")
for k, v := range u.headers {
sb.WriteString("&")
sb.WriteString(k)
sb.WriteString("=")
sb.WriteString(v)
}
}
return sb.String()
}
//对于gb28181request-uri 不带参数
func NewURI(host string) URI {
return URI{
scheme: "sip",
host: host,
}
}
func parseURI(str string) (ret URI, err error) {
ret = URI{}
//解析scheme
str = strings.TrimSpace(str)
n1 := strings.IndexByte(str, ':')
if n1 == -1 {
err = errors.New("invalid sheme")
return
}
ret.scheme = str[:n1]
str = str[n1+1:]
if len(str) == 0 {
return
}
//解析host
n2 := strings.IndexByte(str, ';')
if n2 == -1 {
ret.host = str
return
}
ret.host = str[:n2]
str = str[n2+1:]
if len(str) == 0 {
return
}
//解析params and headers
var paramStr, headerStr = "", ""
n3 := strings.IndexByte(str, '?')
if n3 == -1 {
paramStr = str
} else {
paramStr = str[:n3]
headerStr = str[n3+1:]
}
//k1=v1;k2=v2
if paramStr != "" {
ret.params = make(map[string]string)
paramStr = strings.Trim(paramStr, ";")
arr1 := strings.Split(paramStr, ";")
for _, one := range arr1 {
tmp := strings.Split(one, "=")
if len(tmp) == 2 {
k, v := tmp[0], tmp[1]
ret.params[k] = v
} else {
ret.params[tmp[0]] = ""
}
}
}
//k1=v1&k2=v2
if headerStr != "" {
ret.headers = make(map[string]string)
arr2 := strings.Split(paramStr, "&")
for _, one := range arr2 {
tmp := strings.Split(one, "=")
k, v := tmp[0], tmp[1]
ret.headers[k] = v
}
}
return
}

View File

@@ -1,114 +0,0 @@
package sip
import (
"fmt"
"testing"
)
func TestContact(t *testing.T) {
str1 := "\"Mr.Watson\" <sip:watson@worcester.bell-telephone.com>;q=0.7; expires=3600,\"Mr.Watson\" <mailto:watson@bell-telephone.com>"
//str1 := `"Mr.Watson" <sip:watson@worcester.bell-telephone.com>;q=0.7;`
c := &Contact{}
err := c.Parse(str1)
if err != nil {
t.Error(err)
return
}
fmt.Println("source:", str1)
fmt.Println("result:", c.String())
}
func TestVia(t *testing.T) {
str1 := "SIP / 2.0 / UDP first.example.com: 4000;ttl=16 ;maddr=224.2.0.1 ;branch=z9hG4bKa7c6a8dlze.1"
str2 := "SIP/2.0/UDP 192.168.1.64:5060;rport;received=192.168.1.64;branch=z9hG4bK1000615294"
var err error
v1 := &Via{}
err = v1.Parse(str1)
if err != nil {
fmt.Println("error:", err.Error())
return
}
fmt.Printf("source:%v\n", str1)
fmt.Printf("result:%v\n", v1.String())
v2 := &Via{}
err = v2.Parse(str2)
if err != nil {
fmt.Println("error:", err.Error())
return
}
fmt.Printf("source:%v\n", str2)
fmt.Printf("result:%v\n", v2.String())
}
func TestMessage1(t *testing.T) {
str1 := `REGISTER sip:34020000002000000001@3402000000 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.64:5060;rport;branch=z9hG4bK385701375
From: <sip:34020000001320000001@3402000000>;tag=1840661473
To: <sip:34020000001320000001@3402000000>
Call-ID: 418133739
CSeq: 1 REGISTER
Contact: <sip:34020000001320000001@192.168.1.64:5060>
Max-Forwards: 70
User-Agent: IP Camera
Expires: 3600
Content-Length: 0`
fmt.Println("input:")
fmt.Println(str1)
msg, err := Decode([]byte(str1))
if err != nil {
fmt.Println("decode message failed:", err.Error())
return
}
out, err := Encode(msg)
if err != nil {
fmt.Println("encode message failed:", err.Error())
return
}
fmt.Println("=====================================")
fmt.Println("output:")
fmt.Println(string(out))
}
func TestMessage2(t *testing.T) {
str1 := `SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.151:5060;rport=5060;branch=SrsGbB56116414
From: <sip:34020000002000000001@3402000000>;tag=SrsGbF72006729
To: <sip:34020000001320000001@3402000000>;tag=416442565
Call-ID: 202093500940
CSeq: 101 INVITE
Contact: <sip:34020000001320000001@192.168.1.64:5060>
Content-Type: application/sdp
User-Agent: IP Camera
Content-Length: 185
v=0
o=34020000001320000001 1835 1835 IN IP4 192.168.1.64
s=Play
c=IN IP4 192.168.1.64
t=0 0
m=video 15060 RTP/AVP 96
a=sendonly
a=rtpmap:96 PS/90000
a=filesize:0
y=0009093131`
fmt.Println("input:")
fmt.Println(str1)
msg, err := Decode([]byte(str1))
if err != nil {
fmt.Println("decode message failed:", err.Error())
return
}
out, err := Encode(msg)
if err != nil {
fmt.Println("encode message failed:", err.Error())
return
}
fmt.Println("=====================================")
fmt.Println("output:")
fmt.Println(string(out))
}

View File

@@ -1,463 +0,0 @@
package sip
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/Monibuca/plugin-gb28181/v3/utils"
)
//Content-Type: Application/MANSCDP+xml
//Content-Type: Application/SDP
//Call-ID: 202081530679
//Max-Forwards: 70
//User-Agent: SRS/4.0.32(Leo)
//Subject: 34020000001320000001:0009093128,34020000002000000001:0
//Content-Length: 164
type Message struct {
Mode Mode //0:REQUEST, 1:RESPONSE
StartLine *StartLine
Via *Via //Via
From *Contact //From
To *Contact //To
CallID string //Call-ID
CSeq *CSeq //CSeq
Contact *Contact //Contact
Authorization string //Authorization
MaxForwards int //Max-Forwards
UserAgent string //User-Agent
Subject string //Subject
ContentType string //Content-Type
Expires int //Expires
ContentLength int //Content-Length
Route *Contact
Body string
Addr string
}
func (m *Message) BuildResponse(code int) *Message {
response := Message{
Mode: SIP_MESSAGE_RESPONSE,
From: m.From,
To: m.To,
CallID: m.CallID,
CSeq: m.CSeq,
Via: m.Via,
MaxForwards: m.MaxForwards,
StartLine: &StartLine{
Code: code,
},
}
return &response
}
//z9hG4bK + 10个随机数字
func randBranch() string {
return fmt.Sprintf("z9hG4bK%s", utils.RandNumString(8))
}
func BuildMessageRequest(method Method, transport, sipSerial, sipRealm, username, srcIP string, srcPort uint16, expires, cseq int, body string) *Message {
server := fmt.Sprintf("%s@%s", sipSerial, sipRealm)
client := fmt.Sprintf("%s@%s", username, sipRealm)
msg := &Message{
Mode: SIP_MESSAGE_REQUEST,
MaxForwards: 70,
UserAgent: "IPC",
Expires: expires,
ContentLength: 0,
}
msg.StartLine = &StartLine{
Method: method,
Uri: NewURI(server),
}
msg.Via = &Via{
Transport: transport,
Host: client,
}
msg.Via.Params = map[string]string{
"branch": randBranch(),
"rport": "-1", //only key,no-value
}
msg.From = &Contact{
Uri: NewURI(client),
Params: nil,
}
msg.From.Params = map[string]string{
"tag": utils.RandNumString(10),
}
msg.To = &Contact{
Uri: NewURI(client),
}
msg.CallID = utils.RandNumString(8)
msg.CSeq = &CSeq{
ID: uint32(cseq),
Method: method,
}
msg.Contact = &Contact{
Uri: NewURI(fmt.Sprintf("%s@%s:%d", username, srcIP, srcPort)),
}
if len(body) > 0 {
msg.ContentLength = len(body)
msg.Body = body
}
return msg
}
func (m *Message) GetMode() Mode {
return m.Mode
}
func (m *Message) IsRequest() bool {
return m.Mode == SIP_MESSAGE_REQUEST
}
func (m *Message) IsResponse() bool {
return m.Mode == SIP_MESSAGE_RESPONSE
}
func (m *Message) GetMethod() Method {
if m.CSeq == nil {
b, _ := Encode(m)
println(string(b))
return MESSAGE
}
return m.CSeq.Method
}
//此消息是否使用可靠传输
func (m *Message) IsReliable() bool {
protocol := strings.ToUpper(m.Via.Transport)
return "TCP" == protocol || "TLS" == protocol || "SCTP" == protocol
}
//response code
func (m *Message) GetStatusCode() int {
return m.StartLine.Code
}
//response code and reason
func (m *Message) GetReason() string {
return DumpError(m.StartLine.Code)
}
func (m *Message) GetBranch() string {
if m.Via == nil {
panic("invalid via")
}
if m.Via.Params == nil {
panic("invalid via params")
}
b, ok := m.Via.Params["branch"]
if !ok {
panic("invalid via paramas branch")
}
return b
}
//构建响应消息的时候,会使用请求消息的 source 和 destination
//请求消息的source格式 host:port
func (m *Message) Source() string {
if m.Mode == SIP_MESSAGE_RESPONSE {
fmt.Println("only for request message")
return ""
}
if m.Via == nil {
fmt.Println("invalid request message")
return ""
}
var (
host, port string
via = m.Via
)
if received, ok := via.Params["received"]; ok && received != "" {
host = received
} else {
host = via.Host
}
if rport, ok := via.Params["rport"]; ok && rport != "-1" && rport != "0" && rport != "" {
port = rport
} else if via.Port != "" {
port = via.Port
} else {
//如果port为空则上层构建消息的时候根据sip服务的默认端口来选择
}
return fmt.Sprintf("%v:%v", host, port)
}
//目标地址这个应该是用于通过route头域实现proxy这样的功能暂时不支持
func (m *Message) Destination() string {
//TODO:
return ""
}
//=======================================================================================================
func Decode(data []byte) (msg *Message, err error) {
msg = &Message{}
content := string(data)
content = strings.Trim(content, CRLFCRLF)
msgArr := strings.Split(content, CRLFCRLF)
//第一部分header
//第二部分body
if len(msgArr) == 0 {
fmt.Println("invalid sip message:", data)
err = errors.New("invalid sip message")
return
}
headStr := strings.TrimSpace(msgArr[0])
if msgArrLen := len(msgArr); msgArrLen > 1 {
for i := 1; i < msgArrLen; i++ {
msg.Body += strings.TrimSpace(msgArr[i])
}
}
headStr = strings.Trim(headStr, CRLF)
headArr := strings.Split(headStr, CRLF)
for i, line := range headArr {
//fmt.Printf("%02d --- %s ---- %d\n", i, line, len(line))
if i == 0 {
firstline := strings.Trim(line, " ")
tmp := strings.Split(firstline, " ")
//if len(tmp) != 3 {
// fmt.Println("parse first line failed:", firstline)
// err = errors.New("invalid first line")
// return
//}
if strings.HasPrefix(firstline, VERSION) {
//status line
//SIP/2.0 200 OK
var num int64
num, err = strconv.ParseInt(tmp[1], 10, 64)
if err != nil {
return
}
msg.Mode = SIP_MESSAGE_RESPONSE
msg.StartLine = &StartLine{
raw: firstline,
Version: VERSION,
Code: int(num),
phrase: strings.Join(tmp[2:], " "),
}
} else {
//request line
//REGISTER sip:34020000002000000001@3402000000 SIP/2.0
//MESSAGE sip:34020000002000000001@3402000000 SIP/2.0
msg.Mode = SIP_MESSAGE_REQUEST
msg.StartLine = &StartLine{
raw: firstline,
Method: Method(tmp[0]),
Version: VERSION,
}
if len(tmp) > 1 {
msg.StartLine.Uri, err = parseURI(tmp[1])
if err != nil {
return
}
} else {
fmt.Println(firstline)
}
}
continue
}
pos := strings.IndexByte(line, ':')
if pos == -1 {
continue
}
k := strings.ToLower(strings.TrimSpace(line[:pos]))
v := strings.TrimSpace(line[pos+1:])
//fmt.Printf("%02d ---k = %s , v = %s\n", i, k, v)
if len(v) == 0 {
continue
}
switch k {
case "via":
//Via: SIP/2.0/UDP 192.168.1.64:5060;rport;branch=z9hG4bK385701375
msg.Via = &Via{}
err = msg.Via.Parse(v)
if err != nil {
return
}
case "from":
msg.From = &Contact{}
err = msg.From.Parse(v)
if err != nil {
return
}
case "to":
msg.To = &Contact{}
err = msg.To.Parse(v)
if err != nil {
return
}
case "call-id":
msg.CallID = v
case "cseq":
//CSeq: 2 REGISTER
msg.CSeq = &CSeq{}
err = msg.CSeq.Parse(v)
if err != nil {
return
}
case "contact":
msg.Contact = &Contact{}
err = msg.Contact.Parse(v)
if err != nil {
return
}
case "max-forwards":
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
fmt.Printf("parse head faield: %s,%s\n", k, v)
return nil, err
}
msg.MaxForwards = int(n)
case "user-agent":
msg.UserAgent = v
case "expires":
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
fmt.Printf("parse head faield: %s,%s\n", k, v)
return nil, err
}
msg.Expires = int(n)
case "content-length":
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
fmt.Printf("parse head faield: %s,%s\n", k, v)
return nil, err
}
msg.ContentLength = int(n)
case "authorization":
msg.Authorization = v
case "content-type":
msg.ContentType = v
case "route":
//msg.Route = new(Contact)
//msg.Route.Parse(v)
default:
fmt.Printf("invalid sip head: %s,%s\n", k, v)
}
}
return
}
func Encode(msg *Message) ([]byte, error) {
sb := strings.Builder{}
sb.WriteString(msg.StartLine.String())
sb.WriteString(CRLF)
if msg.Via != nil {
sb.WriteString("Via: ")
sb.WriteString(msg.Via.String())
sb.WriteString(CRLF)
}
if msg.From != nil {
sb.WriteString("From: ")
sb.WriteString(msg.From.String())
sb.WriteString(CRLF)
}
if msg.To != nil {
sb.WriteString("To: ")
sb.WriteString(msg.To.String())
sb.WriteString(CRLF)
}
if msg.CallID != "" {
sb.WriteString("Call-ID: ")
sb.WriteString(msg.CallID)
sb.WriteString(CRLF)
}
if msg.CSeq != nil {
sb.WriteString("CSeq: ")
sb.WriteString(msg.CSeq.String())
sb.WriteString(CRLF)
}
if msg.Contact != nil {
sb.WriteString("Contact: ")
sb.WriteString(msg.Contact.String())
sb.WriteString(CRLF)
}
if msg.UserAgent != "" {
sb.WriteString("User-Agent: ")
sb.WriteString(msg.UserAgent)
sb.WriteString(CRLF)
}
if msg.ContentType != "" {
sb.WriteString("Content-Type: ")
sb.WriteString(msg.ContentType)
sb.WriteString(CRLF)
}
if msg.Expires != 0 {
sb.WriteString("Expires: ")
sb.WriteString(strconv.Itoa(msg.Expires))
sb.WriteString(CRLF)
}
if msg.Subject != "" {
sb.WriteString("Subject: ")
sb.WriteString(msg.Subject)
sb.WriteString(CRLF)
}
if msg.IsRequest() {
//request only
sb.WriteString("Max-Forwards: ")
sb.WriteString(strconv.Itoa(msg.MaxForwards))
sb.WriteString(CRLF)
if msg.Authorization != "" {
sb.WriteString("Authorization: ")
sb.WriteString(msg.Authorization)
sb.WriteString(CRLF)
}
} else {
//response only
}
sb.WriteString("Content-Length: ")
sb.WriteString(strconv.Itoa(msg.ContentLength))
sb.WriteString(CRLFCRLF)
if msg.Body != "" {
sb.WriteString(msg.Body)
}
return []byte(sb.String()), nil
}

View File

@@ -1,36 +0,0 @@
#### 事物
transaction事务是SIP的基本组成部分。
一个事务是客户发送的一个请求事务(通过通讯层)发送到一个服务器事务,连同服务器事务的所有的该请求的应答发送回客户端事务。
事务层处理应用服务层的重发,匹配请求的应答,以及应用服务层的超时。
任何一个用户代理客户端user agent client UAC完成的事情都是由一组事务构成的。
用户代理包含一个事务层,来实现有状态的代理服务器。
事务层包含一个客户元素(可以认为是一个客户事务)和一个服务器元素(可以认为是一个服务器事务),他们都可以用一个有限状态机来处理特定的请求。
在状态机层面事物分为ict、ist、nict、nist四种。
但底层事物方面,仅根据 method 等信息,分支处理。
#### 关于事物管理
事物在map里面管理事物ID的选择是要和事物匹配相关。
```
客户端事件的匹配
当客户端中的传输层收到响应时它必须确定哪个客户端事务将处理该响应以便可以进行17.1.1和17.1.2节的处理。头域 Via 字段中的branch参数用于匹配规则。 一个匹配的响应应该满足下面两个条件:
1.如果响应的头域 Via头字段中的branch参数值与创建事务的请求的头域 Via头字段中的branch参数值相同。
2.如果CSeq标头字段中的method参数与创建事务的请求的方法匹配。由于CANCEL请求会创建新的事务但共享相同的branch参数值。所以仅用branch参数是不够的
服务端事物匹配
首先要检查请求中的Via头域的branch参数。如果他以”z9hG4bk”开头那么这个请求一定是由客户端事务根据本规范产生的。因此branch参数在该客户端发出的所有的事务中都是唯一的。根据下列规则我们可以判定请求是否和事务匹配
1、 请求的Via头域的branch参数和创建本事务的请求的Via头域的branch参数一样并且
2、 请求的Via头域的sendby参数和创建本事务的请求的Via头域的send-by参数一样可能存在来自不同客户端的branch参数的意外或恶意重复所以将 send-by 值用作匹配的一部分),并且:
3、 请求的方法与创建事务的方法匹配但ACK除外在ACK中创建事务的请求的方法为INVITE。
```
所以根据匹配规则事物的ID 使用 branch然后在匹配逻辑里面再做条件判断。而因为branch可能会重复所以如果使用map来简化transaction的管理key的取值应该
客户端事物: branch+method
服务端事物: branch + sendby + method,method中ack还要除外。所以只能用branch + sendby

View File

@@ -1,61 +0,0 @@
package transaction
//SIP服务器静态配置信息
/*
# sip监听udp端口
listen 5060;
# SIP server ID(SIP服务器ID).
# 设备端配置编号需要与该值一致,否则无法注册
serial 34020000002000000001;
# SIP server domain(SIP服务器域)
realm 3402000000;
# 服务端发送ack后接收回应的超时时间单位为秒
# 如果指定时间没有回应,认为失败
ack_timeout 30;
# 设备心跳维持时间,如果指定时间内(秒)没有接收一个心跳
# 认为设备离线
keepalive_timeout 120;
# 注册之后是否自动给设备端发送invite
# on: 是 off 不是需要通过api控制
auto_play on;
# 设备将流发送的端口,是否固定
# on 发送流到多路复用端口 如9000
# off 自动从rtp_mix_port - rtp_max_port 之间的值中
# 选一个可以用的端口
invite_port_fixed on;
# 向设备或下级域查询设备列表的间隔,单位(秒)
# 默认60秒
query_catalog_interval 60;
*/
type Config struct {
//sip服务器的配置
SipNetwork string //传输协议默认UDP可选TCP
SipIP string //sip 服务器公网IP
SipPort uint16 //sip 服务器端口,默认 5060
Serial string //sip 服务器 id, 默认 34020000002000000001
Realm string //sip 服务器域,默认 3402000000
AckTimeout uint16 //sip 服务应答超时,单位秒
RegisterValidity int //注册有效期,单位秒,默认 3600
RegisterInterval int //注册间隔,单位秒,默认 60
HeartbeatInterval int //心跳间隔,单位秒,默认 60
HeartbeatRetry int //心跳超时次数,默认 3
//媒体服务器配置
MediaIP string //媒体服务器地址
MediaPort uint16 //媒体服务器端口
MediaPortMin uint16
MediaPortMax uint16
MediaIdleTimeout uint16 //推流超时时间,超过则断开链接,让设备重连
AudioEnable bool //是否开启音频
WaitKeyFrame bool //是否等待关键帧,如果等待,则在收到第一个关键帧之前,忽略所有媒体流
CatalogInterval int //目录查询间隔
}

View File

@@ -1,380 +0,0 @@
package transaction
import (
"context"
"fmt"
"net"
"sync"
"time"
"github.com/Monibuca/plugin-gb28181/v3/sip"
"github.com/Monibuca/plugin-gb28181/v3/transport"
)
//Core: transactions manager
//管理所有 transactions以及相关全局参数、运行状态机
type Core struct {
ctx context.Context //上下文
handlers map[State]map[Event]Handler //每个状态都可以处理有限个事件。不必加锁。
transactions map[string]*Transaction //管理所有 transactions,key:tid,value:transaction
mutex sync.RWMutex //transactions的锁
tp transport.ITransport //transport
*Config //sip server配置信息
OnRegister func(*sip.Message)
OnMessage func(*sip.Message) bool
}
//初始化一个 Core需要能响应请求也要能发起请求
//client 发起请求
//server 响应请求
//TODO:根据角色,增加相关配置信息
//TODO:通过context管理子线程
//TODO:单元测试
func NewCore(config *Config) *Core {
core := &Core{
handlers: make(map[State]map[Event]Handler),
transactions: make(map[string]*Transaction),
Config: config,
ctx: context.Background(),
}
if config.SipNetwork == "TCP" {
core.tp = transport.NewTCPServer(config.SipPort, true)
} else {
core.tp = transport.NewUDPServer(config.SipPort)
}
//填充fsm
core.addICTHandler()
core.addISTHandler()
core.addNICTHandler()
core.addNISTHandler()
return core
}
//add transaction to core
func (c *Core) AddTransaction(ta *Transaction) {
c.mutex.Lock()
c.transactions[ta.id] = ta
c.mutex.Unlock()
}
//delete transaction
func (c *Core) DelTransaction(tid string) {
c.mutex.Lock()
delete(c.transactions, tid)
c.mutex.Unlock()
}
//创建事物
//填充此事物的参数via、from、to、callID、cseq
func (c *Core) initTransaction(ctx context.Context, tid string, m *sip.Message) *Transaction {
//ack要么属于一个invite事物要么由TU层直接管理不通过事物管理。
if m.GetMethod() == sip.ACK {
fmt.Println("ack nerver create transaction")
return nil
}
ta := &Transaction{
id: tid,
core: c,
response: make(chan *Response),
startAt: time.Now(),
endAt: time.Now().Add(1000000 * time.Hour),
}
ta.Context, ta.cancel = context.WithTimeout(ctx,time.Second*5)
//填充其他transaction的信息
ta.via = m.Via
ta.from = m.From
ta.to = m.To
ta.callID = m.CallID
ta.cseq = m.CSeq
ta.origRequest = m
return ta
}
//状态机初始化:ICT
func (c *Core) addICTHandler() {
c.addHandler(ICT_PRE_CALLING, SND_REQINVITE, ict_snd_invite)
c.addHandler(ICT_CALLING, TIMEOUT_A, osip_ict_timeout_a_event)
c.addHandler(ICT_CALLING, TIMEOUT_B, osip_ict_timeout_b_event)
c.addHandler(ICT_CALLING, RCV_STATUS_1XX, ict_rcv_1xx)
c.addHandler(ICT_CALLING, RCV_STATUS_2XX, ict_rcv_2xx)
c.addHandler(ICT_CALLING, RCV_STATUS_3456XX, ict_rcv_3456xx)
c.addHandler(ICT_PROCEEDING, RCV_STATUS_1XX, ict_rcv_1xx)
c.addHandler(ICT_PROCEEDING, RCV_STATUS_2XX, ict_rcv_2xx)
c.addHandler(ICT_PROCEEDING, RCV_STATUS_3456XX, ict_rcv_3456xx)
c.addHandler(ICT_COMPLETED, RCV_STATUS_3456XX, ict_retransmit_ack)
c.addHandler(ICT_COMPLETED, TIMEOUT_D, osip_ict_timeout_d_event)
}
//状态机初始化:IST
func (c *Core) addISTHandler() {
c.addHandler(IST_PRE_PROCEEDING, RCV_REQINVITE, ist_rcv_invite)
c.addHandler(IST_PROCEEDING, RCV_REQINVITE, ist_rcv_invite)
c.addHandler(IST_COMPLETED, RCV_REQINVITE, ist_rcv_invite)
c.addHandler(IST_COMPLETED, TIMEOUT_G, osip_ist_timeout_g_event)
c.addHandler(IST_COMPLETED, TIMEOUT_H, osip_ist_timeout_h_event)
c.addHandler(IST_PROCEEDING, SND_STATUS_1XX, ist_snd_1xx)
c.addHandler(IST_PROCEEDING, SND_STATUS_2XX, ist_snd_2xx)
c.addHandler(IST_PROCEEDING, SND_STATUS_3456XX, ist_snd_3456xx)
c.addHandler(IST_COMPLETED, RCV_REQACK, ist_rcv_ack)
c.addHandler(IST_CONFIRMED, RCV_REQACK, ist_rcv_ack)
c.addHandler(IST_CONFIRMED, TIMEOUT_I, osip_ist_timeout_i_event)
}
//状态机初始化:NICT
func (c *Core) addNICTHandler() {
c.addHandler(NICT_PRE_TRYING, SND_REQUEST, nict_snd_request)
c.addHandler(NICT_TRYING, TIMEOUT_F, osip_nict_timeout_f_event)
c.addHandler(NICT_TRYING, TIMEOUT_E, osip_nict_timeout_e_event)
c.addHandler(NICT_TRYING, RCV_STATUS_1XX, nict_rcv_1xx)
c.addHandler(NICT_TRYING, RCV_STATUS_2XX, nict_rcv_23456xx)
c.addHandler(NICT_TRYING, RCV_STATUS_3456XX, nict_rcv_23456xx)
c.addHandler(NICT_PROCEEDING, TIMEOUT_F, osip_nict_timeout_f_event)
c.addHandler(NICT_PROCEEDING, TIMEOUT_E, osip_nict_timeout_e_event)
c.addHandler(NICT_PROCEEDING, RCV_STATUS_1XX, nict_rcv_1xx)
c.addHandler(NICT_PROCEEDING, RCV_STATUS_2XX, nict_rcv_23456xx)
c.addHandler(NICT_PROCEEDING, RCV_STATUS_3456XX, nict_rcv_23456xx)
c.addHandler(NICT_COMPLETED, TIMEOUT_K, osip_nict_timeout_k_event)
}
//状态机初始化:NIST
func (c *Core) addNISTHandler() {
c.addHandler(NIST_PRE_TRYING, RCV_REQUEST, nist_rcv_request)
c.addHandler(NIST_TRYING, SND_STATUS_1XX, nist_snd_1xx)
c.addHandler(NIST_TRYING, SND_STATUS_2XX, nist_snd_23456xx)
c.addHandler(NIST_TRYING, SND_STATUS_3456XX, nist_snd_23456xx)
c.addHandler(NIST_PROCEEDING, SND_STATUS_1XX, nist_snd_1xx)
c.addHandler(NIST_PROCEEDING, SND_STATUS_2XX, nist_snd_23456xx)
c.addHandler(NIST_PROCEEDING, SND_STATUS_3456XX, nist_snd_23456xx)
c.addHandler(NIST_PROCEEDING, RCV_REQUEST, nist_rcv_request)
c.addHandler(NIST_COMPLETED, TIMEOUT_J, osip_nist_timeout_j_event)
c.addHandler(NIST_COMPLETED, RCV_REQUEST, nist_rcv_request)
}
//状态机初始化根据state 匹配到对应的状态机
func (c *Core) addHandler(state State, event Event, handler Handler) {
m := c.handlers
if state >= DIALOG_CLOSE {
fmt.Println("invalid state:", state)
return
}
if event >= UNKNOWN_EVT {
fmt.Println("invalid event:", event)
return
}
if _, ok := m[state]; !ok {
m[state] = make(map[Event]Handler)
}
if _, ok := m[state][event]; ok {
fmt.Printf("state:%d,event:%d, has been exist\n", state, event)
} else {
m[state][event] = handler
}
}
func (c *Core) Start() {
go c.Handler()
c.tp.Start()
}
func (c *Core) Handler() {
//阻塞读取消息
for p := range c.tp.ReadPacketChan() {
if len(p.Data) < 5 {
continue
}
if err := c.HandleReceiveMessage(p); err != nil {
fmt.Println("handler sip response message failed:", err.Error())
continue
}
}
}
//发送消息:发送请求或者响应
//发送消息仅负责发送。报错有两种1、发送错误。2、发送了但是超时没有收到响应
//如果发送成功,如何判断是否收到响应?没有收到响应要重传
//所以一个transaction 有read和wriet的chan。
//发送的时候写 write chan
//接收的时候读取 read chan
//发送之后就开启timer超时重传还要记录和修改每次超时时间。不超时的话记得删掉timer
//发送 register 消息
func (c *Core) SendMessage(msg *sip.Message) *Response {
method := msg.GetMethod()
// data, _ := sip.Encode(msg)
// fmt.Println("send message:", method)
evt := getOutGoingMessageEvent(msg)
tid := getMessageTransactionID(msg)
//匹配事物
c.mutex.RLock()
ta, ok := c.transactions[tid]
c.mutex.RUnlock()
if !ok {
//新的请求
ta = c.initTransaction(c.ctx, tid, msg)
//如果是sip 消息事件则将消息缓存填充typo和state
if msg.IsRequest() {
//as uac
if method == sip.INVITE || method == sip.ACK {
ta.typo = FSM_ICT
ta.state = ICT_PRE_CALLING
} else {
ta.typo = FSM_NICT
ta.state = NICT_PRE_TRYING
}
} else {
//as uas:send response
}
c.AddTransaction(ta)
}
//把event推到transaction
ta.Run(evt, msg)
<-ta.Done()
if ta.lastResponse != nil {
return &Response{
Code: ta.lastResponse.GetStatusCode(),
Data: ta.lastResponse,
Message: ta.lastResponse.GetReason(),
}
} else {
return &Response{
Code: 504,
}
}
}
//接收到的消息处理
//收到消息有两种1、请求消息 2、响应消息
//请求消息则直接响应处理。
//响应消息则需要匹配到请求让请求的transaction来处理。
//TODO参考srs和osip的流程以及文档做最终处理。需要将逻辑分成两层TU 层和 transaction 层
func (c *Core) HandleReceiveMessage(p *transport.Packet) (err error) {
// fmt.Println("packet content:", string(p.Data))
var msg *sip.Message
msg, err = sip.Decode(p.Data)
if err != nil {
fmt.Println("parse sip message failed:", err.Error())
return ErrorParse
}
if msg.Via == nil {
return ErrorParse
}
//这里不处理超过MTU的包不处理半包
err = checkMessage(msg)
if err != nil {
return err
}
//fmt.Println("receive message:", msg.GetMethod())
evt := getInComingMessageEvent(msg)
tid := getMessageTransactionID(msg)
//一般应该是uas对于接收到的request做预处理
if msg.IsRequest() {
fixReceiveMessageViaParams(msg, p.Addr)
} else {
//TODO:对于uac收到response消息是否要检查 rport 和 received 呢因为uas可能对此做了修改
}
//TODOCANCEL、BYE 和 ACK 需要特殊处理使用事物或者直接由TU层处理
//查找transaction
c.mutex.RLock()
ta, ok := c.transactions[tid]
c.mutex.RUnlock()
method := msg.GetMethod()
if msg.IsRequest() {
switch method {
case sip.ACK:
//TODO:this should be a ACK for 2xx (but could be a late ACK!)
return
case sip.BYE:
c.Send(msg.BuildResponse(200))
return
case sip.MESSAGE:
if c.OnMessage(msg) && ta == nil {
c.Send(msg.BuildResponse(200))
}
if ta != nil {
m := msg.BuildResponse(200)
ta.Run(getOutGoingMessageEvent(m), m)
}
case sip.REGISTER:
if !ok {
ta = c.initTransaction(c.ctx, tid, msg)
ta.typo = FSM_NIST
ta.state = NIST_PROCEEDING
c.AddTransaction(ta)
}
c.OnRegister(msg)
m := msg.BuildResponse(200)
ta.Run(getOutGoingMessageEvent(m), m)
//case sip.INVITE:
// ta.typo = FSM_IST
// ta.state = IST_PRE_PROCEEDING
case sip.CANCEL:
//TODO:CANCEL处理
/* special handling for CANCEL */
/* in the new spec, if the CANCEL has a Via branch, then it
is the same as the one in the original INVITE */
return
}
} else if ok {
ta.Run(evt, msg)
}
//TODOTU层处理根据需要创建或者匹配 Dialog
//通过tag匹配到call和dialog
//处理是否要重传ack
return
}
func (c *Core) Send(msg *sip.Message) error {
addr := msg.Addr
if addr == "" {
viaParams := msg.Via.Params
var host, port string
var ok1, ok2 bool
if host, ok1 = viaParams["maddr"]; !ok1 {
if host, ok2 = viaParams["received"]; !ok2 {
host = msg.Via.Host
}
}
//port
port = viaParams["rport"]
if port == "" || port == "0" || port == "-1" {
port = msg.Via.Port
}
if port == "" {
port = "5060"
}
addr = fmt.Sprintf("%s:%s", host, port)
}
// fmt.Println("dest addr:", addr)
var err1, err2 error
pkt := &transport.Packet{}
pkt.Data, err1 = sip.Encode(msg)
if msg.Via.Transport == "UDP" {
pkt.Addr, err2 = net.ResolveUDPAddr("udp", addr)
} else {
pkt.Addr, err2 = net.ResolveTCPAddr("tcp", addr)
}
if err1 != nil {
return err1
}
if err2 != nil {
return err2
}
c.tp.WritePacket(pkt)
return nil
}

View File

@@ -1,13 +0,0 @@
package transaction
import (
"errors"
)
//transaction 的错误定义
var (
ErrorSyntax = errors.New("message syntax error")
ErrorCheck = errors.New("message check failed")
ErrorParse = errors.New("message parse failed")
ErrorUnknown = errors.New("message unknown")
)

View File

@@ -1,155 +0,0 @@
package transaction
import (
"github.com/Monibuca/plugin-gb28181/v3/sip"
)
/*
|INVITE from TU
Timer A fires |INVITE sent
Reset A, V Timer B fires
INVITE sent +-----------+ or Transport Err.
+---------| |---------------+inform TU
| | Calling | |
+-------->| |-------------->|
+-----------+ 2xx |
| | 2xx to TU |
| |1xx |
300-699 +---------------+ |1xx to TU |
ACK sent | | |
resp. to TU | 1xx V |
| 1xx to TU -----------+ |
| +---------| | |
| | |Proceeding |-------------->|
| +-------->| | 2xx |
| +-----------+ 2xx to TU |
| 300-699 | |
| ACK sent, | |
| resp. to TU| |
| | | NOTE:
| 300-699 V |
| ACK sent +-----------+Transport Err. | transitions
| +---------| |Inform TU | labeled with
| | | Completed |-------------->| the event
| +-------->| | | over the action
| +-----------+ | to take
| ^ | |
| | | Timer D fires |
+--------------+ | - |
| |
V |
+-----------+ |
| | |
| Terminated|<--------------+
| |
+-----------+
Figure 5: INVITE client transaction
*/
func ict_snd_invite(t *Transaction, evt Event, m *sip.Message) error {
t.isReliable = m.IsReliable()
t.origRequest = m
t.state = ICT_CALLING
//发送出去之后,开启 timer
if m.IsReliable() {
//stop timer E in reliable transport
//fmt.Println("Reliabel")
} else {
//fmt.Println("Not Reliable")
//发送定时器,每次加倍,没有上限?
t.timerA = NewSipTimer(T1, 0, func() {
if t.Err() == nil {
t.Run(TIMEOUT_A, nil)
}
})
}
t.RunAfter(TimeB, TIMEOUT_B)
return nil
}
func osip_ict_timeout_a_event(t *Transaction, evt Event, m *sip.Message) error {
err := t.SipSend(t.origRequest)
if err != nil {
//发送失败
t.Terminate()
return err
}
t.timerA.Reset(t.timerA.timeout * 2)
return nil
}
func osip_ict_timeout_b_event(t *Transaction, evt Event, m *sip.Message) error {
t.Terminate()
return nil
}
func ict_rcv_1xx(t *Transaction, evt Event, m *sip.Message) error {
t.lastResponse = m
t.state = ICT_PROCEEDING
return nil
}
func ict_rcv_2xx(t *Transaction, evt Event, m *sip.Message) error {
t.lastResponse = m
t.Terminate()
return nil
}
func ict_rcv_3456xx(t *Transaction, evt Event, m *sip.Message) error {
t.lastResponse = m
if t.state != ICT_COMPLETED {
/* not a retransmission */
/* automatic handling of ack! */
ack := ict_create_ack(t, m)
t.ack = ack
_ = t.SipSend(t.ack)
t.Terminate()
}
t.RunAfter(TimeD, TIMEOUT_D)
t.state = ICT_COMPLETED
return nil
}
func ict_create_ack(t *Transaction, resp *sip.Message) *sip.Message {
return &sip.Message{
Mode: t.origRequest.Mode,
Addr: t.origRequest.Addr,
StartLine: &sip.StartLine{
Method: sip.ACK,
Uri: t.origRequest.StartLine.Uri,
},
MaxForwards: t.origRequest.MaxForwards,
CallID: t.callID,
Contact: t.origRequest.Contact,
UserAgent: t.origRequest.UserAgent,
Via: t.via,
From: t.from,
To: t.to,
CSeq: &sip.CSeq{
ID: 1,
Method: sip.ACK,
},
}
}
func ict_retransmit_ack(t *Transaction, evt Event, m *sip.Message) error {
if t.ack == nil {
/* ??? we should make a new ACK and send it!!! */
return nil
}
err := t.SipSend(t.ack)
if err != nil {
return err
}
t.state = ICT_COMPLETED
return nil
}
func osip_ict_timeout_d_event(t *Transaction, evt Event, m *sip.Message) error {
t.Terminate()
return nil
}

View File

@@ -1,28 +0,0 @@
package transaction
import "github.com/Monibuca/plugin-gb28181/v3/sip"
func ist_rcv_invite(t *Transaction, evt Event,m *sip.Message) error {
return nil
}
func osip_ist_timeout_g_event(t *Transaction, evt Event,m *sip.Message) error {
return nil
}
func osip_ist_timeout_h_event(t *Transaction, evt Event,m *sip.Message) error {
return nil
}
func ist_snd_1xx(t *Transaction, evt Event,m *sip.Message) error {
return nil
}
func ist_snd_2xx(t *Transaction, evt Event,m *sip.Message) error {
return nil
}
func ist_snd_3456xx(t *Transaction, evt Event,m *sip.Message) error {
return nil
}
func ist_rcv_ack(t *Transaction, evt Event,m *sip.Message) error {
return nil
}
func osip_ist_timeout_i_event(t *Transaction, evt Event,m *sip.Message) error {
return nil
}

View File

@@ -1,134 +0,0 @@
package transaction
import (
"github.com/Monibuca/plugin-gb28181/v3/sip"
)
/*
非invite事物的状态机
|Request from TU
|send request
Timer E V
send request +-----------+
+---------| |-------------------+
| | Trying | Timer F |
+-------->| | or Transport Err.|
+-----------+ inform TU |
200-699 | | |
resp. to TU | |1xx |
+---------------+ |resp. to TU |
| | |
| Timer E V Timer F |
| send req +-----------+ or Transport Err. |
| +---------| | inform TU |
| | |Proceeding |------------------>|
| +-------->| |-----+ |
| +-----------+ |1xx |
| | ^ |resp to TU |
| 200-699 | +--------+ |
| resp. to TU | |
| | |
| V |
| +-----------+ |
| | | |
| | Completed | |
| | | |
| +-----------+ |
| ^ | |
| | | Timer K |
+--------------+ | - |
| |
V |
NOTE: +-----------+ |
| | |
transitions | Terminated|<------------------+
labeled with | |
the event +-----------+
over the action
to take
Figure 6: non-INVITE client transaction
*/
func nict_snd_request(t *Transaction, evt Event, m *sip.Message) error {
//fmt.Println("nict request:", msg.GetMethod())
t.origRequest = m
t.state = NICT_TRYING
err := t.SipSend(m)
if err != nil {
t.Terminate()
return err
}
//发送出去之后,开启 timer
if m.IsReliable() {
//stop timer E in reliable transport
//fmt.Println("Reliabel")
} else {
//fmt.Println("Not Reliable")
//发送定时器
t.timerE = NewSipTimer(T1, T2, func() {
if t.Err() == nil {
t.Run(TIMEOUT_E, nil)
}
})
}
t.RunAfter(TimeF, TIMEOUT_F)
return nil
}
//事物超时
func osip_nict_timeout_f_event(t *Transaction, evt Event, m *sip.Message) error {
t.Terminate()
return nil
}
func osip_nict_timeout_e_event(t *Transaction, evt Event, m *sip.Message) error {
if t.state == NICT_TRYING {
//reset timer
t.timerE.Reset(t.timerE.timeout * 2)
} else {
//in PROCEEDING STATE, TIMER is always T2
t.timerE.Reset(T2)
}
//resend origin request
err := t.SipSend(t.origRequest)
if err != nil {
t.Terminate()
return err
}
return nil
}
func nict_rcv_1xx(t *Transaction, evt Event, m *sip.Message) error {
t.lastResponse = m
t.state = NICT_PROCEEDING
//重置发送定时器
t.timerE.Reset(T2)
return nil
}
func nict_rcv_23456xx(t *Transaction, evt Event, m *sip.Message) error {
t.lastResponse = m
t.state = NICT_COMPLETED
if m.IsReliable() {
//不设置timerK
} else {
t.RunAfter(T4*64, TIMEOUT_K)
}
return nil
}
func osip_nict_timeout_k_event(t *Transaction, evt Event, m *sip.Message) error {
t.Terminate()
return nil
}

View File

@@ -1,105 +0,0 @@
package transaction
import (
"fmt"
"github.com/Monibuca/plugin-gb28181/v3/sip"
)
/*
|Request received
|pass to TU
V
+-----------+
| |
| Trying |-------------+
| | |
+-----------+ |200-699 from TU
| |send response
|1xx from TU |
|send response |
| |
Request V 1xx from TU |
send response+-----------+send response|
+--------| |--------+ |
| | Proceeding| | |
+------->| |<-------+ |
+<--------------| | |
|Trnsprt Err +-----------+ |
|Inform TU | |
| | |
| |200-699 from TU |
| |send response |
| Request V |
| send response+-----------+ |
| +--------| | |
| | | Completed |<------------+
| +------->| |
+<--------------| |
|Trnsprt Err +-----------+
|Inform TU |
| |Timer J fires
| |-
| |
| V
| +-----------+
| | |
+-------------->| Terminated|
| |
+-----------+
Figure 8: non-INVITE server transaction
*/
func nist_rcv_request(t *Transaction, evt Event, m *sip.Message) error {
fmt.Println("rcv request: ", m.GetMethod())
fmt.Println("transaction state: ", t.state.String())
if t.state != NIST_PRE_TRYING {
fmt.Println("rcv request retransmission,do response")
if t.lastResponse != nil {
err := t.SipSend(t.lastResponse)
if err != nil {
//transport error
return err
}
}
return nil
} else {
t.origRequest = m
t.state = NIST_TRYING
t.isReliable = m.IsReliable()
}
return nil
}
func nist_snd_1xx(t *Transaction, evt Event, m *sip.Message) error {
t.lastResponse = m
err := t.SipSend(t.lastResponse)
if err != nil {
return err
}
t.state = NIST_PROCEEDING
return nil
}
func nist_snd_23456xx(t *Transaction, evt Event, m *sip.Message) error {
t.lastResponse = m
if err := t.SipSend(t.lastResponse); err != nil {
return err
}
if t.state != NIST_COMPLETED {
if !t.isReliable {
t.RunAfter(T1*64, TIMEOUT_J)
}
}
t.state = NIST_COMPLETED
return nil
}
func osip_nist_timeout_j_event(t *Transaction, evt Event, m *sip.Message) error {
t.Terminate()
return nil
}

View File

@@ -1,430 +0,0 @@
package transaction
import (
"context"
"fmt"
"net"
"sync"
"time"
"github.com/Monibuca/plugin-gb28181/v3/sip"
"github.com/Monibuca/plugin-gb28181/v3/transport"
)
//状态机之状态
type State int
const (
/* STATES for invite client transaction */
ICT_PRE_CALLING State = iota
ICT_CALLING
ICT_PROCEEDING
ICT_COMPLETED
ICT_TERMINATED
/* STATES for invite server transaction */
IST_PRE_PROCEEDING
IST_PROCEEDING
IST_COMPLETED
IST_CONFIRMED
IST_TERMINATED
/* STATES for NON-invite client transaction */
NICT_PRE_TRYING
NICT_TRYING
NICT_PROCEEDING
NICT_COMPLETED
NICT_TERMINATED
/* STATES for NON-invite server transaction */
NIST_PRE_TRYING
NIST_TRYING
NIST_PROCEEDING
NIST_COMPLETED
NIST_TERMINATED
/* STATES for dialog */
DIALOG_EARLY
DIALOG_CONFIRMED
DIALOG_CLOSE
)
var stateMap = map[State]string{
ICT_PRE_CALLING: "ICT_PRE_CALLING",
ICT_CALLING: "ICT_CALLING",
ICT_PROCEEDING: "ICT_PROCEEDING",
ICT_COMPLETED: "ICT_COMPLETED",
ICT_TERMINATED: "ICT_TERMINATED",
IST_PRE_PROCEEDING: "IST_PRE_PROCEEDING",
IST_PROCEEDING: "IST_PROCEEDING",
IST_COMPLETED: "IST_COMPLETED",
IST_CONFIRMED: "IST_CONFIRMED",
IST_TERMINATED: "IST_TERMINATED",
NICT_PRE_TRYING: "NICT_PRE_TRYING",
NICT_TRYING: "NICT_TRYING",
NICT_PROCEEDING: "NICT_PROCEEDING",
NICT_COMPLETED: "NICT_COMPLETED",
NICT_TERMINATED: "NICT_TERMINATED",
NIST_PRE_TRYING: "NIST_PRE_TRYING",
NIST_TRYING: "NIST_TRYING",
NIST_PROCEEDING: "NIST_PROCEEDING",
NIST_COMPLETED: "NIST_COMPLETED",
NIST_TERMINATED: "NIST_TERMINATED",
DIALOG_EARLY: "DIALOG_EARLY",
DIALOG_CONFIRMED: "DIALOG_CONFIRMED",
DIALOG_CLOSE: "DIALOG_CLOSE",
}
func (s State) String() string {
return stateMap[s]
}
//状态机之事件
type Event int
const (
/* TIMEOUT EVENTS for ICT */
TIMEOUT_A Event = iota /**< Timer A */
TIMEOUT_B /**< Timer B */
TIMEOUT_D /**< Timer D */
/* TIMEOUT EVENTS for NICT */
TIMEOUT_E /**< Timer E */
TIMEOUT_F /**< Timer F */
TIMEOUT_K /**< Timer K */
/* TIMEOUT EVENTS for IST */
TIMEOUT_G /**< Timer G */
TIMEOUT_H /**< Timer H */
TIMEOUT_I /**< Timer I */
/* TIMEOUT EVENTS for NIST */
TIMEOUT_J /**< Timer J */
/* FOR INCOMING MESSAGE */
RCV_REQINVITE /**< Event is an incoming INVITE request */
RCV_REQACK /**< Event is an incoming ACK request */
RCV_REQUEST /**< Event is an incoming NON-INVITE and NON-ACK request */
RCV_STATUS_1XX /**< Event is an incoming informational response */
RCV_STATUS_2XX /**< Event is an incoming 2XX response */
RCV_STATUS_3456XX /**< Event is an incoming final response (not 2XX) */
/* FOR OUTGOING MESSAGE */
SND_REQINVITE /**< Event is an outgoing INVITE request */
SND_REQACK /**< Event is an outgoing ACK request */
SND_REQUEST /**< Event is an outgoing NON-INVITE and NON-ACK request */
SND_STATUS_1XX /**< Event is an outgoing informational response */
SND_STATUS_2XX /**< Event is an outgoing 2XX response */
SND_STATUS_3456XX /**< Event is an outgoing final response (not 2XX) */
KILL_TRANSACTION /**< Event to 'kill' the transaction before termination */
UNKNOWN_EVT /**< Max event */
)
var eventMap = map[Event]string{
TIMEOUT_A: "TIMEOUT_A",
TIMEOUT_B: "TIMEOUT_B",
TIMEOUT_D: "TIMEOUT_D",
TIMEOUT_E: "TIMEOUT_E",
TIMEOUT_F: "TIMEOUT_F",
TIMEOUT_K: "TIMEOUT_K",
TIMEOUT_G: "TIMEOUT_G",
TIMEOUT_H: "TIMEOUT_H",
TIMEOUT_I: "TIMEOUT_I",
TIMEOUT_J: "TIMEOUT_J",
RCV_REQINVITE: "RCV_REQINVITE",
RCV_REQACK: "RCV_REQACK",
RCV_REQUEST: "RCV_REQUEST",
RCV_STATUS_1XX: "RCV_STATUS_1XX",
RCV_STATUS_2XX: "RCV_STATUS_2XX",
RCV_STATUS_3456XX: "RCV_STATUS_3456XX",
SND_REQINVITE: "SND_REQINVITE",
SND_REQACK: "SND_REQACK",
SND_REQUEST: "SND_REQUEST",
SND_STATUS_1XX: "SND_STATUS_1XX",
SND_STATUS_2XX: "SND_STATUS_2XX",
SND_STATUS_3456XX: "SND_STATUS_3456XX",
KILL_TRANSACTION: "KILL_TRANSACTION",
UNKNOWN_EVT: "UNKNOWN_EVT",
}
func (e Event) String() string {
return eventMap[e]
}
//incoming SIP MESSAGE
func (e Event) IsIncomingMessage() bool {
return e >= RCV_REQINVITE && e <= RCV_STATUS_3456XX
}
//incoming SIP REQUEST
func (e Event) IsIncomingRequest() bool {
return e == RCV_REQINVITE || e == RCV_REQACK || e == RCV_REQUEST
}
//incoming SIP RESPONSE
func (e Event) IsIncomingResponse() bool {
return e == RCV_STATUS_1XX || e == RCV_STATUS_2XX || e == RCV_STATUS_3456XX
}
//outgoing SIP MESSAGE
func (e Event) IsOutgoingMessage() bool {
return e >= SND_REQINVITE && e <= SND_REQINVITE
}
//outgoing SIP REQUEST
func (e Event) IsOutgoingRequest() bool {
return e == SND_REQINVITE || e == SND_REQACK || e == SND_REQUEST
}
//outgoing SIP RESPONSE
func (e Event) IsOutgoingResponse() bool {
return e == SND_STATUS_1XX || e == SND_STATUS_2XX || e == SND_STATUS_3456XX
}
//a SIP MESSAGE
func (e Event) IsSipMessage() bool {
return e >= RCV_REQINVITE && e <= SND_STATUS_3456XX
}
type EventObj struct {
evt Event // event type
tid string // transaction id
msg *sip.Message
}
//状态机类型
type FSMType int
const (
FSM_ICT FSMType = iota /**< Invite Client (outgoing) Transaction */
FSM_IST /**< Invite Server (incoming) Transaction */
FSM_NICT /**< Non-Invite Client (outgoing) Transaction */
FSM_NIST /**< Non-Invite Server (incoming) Transaction */
FSM_UNKNOWN /**< Invalid Transaction */
)
var typeMap = map[FSMType]string{
FSM_ICT: "FSM_ICT",
FSM_IST: "FSM_IST",
FSM_NICT: "FSM_NICT",
FSM_NIST: "FSM_NIST",
FSM_UNKNOWN: "FSM_UNKNOWN",
}
func (t FSMType) String() string {
return typeMap[t]
}
//对外将sip通讯封装成请求和响应
//TODO可参考http的request和response屏蔽sip协议细节
type Request struct {
data *sip.Message
}
//Code = 0则响应正常
//Code != 0打印错误提示信息 Message
type Response struct {
Code int
Message string
Data *sip.Message
}
type Handler func(*Transaction, Event, *sip.Message) error //操作
type Header map[string]string
// timer相关基础常量、方法等定义
const (
T1 = 100 * time.Millisecond
T2 = 4 * time.Second
T4 = 5 * time.Second
TimeA = T1
TimeB = 64 * T1
TimeD = 32 * time.Second
TimeE = T1
TimeF = 64 * T1
TimeG = T1
TimeH = 64 * T1
TimeI = T4
TimeJ = 64 * T1
TimeK = T4
Time1xx = 100 * time.Millisecond
)
//TODO是否要管理当前 transaction 的多次请求和响应的message
//TODO是否要管理当前 transaction 的头域
//TODO多种transaction在一个struct里面管理不太方便暂时写在一起后期重构分开并使用interface 解耦
//是否需要tp layer
type Transaction struct {
cancel context.CancelFunc
context.Context //线程管理、其他参数
sync.Mutex
id string //transaction ID
isReliable bool //是否可靠传输
core *Core //全局参数
typo FSMType //状态机类型
state State //当前状态
response chan *Response //输出的响应
startAt time.Time //开始时间
endAt time.Time //结束时间
//messages []*sip.Message //传输的消息缓存origin request/last response/request ack...
//header Header //创建事物的消息头域参数:Via From To CallID CSeq
via *sip.Via
from *sip.Contact
to *sip.Contact
callID string
cseq *sip.CSeq
origRequest *sip.Message //Initial request
lastResponse *sip.Message //Last response可能是临时的也可能是最终的
ack *sip.Message //ack request sent
//timer for ict
timerA *SipTimer
//timer for nict
timerE *SipTimer
}
type SipTimer struct {
tm *time.Timer
timeout time.Duration //当前超时时间
max time.Duration //最大超时时间
}
func NewSipTimer(d, max time.Duration, f func()) *SipTimer {
return &SipTimer{
tm: time.AfterFunc(d, f),
timeout: d,
max: max,
}
}
func (t *SipTimer) Reset(d time.Duration) {
t.timeout = d
if t.timeout > t.max && t.max != 0 {
t.timeout = t.max
}
t.tm.Reset(t.timeout)
}
func (ta *Transaction) SetState(s State) {
ta.state = s
}
func (ta *Transaction) GetTid() string {
return ta.id
}
func (ta *Transaction) RunAfter(t time.Duration, evt Event) {
time.AfterFunc(t, func() {
if ta.Err() == nil {
ta.Run(evt, nil)
}
})
}
//每一个transaction至少有一个状态机线程运行
//TODO:如果是一个uac的transaction则把最后响应的消息返回通过response chan
//transaction有很多消息需要传递到TU也接收来自TU的消息。
func (ta *Transaction) Run(evt Event, m *sip.Message) {
//根据event调用对应的handler
//fmt.Println("fsm run event:", e.evt.String())
core := ta.core
ta.Lock()
defer ta.Unlock()
evtHandlers, ok1 := core.handlers[ta.state]
if !ok1 {
//fmt.Println("invalid state:", ta.state.String())
return
}
f, ok2 := evtHandlers[evt]
if !ok2 {
//fmt.Println("invalid handler for this event:", e.evt.String())
return
}
//fmt.Printf("state:%s, event:%s\n", state.String(), e.evt.String())
err := f(ta, evt, m)
if err != nil {
//fmt.Printf("transaction run failed, state:%s, event:%s\n", state.String(), e.evt.String())
}
}
//Terminated:事物的终止
//TODOcheck调用时机
func (ta *Transaction) Terminate() {
ta.state = NICT_TERMINATED
switch ta.typo {
case FSM_ICT:
ta.state = ICT_TERMINATED
case FSM_NICT:
ta.state = NICT_TERMINATED
case FSM_IST:
ta.state = IST_TERMINATED
case FSM_NIST:
ta.state = NIST_TERMINATED
}
//关掉事物
ta.cancel()
//TODO某些timer需要检查并关掉并且设置为nil
ta.core.DelTransaction(ta.id)
}
//根据sip消息解析出目标服务器地址发送消息
func (ta *Transaction) SipSend(msg *sip.Message) error {
err := checkMessage(msg)
if err != nil {
return err
}
addr := msg.Addr
if addr == "" {
viaParams := msg.Via.Params
//host
var host, port string
var ok1, ok2 bool
if host, ok1 = viaParams["maddr"]; !ok1 {
if host, ok2 = viaParams["received"]; !ok2 {
host = msg.Via.Host
}
}
//port
port = viaParams["rport"]
if port == "" || port == "0" || port == "-1" {
port = msg.Via.Port
}
if port == "" {
port = "5060"
}
addr = fmt.Sprintf("%s:%s", host, port)
}
//fmt.Println("dest addr:", addr)
var err1, err2 error
pkt := &transport.Packet{}
pkt.Data, err1 = sip.Encode(msg)
if msg.Via.Transport == "UDP" {
pkt.Addr, err2 = net.ResolveUDPAddr("udp", addr)
} else {
pkt.Addr, err2 = net.ResolveTCPAddr("tcp", addr)
}
if err1 != nil {
return err1
}
if err2 != nil {
return err2
}
ta.core.tp.WritePacket(pkt)
return nil
}

View File

@@ -1,115 +0,0 @@
package transaction
import (
"fmt"
"github.com/Monibuca/plugin-gb28181/v3/sip"
"net"
"strings"
)
//=====================================================sip message utils
//The branch ID parameter in the Via header field values serves as a transaction identifier,
//and is used by proxies to detect loops.
//The branch parameter in the topmost Via header field of the request
// is examined. If it is present and begins with the magic cookie
// "z9hG4bK", the request was generated by a client transaction
// compliant to this specification.
//参考RFC3261
func getMessageTransactionID(m *sip.Message) string {
if m.GetMethod() == sip.ACK {
//TODO在匹配服务端事物的ACK中创建事务的请求的方法为INVITE。所以ACK消息匹配事物的时候需要注意
}
return string(m.GetMethod()) + "_" + m.GetBranch()
}
//根据收到的响应的消息的状态码,获取事件
func getInComingMessageEvent(m *sip.Message) Event {
//request根据请求方法来确认事件
if m.IsRequest() {
method := m.GetMethod()
if method == sip.INVITE {
return RCV_REQINVITE
} else if method == sip.ACK {
return RCV_REQACK
} else {
return RCV_REQUEST
}
}
//response根据状态码来确认事件
status := m.StartLine.Code
if status >= 100 && status < 200 {
return RCV_STATUS_1XX
}
if status >= 200 && status < 300 {
return RCV_STATUS_2XX
}
if status >= 300 {
return RCV_STATUS_3456XX
}
return UNKNOWN_EVT
}
//根据发出的响应的消息的状态码,获取事件
func getOutGoingMessageEvent(m *sip.Message) Event {
//request:get event by method
if m.IsRequest() {
method := m.GetMethod()
if method == sip.INVITE {
return SND_REQINVITE
} else if method == sip.ACK {
return SND_REQACK
} else {
return SND_REQUEST
}
}
//response:get event by status
status := m.StartLine.Code
if status >= 100 && status < 200 {
return SND_STATUS_1XX
}
if status >= 200 && status < 300 {
return SND_STATUS_2XX
}
if status >= 300 {
return SND_STATUS_3456XX
}
return UNKNOWN_EVT
}
func checkMessage(msg *sip.Message) error {
//TODO:sip消息解析成功之后检查必要元素如果失败则返回 ErrorCheckMessage
//检查头域字段callID via startline 等
//检查seq、method等
//不可以有router
//是否根据消息是接收还是发送检查?
if msg == nil {
return ErrorCheck
}
return nil
}
//fix via header,add send-by info,
func fixReceiveMessageViaParams(msg *sip.Message, addr net.Addr) {
rport := msg.Via.Params["rport"]
if rport == "" || rport == "0" || rport == "-1" {
arr := strings.Split(addr.String(), ":")
if len(arr) == 2 {
msg.Via.Params["rport"] = arr[1]
if msg.Via.Host != arr[0] {
msg.Via.Params["received"] = arr[0]
}
} else {
//TODO数据报的地址错误
fmt.Println("packet handle > invalid addr :", addr.String())
}
} else {
fmt.Println("sip message has have send-by info:", msg.Via.GetSendBy())
}
}

View File

@@ -1,18 +0,0 @@
#### 介绍
transport 包括客户端和服务器端仅实现tcp和udp的传输层不负责具体消息的解析和处理。不负责粘包、半包、消息解析、心跳处理、状态管理等工作。
比如设备关闭或者离线要修改缓存状态、数据库状态、发送离线通知、执行离线回调等等都在上层处理。tcp server 和 udp server、tcp client 和 udp client 消息的接收和发送都在外面处理。
tcp是流传输需要注意粘包和半包的处理。在上层处理tcp包的时候可以尝试使用 ring buffer
#### usage
参考 example.go
#### TODO
- sip协议的传输层TCP和UDP有所不同比如重传以及超时的错误信息等。所以需要在 transaction上面处理消息重传、错误上报等。具体参考RFC3261。

View File

@@ -1,94 +0,0 @@
package transport
import (
"fmt"
"github.com/Monibuca/plugin-gb28181/v3/utils"
"os"
"time"
)
//默认端口TCP/UDP是50605061是在TCP上的TLS
//对于服务器监听UDP的任何端口和界面都必须在TCP上也进行同样的监听。这是因为可能消息还需要通过TCP进行传输比如消息过大的情况。
const SipHost string = "127.0.0.1"
const SipPort uint16 = 5060
func RunServerTCP() {
tcp := NewTCPServer(SipPort, true)
go PacketHandler(tcp)
go func() {
_ = tcp.Start()
}()
select {}
}
//测试通讯,客户端先发一条消息
func RunClientTCP() {
c := NewTCPClient(SipHost, SipPort)
go PacketHandler(c)
go func() {
_ = c.Start()
}()
//发送测试数据
fmt.Println("send test data")
go func() {
for {
c.WritePacket(&Packet{Data: []byte("from client : " + time.Now().String())})
time.Sleep(2 * time.Second)
}
}()
select {}
}
func PacketHandler(s ITransport) {
defer func() {
if err := recover(); err != nil {
fmt.Println("packet handler panic: ", err)
utils.PrintStack()
os.Exit(1)
}
}()
fmt.Println("PacketHandler ========== ", s.Name())
ch := s.ReadPacketChan()
//阻塞读取消息
for {
select {
case p := <-ch:
fmt.Println("packet content:", string(p.Data))
//TODO:message parse
}
}
}
//======================================================================
func RunServerUDP() {
udp := NewUDPServer(SipPort)
go PacketHandler(udp)
go func() {
_ = udp.Start()
}()
select {}
}
func RunClientUDP() {
c := NewUDPClient(SipHost, SipPort)
go PacketHandler(c)
go func() {
_ = c.Start()
}()
//发送测试数据
go func() {
for {
time.Sleep(1 * time.Second)
c.WritePacket(&Packet{
Data: []byte("hello " + time.Now().String())})
}
}()
select {}
}

View File

@@ -1,115 +0,0 @@
package transport
import (
"fmt"
"net"
)
type TCPClient struct {
Statistic
host string
port uint16
conn net.Conn
readChan chan *Packet
writeChan chan *Packet
remoteAddr net.Addr
localAddr net.Addr
done chan struct{}
}
func NewTCPClient(host string, port uint16) IClient {
return &TCPClient{
host: host,
port: port,
readChan: make(chan *Packet, 10),
writeChan: make(chan *Packet, 10),
done: make(chan struct{}),
}
}
func (c *TCPClient) IsReliable() bool {
return true
}
func (c *TCPClient) Name() string {
return fmt.Sprintf("tcp client to:%s", fmt.Sprintf("%s:%d", c.host, c.port))
}
func (c *TCPClient) LocalAddr() net.Addr {
return c.localAddr
}
func (c *TCPClient) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *TCPClient) Start() error {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.host, c.port))
if err != nil {
fmt.Println("dial tcp server failed :", err.Error())
return err
} else {
fmt.Println("start tcp client")
}
c.conn = conn
c.remoteAddr = conn.RemoteAddr()
c.localAddr = conn.LocalAddr()
//开启写线程
go func() {
for {
select {
case p := <-c.writeChan:
_, err := c.conn.Write(p.Data)
if err != nil {
fmt.Println("client write failed:", err.Error())
_ = c.Close()
return
}
case <-c.done:
return
}
}
}()
fmt.Println("start tcp client")
fmt.Printf("remote addr: %s, local addr: %s\n", conn.RemoteAddr().String(), conn.LocalAddr().String())
//读线程,阻塞
for {
buf := make([]byte, 2048)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("tcp client read error:", err.Error())
return err
}
c.readChan <- &Packet{
Addr: c.remoteAddr,
Data: buf[:n],
}
}
}
func (c *TCPClient) ReadPacketChan() <-chan *Packet {
return c.readChan
}
func (c *TCPClient) WritePacket(packet *Packet) {
c.writeChan <- packet
}
func (c *TCPClient) Close() error {
close(c.done)
return c.conn.Close()
}
//外部定期调用此接口,实现心跳
func (c *TCPClient) Heartbeat(p *Packet) {
if p == nil {
p = &Packet{
Data: []byte("ping"),
}
}
c.WritePacket(p)
}

View File

@@ -1,174 +0,0 @@
package transport
import (
"fmt"
"io"
"net"
"sync"
"time"
)
type TCPServer struct {
Statistic
addr string
listener net.Listener
readChan chan *Packet
writeChan chan *Packet
done chan struct{}
Keepalive bool
sessions sync.Map //key 是 remote-addr , value:*Connection。
}
func NewTCPServer(port uint16, keepalive bool) IServer {
tcpAddr := fmt.Sprintf(":%d", port)
return &TCPServer{
addr: tcpAddr,
Keepalive: keepalive,
readChan: make(chan *Packet, 10),
writeChan: make(chan *Packet, 10),
done: make(chan struct{}),
}
}
func (s *TCPServer) IsReliable() bool {
return true
}
func (s *TCPServer) Name() string {
return fmt.Sprintf("tcp server at:%s", s.addr)
}
func (s *TCPServer) IsKeepalive() bool {
return s.Keepalive
}
func (s *TCPServer) Start() error {
//监听端口
//开启tcp连接线程
var err error
s.listener, err = net.Listen("tcp", s.addr)
//s.listener, err = tls.Listen("tcp", s.tcpAddr, tlsConfig)
if err != nil {
fmt.Println("TCP Listen failed:", err)
return err
}
defer s.listener.Close()
fmt.Println("start tcp server at: ", s.addr)
//心跳线程
if s.Keepalive {
//TODO:start heartbeat thread
}
//写线程
go func() {
for {
select {
case p := <-s.writeChan:
val, ok := s.sessions.Load(p.Addr.String())
if !ok {
return
}
c := val.(*Connection)
_, _ = c.Conn.Write(p.Data)
case <-s.done:
return
}
}
}()
//读线程
for {
conn, err := s.listener.Accept()
if err != nil {
var tempDelay time.Duration // how long to sleep on accept failure
fmt.Println("accept err :", err.Error())
// 重连。参考http server
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
time.Sleep(tempDelay)
continue
}
fmt.Println("accept error, retry failed & exit.")
return err
}
// conn.SetReadDeadline(time.Now().Add(600 * time.Second))
session := &Connection{Conn: conn, Addr: conn.RemoteAddr()}
address := session.Addr.String()
s.sessions.Store(address, session)
fmt.Println(fmt.Sprintf("new tcp client remoteAddr: %v", address))
go s.handlerSession(session)
}
}
func (s *TCPServer) handlerSession(c *Connection) {
addrStr := c.Addr.String()
//recovery from panic
defer func() {
s.CloseOne(addrStr)
if err := recover(); err != nil {
fmt.Println("client receiver handler panic: ", err)
}
}()
buf := make([]byte, 2048)
for {
n, err := c.Conn.Read(buf)
switch {
case err == nil:
p := &Packet{
Addr: c.Addr,
Data: buf[:n],
}
s.readChan <- p
case err == io.EOF:
fmt.Println(fmt.Sprintf("io.EOF,client close --- remoteAddr: %v", c.Addr))
return
case err != nil:
fmt.Println("client other err: ", err)
fmt.Println(fmt.Sprintf("client other err --- remoteAddr: %v", addrStr))
return
}
}
}
func (s *TCPServer) CloseOne(addr string) {
val, ok := s.sessions.Load(addr)
if !ok {
return
}
c := val.(*Connection)
_ = c.Conn.Close()
s.sessions.Delete(addr)
}
func (s *TCPServer) ReadPacketChan() <-chan *Packet {
return s.readChan
}
func (s *TCPServer) WritePacket(packet *Packet) {
s.writeChan <- packet
}
func (s *TCPServer) Close() error {
//TODOTCP服务退出之前需要先close掉所有客户端的连接
s.sessions.Range(func(key, value interface{}) bool {
c := value.(*Connection)
_ = c.Conn.Close()
s.sessions.Delete(key)
return true
})
return nil
}

View File

@@ -1,70 +0,0 @@
package transport
import (
"net"
"time"
)
/*
transport层仅实现数据的读写连接的关闭
TCP和UDP的区别
- TCP面向链接在服务关闭的时候要先close掉所有客户端连接。所以使用一个map简单做session管理key 是 remote address。
- udp不需要管理session。
*/
//transport server and client interface
//对于面向连接的服务需要有两个关闭接口Close and CloseOne
//非面向连接的服务,不必实现
//TODO心跳管理使用timewheel
type ITransport interface {
Name() string
ReadPacketChan() <-chan *Packet //读消息,消息处理器需在循环中阻塞读取
WritePacket(packet *Packet) //写消息
Start() error //开启连接,阻塞接收消息
Close() error //关闭连接
IsReliable() bool //是否可靠传输
}
type IServer interface {
ITransport
CloseOne(addr string) //对于关闭某个客户端连接,比如没有鉴权的非法链接,心跳超时等
IsKeepalive() bool //persistent connection or not
}
//transport 需要实现的接口如下
type IClient interface {
ITransport
LocalAddr() net.Addr //本地地址
RemoteAddr() net.Addr //远程地址
Heartbeat(packet *Packet) //客户端需要定期发送心跳包到服务器端
}
type Packet struct {
Type string //消息类型,预留字段,对于客户端主动关闭等消息的上报、心跳超时等。如果为空,则仅透传消息。
Addr net.Addr
Data []byte
}
//对于面向连接的UDP或者TCP都可以面向连接维持心跳即可必须有session
type Connection struct {
Addr net.Addr
Conn net.Conn
Online bool
ReconnectCount int64 //重连次数
}
func (s *Connection) Close() {
//TODO处理session的关闭修改缓存状态、数据库状态、发送离线通知、执行离线回调等等
}
//通讯统计
type Statistic struct {
startTime time.Time
stopTime time.Time
recvCount int64
sendCount int64
errCount int64
}

View File

@@ -1,119 +0,0 @@
package transport
import (
"fmt"
"net"
"os"
)
type UDPClient struct {
Statistic
host string
port uint16
conn *net.UDPConn
readChan chan *Packet
writeChan chan *Packet
done chan struct{}
remoteAddr net.Addr
localAddr net.Addr
}
func NewUDPClient(host string, port uint16) IClient {
return &UDPClient{
host: host,
port: port,
readChan: make(chan *Packet, 10),
writeChan: make(chan *Packet, 10),
done: make(chan struct{}),
}
}
func (c *UDPClient) IsReliable() bool {
return false
}
func (c *UDPClient) Name() string {
return fmt.Sprintf("udp client to:%s", fmt.Sprintf("%s:%d", c.host, c.port))
}
func (c *UDPClient) LocalAddr() net.Addr {
return c.localAddr
}
func (c *UDPClient) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *UDPClient) Start() error {
addrStr := fmt.Sprintf("%s:%d", c.host, c.port)
addr, err := net.ResolveUDPAddr("udp", addrStr)
if err != nil {
fmt.Println("Can't resolve address: ", err)
os.Exit(1)
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
fmt.Println("Can't dial: ", err)
os.Exit(1)
}
defer conn.Close()
c.remoteAddr = conn.RemoteAddr()
c.localAddr = conn.LocalAddr()
fmt.Println("udp client remote addr:", conn.RemoteAddr().String())
fmt.Println("udp client local addr:", conn.LocalAddr().String())
//写线程
go func() {
for {
select {
case p := <-c.writeChan:
_, err = conn.Write(p.Data)
if err != nil {
fmt.Println("udp client write failed:", err.Error())
continue
}
case <-c.done:
return
}
}
}()
for {
buf := make([]byte, 4096)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("failed to read UDP msg because of ", err)
os.Exit(1)
}
c.readChan <- &Packet{
Addr: c.remoteAddr,
Data: buf[:n],
}
}
}
func (c *UDPClient) ReadPacketChan() <-chan *Packet {
return c.readChan
}
func (c *UDPClient) WritePacket(packet *Packet) {
c.writeChan <- packet
}
func (c *UDPClient) Close() error {
close(c.done)
return c.conn.Close()
}
//外部定期调用此接口,实现心跳
func (c *UDPClient) Heartbeat(p *Packet) {
if p == nil {
p = &Packet{
Data: []byte("ping"),
}
}
c.WritePacket(p)
}

View File

@@ -1,106 +0,0 @@
package transport
import (
"fmt"
"net"
"os"
)
type UDPServer struct {
Statistic
addr string
Conn *net.UDPConn
ReadChan chan *Packet
WriteChan chan *Packet
done chan struct{}
Keepalive bool
//Sessions sync.Map // key is remote-addr的string , value:*Connection。UDP不需要
}
func NewUDPServer(port uint16) IServer {
addrStr := fmt.Sprintf(":%d", port)
return &UDPServer{
addr: addrStr,
ReadChan: make(chan *Packet, 10),
WriteChan: make(chan *Packet, 10),
done: make(chan struct{}),
}
}
func (s *UDPServer) IsReliable() bool {
return false
}
func (s *UDPServer) Name() string {
return fmt.Sprintf("udp client to:%s", s.addr)
}
func (s *UDPServer) IsKeepalive() bool {
return s.Keepalive
}
func (s *UDPServer) Start() error {
addr, err := net.ResolveUDPAddr("udp", s.addr)
if err != nil {
fmt.Println("Can't resolve address: ", err)
return err
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println("Error listenUDP :", err)
os.Exit(1)
}
defer func() {
_ = conn.Close()
}()
s.Conn = conn
fmt.Println("start udp server at: ", s.addr)
//心跳线程
if s.Keepalive {
//TODO:start heartbeat thread
}
//写线程
go func() {
for {
select {
case p := <-s.WriteChan:
_, _ = s.Conn.WriteTo(p.Data, p.Addr)
case <-s.done:
return
}
}
}()
//读线程
for {
data := make([]byte, 4096)
n, remoteAddr, err := conn.ReadFromUDP(data)
if err != nil {
fmt.Println("failed to read UDP msg because of ", err.Error())
continue
}
s.ReadChan <- &Packet{
Addr: remoteAddr,
Data: data[:n],
}
}
}
func (s *UDPServer) ReadPacketChan() <-chan *Packet {
return s.ReadChan
}
func (s *UDPServer) WritePacket(packet *Packet) {
s.WriteChan <- packet
}
func (s *UDPServer) Close() error {
//所有session离线和关闭处理
return nil
}
func (s *UDPServer) CloseOne(addr string) {
//处理某设备离线
}

View File

@@ -1,24 +0,0 @@
Transaction User(TU)事务用户在transaction 层之上的协议层。TU包括了UAC core、UAS core,和proxy core。
tu处理业务逻辑并对事物层进行操作。
#### 类型
SIP服务器典型有以下几类:
a. 注册服务器 -即只管Register消息,这里相当于location也在这里了
b. 重定向服务器 -给ua回一条302后,转给其它的服务器,这样保证全系统统一接入
c. 代理服务器 -只做proxy,即对SIP消息转发
d. 媒体服务器-只做rtp包相关处理,即media server
e. B2BUA - 这个里包实际一般是可以含以上几种服务器类型
暂时仅处理gb28181 相关
#### TU
tu负责根据应用层需求发起操作。
比如注册到sip服务器、发起会话、取消会话等。

View File

@@ -1,92 +0,0 @@
package tu
import (
"fmt"
"github.com/Monibuca/plugin-gb28181/v3/transaction"
)
//sip server和client的配置可以得到sip URIsip
//格式user:password@host:port;uri-parameters?headers
//在这些URI里边包含了足够的信息来发起和维持到这个资源的一个通讯会话。
//client静态配置
type ClientStatic struct {
LocalIP string //设备本地IP
LocalPort uint16 //客户端SIP端口
Username string //SIP用户名一般是取通道ID默认 34020000001320000001
AuthID string //SIP用户认证ID一般是通道ID 默认 34020000001320000001
Password string //密码
}
//client运行时信息
type ClientRuntime struct {
RemoteAddress string //设备的公网的IP和端口格式x.x.x.x:x
Online bool //在线状态
Branch string //branch
Cseq int //消息序列号,发送消息递增, uint32
FromTag string //from tag
ToTag string //to tag
Received string //remote ip
Rport string //remote port
}
type Client struct {
*transaction.Core //transaction manager
static *ClientStatic //静态配置
runtime *ClientRuntime //运行时信息
}
//config:sip信令服务器配置
//static:sip客户端配置
func NewClient(config *transaction.Config, static *ClientStatic) *Client {
return &Client{
Core: transaction.NewCore(config),
static: static,
runtime: &ClientRuntime{},
}
}
//TODO对于一个TU开启之后
//运行一个sip client
func RunClient() {
config := &transaction.Config{
SipIP: "192.168.1.102",
SipPort: 5060,
SipNetwork: "UDP",
Serial: "34020000002000000001",
Realm: "3402000000",
AckTimeout: 10,
RegisterValidity: 3600,
RegisterInterval: 60,
HeartbeatInterval: 60,
HeartbeatRetry: 3,
AudioEnable: true,
WaitKeyFrame: true,
MediaPortMin: 58200,
MediaPortMax: 58300,
MediaIdleTimeout: 30,
}
static := &ClientStatic{
LocalIP: "192.168.1.65",
LocalPort: 5060,
Username: "34020000001320000001",
AuthID: "34020000001320000001",
Password: "123456",
}
c := NewClient(config, static)
go c.Start()
//TODO先发起注册
//TODO:build sip message
msg := BuildMessageRequest("", "", "", "", "", "",
0, 0, 0, "")
resp := c.SendMessage(msg)
if resp.Code != 0 {
fmt.Println("request failed")
}
fmt.Println("response: ", resp.Data)
select {}
}

View File

@@ -1,75 +0,0 @@
package tu
import (
"fmt"
"github.com/Monibuca/plugin-gb28181/v3/sip"
"github.com/Monibuca/plugin-gb28181/v3/utils"
)
//根据参数构建各种消息
//参数来自于session/transaction等会话管理器
/*
method:请求方法
transportUDP/TCP
sipSerial: sip server ID
sipRealm: sip domain
username: 用户名/设备序列号
srcIP 源IP
srcPort源端口
expires: 过期时间
cseq消息序列号当前对话递增
*/
//构建消息以客户端可能是IPC也可能是SIP Server的角度
func BuildMessageRequest(method sip.Method, transport, sipSerial, sipRealm, username, srcIP string, srcPort uint16, expires, cseq int, body string) *sip.Message {
server := fmt.Sprintf("%s@%s", sipSerial, sipRealm)
client := fmt.Sprintf("%s@%s", username, sipRealm)
msg := &sip.Message{
Mode: sip.SIP_MESSAGE_REQUEST,
MaxForwards: 70,
UserAgent: "IPC",
Expires: expires,
ContentLength: 0,
}
msg.StartLine = &sip.StartLine{
Method: method,
Uri: sip.NewURI(server),
}
msg.Via = &sip.Via{
Transport: transport,
Host: client,
}
msg.Via.Params = map[string]string{
"branch": randBranch(),
"rport": "-1", //only key,no-value
}
msg.From = &sip.Contact{
Uri: sip.NewURI(client),
Params: nil,
}
msg.From.Params = map[string]string{
"tag": utils.RandNumString(10),
}
msg.To = &sip.Contact{
Uri: sip.NewURI(client),
}
msg.CallID = utils.RandNumString(8)
msg.CSeq = &sip.CSeq{
ID: uint32(cseq),
Method: method,
}
msg.Contact = &sip.Contact{
Uri: sip.NewURI(fmt.Sprintf("%s@%s:%d", username, srcIP, srcPort)),
}
if len(body) > 0 {
msg.ContentLength = len(body)
msg.Body = body
}
return msg
}
//z9hG4bK + 10个随机数字
func randBranch() string {
return fmt.Sprintf("z9hG4bK%s", utils.RandNumString(8))
}

View File

@@ -1,49 +0,0 @@
package tu
import (
"sync"
"github.com/Monibuca/plugin-gb28181/v3/transaction"
)
//TODO:参考http服务使用者仅需要根据需要实现某些handler替换某些header fileds or body信息。其他的处理都由库来实现。
type Server struct {
*transaction.Core //SIP transaction manager
registers sync.Map //管理所有已经注册的设备端
//routers:TODO:消息路由应用层可以处理消息体或者针对某些消息的callback
}
//提供config参数
func NewServer(config *transaction.Config) *Server {
return &Server{
Core: transaction.NewCore(config),
}
}
//运行一个sip server
func RunServer() {
config := &transaction.Config{
SipIP: "192.168.1.102",
SipPort: 5060,
SipNetwork: "UDP",
Serial: "34020000002000000001",
Realm: "3402000000",
AckTimeout: 10,
RegisterValidity: 3600,
RegisterInterval: 60,
HeartbeatInterval: 60,
HeartbeatRetry: 3,
AudioEnable: true,
WaitKeyFrame: true,
MediaPortMin: 58200,
MediaPortMax: 58300,
MediaIdleTimeout: 30,
}
s := NewServer(config)
s.Start()
select {}
}

View File

@@ -1,39 +1,112 @@
package utils
import (
"bytes"
"encoding/binary"
"errors"
"io"
)
var ErrEOF = errors.New("eof")
type IOBuffer struct {
bytes.Buffer
buf []byte // contents are the bytes buf[off : len(buf)]
off int // read at &buf[off], write at &buf[len(buf)]
}
func (r *IOBuffer) Uint16() (uint16, error) {
if r.Len() > 1 {
return binary.BigEndian.Uint16(r.Next(2)), nil
func (b *IOBuffer) Next(n int) []byte {
m := b.Len()
if n > m {
n = m
}
return 0, ErrEOF
data := b.buf[b.off : b.off+n]
b.off += n
return data
}
func (b *IOBuffer) Uint16() (uint16, error) {
if b.Len() > 1 {
return binary.BigEndian.Uint16(b.Next(2)), nil
}
return 0, io.EOF
}
func (r *IOBuffer) Skip(n int) (err error) {
_, err = r.ReadN(n)
func (b *IOBuffer) Skip(n int) (err error) {
_, err = b.ReadN(n)
return
}
func (r *IOBuffer) Uint32() (uint32, error) {
if r.Len() > 3 {
return binary.BigEndian.Uint32(r.Next(4)), nil
func (b *IOBuffer) Uint32() (uint32, error) {
if b.Len() > 3 {
return binary.BigEndian.Uint32(b.Next(4)), nil
}
return 0, ErrEOF
return 0, io.EOF
}
func (r *IOBuffer) ReadN(length int) ([]byte, error) {
if r.Len() >= length {
return r.Next(length), nil
func (b *IOBuffer) ReadN(length int) ([]byte, error) {
if b.Len() >= length {
return b.Next(length), nil
}
return nil, ErrEOF
return nil, io.EOF
}
//func (b *IOBuffer) Read(buf []byte) (n int, err error) {
// var ret []byte
// ret, err = b.ReadN(len(buf))
// copy(buf, ret)
// return len(ret), err
//}
// empty reports whether the unread portion of the buffer is empty.
func (b *IOBuffer) empty() bool { return b.Len() <= b.off }
func (b *IOBuffer) ReadByte() (byte, error) {
if b.empty() {
// Buffer is empty, reset to recover space.
b.Reset()
return 0, io.EOF
}
c := b.buf[b.off]
b.off++
return c, nil
}
func (b *IOBuffer) Reset() {
b.buf = b.buf[:0]
b.off = 0
}
func (b *IOBuffer) Len() int { return len(b.buf) - b.off }
// tryGrowByReslice is a inlineable version of grow for the fast-case where the
// internal buffer only needs to be resliced.
// It returns the index where bytes should be written and whether it succeeded.
func (b *IOBuffer) tryGrowByReslice(n int) (int, bool) {
if l := len(b.buf); n <= cap(b.buf)-l {
b.buf = b.buf[:l+n]
return l, true
}
return 0, false
}
var ErrTooLarge = errors.New("IOBuffer: too large")
func (b *IOBuffer) Write(p []byte) (n int, err error) {
l := copy(b.buf, b.buf[b.off:])
b.buf = append(b.buf[:l], p...)
b.off = 0
// println(b.buf, b.off, b.buf[b.off], b.buf[b.off+1], b.buf[b.off+2], b.buf[b.off+3])
return len(p), nil
// defer func() {
// if recover() != nil {
// panic(ErrTooLarge)
// }
// }()
// l := len(p)
// oldLen := len(b.buf)
// m, ok := b.tryGrowByReslice(l)
// if !ok {
// m = oldLen - b.off
// buf := append(append(([]byte)(nil), b.buf[b.off:]...), p...)
// b.off = 0
// b.buf = buf
// }
// return copy(b.buf[m:], p), nil
}

150
utils/bufferpool.go Normal file
View File

@@ -0,0 +1,150 @@
package utils
import (
"bytes"
"sort"
"sync"
"sync/atomic"
)
const (
minBitSize = 6 // 2**6=64 is a CPU cache line size
steps = 20
minSize = 1 << minBitSize
maxSize = 1 << (minBitSize + steps - 1)
calibrateCallsThreshold = 42000
maxPercentile = 0.95
)
// Pool represents byte buffer pool.
//
// Distinct pools may be used for distinct types of byte buffers.
// Properly determined byte buffer types with their own pools may help reducing
// memory waste.
type Pool struct {
calls [steps]uint64
calibrating uint64
defaultSize uint64
maxSize uint64
pool sync.Pool
}
var defaultPool Pool
// Get returns an empty byte buffer from the pool.
//
// Got byte buffer may be returned to the pool via Put call.
// This reduces the number of memory allocations required for byte buffer
// management.
func Get() *bytes.Buffer { return defaultPool.Get() }
// Get returns new byte buffer with zero length.
//
// The byte buffer may be returned to the pool via Put after the use
// in order to minimize GC overhead.
func (p *Pool) Get() *bytes.Buffer {
v := p.pool.Get()
if v != nil {
return v.(*bytes.Buffer)
}
return bytes.NewBuffer(make([]byte, 0, atomic.LoadUint64(&p.defaultSize)))
}
// Put returns byte buffer to the pool.
//
// bytes.Buffer.B mustn't be touched after returning it to the pool.
// Otherwise data races will occur.
func Put(b *bytes.Buffer) { b.Reset(); defaultPool.Put(b) }
// Put releases byte buffer obtained via Get to the pool.
//
// The buffer mustn't be accessed after returning to the pool.
func (p *Pool) Put(b *bytes.Buffer) {
idx := index(b.Len())
if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
p.calibrate()
}
maxSize := int(atomic.LoadUint64(&p.maxSize))
if maxSize == 0 || b.Cap() <= maxSize {
b.Reset()
p.pool.Put(b)
}
}
func (p *Pool) calibrate() {
if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
return
}
a := make(callSizes, 0, steps)
var callsSum uint64
for i := uint64(0); i < steps; i++ {
calls := atomic.SwapUint64(&p.calls[i], 0)
callsSum += calls
a = append(a, callSize{
calls: calls,
size: minSize << i,
})
}
sort.Sort(a)
defaultSize := a[0].size
maxSize := defaultSize
maxSum := uint64(float64(callsSum) * maxPercentile)
callsSum = 0
for i := 0; i < steps; i++ {
if callsSum > maxSum {
break
}
callsSum += a[i].calls
size := a[i].size
if size > maxSize {
maxSize = size
}
}
atomic.StoreUint64(&p.defaultSize, defaultSize)
atomic.StoreUint64(&p.maxSize, maxSize)
atomic.StoreUint64(&p.calibrating, 0)
}
type callSize struct {
calls uint64
size uint64
}
type callSizes []callSize
func (ci callSizes) Len() int {
return len(ci)
}
func (ci callSizes) Less(i, j int) bool {
return ci[i].calls > ci[j].calls
}
func (ci callSizes) Swap(i, j int) {
ci[i], ci[j] = ci[j], ci[i]
}
func index(n int) int {
n--
n >>= minBitSize
idx := 0
for n > 0 {
n >>= 1
idx++
}
if idx >= steps {
idx = steps - 1
}
return idx
}

159
utils/log.go Executable file
View File

@@ -0,0 +1,159 @@
package utils
import (
"encoding/json"
"github.com/ghettovoice/gosip/log"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
m7slog "m7s.live/engine/v4/log"
)
type ZapLogger struct {
log *m7slog.Logger
prefix string
fields log.Fields
sugared *zap.SugaredLogger
level log.Level
}
func NewZapLogger(log *m7slog.Logger, prefix string, fields log.Fields) (z *ZapLogger) {
z = &ZapLogger{
log: log,
prefix: prefix,
fields: fields,
}
z.sugared = z.prepareEntry()
return
}
func (l *ZapLogger) Print(args ...interface{}) {
if l.level >= log.InfoLevel {
l.sugared.Info(args...)
}
}
func (l *ZapLogger) Printf(format string, args ...interface{}) {
if l.level >= log.InfoLevel {
l.sugared.Infof(format, args...)
}
}
func (l *ZapLogger) Trace(args ...interface{}) {
if l.level >= log.TraceLevel {
l.sugared.Debug(args...)
}
}
func (l *ZapLogger) Tracef(format string, args ...interface{}) {
if l.level >= log.TraceLevel {
l.sugared.Debugf(format, args...)
}
}
func (l *ZapLogger) Debug(args ...interface{}) {
if l.level >= log.DebugLevel {
l.sugared.Debug(args...)
}
}
func (l *ZapLogger) Debugf(format string, args ...interface{}) {
if l.level >= log.DebugLevel {
l.sugared.Debugf(format, args...)
}
}
func (l *ZapLogger) Info(args ...interface{}) {
if l.level >= log.InfoLevel {
l.sugared.Info(args...)
}
}
func (l *ZapLogger) Infof(format string, args ...interface{}) {
if l.level >= log.InfoLevel {
l.sugared.Infof(format, args...)
}
}
func (l *ZapLogger) Warn(args ...interface{}) {
if l.level >= log.WarnLevel {
l.sugared.Warn(args...)
}
}
func (l *ZapLogger) Warnf(format string, args ...interface{}) {
if l.level >= log.WarnLevel {
l.sugared.Warnf(format, args...)
}
}
func (l *ZapLogger) Error(args ...interface{}) {
if l.level >= log.ErrorLevel {
l.sugared.Error(args...)
}
}
func (l *ZapLogger) Errorf(format string, args ...interface{}) {
if l.level >= log.ErrorLevel {
l.sugared.Errorf(format, args...)
}
}
func (l *ZapLogger) Fatal(args ...interface{}) {
if l.level >= log.FatalLevel {
l.sugared.Fatal(args...)
}
}
func (l *ZapLogger) Fatalf(format string, args ...interface{}) {
if l.level >= log.FatalLevel {
l.sugared.Fatalf(format, args...)
}
}
func (l *ZapLogger) Panic(args ...interface{}) {
if l.level >= log.PanicLevel {
l.sugared.Panic(args...)
}
}
func (l *ZapLogger) Panicf(format string, args ...interface{}) {
if l.level >= log.PanicLevel {
l.sugared.Panicf(format, args...)
}
}
func (l *ZapLogger) WithPrefix(prefix string) log.Logger {
return NewZapLogger(l.log, prefix, l.Fields())
}
func (l *ZapLogger) Prefix() string {
return l.prefix
}
func (l *ZapLogger) WithFields(fields map[string]interface{}) log.Logger {
return NewZapLogger(l.log, l.Prefix(), l.Fields().WithFields(fields))
}
func (l *ZapLogger) Fields() log.Fields {
return l.fields
}
func (l *ZapLogger) prepareEntry() *zap.SugaredLogger {
newlog := l.log.With(zap.String("prefix", l.Prefix()))
if l.fields != nil {
fields := make([]zapcore.Field, len(l.fields))
idx := 0
for k, v := range l.fields {
s, _ := json.Marshal(v)
fields[idx] = zap.String(k, string(s))
idx++
}
newlog = newlog.With(fields...)
}
return newlog.Sugar()
}
func (l *ZapLogger) SetLevel(level uint32) {
l.level = log.Level(level)
}

View File

@@ -1,320 +0,0 @@
package utils
import (
"errors"
"github.com/Monibuca/utils/v3"
)
//
const (
UDPTransfer int = 0
TCPTransferActive int = 1
TCPTransferPassive int = 2
LocalCache int = 3
StreamTypeH264 = 0x1b
StreamTypeH265 = 0x24
G711A = 0x90 //PCMA
G7221AUDIOTYPE = 0x92
G7231AUDIOTYPE = 0x93
G729AUDIOTYPE = 0x99
StreamIDVideo = 0xe0
StreamIDAudio = 0xc0
StartCodePS = 0x000001ba
StartCodeSYS = 0x000001bb
StartCodeMAP = 0x000001bc
StartCodeVideo = 0x000001e0
StartCodeAudio = 0x000001c0
HaiKangCode = 0x000001bd
MEPGProgramEndCode = 0x000001b9
RTPHeaderLength int = 12
PSHeaderLength int = 14
SystemHeaderLength int = 18
MAPHeaderLength int = 24
PESHeaderLength int = 19
RtpLoadLength int = 1460
PESLoadLength int = 0xFFFF
MAXFrameLen int = 1024 * 1024 * 2
)
var (
ErrNotFoundStartCode = errors.New("not found the need start code flag")
ErrMarkerBit = errors.New("marker bit value error")
ErrFormatPack = errors.New("not package standard")
ErrParsePakcet = errors.New("parse ps packet error")
)
type Pusher interface {
PushVideo(uint32, uint32, []byte)
PushAudio(uint32, []byte)
}
/*
This implement from VLC source code
notes: https://github.com/videolan/vlc/blob/master/modules/mux/mpeg/bits.h
*/
//bitsBuffer bits buffer
// type bitsBuffer struct {
// iSize int
// iData int
// iMask uint8
// pData []byte
// }
// func bitsInit(isize int, buffer []byte) *bitsBuffer {
// bits := &bitsBuffer{
// iSize: isize,
// iData: 0,
// iMask: 0x80,
// pData: buffer,
// }
// if bits.pData == nil {
// bits.pData = make([]byte, isize)
// }
// return bits
// }
// func bitsAlign(bits *bitsBuffer) {
// if bits.iMask != 0x80 && bits.iData < bits.iSize {
// bits.iMask = 0x80
// bits.iData++
// bits.pData[bits.iData] = 0x00
// }
// }
// func bitsWrite(bits *bitsBuffer, count int, src uint64) *bitsBuffer {
// for count > 0 {
// count--
// if ((src >> uint(count)) & 0x01) != 0 {
// bits.pData[bits.iData] |= bits.iMask
// } else {
// bits.pData[bits.iData] &= ^bits.iMask
// }
// bits.iMask >>= 1
// if bits.iMask == 0 {
// bits.iData++
// bits.iMask = 0x80
// }
// }
// return bits
// }
/*
https://github.com/videolan/vlc/blob/master/modules/demux/mpeg
*/
type DecPSPackage struct {
systemClockReferenceBase uint64
systemClockReferenceExtension uint64
programMuxRate uint32
VideoStreamType uint32
AudioStreamType uint32
IOBuffer
Payload []byte
PTS uint32
DTS uint32
}
func (dec *DecPSPackage) clean() {
dec.systemClockReferenceBase = 0
dec.systemClockReferenceExtension = 0
dec.programMuxRate = 0
dec.Payload = nil
dec.PTS = 0
dec.DTS = 0
}
func (dec *DecPSPackage) ReadPayload() (payload []byte, err error) {
payloadlen, err := dec.Uint16()
if err != nil {
return
}
return dec.ReadN(int(payloadlen))
}
func (dec *DecPSPackage) Read(ts uint32, pusher Pusher) error {
var nextStartCode uint32
dec.clean()
if err := dec.Skip(9); err != nil {
return err
}
psl, err := dec.ReadByte()
if err != nil {
return err
}
psl &= 0x07
if err = dec.Skip(int(psl)); err != nil {
return err
}
var video []byte
var videoTs, videoCts uint32
defer func() {
if video != nil {
pusher.PushVideo(videoTs, videoCts, video)
video = nil
}
if nextStartCode == StartCodePS {
err = dec.Read(ts, pusher)
}
}()
for err == nil {
nextStartCode, err = dec.Uint32()
if err != nil {
return err
}
switch nextStartCode {
case StartCodeSYS:
dec.ReadPayload()
//err = dec.decSystemHeader()
case StartCodeMAP:
err = dec.decProgramStreamMap()
case StartCodeVideo:
var cts uint32
if err = dec.decPESPacket(); err == nil {
if video == nil {
if dec.PTS == 0 {
dec.PTS = ts
}
if dec.DTS != 0 {
cts = dec.PTS - dec.DTS
} else {
dec.DTS = dec.PTS
}
videoTs = dec.DTS / 90
videoCts = cts / 90
}
video = append(video, dec.Payload...)
} else {
utils.Println("video", err)
}
case StartCodeAudio:
if err = dec.decPESPacket(); err == nil {
var payload []byte
ts := ts >> 3
if dec.PTS != 0 {
ts = dec.PTS >> 3
}
pusher.PushAudio(ts, append(payload, dec.Payload...))
} else {
utils.Println("audio", err)
}
case StartCodePS:
return nil
default:
dec.ReadPayload()
}
}
return err
}
/*
func (dec *DecPSPackage) decSystemHeader() error {
syslens, err := dec.Uint16()
if err != nil {
return err
}
// drop rate video audio bound and lock flag
syslens -= 6
if err = dec.Skip(6); err != nil {
return err
}
// ONE WAY: do not to parse the stream and skip the buffer
//br.Skip(syslen * 8)
// TWO WAY: parse every stream info
for syslens > 0 {
if nextbits, err := dec.Uint8(); err != nil {
return err
} else if (nextbits&0x80)>>7 != 1 {
break
}
if err = dec.Skip(2); err != nil {
return err
}
syslens -= 3
}
return nil
}
*/
func (dec *DecPSPackage) decProgramStreamMap() error {
psm, err := dec.ReadPayload()
if err != nil {
return err
}
l := len(psm)
index := 2
programStreamInfoLen := utils.BigEndian.Uint16(psm[index:])
index += 2
index += int(programStreamInfoLen)
programStreamMapLen := utils.BigEndian.Uint16(psm[index:])
index += 2
for programStreamMapLen > 0 {
if l <= index+1 {
break
}
streamType := psm[index]
index++
elementaryStreamID := psm[index]
index++
if elementaryStreamID >= 0xe0 && elementaryStreamID <= 0xef {
dec.VideoStreamType = uint32(streamType)
} else if elementaryStreamID >= 0xc0 && elementaryStreamID <= 0xdf {
dec.AudioStreamType = uint32(streamType)
}
if l <= index+1 {
break
}
elementaryStreamInfoLength := utils.BigEndian.Uint16(psm[index:])
index += 2
index += int(elementaryStreamInfoLength)
programStreamMapLen -= 4 + elementaryStreamInfoLength
}
return nil
}
func (dec *DecPSPackage) decPESPacket() error {
payload, err := dec.ReadPayload()
if err != nil {
return err
}
if len(payload) < 4 {
return errors.New("not enough data")
}
//data_alignment_indicator := (payload[0]&0b0001_0000)>>4 == 1
flag := payload[1]
ptsFlag := flag>>7 == 1
dtsFlag := (flag&0b0100_0000)>>6 == 1
var pts, dts uint32
pesHeaderDataLen := payload[2]
payload = payload[3:]
extraData := payload[:pesHeaderDataLen]
if ptsFlag && len(extraData) > 4 {
pts = uint32(extraData[0]&0b0000_1110) << 29
pts += uint32(extraData[1]) << 22
pts += uint32(extraData[2]&0b1111_1110) << 14
pts += uint32(extraData[3]) << 7
pts += uint32(extraData[4]) >> 1
if dtsFlag && len(extraData) > 9 {
dts = uint32(extraData[5]&0b0000_1110) << 29
dts += uint32(extraData[6]) << 22
dts += uint32(extraData[7]&0b1111_1110) << 14
dts += uint32(extraData[8]) << 7
dts += uint32(extraData[9]) >> 1
}
}
dec.PTS = pts
dec.DTS = dts
dec.Payload = payload[pesHeaderDataLen:]
return err
}

98
utils/rtp_sort.go Executable file
View File

@@ -0,0 +1,98 @@
package utils
import (
"container/heap"
"errors"
"github.com/pion/rtp"
)
const MaxRtpDiff = 65000 //相邻两个包之间的最大差值
type PriorityQueueRtp struct {
itemHeap *packets
current *rtp.Packet
priorityMap map[uint16]bool
lastPacket *rtp.Packet
}
func NewPqRtp() *PriorityQueueRtp {
return &PriorityQueueRtp{
itemHeap: &packets{},
priorityMap: make(map[uint16]bool),
}
}
func (p *PriorityQueueRtp) Len() int {
return p.itemHeap.Len()
}
func (p *PriorityQueueRtp) Push(v rtp.Packet) {
if p.priorityMap[v.SequenceNumber] {
return
}
newItem := &packet{
value: v,
priority: v.SequenceNumber,
}
heap.Push(p.itemHeap, newItem)
}
func (p *PriorityQueueRtp) Pop() (rtp.Packet, error) {
if len(*p.itemHeap) == 0 {
return rtp.Packet{}, errors.New("empty queue")
}
item := heap.Pop(p.itemHeap).(*packet)
return item.value, nil
}
func (p *PriorityQueueRtp) Empty() {
old := *p.itemHeap
*p.itemHeap = old[:0]
}
type packets []*packet
type packet struct {
value rtp.Packet
priority uint16
index int
}
func (p *packets) Len() int {
return len(*p)
}
func (p *packets) Less(i, j int) bool {
a, b := (*p)[i].priority, (*p)[j].priority
if int(a)-int(b) > MaxRtpDiff || int(b)-int(a) > MaxRtpDiff {
if a < b {
return false
}
return true
}
return a < b
}
func (p *packets) Swap(i, j int) {
(*p)[i], (*p)[j] = (*p)[j], (*p)[i]
(*p)[i].index = i
(*p)[j].index = j
}
func (p *packets) Push(x interface{}) {
it := x.(*packet)
it.index = len(*p)
*p = append(*p, it)
}
func (p *packets) Pop() interface{} {
old := *p
n := len(old)
item := old[n-1]
old[n-1] = nil // avoid memory leak
item.index = -1 // for safety
*p = old[0 : n-1]
return item
}

View File

@@ -1,8 +1,10 @@
package utils
import (
"errors"
"fmt"
"math/rand"
"net"
"runtime"
"time"
)
@@ -45,8 +47,67 @@ func randStringBySoure(src string, n int) string {
return string(output)
}
// Error Error
type Error struct {
err error
params []interface{}
}
func (err *Error) Error() string {
if err == nil {
return "<nil>"
}
str := fmt.Sprint(err.params...)
if err.err != nil {
str += fmt.Sprintf(" err:%s", err.err.Error())
}
return str
}
// NewError NewError
func NewError(err error, params ...interface{}) error {
return &Error{err, params}
}
func PrintStack() {
var buf [4096]byte
n := runtime.Stack(buf[:], false)
fmt.Printf("==> %s\n", string(buf[:n]))
}
// ResolveSelfIP ResolveSelfIP
func ResolveSelfIP() (net.IP, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an ipv4 address
}
return ip, nil
}
}
return nil, errors.New("server not connected to any network")
}

View File

@@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1