mirror of
https://github.com/Monibuca/plugin-gb28181.git
synced 2025-12-24 13:27:57 +08:00
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b924977085 | ||
|
|
521ee36769 | ||
|
|
583754ea82 | ||
|
|
58b6a818bd | ||
|
|
8b1b176f51 | ||
|
|
68d6cbaab9 | ||
|
|
f88d4d264e | ||
|
|
55aa20e868 | ||
|
|
9f4ad83da7 | ||
|
|
68ff4dba5b | ||
|
|
e9f576e3f4 | ||
|
|
b271cb8e50 | ||
|
|
2b82a0ffc4 | ||
|
|
940d7c5e59 | ||
|
|
2142a474a3 | ||
|
|
86e9bccb85 | ||
|
|
bb3a679a60 | ||
|
|
ecd97c8439 | ||
|
|
bfd71a72d8 | ||
|
|
c6bef8ccd8 | ||
|
|
20c0ac52cb | ||
|
|
34f5b7da79 | ||
|
|
0d3a795dc2 | ||
|
|
b52d457990 | ||
|
|
4a214cebeb | ||
|
|
8f78f992ca | ||
|
|
228d7b0cd2 | ||
|
|
e99150b0be | ||
|
|
31112e0052 | ||
|
|
8663b8e171 | ||
|
|
5960f07fc3 | ||
|
|
eb6004d6ef | ||
|
|
cce5f67ab9 | ||
|
|
fdfb462d46 | ||
|
|
c05adce562 | ||
|
|
aa3727f582 | ||
|
|
6e8709176e | ||
|
|
3e6c43f6ff | ||
|
|
4a7aa94bd2 | ||
|
|
fd13c6d9ab | ||
|
|
085d413d2b | ||
|
|
4eba0e23f9 | ||
|
|
f0324c4283 | ||
|
|
f6b5f15b83 | ||
|
|
a6de68496b | ||
|
|
709988cf09 | ||
|
|
9cf8c1acc1 | ||
|
|
03bd17bd24 | ||
|
|
ed0b751e89 | ||
|
|
b8a2812c40 | ||
|
|
abd7b6ba77 | ||
|
|
a09080641d | ||
|
|
cf761e09d7 | ||
|
|
e4569c1dd7 | ||
|
|
50bfd5b390 | ||
|
|
e937325058 | ||
|
|
158c317511 | ||
|
|
e1a2587b48 | ||
|
|
8f2a90e8ac | ||
|
|
179280b67a | ||
|
|
35c8d5171f | ||
|
|
75342e5bb3 | ||
|
|
c742f226e9 | ||
|
|
11b9da4c9d | ||
|
|
490c85b46a | ||
|
|
5d7d81a46b | ||
|
|
eb53a9594a | ||
|
|
37b4774e9a | ||
|
|
64861c37b1 | ||
|
|
16b5fa375c | ||
|
|
ee419570a2 | ||
|
|
e110acfb56 | ||
|
|
07c4dd0c21 | ||
|
|
0299a9ef7e | ||
|
|
bbeae2bc9c | ||
|
|
b0a23ac3d2 | ||
|
|
7f69c61eaf | ||
|
|
d795e7c80a |
127
README.md
127
README.md
@@ -16,68 +16,63 @@ _ "m7s.live/plugin/gb28181/v4"
|
||||
|
||||
## 默认插件配置
|
||||
|
||||
```yml
|
||||
```yaml
|
||||
gb28181:
|
||||
autoInvite: true
|
||||
autoCloseAfter: -1
|
||||
preFetchRecord: false
|
||||
udpCacheSize: 0
|
||||
sipNetwork: udp
|
||||
sipIP: 127.0.0.1
|
||||
sipPort: 5060
|
||||
autoinvite: true
|
||||
position:
|
||||
autosubposition: false #是否自动订阅定位
|
||||
expires: 3600s #订阅周期(单位:秒),默认3600
|
||||
interval: 6s #订阅间隔(单位:秒),默认6
|
||||
prefetchrecord: false
|
||||
udpcachesize: 0
|
||||
sipnetwork: udp
|
||||
sipip: ""
|
||||
sipport: 5060
|
||||
serial: "34020000002000000001"
|
||||
realm: "3402000000"
|
||||
username: ""
|
||||
password: ""
|
||||
|
||||
ackTimeout: 10
|
||||
registerValidity: 60
|
||||
registerInterval: 60
|
||||
heartbeatInterval: 60
|
||||
heartbeatRetry: 3
|
||||
registervalidity: 60s
|
||||
|
||||
mediaIP: 127.0.0.1
|
||||
mediaPort: 58200
|
||||
mediaIdleTimeout: 30
|
||||
mediaNetwork: udp
|
||||
|
||||
removeBanInterval: 600
|
||||
logVerbose: false
|
||||
audioEnable: true
|
||||
waitKeyFrame: true
|
||||
mediaip: ""
|
||||
mediaport: 58200
|
||||
mediaidletimeout: 30
|
||||
medianetwork: udp
|
||||
mediaportmin: 0
|
||||
meidaportmax: 0
|
||||
|
||||
removebaninterval: 10m
|
||||
loglevel: info
|
||||
```
|
||||
|
||||
- `AutoInvite` bool 表示自动发起invite,当Server(SIP)接收到设备信息时,立即向设备发送invite命令获取流
|
||||
- `AutoCloseAfter` int 如果设置大于等于0,则当某个流最后一个订阅者取消订阅时会延迟N秒,会自动发送bye,节省流量。如果为了响应及时,可以设置成-1,保持流的连接
|
||||
- `PreFetchRecord` bool
|
||||
|
||||
* sip服务器的配置
|
||||
- `SipNetwork` string 传输协议,默认UDP,可选TCP
|
||||
- `SipIP` string sip 服务器公网IP
|
||||
- `SipIP` string sip 服务器公网IP 默认 自动适配设备网段
|
||||
- `SipPort` uint16 sip 服务器端口,默认 5060
|
||||
- `Serial` string sip 服务器 id, 默认 34020000002000000001
|
||||
- `Realm` string sip 服务器域,默认 3402000000
|
||||
- `Username` string sip 服务器账号
|
||||
- `Password` string sip 服务器密码
|
||||
|
||||
- `AckTimeout` uint16 sip 服务应答超时,单位秒
|
||||
- `RegisterValidity` int 注册有效期,单位秒,默认 3600
|
||||
- `RegisterInterval` int 注册间隔,单位秒,默认 60
|
||||
- `HeartbeatInterval` int 心跳间隔,单位秒,默认 60
|
||||
- `HeartbeatRetry` int 心跳超时次数,默认 3
|
||||
- `RegisterValidity` time.Duration 注册有效期,单位秒,默认 60
|
||||
|
||||
* 媒体服务器配置
|
||||
- `MediaIP` string 媒体服务器地址
|
||||
- `MediaIP` string 媒体服务器地址 默认 自动适配设备网段
|
||||
- `MediaPort` uint16 媒体服务器端口
|
||||
- `MediaNetwork` string 媒体传输协议,默认UDP,可选TCP
|
||||
- `MediaIdleTimeout` uint16 推流超时时间,超过则断开链接,让设备重连
|
||||
|
||||
- `AudioEnable` bool 是否开启音频
|
||||
- `LogVerbose` bool
|
||||
- `WaitKeyFrame` bool 是否等待关键帧,如果等待,则在收到第一个关键帧之前,忽略所有媒体流
|
||||
- `RemoveBanInterval` int 定时移除注册失败的设备黑名单,单位秒,默认10分钟(600秒)
|
||||
- `MediaPortMin` uint16 媒体服务器端口范围最小值
|
||||
- `MediaPortMax` uint16 媒体服务器端口范围最大值
|
||||
- `LogLevel` string 日志级别,默认 info(trace,debug,info,warn,error,fatal, panic)
|
||||
- `RemoveBanInterval` time.Duration 定时移除注册失败的设备黑名单,单位秒,默认10分钟(600秒)
|
||||
- `UdpCacheSize` int 表示UDP缓存大小,默认为0,不开启。仅当TCP关闭,切缓存大于0时才开启,会最多缓存最多N个包,并排序,修复乱序造成的无法播放问题,注意开启后,会有一定的性能损耗,并丢失部分包。
|
||||
|
||||
**如果配置了端口范围,将采用范围端口机制,每一个流对应一个端口
|
||||
|
||||
**注意某些摄像机没有设置用户名的地方,摄像机会以自身的国标id作为用户名,这个时候m7s会忽略使用摄像机的用户名,忽略配置的用户名**
|
||||
如果设备配置了错误的用户名和密码,连续三次上报错误后,m7s会记录设备id,并在10分钟内禁止设备注册
|
||||
|
||||
@@ -105,7 +100,7 @@ gb28181:
|
||||
|
||||
### 罗列所有的gb28181协议的设备
|
||||
|
||||
`/api/gb28181/list`
|
||||
`/gb28181/api/list`
|
||||
设备的结构体如下
|
||||
|
||||
```go
|
||||
@@ -128,43 +123,53 @@ type Device struct {
|
||||
|
||||
### 从设备拉取视频流
|
||||
|
||||
`/api/gb28181/invite`
|
||||
`/gb28181/api/invite`
|
||||
|
||||
参数名 | 必传 | 含义
|
||||
|----|---|---
|
||||
id|是 | 设备ID
|
||||
channel|是|通道编号
|
||||
startTime|否|开始时间(纯数字Unix时间戳)
|
||||
endTime|否|结束时间(纯数字Unix时间戳)
|
||||
| 参数名 | 必传 | 含义 |
|
||||
| --------- | ---- | ---------------------------- |
|
||||
| id | 是 | 设备ID |
|
||||
| channel | 是 | 通道编号 |
|
||||
| startTime | 否 | 开始时间(纯数字Unix时间戳) |
|
||||
| endTime | 否 | 结束时间(纯数字Unix时间戳) |
|
||||
|
||||
返回200代表成功
|
||||
|
||||
### 停止从设备拉流
|
||||
|
||||
`/api/gb28181/bye`
|
||||
`/gb28181/api/bye`
|
||||
|
||||
参数名 | 必传 | 含义
|
||||
|----|---|---
|
||||
id|是 | 设备ID
|
||||
channel|是|通道编号
|
||||
| 参数名 | 必传 | 含义 |
|
||||
| ------- | ---- | -------- |
|
||||
| id | 是 | 设备ID |
|
||||
| channel | 是 | 通道编号 |
|
||||
|
||||
### 发送控制命令
|
||||
|
||||
`/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 | 否 | 开始时间(字符串,格式:2021-7-23T12:00:00) |
|
||||
| endTime | 否 | 结束时间(字符串格式同上) |
|
||||
|
||||
### 移动位置订阅
|
||||
|
||||
`/gb28181/api/position`
|
||||
|
||||
| 参数名 | 必传 | 含义 |
|
||||
| -------- | ---- | -------------- |
|
||||
| id | 是 | 设备ID |
|
||||
| expires | 是 | 订阅周期(秒) |
|
||||
| interval | 是 | 订阅间隔(秒) |
|
||||
|
||||
373
channel.go
373
channel.go
@@ -1,23 +1,24 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"go.uber.org/zap"
|
||||
. "m7s.live/engine/v4"
|
||||
"m7s.live/plugin/gb28181/v4/utils"
|
||||
)
|
||||
|
||||
type ChannelEx struct {
|
||||
device *Device `json:"-"`
|
||||
inviteRes *sip.Response `json:"-"`
|
||||
recordInviteRes *sip.Response `json:"-"`
|
||||
RecordPublisher *GBPublisher
|
||||
device *Device
|
||||
RecordPublisher *GBPublisher `json:"-"`
|
||||
LivePublisher *GBPublisher
|
||||
LiveSubSP string //实时子码流
|
||||
Records []*Record
|
||||
@@ -25,7 +26,7 @@ type ChannelEx struct {
|
||||
RecordEndTime string
|
||||
recordStartTime time.Time
|
||||
recordEndTime time.Time
|
||||
state int32
|
||||
liveInviteLock sync.Mutex
|
||||
tcpPortIndex uint16
|
||||
GpsTime time.Time //gps时间
|
||||
Longitude string //经度
|
||||
@@ -42,6 +43,7 @@ type Channel struct {
|
||||
Owner string
|
||||
CivilCode string
|
||||
Address string
|
||||
Port int
|
||||
Parental int
|
||||
SafetyWay int
|
||||
RegisterWay int
|
||||
@@ -51,19 +53,82 @@ type Channel struct {
|
||||
*ChannelEx //自定义属性
|
||||
}
|
||||
|
||||
func (c *Channel) Copy(v *Channel) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.DeviceID = v.DeviceID
|
||||
c.ParentID = v.ParentID
|
||||
c.Name = v.Name
|
||||
c.Manufacturer = v.Manufacturer
|
||||
c.Model = v.Model
|
||||
c.Owner = v.Owner
|
||||
c.CivilCode = v.CivilCode
|
||||
c.Address = v.Address
|
||||
c.Parental = v.Parental
|
||||
c.SafetyWay = v.SafetyWay
|
||||
c.RegisterWay = v.RegisterWay
|
||||
c.Secrecy = v.Secrecy
|
||||
c.Status = v.Status
|
||||
c.Status = v.Status
|
||||
c.ChannelEx = v.ChannelEx
|
||||
}
|
||||
|
||||
func (c *Channel) CreateRequst(Method sip.RequestMethod) (req sip.Request) {
|
||||
return c.device.CreateRequest(Method)
|
||||
// request = &Request{}
|
||||
// request.Message = c.device.CreateMessage(Method)
|
||||
// request.Message.StartLine.Uri = NewURI(c.DeviceID + "@" + c.device.to.Uri.Domain())
|
||||
// request.Message.To = &Contact{
|
||||
// Uri: request.Message.StartLine.Uri,
|
||||
// }
|
||||
// request.Message.From = &Contact{
|
||||
// Uri: NewURI(c.device.config.Serial + "@" + c.device.config.Realm),
|
||||
// Params: map[string]string{"tag": utils.RandNumString(9)},
|
||||
// }
|
||||
// return req
|
||||
d := c.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,
|
||||
}
|
||||
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)}),
|
||||
}
|
||||
//非同一域的目标地址需要使用@host
|
||||
host := conf.Realm
|
||||
if c.DeviceID[0:9] != host {
|
||||
deviceIp := d.NetAddr
|
||||
deviceIp = deviceIp[0:strings.LastIndex(deviceIp, ":")]
|
||||
host = fmt.Sprintf("%s:%d", deviceIp, c.Port)
|
||||
}
|
||||
|
||||
channelAddr := sip.Address{
|
||||
//DisplayName: sip.String{Str: d.serverConfig.Serial},
|
||||
Uri: &sip.SipUri{FUser: sip.String{Str: c.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
|
||||
@@ -84,7 +149,7 @@ func (channel *Channel) QueryRecord(startTime, endTime string) int {
|
||||
<EndTime>%s</EndTime>
|
||||
<Secrecy>0</Secrecy>
|
||||
<Type>all</Type>
|
||||
</Query>`, d.sn, d.ID, startTime, endTime)
|
||||
</Query>`, d.sn, channel.DeviceID, startTime, endTime)
|
||||
request.SetBody(body, true)
|
||||
resp, err := d.SipRequestForResponse(request)
|
||||
if err != nil {
|
||||
@@ -103,7 +168,7 @@ func (channel *Channel) Control(PTZCmd string) int {
|
||||
<SN>%d</SN>
|
||||
<DeviceID>%s</DeviceID>
|
||||
<PTZCmd>%s</PTZCmd>
|
||||
</Control>`, d.sn, d.ID, PTZCmd)
|
||||
</Control>`, d.sn, channel.DeviceID, PTZCmd)
|
||||
request.SetBody(body, true)
|
||||
resp, err := d.SipRequestForResponse(request)
|
||||
if err != nil {
|
||||
@@ -112,6 +177,63 @@ func (channel *Channel) Control(PTZCmd string) int {
|
||||
return int(resp.StatusCode())
|
||||
}
|
||||
|
||||
type InviteOptions struct {
|
||||
Start int
|
||||
End int
|
||||
dump string
|
||||
ssrc string
|
||||
SSRC uint32
|
||||
MediaPort uint16
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/*
|
||||
f字段: f = v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
|
||||
各项具体含义:
|
||||
@@ -156,182 +278,143 @@ 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.liveInviteLock.TryLock() {
|
||||
return 304, nil
|
||||
}
|
||||
defer func() {
|
||||
if code != 200 {
|
||||
atomic.StoreInt32(&channel.state, 0)
|
||||
channel.liveInviteLock.Unlock()
|
||||
}
|
||||
}()
|
||||
channel.Bye(true)
|
||||
} else {
|
||||
channel.Bye(false)
|
||||
}
|
||||
sint, err1 := strconv.ParseInt(start, 10, 0)
|
||||
eint, err2 := strconv.ParseInt(end, 10, 0)
|
||||
channel.Bye(opt.IsLive())
|
||||
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)
|
||||
} else {
|
||||
ssrc[0] = '0'
|
||||
streamPath = fmt.Sprintf("%s/%s/%d-%d", d.ID, channel.DeviceID, opt.Start, opt.End)
|
||||
}
|
||||
if opt.dump == "" {
|
||||
opt.dump = conf.DumpPath
|
||||
}
|
||||
config := channel.device.config
|
||||
// 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)))
|
||||
publisher := &GBPublisher{
|
||||
InviteOptions: opt,
|
||||
channel: channel,
|
||||
}
|
||||
protocol := ""
|
||||
port := config.MediaPort
|
||||
if config.IsMediaNetworkTCP() {
|
||||
if conf.IsMediaNetworkTCP() {
|
||||
protocol = "TCP/"
|
||||
port = config.MediaPort + channel.tcpPortIndex
|
||||
if channel.tcpPortIndex++; channel.tcpPortIndex >= config.MediaPortMax {
|
||||
channel.tcpPortIndex = 0
|
||||
if conf.tcpPorts.Valid {
|
||||
opt.MediaPort, err = publisher.ListenTCP()
|
||||
if err != nil {
|
||||
return 500, err
|
||||
}
|
||||
} else if opt.MediaPort == 0 {
|
||||
opt.MediaPort = conf.MediaPort
|
||||
}
|
||||
} else {
|
||||
if conf.udpPorts.Valid {
|
||||
opt.MediaPort, err = publisher.ListenUDP()
|
||||
if err != nil {
|
||||
code = 500
|
||||
return
|
||||
}
|
||||
} else if opt.MediaPort == 0 {
|
||||
opt.MediaPort = conf.MediaPort
|
||||
}
|
||||
}
|
||||
// if opt.MediaPort == 0 {
|
||||
// opt.MediaPort = conf.MediaPort
|
||||
// if conf.IsMediaNetworkTCP() {
|
||||
// protocol = "TCP/"
|
||||
// opt.MediaPort = conf.MediaPort + channel.tcpPortIndex
|
||||
// if channel.tcpPortIndex++; channel.tcpPortIndex >= conf.MediaPortMax {
|
||||
// channel.tcpPortIndex = 0
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
sdpInfo := []string{
|
||||
"v=0",
|
||||
fmt.Sprintf("o=%s 0 0 IN IP4 %s", d.ID, d.config.SipIP),
|
||||
fmt.Sprintf("o=%s 0 0 IN IP4 %s", channel.DeviceID, d.mediaIP),
|
||||
"s=" + s,
|
||||
"u=" + channel.DeviceID + ":0",
|
||||
"c=IN IP4 " + d.config.MediaIP,
|
||||
fmt.Sprintf("t=%d %d", sint, eint),
|
||||
fmt.Sprintf("m=video %d %sRTP/AVP 96", port, protocol),
|
||||
"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",
|
||||
"y=" + opt.ssrc,
|
||||
"",
|
||||
}
|
||||
if conf.IsMediaNetworkTCP() {
|
||||
sdpInfo = append(sdpInfo, "a=setup:passive", "a=connection:new")
|
||||
}
|
||||
// if config.IsMediaNetworkTCP() {
|
||||
// sdpInfo = append(sdpInfo, "a=setup:passive", "a=connection:new")
|
||||
// }
|
||||
invite := channel.CreateRequst(sip.INVITE)
|
||||
contentType := sip.ContentType("application/sdp")
|
||||
invite.AppendHeader(&contentType)
|
||||
|
||||
invite.SetBody(strings.Join(sdpInfo, "\r\n")+"\r\ny="+string(ssrc)+"\r\n", true)
|
||||
invite.SetBody(strings.Join(sdpInfo, "\r\n"), true)
|
||||
|
||||
subject := sip.GenericHeader{
|
||||
HeaderName: "Subject", Contents: fmt.Sprintf("%s:%s,%s:0", channel.DeviceID, ssrc, config.Serial),
|
||||
HeaderName: "Subject", Contents: fmt.Sprintf("%s:%s,%s:0", channel.DeviceID, opt.ssrc, conf.Serial),
|
||||
}
|
||||
invite.AppendHeader(&subject)
|
||||
response, err := d.SipRequestForResponse(invite)
|
||||
if response == nil || err != nil {
|
||||
return http.StatusRequestTimeout
|
||||
publisher.inviteRes, err = d.SipRequestForResponse(invite)
|
||||
if err != nil {
|
||||
return http.StatusRequestTimeout, err
|
||||
}
|
||||
plugin.Info(fmt.Sprintf("Channel :%s invite response status code: %d", channel.DeviceID, response.StatusCode()))
|
||||
|
||||
if response.StatusCode() == 200 {
|
||||
ds := strings.Split(response.Body(), "\r\n")
|
||||
_SSRC, _ := strconv.ParseInt(string(ssrc), 10, 0)
|
||||
SSRC := uint32(_SSRC)
|
||||
code = int(publisher.inviteRes.StatusCode())
|
||||
plugin.Info(fmt.Sprintf("Channel :%s invite response status code: %d", channel.DeviceID, code))
|
||||
if code == 200 {
|
||||
ds := strings.Split(publisher.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)
|
||||
if _ssrc, err := strconv.ParseInt(ls[1], 10, 0); err == nil {
|
||||
opt.SSRC = uint32(_ssrc)
|
||||
} else {
|
||||
plugin.Error("read invite response y ", zap.Error(err))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
publisher := &GBPublisher{
|
||||
StreamPath: streamPath,
|
||||
config: config,
|
||||
}
|
||||
if config.UdpCacheSize > 0 && !config.IsMediaNetworkTCP() {
|
||||
if conf.UdpCacheSize > 0 && !conf.IsMediaNetworkTCP() {
|
||||
publisher.udpCache = utils.NewPqRtp()
|
||||
}
|
||||
publishers := &config.publishers
|
||||
if start == "" {
|
||||
publisher.Type = "GB28181 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("", "")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
publisher.Type = "GB28181 Record"
|
||||
publisher.OnClose = func() {
|
||||
publishers.Remove(SSRC)
|
||||
channel.RecordPublisher = nil
|
||||
channel.ByeBye(channel.recordInviteRes)
|
||||
channel.recordInviteRes = nil
|
||||
}
|
||||
if err = plugin.Publish(streamPath, publisher); err != nil {
|
||||
code = 403
|
||||
return
|
||||
}
|
||||
if !publisher.Publish() {
|
||||
return 403
|
||||
}
|
||||
publishers.Add(SSRC, publisher)
|
||||
if start == "" {
|
||||
channel.inviteRes = &response
|
||||
channel.LivePublisher = publisher
|
||||
} else {
|
||||
channel.RecordPublisher = publisher
|
||||
channel.recordInviteRes = &response
|
||||
}
|
||||
ack := sip.NewAckRequest("", invite, response, "", nil)
|
||||
(*GetSipServer()).Send(ack)
|
||||
} else if start == "" && config.AutoInvite {
|
||||
ack := sip.NewAckRequest("", invite, publisher.inviteRes, "", nil)
|
||||
srv.Send(ack)
|
||||
} else if opt.IsLive() && conf.AutoInvite {
|
||||
time.AfterFunc(time.Second*5, func() {
|
||||
channel.Invite("", "")
|
||||
channel.Invite(InviteOptions{})
|
||||
})
|
||||
}
|
||||
return int(response.StatusCode())
|
||||
return
|
||||
}
|
||||
|
||||
func (channel *Channel) Bye(live bool) int {
|
||||
if live && channel.inviteRes != nil {
|
||||
defer func() {
|
||||
channel.inviteRes = nil
|
||||
if channel.LivePublisher != nil {
|
||||
channel.LivePublisher.Close()
|
||||
}
|
||||
}()
|
||||
return int((*channel.ByeBye(channel.inviteRes)).StatusCode())
|
||||
d := channel.device
|
||||
streamPath := fmt.Sprintf("%s/%s", d.ID, channel.DeviceID)
|
||||
if s := Streams.Get(streamPath); s != nil {
|
||||
s.Close()
|
||||
}
|
||||
if !live && channel.recordInviteRes != nil {
|
||||
defer func() {
|
||||
channel.recordInviteRes = nil
|
||||
if channel.RecordPublisher != nil {
|
||||
channel.RecordPublisher.Close()
|
||||
}
|
||||
}()
|
||||
return int((*channel.ByeBye(channel.recordInviteRes)).StatusCode())
|
||||
if live && channel.LivePublisher != nil {
|
||||
return channel.LivePublisher.Bye()
|
||||
}
|
||||
if !live && channel.RecordPublisher != nil {
|
||||
return channel.RecordPublisher.Bye()
|
||||
}
|
||||
return 404
|
||||
}
|
||||
func (c *Channel) ByeBye(res *sip.Response) *sip.Response {
|
||||
if res == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
d := c.device
|
||||
bye := c.CreateRequst(sip.BYE)
|
||||
from, _ := (*res).From()
|
||||
to, _ := (*res).To()
|
||||
callId, _ := (*res).CallID()
|
||||
|
||||
bye.ReplaceHeaders(from.Name(), []sip.Header{from})
|
||||
bye.ReplaceHeaders(to.Name(), []sip.Header{to})
|
||||
bye.ReplaceHeaders(callId.Name(), []sip.Header{callId})
|
||||
|
||||
resp, _ := d.SipRequestForResponse(bye)
|
||||
return &resp
|
||||
|
||||
}
|
||||
|
||||
164
device.go
164
device.go
@@ -2,8 +2,10 @@ package gb28181
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -14,6 +16,7 @@ import (
|
||||
|
||||
// . "github.com/logrusorgru/aurora"
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
myip "github.com/husanpao/ip"
|
||||
)
|
||||
|
||||
const TIME_LAYOUT = "2006-01-02T15:04:05"
|
||||
@@ -43,7 +46,6 @@ var (
|
||||
|
||||
type Device struct {
|
||||
//*transaction.Core `json:"-"`
|
||||
config *GB28181Config
|
||||
ID string
|
||||
Name string
|
||||
Manufacturer string
|
||||
@@ -56,7 +58,8 @@ type Device struct {
|
||||
Channels []*Channel
|
||||
sn int
|
||||
addr sip.Address
|
||||
tx *sip.ServerTransaction
|
||||
sipIP string //设备对应网卡的服务器ip
|
||||
mediaIP string //设备对应网卡的服务器ip
|
||||
NetAddr string
|
||||
channelMap map[string]*Channel
|
||||
channelMutex sync.RWMutex
|
||||
@@ -64,39 +67,124 @@ type Device struct {
|
||||
CallID string
|
||||
Timeout time.Time
|
||||
}
|
||||
GpsTime time.Time //gps时间
|
||||
Longitude string //经度
|
||||
Latitude string //纬度
|
||||
}
|
||||
|
||||
func (config *GB28181Config) StoreDevice(id string, req sip.Request, tx *sip.ServerTransaction) {
|
||||
func (config *GB28181Config) RecoverDevice(d *Device, req sip.Request) {
|
||||
from, _ := req.From()
|
||||
d.addr = sip.Address{
|
||||
DisplayName: from.DisplayName,
|
||||
Uri: from.Address,
|
||||
}
|
||||
deviceIp := req.Source()
|
||||
servIp := req.Recipient().Host()
|
||||
//根据网卡ip获取对应的公网ip
|
||||
sipIP := config.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 config.SipIP != "" {
|
||||
sipIP = config.SipIP
|
||||
} else if sipIP == "" {
|
||||
sipIP = myip.InternalIPv4()
|
||||
}
|
||||
mediaIp := sipIP
|
||||
if config.MediaIP != "" {
|
||||
mediaIp = config.MediaIP
|
||||
}
|
||||
plugin.Info("RecoverDevice", zap.String("id", d.ID), zap.String("deviceIp", deviceIp), zap.String("servIp", servIp), zap.String("sipIP", sipIP), zap.String("mediaIp", mediaIp))
|
||||
d.Status = string(sip.REGISTER)
|
||||
d.sipIP = sipIP
|
||||
d.mediaIP = mediaIp
|
||||
d.NetAddr = deviceIp
|
||||
d.UpdateTime = time.Now()
|
||||
d.channelMap = make(map[string]*Channel)
|
||||
go d.Catalog()
|
||||
}
|
||||
func (config *GB28181Config) StoreDevice(id string, req sip.Request) {
|
||||
var d *Device
|
||||
plugin.Debug("StoreDevice", zap.String("id", id))
|
||||
|
||||
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 = req.Source()
|
||||
d.NetAddr = deviceIp
|
||||
d.addr = deviceAddr
|
||||
plugin.Debug("UpdateDevice", zap.String("id", id), zap.String("netaddr", d.NetAddr))
|
||||
} else {
|
||||
servIp := req.Recipient().Host()
|
||||
//根据网卡ip获取对应的公网ip
|
||||
sipIP := config.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 config.SipIP != "" {
|
||||
sipIP = config.SipIP
|
||||
} else if sipIP == "" {
|
||||
sipIP = myip.InternalIPv4()
|
||||
}
|
||||
mediaIp := sipIP
|
||||
if config.MediaIP != "" {
|
||||
mediaIp = config.MediaIP
|
||||
}
|
||||
plugin.Info("StoreDevice", zap.String("id", id), zap.String("deviceIp", deviceIp), zap.String("servIp", servIp), zap.String("sipIP", sipIP), zap.String("mediaIp", mediaIp))
|
||||
d = &Device{
|
||||
ID: id,
|
||||
RegisterTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
Status: string(sip.REGISTER),
|
||||
addr: deviceAddr,
|
||||
tx: tx,
|
||||
NetAddr: req.Source(),
|
||||
sipIP: sipIP,
|
||||
mediaIP: mediaIp,
|
||||
NetAddr: deviceIp,
|
||||
channelMap: make(map[string]*Channel),
|
||||
config: config,
|
||||
}
|
||||
Devices.Store(id, d)
|
||||
SaveDevices()
|
||||
go d.Catalog()
|
||||
}
|
||||
}
|
||||
func 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"
|
||||
Devices.Store(item.ID, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func 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) addChannel(channel *Channel) {
|
||||
for _, c := range d.Channels {
|
||||
@@ -122,7 +210,7 @@ func (d *Device) UpdateChannels(list []*Channel) {
|
||||
d.channelMutex.Lock()
|
||||
defer d.channelMutex.Unlock()
|
||||
for _, c := range list {
|
||||
if _, ok := d.config.Ignores[c.DeviceID]; ok {
|
||||
if _, ok := conf.Ignores[c.DeviceID]; ok {
|
||||
continue
|
||||
}
|
||||
if c.ParentID != "" {
|
||||
@@ -140,7 +228,7 @@ func (d *Device) UpdateChannels(list []*Channel) {
|
||||
}
|
||||
if old, ok := d.channelMap[c.DeviceID]; ok {
|
||||
c.ChannelEx = old.ChannelEx
|
||||
if d.config.PreFetchRecord {
|
||||
if conf.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 &&
|
||||
@@ -148,25 +236,22 @@ func (d *Device) UpdateChannels(list []*Channel) {
|
||||
go c.QueryRecord(n.Format(TIME_LAYOUT), n.Add(time.Hour*24-time.Second).Format(TIME_LAYOUT))
|
||||
}
|
||||
}
|
||||
if d.config.AutoInvite &&
|
||||
(c.LivePublisher == nil) {
|
||||
c.Invite("", "")
|
||||
}
|
||||
|
||||
old.Copy(c)
|
||||
c = old
|
||||
} else {
|
||||
c.ChannelEx = &ChannelEx{
|
||||
device: d,
|
||||
}
|
||||
if d.config.AutoInvite {
|
||||
c.Invite("", "")
|
||||
}
|
||||
d.channelMap[c.DeviceID] = c
|
||||
}
|
||||
if conf.AutoInvite && (c.LivePublisher == nil) {
|
||||
go c.Invite(InviteOptions{})
|
||||
}
|
||||
if s := engine.Streams.Get("sub/" + c.DeviceID); s != nil {
|
||||
c.LiveSubSP = s.Path
|
||||
} else {
|
||||
c.LiveSubSP = ""
|
||||
}
|
||||
d.channelMap[c.DeviceID] = c
|
||||
}
|
||||
}
|
||||
func (d *Device) UpdateRecord(channelId string, list []*Record) {
|
||||
@@ -182,16 +267,17 @@ func (d *Device) CreateRequest(Method sip.RequestMethod) (req sip.Request) {
|
||||
|
||||
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(d.config.SipPort)
|
||||
port := sip.Port(conf.SipPort)
|
||||
serverAddr := sip.Address{
|
||||
//DisplayName: sip.String{Str: d.config.Serial},
|
||||
Uri: &sip.SipUri{
|
||||
FUser: sip.String{Str: d.config.Serial},
|
||||
FHost: d.config.SipIP,
|
||||
FUser: sip.String{Str: conf.Serial},
|
||||
FHost: d.sipIP,
|
||||
FPort: &port,
|
||||
},
|
||||
Params: sip.NewParams().Add("tag", sip.String{Str: utils.RandNumString(9)}),
|
||||
@@ -207,13 +293,14 @@ func (d *Device) CreateRequest(Method sip.RequestMethod) (req sip.Request) {
|
||||
&callId,
|
||||
&userAgent,
|
||||
&cseq,
|
||||
&maxForwards,
|
||||
serverAddr.AsContactHeader(),
|
||||
},
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
|
||||
req.SetTransport(d.config.SipNetwork)
|
||||
req.SetTransport(conf.SipNetwork)
|
||||
req.SetDestination(d.NetAddr)
|
||||
//fmt.Printf("构建请求参数:%s", *&req)
|
||||
// requestMsg.DestAdd, err2 = d.ResolveAddress(requestMsg)
|
||||
@@ -315,23 +402,23 @@ func (d *Device) QueryDeviceInfo(req *sip.Request) {
|
||||
}
|
||||
|
||||
func (d *Device) SipRequestForResponse(request sip.Request) (sip.Response, error) {
|
||||
return (*GetSipServer()).RequestWithContext(context.Background(), request)
|
||||
return srv.RequestWithContext(context.Background(), request)
|
||||
}
|
||||
|
||||
// MobilePositionSubscribe 移动位置订阅
|
||||
func (d *Device) MobilePositionSubscribe(id string, expires int, interval int) (code int) {
|
||||
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)
|
||||
d.subscriber.Timeout = time.Now().Add(time.Second * time.Duration(expires))
|
||||
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, interval), true)
|
||||
mobilePosition.SetBody(BuildDevicePositionXML(d.sn, id, int(interval/time.Second)), true)
|
||||
|
||||
response, err := d.SipRequestForResponse(mobilePosition)
|
||||
if err == nil && response != nil {
|
||||
@@ -349,12 +436,16 @@ func (d *Device) MobilePositionSubscribe(id string, expires int, interval int) (
|
||||
// UpdateChannelPosition 更新通道GPS坐标
|
||||
func (d *Device) UpdateChannelPosition(channelId string, gpsTime string, lng string, lat string) {
|
||||
if c, ok := d.channelMap[channelId]; ok {
|
||||
c.ChannelEx.GpsTime, _ = time.ParseInLocation("2006-01-02 15:04:05", gpsTime, time.Local)
|
||||
c.ChannelEx.GpsTime = time.Now() //时间取系统收到的时间,避免设备时间和格式问题
|
||||
c.ChannelEx.Longitude = lng
|
||||
c.ChannelEx.Latitude = lat
|
||||
plugin.Sugar().Debugf("更新通道[%s]坐标成功\n", c.Name)
|
||||
} else {
|
||||
plugin.Sugar().Debugf("更新失败,未找到通道[%s]\n", channelId)
|
||||
//如果未找到通道,则更新到设备上
|
||||
d.GpsTime = time.Now() //时间取系统收到的时间,避免设备时间和格式问题
|
||||
d.Longitude = lng
|
||||
d.Latitude = lat
|
||||
plugin.Sugar().Debugf("未找到通道[%s],更新设备[%s]坐标成功\n", channelId, d.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +467,7 @@ func (d *Device) UpdateChannelStatus(deviceList []*notifyMessage) {
|
||||
d.channelOffline(v.DeviceID)
|
||||
case "ADD":
|
||||
plugin.Debug("收到通道新增通知")
|
||||
channel := Channel{
|
||||
channel := &Channel{
|
||||
DeviceID: v.DeviceID,
|
||||
ParentID: v.ParentID,
|
||||
Name: v.Name,
|
||||
@@ -385,17 +476,19 @@ func (d *Device) UpdateChannelStatus(deviceList []*notifyMessage) {
|
||||
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: v.Status,
|
||||
}
|
||||
d.addChannel(&channel)
|
||||
channels := []*Channel{channel}
|
||||
d.UpdateChannels(channels)
|
||||
case "DEL":
|
||||
//删除
|
||||
plugin.Debug("收到通道删除通知")
|
||||
delete(d.channelMap, v.DeviceID)
|
||||
d.channelOffline(v.DeviceID)
|
||||
case "UPDATE":
|
||||
plugin.Debug("收到通道更新通知")
|
||||
// 更新通道
|
||||
@@ -408,6 +501,7 @@ func (d *Device) UpdateChannelStatus(deviceList []*notifyMessage) {
|
||||
Owner: v.Owner,
|
||||
CivilCode: v.CivilCode,
|
||||
Address: v.Address,
|
||||
Port: v.Port,
|
||||
Parental: v.Parental,
|
||||
SafetyWay: v.SafetyWay,
|
||||
RegisterWay: v.RegisterWay,
|
||||
|
||||
63
go.mod
63
go.mod
@@ -3,44 +3,59 @@ module m7s.live/plugin/gb28181/v4
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/ghettovoice/gosip v0.0.0-20220203123749-bc456081bd7a
|
||||
github.com/ghettovoice/gosip v0.0.0-20221121090201-9a2ed2233b6d
|
||||
github.com/husanpao/ip v0.0.0-20220711082147-73160bb611a8
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/pion/rtp/v2 v2.0.0-20220302185659-b3d10fc096b0
|
||||
go.uber.org/zap v1.21.0
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
|
||||
golang.org/x/text v0.3.7
|
||||
m7s.live/engine/v4 v4.0.0-beta5
|
||||
go.uber.org/zap v1.23.0
|
||||
golang.org/x/net v0.2.0
|
||||
golang.org/x/text v0.4.0
|
||||
m7s.live/engine/v4 v4.8.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cnotch/ipchub v1.1.0 // 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.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.1.0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.8 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.31.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/q191201771/naza v0.19.1 // indirect
|
||||
github.com/pion/rtp v1.7.13 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
|
||||
github.com/q191201771/naza v0.30.8 // indirect
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.22.1 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2 // indirect
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.22.10 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/tevino/abool v1.2.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
golang.org/x/term v0.2.0 // indirect
|
||||
golang.org/x/tools v0.3.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
156
go.sum
156
go.sum
@@ -1,6 +1,5 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
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=
|
||||
@@ -9,6 +8,7 @@ github.com/cnotch/queue v0.0.0-20200326024423-6e88bdbf2ad4/go.mod h1:zOssjAlNusO
|
||||
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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=
|
||||
@@ -16,12 +16,14 @@ github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca h1:cTTdXpkQ1aVbOOm
|
||||
github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca/go.mod h1:W+3LQaEkN8qAwwcw0KC546sUEnX86GIT8CcMLZC4mG0=
|
||||
github.com/emitter-io/address v1.0.0/go.mod h1:GfZb5+S/o8694B1GMGK2imUYQyn2skszMvGNA5D84Ug=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghettovoice/gosip v0.0.0-20220203123749-bc456081bd7a h1:tcQBkP7K5Ew3+eIqOStyrFxyoeIu7DL0aM+aLVexIaw=
|
||||
github.com/ghettovoice/gosip v0.0.0-20220203123749-bc456081bd7a/go.mod h1:yTr3BEYSFe9As6XM7ldyrVgqsPwlnw8Ahc4N28VFM2g=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/ghettovoice/gosip v0.0.0-20221121090201-9a2ed2233b6d h1:f1JRfm0MwkluwtUsbYxuVReDMajlc9Wn6zc2orX4sRE=
|
||||
github.com/ghettovoice/gosip v0.0.0-20221121090201-9a2ed2233b6d/go.mod h1:yTr3BEYSFe9As6XM7ldyrVgqsPwlnw8Ahc4N28VFM2g=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
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=
|
||||
@@ -29,6 +31,8 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
||||
github.com/gobwas/ws v1.1.0-rc.1/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
|
||||
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
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/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=
|
||||
@@ -36,119 +40,147 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
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.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
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-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
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/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/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 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
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/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lucas-clemente/quic-go v0.31.0 h1:MfNp3fk0wjWRajw6quMFA3ap1AVtlU+2mtwmbVogB2M=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY=
|
||||
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.5 h1:obHEce3upls1IBn1gTw/o7bCv7OJb6Ib/o7wNO+4eKw=
|
||||
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/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
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.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
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 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U=
|
||||
github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ=
|
||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||
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.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/rtp/v2 v2.0.0-20220302185659-b3d10fc096b0 h1:zyOGxHutZ6IhksQSMtwf3OFXB29W5R18yFQWOQJYWjU=
|
||||
github.com/pion/rtp/v2 v2.0.0-20220302185659-b3d10fc096b0/go.mod h1:Vj+rrFbJCT3yxqE/VSwaOo9DQ2pMKGPxuE7hplGOlOs=
|
||||
github.com/pixelbender/go-sdp v1.1.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/q191201771/naza v0.19.1 h1:4KLcxT2CHztO+7miPRtBG3FFgadSQYQw1gPPPKN7rnY=
|
||||
github.com/q191201771/naza v0.19.1/go.mod h1:5LeGupZZFtYP1g/S203n9vXoUNVdlRnPIfM6rExjqt0=
|
||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI=
|
||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/q191201771/naza v0.30.8 h1:Lhh29o65C4PmTDj2l+eKfsw9dddpgWZk4bFICtcnSaA=
|
||||
github.com/q191201771/naza v0.30.8/go.mod h1:n+dpJjQSh90PxBwxBNuifOwQttywvSIN5TkWSSYCeBk=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
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/shirou/gopsutil/v3 v3.22.1 h1:33y31Q8J32+KstqPfscvFwBlNJ6xLaBy4xqBXzlYV5w=
|
||||
github.com/shirou/gopsutil/v3 v3.22.1/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/shirou/gopsutil/v3 v3.22.10 h1:4KMHdfBRYXGF9skjDWiL4RA2N+E8dRdodU/bOZpPoVg=
|
||||
github.com/shirou/gopsutil/v3 v3.22.10/go.mod h1:QNza6r4YQoydyCfo6rH0blGfKahgibh4dQmV5xdFkQk=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
|
||||
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE=
|
||||
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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
|
||||
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
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.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
|
||||
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.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
|
||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -166,28 +198,31 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-20201214095126-aec9a390925b/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/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.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
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.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
|
||||
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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
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=
|
||||
@@ -195,8 +230,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
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.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
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=
|
||||
@@ -205,13 +240,10 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||
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.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
m7s.live/engine/v4 v4.0.0-beta h1:8PlvLNPW7O2uoZenz8Y5NrTmgor5uaTqivbqVrIDQb0=
|
||||
m7s.live/engine/v4 v4.0.0-beta/go.mod h1:Ez870pCfemZCUQQYP88jIHsgbRCb9ELjtCau14YTl7E=
|
||||
m7s.live/engine/v4 v4.0.0-beta5 h1:zGMTR6x7P0fP3OFpDc7tmG37QW/NokiRNNYrrOv3J1o=
|
||||
m7s.live/engine/v4 v4.0.0-beta5/go.mod h1:Ez870pCfemZCUQQYP88jIHsgbRCb9ELjtCau14YTl7E=
|
||||
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.8.5 h1:bUH71X7Ravj4gxINy/UrA67Nxycz26saiLeL3hBzuMU=
|
||||
m7s.live/engine/v4 v4.8.5/go.mod h1:Knz1H4ZhJDooORkHOuHGNquSyA4txJFgVCng5rTEAm8=
|
||||
|
||||
28
handle.go
28
handle.go
@@ -92,10 +92,20 @@ func (config *GB28181Config) OnRegister(req sip.Request, tx sip.ServerTransactio
|
||||
}
|
||||
}
|
||||
if passAuth {
|
||||
config.StoreDevice(id, req, &tx)
|
||||
config.StoreDevice(id, req)
|
||||
DeviceNonce.Delete(id)
|
||||
DeviceRegisterCount.Delete(id)
|
||||
_ = tx.Respond(sip.NewResponseFromRequest("", req, http.StatusOK, "OK", ""))
|
||||
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)
|
||||
} else {
|
||||
response := sip.NewResponseFromRequest("", req, http.StatusUnauthorized, "Unauthorized", "")
|
||||
_nonce, _ := DeviceNonce.LoadOrStore(id, utils.RandNumString(32))
|
||||
@@ -117,7 +127,11 @@ func (config *GB28181Config) OnMessage(req sip.Request, tx sip.ServerTransaction
|
||||
id := from.Address.User().String()
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
d := v.(*Device)
|
||||
if d.Status == string(sip.REGISTER) {
|
||||
switch d.Status {
|
||||
case "RECOVER":
|
||||
config.RecoverDevice(d, req)
|
||||
return
|
||||
case string(sip.REGISTER):
|
||||
d.Status = "ONLINE"
|
||||
//go d.QueryDeviceInfo(req)
|
||||
}
|
||||
@@ -156,13 +170,18 @@ func (config *GB28181Config) OnMessage(req sip.Request, tx sip.ServerTransaction
|
||||
for _, c := range d.Channels {
|
||||
if config.AutoInvite &&
|
||||
(c.LivePublisher == nil) {
|
||||
c.Invite("", "")
|
||||
c.Invite(InviteOptions{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
d.CheckSubStream()
|
||||
//在KeepLive 进行位置订阅的处理,如果开启了自动订阅位置,则去订阅位置
|
||||
if config.Position.AutosubPosition && time.Since(d.GpsTime) > config.Position.Interval*2 {
|
||||
d.MobilePositionSubscribe(d.ID, config.Position.Expires, config.Position.Interval)
|
||||
plugin.Sugar().Debugf("位置自动订阅,设备[%s]成功\n", d.ID)
|
||||
}
|
||||
case "Catalog":
|
||||
d.UpdateChannels(temp.DeviceList)
|
||||
case "RecordInfo":
|
||||
@@ -247,6 +266,7 @@ type notifyMessage struct {
|
||||
Owner string
|
||||
CivilCode string
|
||||
Address string
|
||||
Port int
|
||||
Parental int
|
||||
SafetyWay int
|
||||
RegisterWay int
|
||||
|
||||
201
main.go
201
main.go
@@ -1,89 +1,112 @@
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "m7s.live/engine/v4"
|
||||
"m7s.live/engine/v4/config"
|
||||
)
|
||||
|
||||
type GB28181Config struct {
|
||||
AutoInvite bool
|
||||
AutoCloseAfter int
|
||||
PreFetchRecord bool
|
||||
|
||||
//sip服务器的配置
|
||||
SipNetwork string //传输协议,默认UDP,可选TCP
|
||||
SipIP string //sip 服务器公网IP
|
||||
SipPort uint16 //sip 服务器端口,默认 5060
|
||||
Serial string //sip 服务器 id, 默认 34020000002000000001
|
||||
Realm string //sip 服务器域,默认 3402000000
|
||||
Username string //sip 服务器账号
|
||||
Password string //sip 服务器密码
|
||||
|
||||
AckTimeout uint16 //sip 服务应答超时,单位秒
|
||||
RegisterValidity int //注册有效期,单位秒,默认 3600
|
||||
RegisterInterval int //注册间隔,单位秒,默认 60
|
||||
HeartbeatInterval int //心跳间隔,单位秒,默认 60
|
||||
HeartbeatRetry int //心跳超时次数,默认 3
|
||||
|
||||
//媒体服务器配置
|
||||
MediaIP string //媒体服务器地址
|
||||
MediaPort uint16 //媒体服务器端口
|
||||
MediaNetwork string //媒体传输协议,默认UDP,可选TCP
|
||||
MediaPortMin uint16
|
||||
MediaPortMax uint16
|
||||
MediaIdleTimeout uint16 //推流超时时间,超过则断开链接,让设备重连
|
||||
|
||||
AudioEnable bool //是否开启音频
|
||||
LogVerbose bool
|
||||
WaitKeyFrame bool //是否等待关键帧,如果等待,则在收到第一个关键帧之前,忽略所有媒体流
|
||||
RemoveBanInterval int //移除禁止设备间隔
|
||||
UdpCacheSize int //udp缓存大小
|
||||
|
||||
config.Publish
|
||||
Server
|
||||
}
|
||||
|
||||
func (c *GB28181Config) OnEvent(event any) {
|
||||
switch event.(type) {
|
||||
case FirstConfig:
|
||||
c.startServer()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GB28181Config) IsMediaNetworkTCP() bool {
|
||||
return strings.ToLower(c.MediaNetwork) == "tcp"
|
||||
}
|
||||
|
||||
var conf = &GB28181Config{
|
||||
AutoInvite: true,
|
||||
AutoCloseAfter: -1,
|
||||
PreFetchRecord: false,
|
||||
UdpCacheSize: 0,
|
||||
SipNetwork: "udp",
|
||||
SipIP: "127.0.0.1",
|
||||
SipPort: 5060,
|
||||
Serial: "34020000002000000001",
|
||||
Realm: "3402000000",
|
||||
Username: "",
|
||||
Password: "",
|
||||
|
||||
AckTimeout: 10,
|
||||
RegisterValidity: 60,
|
||||
RegisterInterval: 60,
|
||||
HeartbeatInterval: 60,
|
||||
HeartbeatRetry: 3,
|
||||
|
||||
MediaIP: "127.0.0.1",
|
||||
MediaPort: 58200,
|
||||
MediaIdleTimeout: 30,
|
||||
MediaNetwork: "udp",
|
||||
|
||||
RemoveBanInterval: 600,
|
||||
LogVerbose: false,
|
||||
AudioEnable: true,
|
||||
WaitKeyFrame: true,
|
||||
}
|
||||
|
||||
var plugin = InstallPlugin(conf)
|
||||
package gb28181
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
myip "github.com/husanpao/ip"
|
||||
. "m7s.live/engine/v4"
|
||||
"m7s.live/engine/v4/config"
|
||||
)
|
||||
|
||||
type GB28181PositionConfig struct {
|
||||
AutosubPosition bool //是否自动订阅定位
|
||||
Expires time.Duration //订阅周期(单位:秒)
|
||||
Interval time.Duration //订阅间隔(单位:秒)
|
||||
}
|
||||
|
||||
type GB28181Config struct {
|
||||
AutoInvite bool
|
||||
PreFetchRecord bool
|
||||
|
||||
//sip服务器的配置
|
||||
SipNetwork string //传输协议,默认UDP,可选TCP
|
||||
SipIP string //sip 服务器公网IP
|
||||
SipPort uint16 //sip 服务器端口,默认 5060
|
||||
Serial string //sip 服务器 id, 默认 34020000002000000001
|
||||
Realm string //sip 服务器域,默认 3402000000
|
||||
Username string //sip 服务器账号
|
||||
Password string //sip 服务器密码
|
||||
|
||||
// AckTimeout uint16 //sip 服务应答超时,单位秒
|
||||
RegisterValidity time.Duration //注册有效期,单位秒,默认 3600
|
||||
// RegisterInterval int //注册间隔,单位秒,默认 60
|
||||
// HeartbeatInterval int //心跳间隔,单位秒,默认 60
|
||||
// HeartbeatRetry int //心跳超时次数,默认 3
|
||||
|
||||
//媒体服务器配置
|
||||
MediaIP string //媒体服务器地址
|
||||
MediaPort uint16 //媒体服务器端口
|
||||
MediaNetwork string //媒体传输协议,默认UDP,可选TCP
|
||||
MediaPortMin uint16
|
||||
MediaPortMax uint16
|
||||
// MediaIdleTimeout uint16 //推流超时时间,超过则断开链接,让设备重连
|
||||
|
||||
// WaitKeyFrame bool //是否等待关键帧,如果等待,则在收到第一个关键帧之前,忽略所有媒体流
|
||||
RemoveBanInterval time.Duration //移除禁止设备间隔
|
||||
UdpCacheSize int //udp缓存大小
|
||||
|
||||
config.Publish
|
||||
Server
|
||||
LogLevel string //trace, debug, info, warn, error, fatal, panic
|
||||
routes map[string]string
|
||||
DumpPath string //dump PS流本地文件路径
|
||||
|
||||
Position GB28181PositionConfig //关于定位的配置参数
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
plugin.Info(fmt.Sprintf("LocalAndInternalIPs detail: %s", c.routes))
|
||||
}
|
||||
func (c *GB28181Config) OnEvent(event any) {
|
||||
switch event.(type) {
|
||||
case FirstConfig:
|
||||
ReadDevices()
|
||||
go c.initRoutes()
|
||||
c.startServer()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GB28181Config) IsMediaNetworkTCP() bool {
|
||||
return strings.ToLower(c.MediaNetwork) == "tcp"
|
||||
}
|
||||
|
||||
var conf = &GB28181Config{
|
||||
AutoInvite: true,
|
||||
PreFetchRecord: false,
|
||||
UdpCacheSize: 0,
|
||||
SipNetwork: "udp",
|
||||
SipIP: "",
|
||||
SipPort: 5060,
|
||||
Serial: "34020000002000000001",
|
||||
Realm: "3402000000",
|
||||
Username: "",
|
||||
Password: "",
|
||||
|
||||
// AckTimeout: 10,
|
||||
RegisterValidity: 60 * time.Second,
|
||||
// RegisterInterval: 60,
|
||||
// HeartbeatInterval: 60,
|
||||
// HeartbeatRetry: 3,
|
||||
|
||||
MediaIP: "",
|
||||
MediaPort: 58200,
|
||||
// MediaIdleTimeout: 30,
|
||||
MediaNetwork: "udp",
|
||||
|
||||
RemoveBanInterval: 600 * time.Second,
|
||||
LogLevel: "info",
|
||||
// WaitKeyFrame: true,
|
||||
Position: GB28181PositionConfig{AutosubPosition: false, Expires: 3600 * time.Second, Interval: 6 * time.Second},
|
||||
}
|
||||
|
||||
var plugin = InstallPlugin(conf)
|
||||
|
||||
362
publisher.go
362
publisher.go
@@ -2,118 +2,312 @@ package gb28181
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ghettovoice/gosip/sip"
|
||||
"github.com/pion/rtp/v2"
|
||||
"go.uber.org/zap"
|
||||
. "m7s.live/engine/v4"
|
||||
"m7s.live/engine/v4/common"
|
||||
"m7s.live/engine/v4/track"
|
||||
. "m7s.live/engine/v4/codec"
|
||||
"m7s.live/engine/v4/codec/mpegts"
|
||||
. "m7s.live/engine/v4/track"
|
||||
"m7s.live/engine/v4/util"
|
||||
"m7s.live/plugin/gb28181/v4/utils"
|
||||
)
|
||||
|
||||
type GBPublisher struct {
|
||||
Publisher
|
||||
StreamPath string
|
||||
parser *utils.DecPSPackage
|
||||
OnClose func()
|
||||
lastSeq uint16
|
||||
udpCache *utils.PriorityQueueRtp
|
||||
config *GB28181Config
|
||||
vt common.VideoTrack
|
||||
at *track.G711
|
||||
InviteOptions
|
||||
channel *Channel
|
||||
inviteRes sip.Response
|
||||
parser *utils.DecPSPackage
|
||||
lastSeq uint16
|
||||
udpCache *utils.PriorityQueueRtp
|
||||
dumpFile *os.File
|
||||
dumpPrint io.Writer
|
||||
lastReceive time.Time
|
||||
reorder util.RTPReorder[*rtp.Packet]
|
||||
}
|
||||
|
||||
func (p *GBPublisher) PrintDump(s string) {
|
||||
if p.dumpPrint != nil {
|
||||
p.dumpPrint.Write([]byte(s))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *GBPublisher) OnEvent(event any) {
|
||||
if p.channel == nil {
|
||||
p.IO.OnEvent(event)
|
||||
return
|
||||
}
|
||||
switch event.(type) {
|
||||
case IPublisher:
|
||||
if p.IsLive() {
|
||||
p.Type = "GB28181 Live"
|
||||
p.channel.LivePublisher = p
|
||||
} else {
|
||||
p.Type = "GB28181 Playback"
|
||||
p.channel.RecordPublisher = p
|
||||
}
|
||||
conf.publishers.Add(p.SSRC, p)
|
||||
if err := error(nil); p.dump != "" {
|
||||
if p.dumpFile, err = os.OpenFile(p.dump, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
|
||||
p.Error("open dump file failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
case SEwaitPublish:
|
||||
//掉线自动重新拉流
|
||||
if p.IsLive() {
|
||||
if p.channel.LivePublisher != nil {
|
||||
p.channel.LivePublisher = nil
|
||||
p.channel.liveInviteLock.Unlock()
|
||||
}
|
||||
go p.channel.Invite(InviteOptions{})
|
||||
}
|
||||
case SEclose, SEKick:
|
||||
if p.IsLive() {
|
||||
if p.channel.LivePublisher != nil {
|
||||
p.channel.LivePublisher = nil
|
||||
p.channel.liveInviteLock.Unlock()
|
||||
}
|
||||
} else {
|
||||
p.channel.RecordPublisher = nil
|
||||
}
|
||||
conf.publishers.Delete(p.SSRC)
|
||||
if p.dumpFile != nil {
|
||||
p.dumpFile.Close()
|
||||
}
|
||||
p.Bye()
|
||||
}
|
||||
p.Publisher.OnEvent(event)
|
||||
}
|
||||
|
||||
func (p *GBPublisher) Bye() int {
|
||||
res := p.inviteRes
|
||||
if res == nil {
|
||||
return 404
|
||||
}
|
||||
defer p.Stop()
|
||||
p.inviteRes = nil
|
||||
bye := p.channel.CreateRequst(sip.BYE)
|
||||
from, _ := res.From()
|
||||
to, _ := res.To()
|
||||
callId, _ := res.CallID()
|
||||
bye.ReplaceHeaders(from.Name(), []sip.Header{from})
|
||||
bye.ReplaceHeaders(to.Name(), []sip.Header{to})
|
||||
bye.ReplaceHeaders(callId.Name(), []sip.Header{callId})
|
||||
resp, err := p.channel.device.SipRequestForResponse(bye)
|
||||
if err != nil {
|
||||
p.Error("Bye", zap.Error(err))
|
||||
return 500
|
||||
}
|
||||
return int(resp.StatusCode())
|
||||
}
|
||||
|
||||
func (p *GBPublisher) PushVideo(pts uint32, dts uint32, payload []byte) {
|
||||
if p.vt == nil {
|
||||
if p.VideoTrack == nil {
|
||||
switch p.parser.VideoStreamType {
|
||||
case utils.StreamTypeH264:
|
||||
p.vt = NewH264(p.Stream)
|
||||
case utils.StreamTypeH265:
|
||||
p.vt = NewH265(p.Stream)
|
||||
case mpegts.STREAM_TYPE_H264:
|
||||
p.VideoTrack = NewH264(p.Publisher.Stream)
|
||||
case mpegts.STREAM_TYPE_H265:
|
||||
p.VideoTrack = NewH265(p.Publisher.Stream)
|
||||
default:
|
||||
return
|
||||
//推测编码类型
|
||||
var maybe264 H264NALUType
|
||||
maybe264 = maybe264.Parse(payload[4])
|
||||
switch maybe264 {
|
||||
case NALU_Non_IDR_Picture,
|
||||
NALU_IDR_Picture,
|
||||
NALU_SEI,
|
||||
NALU_SPS,
|
||||
NALU_PPS,
|
||||
NALU_Access_Unit_Delimiter:
|
||||
p.VideoTrack = NewH264(p.Publisher.Stream)
|
||||
default:
|
||||
p.Info("maybe h265", zap.Uint8("type", maybe264.Byte()))
|
||||
p.VideoTrack = NewH265(p.Publisher.Stream)
|
||||
}
|
||||
}
|
||||
}
|
||||
p.vt.WriteAnnexB(pts, dts, payload)
|
||||
if len(payload) > 10 {
|
||||
p.PrintDump(fmt.Sprintf("<td>pts:%d dts:%d data: % 2X</td>", pts, dts, payload[:10]))
|
||||
} else {
|
||||
p.PrintDump(fmt.Sprintf("<td>pts:%d dts:%d data: % 2X</td>", pts, dts, payload))
|
||||
}
|
||||
if dts == 0 {
|
||||
dts = pts
|
||||
}
|
||||
p.VideoTrack.WriteAnnexB(pts, dts, payload)
|
||||
}
|
||||
func (p *GBPublisher) PushAudio(ts uint32, payload []byte) {
|
||||
if p.at == nil {
|
||||
if p.AudioTrack == nil {
|
||||
switch p.parser.AudioStreamType {
|
||||
case utils.G711A:
|
||||
at := NewG711(p.Stream, true)
|
||||
at.SampleRate = 8000
|
||||
at.SampleSize = 16
|
||||
at.Channels = 1
|
||||
at.AVCCHead = []byte{(byte(at.CodecID) << 4) | (1 << 1)}
|
||||
p.at = at
|
||||
case utils.G711A + 1:
|
||||
at := NewG711(p.Stream, false)
|
||||
at.SampleRate = 8000
|
||||
at.SampleSize = 16
|
||||
at.Channels = 1
|
||||
at.AVCCHead = []byte{(byte(at.CodecID) << 4) | (1 << 1)}
|
||||
p.at = at
|
||||
case mpegts.STREAM_TYPE_G711A:
|
||||
p.AudioTrack = NewG711(p.Publisher.Stream, true)
|
||||
case mpegts.STREAM_TYPE_G711U:
|
||||
p.AudioTrack = NewG711(p.Publisher.Stream, false)
|
||||
case mpegts.STREAM_TYPE_AAC:
|
||||
p.AudioTrack = NewAAC(p.Publisher.Stream)
|
||||
p.WriteADTS(payload[:7])
|
||||
case 0: //推测编码类型
|
||||
if payload[0] == 0xff && payload[1]>>4 == 0xf {
|
||||
p.AudioTrack = NewAAC(p.Publisher.Stream)
|
||||
p.WriteADTS(payload[:7])
|
||||
}
|
||||
default:
|
||||
return
|
||||
p.Error("audio type not supported yet", zap.Uint32("type", p.parser.AudioStreamType))
|
||||
}
|
||||
} else {
|
||||
p.AudioTrack.WriteRaw(ts, payload)
|
||||
}
|
||||
p.at.WriteAVCC(ts, payload)
|
||||
}
|
||||
|
||||
func (p *GBPublisher) Publish() (result bool) {
|
||||
if err := plugin.Publish(p.StreamPath, p); err == nil {
|
||||
if p.vt != nil {
|
||||
p.vt.Detach()
|
||||
p.vt = nil
|
||||
}
|
||||
if p.at != nil {
|
||||
p.at.Detach()
|
||||
p.at = nil
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 解析rtp封装 https://www.ietf.org/rfc/rfc2250.txt
|
||||
func (p *GBPublisher) PushPS(rtp *rtp.Packet) {
|
||||
originRtp := *rtp
|
||||
if p.config.UdpCacheSize > 0 && !p.config.IsMediaNetworkTCP() {
|
||||
//序号小于第一个包的丢弃,rtp包序号达到65535后会从0开始,所以这里需要判断一下
|
||||
if rtp.SequenceNumber < p.lastSeq && p.lastSeq-rtp.SequenceNumber < utils.MaxRtpDiff {
|
||||
return
|
||||
}
|
||||
p.udpCache.Push(*rtp)
|
||||
rtpTmp, _ := p.udpCache.Pop()
|
||||
rtp = &rtpTmp
|
||||
if p.parser == nil {
|
||||
p.parser = utils.NewDecPSPackage(p)
|
||||
}
|
||||
ps := rtp.Payload
|
||||
if p.lastSeq != 0 {
|
||||
// rtp序号不连续,丢弃PS
|
||||
if p.lastSeq+1 != rtp.SequenceNumber {
|
||||
if p.config.UdpCacheSize > 0 && !p.config.IsMediaNetworkTCP() {
|
||||
if p.udpCache.Len() < p.config.UdpCacheSize {
|
||||
p.udpCache.Push(*rtp)
|
||||
return
|
||||
} else {
|
||||
p.udpCache.Empty()
|
||||
rtp = &originRtp // 还原rtp包,而不是使用缓存中,避免rtp序号断裂
|
||||
if conf.IsMediaNetworkTCP() {
|
||||
p.parser.Feed(rtp)
|
||||
p.lastSeq = rtp.SequenceNumber
|
||||
} else {
|
||||
for rtp = p.reorder.Push(rtp.SequenceNumber, rtp); rtp != nil; rtp = p.reorder.Pop() {
|
||||
if rtp.SequenceNumber != p.lastSeq+1 {
|
||||
p.parser.Drop()
|
||||
if p.VideoTrack != nil {
|
||||
p.VideoTrack.SetLostFlag()
|
||||
}
|
||||
}
|
||||
p.parser.Reset()
|
||||
p.parser.Feed(rtp)
|
||||
p.lastSeq = rtp.SequenceNumber
|
||||
}
|
||||
}
|
||||
p.lastSeq = rtp.SequenceNumber
|
||||
if p.parser == nil {
|
||||
p.parser = new(utils.DecPSPackage)
|
||||
}
|
||||
if len(ps) >= 4 && binary.BigEndian.Uint32(ps) == utils.StartCodePS {
|
||||
if p.parser.Len() > 0 {
|
||||
p.parser.Skip(4)
|
||||
p.parser.Read(rtp.Timestamp, p)
|
||||
p.parser.Reset()
|
||||
}
|
||||
p.parser.Write(ps)
|
||||
} else if p.parser.Len() > 0 {
|
||||
p.parser.Write(ps)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *GBPublisher) Replay(f *os.File) (err error) {
|
||||
var rtpPacket rtp.Packet
|
||||
defer f.Close()
|
||||
if p.dumpPrint != nil {
|
||||
p.PrintDump(`<style type="text/css">
|
||||
.gray {
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
`)
|
||||
p.PrintDump("<table>")
|
||||
defer p.PrintDump("</table>")
|
||||
}
|
||||
var t uint16
|
||||
for l := make([]byte, 6); !p.IsClosed(); time.Sleep(time.Millisecond * time.Duration(t)) {
|
||||
_, err = f.Read(l)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
payload := make([]byte, util.ReadBE[int](l[:4]))
|
||||
t = util.ReadBE[uint16](l[4:])
|
||||
p.PrintDump(fmt.Sprintf("[<b>%d</b> %d]", t, len(payload)))
|
||||
_, err = f.Read(payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rtpPacket.Unmarshal(payload)
|
||||
p.PushPS(&rtpPacket)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *GBPublisher) ListenUDP() (port uint16, err error) {
|
||||
var rtpPacket rtp.Packet
|
||||
networkBuffer := 1048576
|
||||
port, err = conf.udpPorts.GetPort()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
mediaAddr, _ := net.ResolveUDPAddr("udp", addr)
|
||||
conn, err := net.ListenUDP("udp", mediaAddr)
|
||||
if err != nil {
|
||||
conf.udpPorts.Recycle(port)
|
||||
plugin.Error("listen media server udp err", zap.String("addr", addr), zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
p.SetIO(conn)
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
bufUDP := make([]byte, networkBuffer)
|
||||
plugin.Info("Media udp server start.", zap.Uint16("port", port))
|
||||
defer plugin.Info("Media udp server stop", zap.Uint16("port", port))
|
||||
defer conf.udpPorts.Recycle(port)
|
||||
dumpLen := make([]byte, 6)
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second * 10))
|
||||
for n, _, err := conn.ReadFromUDP(bufUDP); err == nil; n, _, err = conn.ReadFromUDP(bufUDP) {
|
||||
ps := bufUDP[:n]
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
plugin.Error("Decode rtp error:", zap.Error(err))
|
||||
}
|
||||
if p.dumpFile != nil {
|
||||
util.PutBE(dumpLen[:4], n)
|
||||
if p.lastReceive.IsZero() {
|
||||
util.PutBE(dumpLen[4:], 0)
|
||||
} else {
|
||||
util.PutBE(dumpLen[4:], uint16(time.Since(p.lastReceive).Milliseconds()))
|
||||
}
|
||||
p.lastReceive = time.Now()
|
||||
p.dumpFile.Write(dumpLen)
|
||||
p.dumpFile.Write(ps)
|
||||
}
|
||||
p.PushPS(&rtpPacket)
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second * 10))
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *GBPublisher) ListenTCP() (port uint16, err error) {
|
||||
port, err = conf.tcpPorts.GetPort()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
mediaAddr, _ := net.ResolveTCPAddr("tcp", addr)
|
||||
listen, err := net.ListenTCP("tcp", mediaAddr)
|
||||
if err != nil {
|
||||
defer conf.tcpPorts.Recycle(port)
|
||||
plugin.Error("listen media server tcp err", zap.String("addr", addr), zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
go func() {
|
||||
plugin.Info("Media tcp server start.", zap.Uint16("port", port))
|
||||
defer conf.tcpPorts.Recycle(port)
|
||||
defer plugin.Info("Media tcp server stop", zap.Uint16("port", port))
|
||||
conn, err := listen.Accept()
|
||||
listen.Close()
|
||||
p.SetIO(conn)
|
||||
if err != nil {
|
||||
plugin.Error("Accept err=", zap.Error(err))
|
||||
return
|
||||
}
|
||||
var rtpPacket rtp.Packet
|
||||
lenBuf := make([]byte, 2)
|
||||
defer conn.Close()
|
||||
for err == nil {
|
||||
if _, err = io.ReadFull(conn, lenBuf); err != nil {
|
||||
return
|
||||
}
|
||||
ps := make([]byte, binary.BigEndian.Uint16(lenBuf))
|
||||
if _, err = io.ReadFull(conn, ps); err != nil {
|
||||
return
|
||||
}
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
plugin.Error("gb28181 decode rtp error:", zap.Error(err))
|
||||
} else if !p.IsClosed() {
|
||||
p.PushPS(&rtpPacket)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
124
restful.go
124
restful.go
@@ -2,33 +2,27 @@ package gb28181
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"m7s.live/engine/v4/util"
|
||||
)
|
||||
|
||||
func (conf *GB28181Config) API_list(w http.ResponseWriter, r *http.Request) {
|
||||
sse := util.NewSSE(w, r.Context())
|
||||
|
||||
for {
|
||||
var list []*Device
|
||||
util.ReturnJson(func() (list []*Device) {
|
||||
Devices.Range(func(key, value interface{}) bool {
|
||||
device := value.(*Device)
|
||||
if time.Since(device.UpdateTime) > time.Duration(conf.RegisterValidity)*time.Second {
|
||||
if time.Since(device.UpdateTime) > conf.RegisterValidity {
|
||||
Devices.Delete(key)
|
||||
} else {
|
||||
list = append(list, device)
|
||||
}
|
||||
return true
|
||||
})
|
||||
sse.WriteJSON(list)
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
case <-sse.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}, time.Second*5, w, r)
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_records(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -39,37 +33,72 @@ func (conf *GB28181Config) API_records(w http.ResponseWriter, r *http.Request) {
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.QueryRecord(startTime, endTime))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_control(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.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_invite(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) //直播流已存在
|
||||
} else {
|
||||
w.WriteHeader(c.Invite(startTime, endTime))
|
||||
}
|
||||
channel := query.Get("channel")
|
||||
port, _ := strconv.Atoi(query.Get("mediaPort"))
|
||||
opt := InviteOptions{
|
||||
dump: query.Get("dump"),
|
||||
MediaPort: uint16(port),
|
||||
}
|
||||
opt.Validate(query.Get("startTime"), query.Get("endTime"))
|
||||
if c := FindChannel(id, channel); c == nil {
|
||||
http.NotFound(w, r)
|
||||
} else if opt.IsLive() && c.LivePublisher != nil {
|
||||
w.WriteHeader(304) //直播流已存在
|
||||
} else if code, err := c.Invite(opt); err == nil {
|
||||
w.WriteHeader(code)
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
http.Error(w, err.Error(), code)
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_replay(w http.ResponseWriter, r *http.Request) {
|
||||
dump := r.URL.Query().Get("dump")
|
||||
printOut := r.URL.Query().Get("print")
|
||||
if dump == "" {
|
||||
dump = conf.DumpPath
|
||||
}
|
||||
f, err := os.OpenFile(dump, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
} else {
|
||||
streamPath := dump
|
||||
if strings.HasPrefix(dump, "/") {
|
||||
streamPath = "replay" + dump
|
||||
} else {
|
||||
streamPath = "replay/" + dump
|
||||
}
|
||||
var pub GBPublisher
|
||||
pub.SetIO(f)
|
||||
if err = plugin.Publish(streamPath, &pub); err == nil {
|
||||
if printOut != "" {
|
||||
pub.dumpPrint = w
|
||||
pub.SetParentCtx(r.Context())
|
||||
err = pub.Replay(f)
|
||||
} else {
|
||||
go pub.Replay(f)
|
||||
w.Write([]byte("ok"))
|
||||
}
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +110,7 @@ func (conf *GB28181Config) API_bye(w http.ResponseWriter, r *http.Request) {
|
||||
if c := FindChannel(id, channel); c != nil {
|
||||
w.WriteHeader(c.Bye(live != "false"))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,13 +124,48 @@ func (conf *GB28181Config) API_position(w http.ResponseWriter, r *http.Request)
|
||||
//订阅间隔(单位:秒)
|
||||
interval := query.Get("interval")
|
||||
|
||||
expiresInt, _ := strconv.Atoi(expires)
|
||||
intervalInt, _ := strconv.Atoi(interval)
|
||||
expiresInt, err := time.ParseDuration(expires)
|
||||
if expires == "" || err != nil {
|
||||
expiresInt = conf.Position.Expires
|
||||
}
|
||||
intervalInt, err := time.ParseDuration(interval)
|
||||
if interval == "" || err != nil {
|
||||
intervalInt = conf.Position.Interval
|
||||
}
|
||||
|
||||
if v, ok := Devices.Load(id); ok {
|
||||
d := v.(*Device)
|
||||
w.WriteHeader(d.MobilePositionSubscribe(id, expiresInt, intervalInt))
|
||||
} else {
|
||||
w.WriteHeader(404)
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
type DevicePosition struct {
|
||||
ID string
|
||||
GpsTime time.Time //gps时间
|
||||
Longitude string //经度
|
||||
Latitude string //纬度
|
||||
}
|
||||
|
||||
func (conf *GB28181Config) API_get_position(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
//设备id
|
||||
id := query.Get("id")
|
||||
|
||||
util.ReturnJson(func() (list []*DevicePosition) {
|
||||
if id == "" {
|
||||
Devices.Range(func(key, value interface{}) bool {
|
||||
d := value.(*Device)
|
||||
if time.Since(d.GpsTime) <= conf.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
|
||||
}, conf.Position.Interval, w, r)
|
||||
}
|
||||
|
||||
137
server.go
Normal file → Executable file
137
server.go
Normal file → Executable file
@@ -8,12 +8,13 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pion/rtp/v2"
|
||||
"go.uber.org/zap"
|
||||
"m7s.live/engine/v4/util"
|
||||
"m7s.live/plugin/gb28181/v4/utils"
|
||||
|
||||
"github.com/ghettovoice/gosip"
|
||||
"github.com/ghettovoice/gosip/log"
|
||||
@@ -22,9 +23,55 @@ import (
|
||||
|
||||
var srv gosip.Server
|
||||
|
||||
type PortManager struct {
|
||||
recycle chan uint16
|
||||
max uint16
|
||||
pos uint16
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (pm *PortManager) Init(start, end uint16) {
|
||||
pm.pos = start
|
||||
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 io.EOF //TODO: 换一个Error
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return 0, io.EOF //TODO: 换一个Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Ignores map[string]struct{}
|
||||
publishers Publishers
|
||||
publishers util.Map[uint32, *GBPublisher]
|
||||
tcpPorts PortManager
|
||||
udpPorts PortManager
|
||||
}
|
||||
|
||||
const MaxRegisterCount = 3
|
||||
@@ -39,47 +86,38 @@ func FindChannel(deviceId string, channelId string) (c *Channel) {
|
||||
return
|
||||
}
|
||||
|
||||
type Publishers struct {
|
||||
data map[uint32]*GBPublisher
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (p *Publishers) Add(key uint32, pp *GBPublisher) {
|
||||
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) *GBPublisher {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
return p.data[key]
|
||||
}
|
||||
|
||||
func GetSipServer() *gosip.Server {
|
||||
return &srv
|
||||
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 (config *GB28181Config) startServer() {
|
||||
config.publishers.data = make(map[uint32]*GBPublisher)
|
||||
|
||||
plugin.Info(fmt.Sprint(aurora.Green("Server gb28181 start at"), aurora.BrightBlue(":"+strconv.Itoa(int(config.SipPort)))))
|
||||
logger := log.NewDefaultLogrusLogger().WithPrefix("GB SIP Server")
|
||||
config.publishers.Init()
|
||||
addr := "0.0.0.0:" + strconv.Itoa(int(config.SipPort))
|
||||
|
||||
logger := utils.NewZapLogger(plugin.Logger, "GB SIP Server", nil)
|
||||
logger.SetLevel(levelMap[config.LogLevel])
|
||||
// logger := log.NewDefaultLogrusLogger().WithPrefix("GB SIP Server")
|
||||
srvConf := gosip.ServerConfig{}
|
||||
|
||||
if config.SipIP != "" {
|
||||
srvConf.Host = config.SipIP
|
||||
}
|
||||
srv = gosip.NewServer(srvConf, nil, nil, logger)
|
||||
srv.OnRequest(sip.REGISTER, config.OnRegister)
|
||||
srv.OnRequest(sip.MESSAGE, config.OnMessage)
|
||||
srv.OnRequest(sip.NOTIFY, config.OnNotify)
|
||||
srv.OnRequest(sip.BYE, config.onBye)
|
||||
|
||||
addr := "0.0.0.0:" + strconv.Itoa(int(config.SipPort))
|
||||
go srv.Listen(strings.ToLower(config.SipNetwork), addr)
|
||||
err := srv.Listen(strings.ToLower(config.SipNetwork), addr)
|
||||
if err != nil {
|
||||
plugin.Logger.Error("gb28181 server listen", zap.Error(err))
|
||||
} else {
|
||||
plugin.Info(fmt.Sprint(aurora.Green("Server gb28181 start at"), aurora.BrightBlue(addr)))
|
||||
}
|
||||
|
||||
go config.startMediaServer()
|
||||
|
||||
@@ -90,9 +128,15 @@ func (config *GB28181Config) startServer() {
|
||||
|
||||
func (config *GB28181Config) startMediaServer() {
|
||||
if config.MediaNetwork == "tcp" {
|
||||
listenMediaTCP(config)
|
||||
config.tcpPorts.Init(config.MediaPortMin, config.MediaPortMax)
|
||||
if !config.tcpPorts.Valid {
|
||||
config.listenMediaTCP()
|
||||
}
|
||||
} else {
|
||||
listenMediaUDP(config)
|
||||
config.udpPorts.Init(config.MediaPortMin, config.MediaPortMax)
|
||||
if !config.udpPorts.Valid {
|
||||
config.listenMediaUDP()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,13 +156,13 @@ func processTcpMediaConn(config *GB28181Config, conn net.Conn) {
|
||||
}
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
plugin.Error("gb28181 decode rtp error:", zap.Error(err))
|
||||
} else if publisher := config.publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Err() == nil {
|
||||
} else if publisher := config.publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Publisher.Err() == nil {
|
||||
publisher.PushPS(&rtpPacket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listenMediaTCP(config *GB28181Config) {
|
||||
func (config *GB28181Config) listenMediaTCP() {
|
||||
addr := ":" + strconv.Itoa(int(config.MediaPort))
|
||||
mediaAddr, _ := net.ResolveTCPAddr("tcp", addr)
|
||||
listen, err := net.ListenTCP("tcp", mediaAddr)
|
||||
@@ -140,7 +184,7 @@ func listenMediaTCP(config *GB28181Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func listenMediaUDP(config *GB28181Config) {
|
||||
func (config *GB28181Config) listenMediaUDP() {
|
||||
var rtpPacket rtp.Packet
|
||||
networkBuffer := 1048576
|
||||
|
||||
@@ -155,13 +199,24 @@ func listenMediaUDP(config *GB28181Config) {
|
||||
bufUDP := make([]byte, networkBuffer)
|
||||
plugin.Info("Media udp server start.", zap.Uint16("port", config.MediaPort))
|
||||
defer plugin.Info("Media udp server stop", zap.Uint16("port", config.MediaPort))
|
||||
|
||||
dumpLen := make([]byte, 6)
|
||||
for n, _, err := conn.ReadFromUDP(bufUDP); err == nil; n, _, err = conn.ReadFromUDP(bufUDP) {
|
||||
ps := bufUDP[:n]
|
||||
if err := rtpPacket.Unmarshal(ps); err != nil {
|
||||
plugin.Error("Decode rtp error:", zap.Error(err))
|
||||
}
|
||||
if publisher := config.publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Err() == nil {
|
||||
if publisher := config.publishers.Get(rtpPacket.SSRC); publisher != nil && publisher.Publisher.Err() == nil {
|
||||
if publisher.dumpFile != nil {
|
||||
util.PutBE(dumpLen[:4], n)
|
||||
if publisher.lastReceive.IsZero() {
|
||||
util.PutBE(dumpLen[4:], 0)
|
||||
} else {
|
||||
util.PutBE(dumpLen[4:], uint16(time.Since(publisher.lastReceive).Milliseconds()))
|
||||
}
|
||||
publisher.lastReceive = time.Now()
|
||||
publisher.dumpFile.Write(dumpLen)
|
||||
publisher.dumpFile.Write(ps)
|
||||
}
|
||||
publisher.PushPS(&rtpPacket)
|
||||
}
|
||||
}
|
||||
@@ -183,7 +238,7 @@ func listenMediaUDP(config *GB28181Config) {
|
||||
// }
|
||||
|
||||
func removeBanDevice(config *GB28181Config) {
|
||||
t := time.NewTicker(time.Duration(config.RemoveBanInterval) * time.Second)
|
||||
t := time.NewTicker(config.RemoveBanInterval)
|
||||
for range t.C {
|
||||
DeviceRegisterCount.Range(func(key, value interface{}) bool {
|
||||
if value.(int) > MaxRegisterCount {
|
||||
|
||||
@@ -60,6 +60,7 @@ func (b *IOBuffer) ReadByte() (byte, error) {
|
||||
b.off++
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (b *IOBuffer) Reset() {
|
||||
b.buf = b.buf[:0]
|
||||
b.off = 0
|
||||
@@ -81,20 +82,24 @@ func (b *IOBuffer) tryGrowByReslice(n int) (int, bool) {
|
||||
var ErrTooLarge = errors.New("IOBuffer: too large")
|
||||
|
||||
func (b *IOBuffer) Write(p []byte) (n int, err error) {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
panic(ErrTooLarge)
|
||||
}
|
||||
}()
|
||||
l := len(p)
|
||||
oldLen := len(b.buf)
|
||||
m, ok := b.tryGrowByReslice(l)
|
||||
if !ok {
|
||||
buf := make([]byte, oldLen+l)
|
||||
copy(buf, b.buf[b.off:])
|
||||
m = oldLen - b.off
|
||||
b.off = 0
|
||||
b.buf = buf
|
||||
}
|
||||
return copy(b.buf[m:], p), nil
|
||||
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
|
||||
}
|
||||
|
||||
158
utils/log.go
Executable file
158
utils/log.go
Executable file
@@ -0,0 +1,158 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ghettovoice/gosip/log"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type ZapLogger struct {
|
||||
log *zap.Logger
|
||||
prefix string
|
||||
fields log.Fields
|
||||
sugared *zap.SugaredLogger
|
||||
level log.Level
|
||||
}
|
||||
|
||||
func NewZapLogger(log *zap.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 log.Fields) 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 log.Level) {
|
||||
l.level = level
|
||||
}
|
||||
287
utils/ps.go
287
utils/ps.go
@@ -4,33 +4,23 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pion/rtp/v2"
|
||||
)
|
||||
|
||||
//
|
||||
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
|
||||
PrivateStreamCode = 0x000001bd
|
||||
MEPGProgramEndCode = 0x000001b9
|
||||
|
||||
RTPHeaderLength int = 12
|
||||
@@ -53,6 +43,7 @@ var (
|
||||
type Pusher interface {
|
||||
PushVideo(uint32, uint32, []byte)
|
||||
PushAudio(uint32, []byte)
|
||||
PrintDump(string)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -113,133 +104,176 @@ type Pusher interface {
|
||||
https://github.com/videolan/vlc/blob/master/modules/demux/mpeg
|
||||
*/
|
||||
type DecPSPackage struct {
|
||||
systemClockReferenceBase uint64
|
||||
systemClockReferenceExtension uint64
|
||||
programMuxRate uint32
|
||||
// systemClockReferenceBase uint64
|
||||
// systemClockReferenceExtension uint64
|
||||
// programMuxRate uint32
|
||||
|
||||
VideoStreamType uint32
|
||||
AudioStreamType uint32
|
||||
IOBuffer
|
||||
Payload []byte
|
||||
PTS uint32
|
||||
DTS uint32
|
||||
Payload []byte
|
||||
videoBuffer []byte
|
||||
audioBuffer []byte
|
||||
aPTS uint32
|
||||
aDTS uint32
|
||||
vPTS uint32
|
||||
vDTS uint32
|
||||
Pusher
|
||||
}
|
||||
|
||||
func (dec *DecPSPackage) clean() {
|
||||
dec.systemClockReferenceBase = 0
|
||||
dec.systemClockReferenceExtension = 0
|
||||
dec.programMuxRate = 0
|
||||
dec.Payload = nil
|
||||
dec.PTS = 0
|
||||
dec.DTS = 0
|
||||
func NewDecPSPackage(p Pusher) *DecPSPackage {
|
||||
p.PrintDump("<tr><td>")
|
||||
return &DecPSPackage{
|
||||
Pusher: p,
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
if l := int(payloadlen); dec.Len() >= l {
|
||||
return dec.Next(l), nil
|
||||
}
|
||||
return dec.Next(dec.Len()), io.EOF
|
||||
}
|
||||
|
||||
//read the buffer and push video or audio
|
||||
func (dec *DecPSPackage) Read(ts uint32, pusher Pusher) error {
|
||||
again:
|
||||
dec.clean()
|
||||
if err := dec.Skip(9); err != nil {
|
||||
return err
|
||||
}
|
||||
// Drop 由于丢包引起的必须丢弃的数据
|
||||
func (dec *DecPSPackage) Drop() {
|
||||
dec.Reset()
|
||||
dec.videoBuffer = nil
|
||||
dec.audioBuffer = nil
|
||||
dec.Payload = nil
|
||||
}
|
||||
|
||||
psl, err := dec.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
func (dec *DecPSPackage) Feed(rtp *rtp.Packet) (err error) {
|
||||
ps := rtp.Payload
|
||||
if len(ps) < 4 {
|
||||
return nil
|
||||
}
|
||||
psl &= 0x07
|
||||
if err = dec.Skip(int(psl)); err != nil {
|
||||
return err
|
||||
}
|
||||
var video []byte
|
||||
var nextStartCode uint32
|
||||
loop:
|
||||
for err == nil {
|
||||
if nextStartCode, err = dec.Uint32(); err != nil {
|
||||
break
|
||||
// println(binary.BigEndian.Uint32(ps))
|
||||
switch binary.BigEndian.Uint32(ps) {
|
||||
case StartCodePS, StartCodeSYS, StartCodeMAP, StartCodeVideo, StartCodeAudio, PrivateStreamCode, MEPGProgramEndCode:
|
||||
defer dec.Write(ps)
|
||||
if dec.Len() >= 4 {
|
||||
//说明需要处理PS包,处理完后,清空缓存
|
||||
defer dec.Reset()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
switch nextStartCode {
|
||||
case StartCodeSYS:
|
||||
dec.ReadPayload()
|
||||
//err = dec.decSystemHeader()
|
||||
case StartCodeMAP:
|
||||
err = dec.decProgramStreamMap()
|
||||
case StartCodeVideo:
|
||||
if err = dec.decPESPacket(); err == nil {
|
||||
if len(video) == 0 {
|
||||
if dec.PTS == 0 {
|
||||
dec.PTS = ts
|
||||
}
|
||||
// if dec.DTS == 0 {
|
||||
// dec.DTS = dec.PTS
|
||||
// }
|
||||
}
|
||||
video = append(video, dec.Payload...)
|
||||
} else {
|
||||
fmt.Println("video", err)
|
||||
default:
|
||||
// 说明是中间数据,直接写入缓存,否则数据不合法需要丢弃
|
||||
if dec.Len() > 0 {
|
||||
dec.Write(ps)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for dec.Len() >= 4 {
|
||||
code, _ := dec.Uint32()
|
||||
// println("code:", code)
|
||||
switch code {
|
||||
case StartCodePS:
|
||||
dec.PrintDump("</td></tr><tr><td>")
|
||||
if len(dec.audioBuffer) > 0 {
|
||||
dec.PushAudio(dec.aPTS, dec.audioBuffer)
|
||||
dec.audioBuffer = nil
|
||||
}
|
||||
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
|
||||
}
|
||||
if len(dec.videoBuffer) > 0 {
|
||||
dec.PushVideo(dec.vPTS, dec.vDTS, dec.videoBuffer)
|
||||
dec.videoBuffer = nil
|
||||
}
|
||||
case StartCodeSYS:
|
||||
dec.PrintDump("</td><td>[sys]")
|
||||
dec.ReadPayload()
|
||||
case StartCodeMAP:
|
||||
dec.decProgramStreamMap()
|
||||
dec.PrintDump("</td><td>[map]")
|
||||
case StartCodeVideo:
|
||||
if dec.videoBuffer == nil {
|
||||
dec.PrintDump("</td><td>")
|
||||
}
|
||||
dec.decPESPacket(&dec.vPTS, &dec.vDTS)
|
||||
dec.videoBuffer = append(dec.videoBuffer, dec.Payload...)
|
||||
// if err != nil {
|
||||
//说明还有后续数据,需要继续处理
|
||||
// println(rtp.SequenceNumber)
|
||||
// }
|
||||
dec.PrintDump("[video]")
|
||||
case StartCodeAudio:
|
||||
if err = dec.decPESPacket(); err == nil {
|
||||
ts := ts / 90
|
||||
if dec.PTS != 0 {
|
||||
ts = dec.PTS / 90
|
||||
}
|
||||
pusher.PushAudio(ts, dec.Payload)
|
||||
|
||||
if dec.audioBuffer == nil {
|
||||
dec.PrintDump("</td><td>")
|
||||
}
|
||||
if err = dec.decPESPacket(&dec.aPTS, &dec.aDTS); err == nil {
|
||||
dec.audioBuffer = append(dec.audioBuffer, dec.Payload...)
|
||||
dec.PrintDump("[audio]")
|
||||
} else {
|
||||
fmt.Println("audio", err)
|
||||
}
|
||||
case StartCodePS:
|
||||
break loop
|
||||
default:
|
||||
case PrivateStreamCode:
|
||||
dec.ReadPayload()
|
||||
dec.PrintDump("</td></tr><tr><td>[ac3]")
|
||||
case MEPGProgramEndCode:
|
||||
dec.PrintDump("</td></tr>")
|
||||
return io.EOF
|
||||
default:
|
||||
fmt.Println("unknow code", code)
|
||||
return ErrParsePakcet
|
||||
}
|
||||
}
|
||||
if len(video) > 0 {
|
||||
pusher.PushVideo(dec.PTS, dec.DTS, video)
|
||||
}
|
||||
if nextStartCode == StartCodePS {
|
||||
fmt.Println(aurora.Red("StartCodePS recursion..."), err)
|
||||
goto again
|
||||
}
|
||||
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) 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()
|
||||
@@ -277,11 +311,9 @@ func (dec *DecPSPackage) decProgramStreamMap() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *DecPSPackage) decPESPacket() error {
|
||||
func (dec *DecPSPackage) decPESPacket(pts *uint32,dts *uint32) error {
|
||||
payload, err := dec.ReadPayload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(payload) < 4 {
|
||||
return errors.New("not enough data")
|
||||
}
|
||||
@@ -289,26 +321,23 @@ func (dec *DecPSPackage) decPESPacket() error {
|
||||
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
|
||||
*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
|
||||
*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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user