mirror of
https://github.com/Monibuca/plugin-rtsp.git
synced 2025-09-27 12:02:20 +08:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7e61ba71f7 | ||
![]() |
d6384dcbd5 | ||
![]() |
2159a6fd9b | ||
![]() |
02f3e91085 | ||
![]() |
7f40078b50 | ||
![]() |
bb563d64c7 | ||
![]() |
f7cb146b89 | ||
![]() |
9bb49cb9f7 | ||
![]() |
087d1aab4d | ||
![]() |
f949464328 | ||
![]() |
d89f1e2405 | ||
![]() |
1d3fbfc20b | ||
![]() |
fd64a69a12 | ||
![]() |
0e4406ad14 | ||
![]() |
22f33886a9 | ||
![]() |
8b1892209d | ||
![]() |
2e9cf9a4ca | ||
![]() |
67da93d8e2 | ||
![]() |
cb733b368f | ||
![]() |
fadeccddab | ||
![]() |
93df7632a6 | ||
![]() |
53c4788df2 | ||
![]() |
f5bdd6a298 | ||
![]() |
eaddc60775 |
@@ -13,12 +13,15 @@ ListenAddr = ":554"
|
||||
BufferLength = 2048
|
||||
AutoPull = false
|
||||
RemoteAddr = "rtsp://localhost/${streamPath}"
|
||||
[[RTSP.AutoPullList]]
|
||||
URL = "rtsp://admin:admin@192.168.1.212:554/cam/realmonitor?channel=1&subtype=1"
|
||||
StreamPath = "live/rtsp"
|
||||
```
|
||||
- ListenAddr 是监听端口,可以将rtsp流推到Monibuca中
|
||||
- BufferLength是指解析拉取的rtp包的缓冲大小
|
||||
- AutoPull是指当有用户订阅一个新流的时候自动向远程拉流转发
|
||||
- RemoteAddr 指远程拉流地址,其中${streamPath}是占位符,实际使用流路径替换。
|
||||
|
||||
- AutoPullList 是一个数组,如果配置了该数组,则会在程序启动时自动启动拉流,StreamPath一定要是唯一的,不能重复
|
||||
|
||||
## 使用方法(拉流转发)
|
||||
```go
|
||||
|
201
client.go
201
client.go
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -16,11 +17,12 @@ import (
|
||||
"time"
|
||||
|
||||
. "github.com/Monibuca/engine/v2"
|
||||
. "github.com/Monibuca/plugin-rtp"
|
||||
)
|
||||
|
||||
// PullStream 从外部拉流
|
||||
func (rtsp *RTSP) PullStream(streamPath string, rtspUrl string) (err error) {
|
||||
if result := rtsp.Publisher.Publish(streamPath); result {
|
||||
if result := rtsp.Publish(streamPath); result {
|
||||
rtsp.Stream.Type = "RTSP"
|
||||
rtsp.RTSPInfo.StreamInfo = &rtsp.Stream.StreamInfo
|
||||
rtsp.TransType = TRANS_TYPE_TCP
|
||||
@@ -29,6 +31,7 @@ func (rtsp *RTSP) PullStream(streamPath string, rtspUrl string) (err error) {
|
||||
rtsp.aRTPChannel = 2
|
||||
rtsp.aRTPControlChannel = 3
|
||||
rtsp.URL = rtspUrl
|
||||
rtsp.UDPServer = &UDPServer{Session: rtsp}
|
||||
if err = rtsp.requestStream(); err != nil {
|
||||
Println(err)
|
||||
rtsp.Close()
|
||||
@@ -77,6 +80,18 @@ func DigestAuth(authLine string, method string, URL string) (string, error) {
|
||||
Authorization := fmt.Sprintf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"", username, realm, nonce, l.String(), response)
|
||||
return Authorization, nil
|
||||
}
|
||||
// auth Basic验证
|
||||
func BasicAuth(authLine string, method string, URL string) (string, error) {
|
||||
l, err := url.Parse(URL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Url parse error:%v,%v", URL, err)
|
||||
}
|
||||
username := l.User.Username()
|
||||
password, _ := l.User.Password()
|
||||
userAndpass := []byte(username + ":" + password)
|
||||
Authorization := fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString(userAndpass))
|
||||
return Authorization, nil
|
||||
}
|
||||
func (client *RTSP) checkAuth(method string, resp *Response) (string, error) {
|
||||
if resp.StatusCode == 401 {
|
||||
// need auth.
|
||||
@@ -90,8 +105,7 @@ func (client *RTSP) checkAuth(method string, resp *Response) (string, error) {
|
||||
client.authLine = authLine
|
||||
return DigestAuth(authLine, method, client.URL)
|
||||
} else if strings.IndexAny(authLine, "Basic") == 0 {
|
||||
// not support yet
|
||||
// TODO..
|
||||
return BasicAuth(authLine, method, client.URL)
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("auth error")
|
||||
@@ -101,9 +115,7 @@ func (client *RTSP) checkAuth(method string, resp *Response) (string, error) {
|
||||
client.authLine = authLine
|
||||
return DigestAuth(authLine, method, client.URL)
|
||||
} else if strings.IndexAny(authLine, "Basic") == 0 {
|
||||
// not support yet
|
||||
// TODO..
|
||||
return "", fmt.Errorf("not support Basic auth yet")
|
||||
return BasicAuth(authLine, method, client.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,7 +155,7 @@ func (client *RTSP) requestStream() (err error) {
|
||||
client.connRW = bufio.NewReadWriter(bufio.NewReaderSize(&timeoutConn, networkBuffer), bufio.NewWriterSize(&timeoutConn, networkBuffer))
|
||||
|
||||
headers := make(map[string]string)
|
||||
headers["Require"] = "implicit-play"
|
||||
//headers["Require"] = "implicit-play"
|
||||
// An OPTIONS request returns the request types the server will accept.
|
||||
resp, err := client.Request("OPTIONS", headers)
|
||||
if err != nil {
|
||||
@@ -188,73 +200,60 @@ func (client *RTSP) requestStream() (err error) {
|
||||
}
|
||||
client.SDPRaw = resp.Body
|
||||
client.SDPMap = ParseSDP(client.SDPRaw)
|
||||
client.VSdp, client.HasVideo = client.SDPMap["video"]
|
||||
client.ASdp, client.HasAudio = client.SDPMap["audio"]
|
||||
session := ""
|
||||
if videoInfo, ok := client.SDPMap["video"]; ok {
|
||||
client.VControl = videoInfo.Control
|
||||
client.VCodec = videoInfo.Codec
|
||||
client.SPS = videoInfo.SpropParameterSets[0]
|
||||
client.PPS = videoInfo.SpropParameterSets[1]
|
||||
var _url = ""
|
||||
if strings.Index(strings.ToLower(client.VControl), "rtsp://") == 0 {
|
||||
_url = client.VControl
|
||||
} else {
|
||||
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(client.VControl, "/")
|
||||
}
|
||||
otherChannel := 4
|
||||
for t, sdpInfo := range client.SDPMap {
|
||||
headers = make(map[string]string)
|
||||
if client.TransType == TRANS_TYPE_TCP {
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.vRTPChannel, client.vRTPControlChannel)
|
||||
} else {
|
||||
if client.UDPServer == nil {
|
||||
client.UDPServer = &UDPServer{Session: client}
|
||||
}
|
||||
//RTP/AVP;unicast;client_port=64864-64865
|
||||
err = client.UDPServer.SetupVideo()
|
||||
if err != nil {
|
||||
Printf("Setup video err.%v", err)
|
||||
return err
|
||||
}
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.VPort, client.UDPServer.VControlPort)
|
||||
client.Conn.timeout = 0 // UDP ignore timeout
|
||||
}
|
||||
if session != "" {
|
||||
headers["Session"] = session
|
||||
}
|
||||
Printf("Parse DESCRIBE response, VIDEO VControl:%s, VCode:%s, url:%s,Session:%s,vRTPChannel:%d,vRTPControlChannel:%d", client.VControl, client.VCodec, _url, session, client.vRTPChannel, client.vRTPControlChannel)
|
||||
if resp, err = client.RequestWithPath("SETUP", _url, headers, true); err != nil {
|
||||
return err
|
||||
var _url = sdpInfo.Control
|
||||
if !strings.HasPrefix(strings.ToLower(sdpInfo.Control), "rtsp://") {
|
||||
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(sdpInfo.Control, "/")
|
||||
}
|
||||
session, _ = resp.Header["Session"].(string)
|
||||
session = strings.Split(session, ";")[0]
|
||||
}
|
||||
if audioInfo, ok := client.SDPMap["audio"]; ok {
|
||||
client.AControl = audioInfo.Control
|
||||
client.ACodec = audioInfo.Codec
|
||||
client.AudioSpecificConfig = audioInfo.Config
|
||||
var _url = ""
|
||||
if strings.Index(strings.ToLower(client.AControl), "rtsp://") == 0 {
|
||||
_url = client.AControl
|
||||
} else {
|
||||
_url = strings.TrimRight(client.URL, "/") + "/" + strings.TrimLeft(client.AControl, "/")
|
||||
}
|
||||
headers = make(map[string]string)
|
||||
if client.TransType == TRANS_TYPE_TCP {
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.aRTPChannel, client.aRTPControlChannel)
|
||||
} else {
|
||||
if client.UDPServer == nil {
|
||||
client.UDPServer = &UDPServer{Session: client}
|
||||
switch t {
|
||||
case "video":
|
||||
if len(sdpInfo.SpropParameterSets) > 1 {
|
||||
client.WriteSPS(sdpInfo.SpropParameterSets[0])
|
||||
client.WritePPS(sdpInfo.SpropParameterSets[1])
|
||||
}
|
||||
err = client.UDPServer.SetupAudio()
|
||||
if err != nil {
|
||||
Printf("Setup audio err.%v", err)
|
||||
return err
|
||||
if client.TransType == TRANS_TYPE_TCP {
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.vRTPChannel, client.vRTPControlChannel)
|
||||
} else {
|
||||
//RTP/AVP;unicast;client_port=64864-64865
|
||||
if err = client.UDPServer.SetupVideo(); err != nil {
|
||||
Printf("Setup video err.%v", err)
|
||||
return err
|
||||
}
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.VPort, client.UDPServer.VControlPort)
|
||||
client.Conn.timeout = 0 // UDP ignore timeout
|
||||
}
|
||||
case "audio":
|
||||
if len(sdpInfo.Config) > 0 {
|
||||
client.WriteASC(sdpInfo.Config)
|
||||
}else{
|
||||
client.setAudioFormat()
|
||||
}
|
||||
if client.TransType == TRANS_TYPE_TCP {
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", client.aRTPChannel, client.aRTPControlChannel)
|
||||
} else {
|
||||
if err = client.UDPServer.SetupAudio(); err != nil {
|
||||
Printf("Setup audio err.%v", err)
|
||||
return err
|
||||
}
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.APort, client.UDPServer.AControlPort)
|
||||
client.Conn.timeout = 0 // UDP ignore timeout
|
||||
}
|
||||
default:
|
||||
if client.TransType == TRANS_TYPE_TCP {
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", otherChannel, otherChannel+1)
|
||||
otherChannel += 2
|
||||
} else {
|
||||
//TODO: UDP support
|
||||
}
|
||||
headers["Transport"] = fmt.Sprintf("RTP/AVP/UDP;unicast;client_port=%d-%d", client.UDPServer.APort, client.UDPServer.AControlPort)
|
||||
client.Conn.timeout = 0 // UDP ignore timeout
|
||||
}
|
||||
if session != "" {
|
||||
headers["Session"] = session
|
||||
}
|
||||
Printf("Parse DESCRIBE response, AUDIO AControl:%s, ACodec:%s, url:%s,Session:%s, aRTPChannel:%d,aRTPControlChannel:%d", client.AControl, client.ACodec, _url, session, client.aRTPChannel, client.aRTPControlChannel)
|
||||
if resp, err = client.RequestWithPath("SETUP", _url, headers, true); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -264,27 +263,51 @@ func (client *RTSP) requestStream() (err error) {
|
||||
headers = make(map[string]string)
|
||||
if session != "" {
|
||||
headers["Session"] = session
|
||||
client.Session = session
|
||||
}
|
||||
resp, err = client.Request("PLAY", headers)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *RTSP) startStream() {
|
||||
//startTime := time.Now()
|
||||
startTime := time.Now()
|
||||
//loggerTime := time.Now().Add(-10 * time.Second)
|
||||
defer client.Stop()
|
||||
defer func() {
|
||||
if client.Err() == nil && config.Reconnect {
|
||||
Printf("reconnecting:%s", client.URL)
|
||||
client.RTSPClientInfo = RTSPClientInfo{}
|
||||
if err := client.requestStream(); err != nil {
|
||||
t := time.NewTicker(time.Second * 5)
|
||||
for {
|
||||
Printf("reconnecting:%s in 5 seconds", client.URL)
|
||||
select {
|
||||
case <-client.Done():
|
||||
client.Stop()
|
||||
return
|
||||
case <-t.C:
|
||||
if err = client.requestStream(); err == nil {
|
||||
go client.startStream()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
go client.startStream()
|
||||
}
|
||||
} else {
|
||||
client.Stop()
|
||||
}
|
||||
}()
|
||||
for client.Err() == nil {
|
||||
//if client.OptionIntervalMillis > 0 {
|
||||
// if time.Since(startTime) > time.Duration(client.OptionIntervalMillis)*time.Millisecond {
|
||||
// startTime = time.Now()
|
||||
// headers := make(map[string]string)
|
||||
// headers["Require"] = "implicit-play"
|
||||
// // An OPTIONS request returns the request types the server will accept.
|
||||
// if err := client.RequestNoResp("OPTIONS", headers); err != nil {
|
||||
// // ignore...
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
if time.Since(startTime) > time.Minute {
|
||||
startTime = time.Now()
|
||||
headers := make(map[string]string)
|
||||
headers["Require"] = "implicit-play"
|
||||
// An OPTIONS request returns the request types the server will accept.
|
||||
if err := client.RequestNoResp("GET_PARAMETER", headers); err != nil {
|
||||
// ignore...
|
||||
}
|
||||
}
|
||||
b, err := client.connRW.ReadByte()
|
||||
if err != nil {
|
||||
Printf("client.connRW.ReadByte err:%v", err)
|
||||
@@ -307,38 +330,30 @@ func (client *RTSP) startStream() {
|
||||
Printf("io.ReadFull err:%v", err)
|
||||
return
|
||||
}
|
||||
rtpBuf := content
|
||||
var pack *RTPPack
|
||||
|
||||
switch channel {
|
||||
case client.aRTPChannel:
|
||||
pack = &RTPPack{
|
||||
Type: RTP_TYPE_AUDIO,
|
||||
Buffer: rtpBuf,
|
||||
Type: RTP_TYPE_AUDIO,
|
||||
}
|
||||
case client.aRTPControlChannel:
|
||||
pack = &RTPPack{
|
||||
Type: RTP_TYPE_AUDIOCONTROL,
|
||||
Buffer: rtpBuf,
|
||||
Type: RTP_TYPE_AUDIOCONTROL,
|
||||
}
|
||||
case client.vRTPChannel:
|
||||
pack = &RTPPack{
|
||||
Type: RTP_TYPE_VIDEO,
|
||||
Buffer: rtpBuf,
|
||||
Type: RTP_TYPE_VIDEO,
|
||||
}
|
||||
case client.vRTPControlChannel:
|
||||
pack = &RTPPack{
|
||||
Type: RTP_TYPE_VIDEOCONTROL,
|
||||
Buffer: rtpBuf,
|
||||
Type: RTP_TYPE_VIDEOCONTROL,
|
||||
}
|
||||
default:
|
||||
Printf("unknow rtp pack type, channel:%v", channel)
|
||||
continue
|
||||
}
|
||||
if pack == nil {
|
||||
Printf("session tcp got nil rtp pack")
|
||||
continue
|
||||
}
|
||||
|
||||
pack.Unmarshal(content)
|
||||
//if client.debugLogEnable {
|
||||
// rtp := ParseRTP(pack.Buffer)
|
||||
// if rtp != nil {
|
||||
@@ -357,7 +372,7 @@ func (client *RTSP) startStream() {
|
||||
//}
|
||||
|
||||
client.InBytes += int(length + 4)
|
||||
client.handleRTP(pack)
|
||||
client.PushPack(pack)
|
||||
|
||||
default: // rtsp
|
||||
builder := bytes.Buffer{}
|
||||
|
14
go.mod
14
go.mod
@@ -3,12 +3,12 @@ module github.com/Monibuca/plugin-rtsp
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Monibuca/engine/v2 v2.0.0
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
|
||||
github.com/gobwas/pool v0.2.0 // indirect
|
||||
github.com/gobwas/ws v1.0.3 // indirect
|
||||
github.com/jinzhu/gorm v1.9.12 // indirect
|
||||
github.com/pixelbender/go-sdp v1.0.0
|
||||
github.com/reactivex/rxgo v1.0.0 // indirect
|
||||
github.com/Monibuca/engine/v2 v2.2.2
|
||||
github.com/Monibuca/plugin-rtp v1.0.0
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||
github.com/mattn/go-colorable v0.1.7 // indirect
|
||||
github.com/pion/rtp v1.6.0 // indirect
|
||||
github.com/shirou/gopsutil v2.20.7+incompatible // indirect
|
||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
||||
golang.org/x/sys v0.0.0-20200828161417-c663848e9a16 // indirect
|
||||
)
|
||||
|
83
go.sum
83
go.sum
@@ -1,87 +1,68 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/EasyDarwin/EasyDarwin v8.1.0+incompatible h1:Rr8dRbZtcJhiJvGx5Vs7IENM6RUUwGkZiIj5+WrNhm8=
|
||||
github.com/EasyDarwin/EasyDarwin v8.1.0+incompatible/go.mod h1:xnmC+Q2+wugEDpQGxivSFNYPOhmNlIQHBfl0hMeriSU=
|
||||
github.com/Monibuca/engine v1.2.1 h1:TJmC6eZA1lR1MScWgempZLiEZD4T6aY/nn/rlQ9UdK8=
|
||||
github.com/Monibuca/engine v1.2.1/go.mod h1:WbDkXENLjcPjyjCR1Mix1GA+uAlwORkv/+8aMVrDX2g=
|
||||
github.com/Monibuca/engine v1.2.2 h1:hNjsrZpOmui8lYhgCJ5ltJU8g/k0Rrdysx2tHNGGnbI=
|
||||
github.com/Monibuca/engine/v2 v2.0.0 h1:8FjaScrtN8QdbcxO9zZYABMC0Re3I1O1T4p94zAXYb0=
|
||||
github.com/Monibuca/engine/v2 v2.0.0/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.1.0 h1:pHeDCEFDusKFsZLpconYj8U5LCaWApnjd+yQRHYgQsQ=
|
||||
github.com/Monibuca/engine/v2 v2.1.0/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.1.2 h1:7dUrHJAPEtvGFOO4GsKGjfMCmcbMrtLyYQ7WoK5EpG0=
|
||||
github.com/Monibuca/engine/v2 v2.1.2/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.1.9 h1:IulMIeP24qv8xWaI+tcg233Y7w3mCaLXxt4iQaVpT7s=
|
||||
github.com/Monibuca/engine/v2 v2.1.9/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.2.0 h1:A4SyWwzVLegd8Oa6LfSW3LpNfBmWq+MHJJLO55gvaYI=
|
||||
github.com/Monibuca/engine/v2 v2.2.0/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.2.1 h1:I9mcnj9ZABl974n+HKGWe4pFcn9z8kETLVpD1fx2zEI=
|
||||
github.com/Monibuca/engine/v2 v2.2.1/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/engine/v2 v2.2.2 h1:ho5M3aFW9Mlj9Lb56Qvk0m+9L8yWc7RhwPh8dRWAeBk=
|
||||
github.com/Monibuca/engine/v2 v2.2.2/go.mod h1:34EYjjV15G6myuHOKaJkO7y5tJ1Arq/NfC9Weacr2mc=
|
||||
github.com/Monibuca/plugin-rtp v0.0.0-20200531014802-504413c0dfcb h1:CnmoQ8XsWxs/6mulbQfTGUa8cPr6c/3bkkTsNozRBwE=
|
||||
github.com/Monibuca/plugin-rtp v0.0.0-20200531014802-504413c0dfcb/go.mod h1:8HxBilkF835Lepe/DLUCjaw1mRiu3MxTDsG7g9UcfZA=
|
||||
github.com/Monibuca/plugin-rtp v1.0.0 h1:yksNsIIGxoKX8UZirkAUK+mGZ/XoEeS2vqbIqtqXyCg=
|
||||
github.com/Monibuca/plugin-rtp v1.0.0/go.mod h1:0xkNm23a/BjVnEMz1zXyOqfEjoVmGe3PJqPNF1KyFGc=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/falconray0704/gortmp v0.0.0-20170613085150-e3f9bb02c7c8 h1:Bkx+0neYCcHW7BUeVCbR2GOn47NesdImh8nHHOKccD4=
|
||||
github.com/falconray0704/gortmp v0.0.0-20170613085150-e3f9bb02c7c8/go.mod h1:/JBZajtCDe9Z4j84v5QWo4PLn1K6jcBHh6qXN/bm/vw=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478 h1:Db9StoJ6RZN3YttC0Pm0I4Y5izITRYch3RMbT59BYN0=
|
||||
github.com/funny/slab v0.0.0-20180511031532-b1fad5e5d478/go.mod h1:0j1+svBH8ABEIPdUP0AIg4qedsybnXGJBakCEw8cfoo=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500 h1:Z0r1CZnoIWFB/Uiwh1BU5FYmuFe6L5NPi6XWQEmsTRg=
|
||||
github.com/funny/utest v0.0.0-20161029064919-43870a374500/go.mod h1:mUn39tBov9jKnTWV1RlOYoNzxdBFHiSzXWdY1FoNGGg=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.3 h1:ZOigqf7iBxkA4jdQ3am7ATzdlOFp9YzA6NmuvEEZc9g=
|
||||
github.com/gobwas/ws v1.0.3/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
|
||||
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
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/mask-pp/rtp-ps v1.0.0 h1:JFxuJL9N+gD1ldgJlAy3b7rYfY8wAVHi9ODNmdP4+EE=
|
||||
github.com/mask-pp/rtp-ps v1.0.0/go.mod h1:jCxsZ2G7z/jX+aqFypEWMePnhNrfnUiXUEKm6Xp0vgU=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw=
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/pixelbender/go-sdp v1.0.0 h1:hLP2ALBN4sLpgp2r3EDcFUSN3AyOkg1jonuWEJniotY=
|
||||
github.com/pixelbender/go-sdp v1.0.0/go.mod h1:6IBlz9+BrUHoFTea7gcp4S54khtOhjCW/nVDLhmZBAs=
|
||||
github.com/pion/randutil v0.0.0 h1:aLWLVhTG2jzoD25F0OlW6nXvXrjoGwiXq2Sz7j7NzL0=
|
||||
github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtp v1.5.4 h1:PuNg6xqV3brIUihatcKZj1YDUs+M45L0ZbrZWYtkDxY=
|
||||
github.com/pion/rtp v1.5.4/go.mod h1:bg60AL5GotNOlYZsqycbhDtEV3TkfbpXG0KBiUq29Mg=
|
||||
github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
|
||||
github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
|
||||
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/reactivex/rxgo v1.0.0 h1:qpT8/kVwAJDSeGsqx4oUXxgk3UCtAq/EreBGWYRxEcA=
|
||||
github.com/reactivex/rxgo v1.0.0/go.mod h1:/S1ygE20oE1BvZGIwd3fXx/m6s6pOX5G6zmXg9ninlQ=
|
||||
github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY=
|
||||
github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v2.20.7+incompatible h1:Ymv4OD12d6zm+2yONe39VSmp2XooJe8za7ngOLW/o/w=
|
||||
github.com/shirou/gopsutil v2.20.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
|
||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
|
||||
github.com/zhangpeihao/goamf v0.0.0-20140409082417-3ff2c19514a8/go.mod h1:RZd/IqzNpFANwOB9rVmsnAYpo/6KesK4PqrN1a5cRgg=
|
||||
github.com/zhangpeihao/log v0.0.0-20170117094621-62e921e41859/go.mod h1:OAvmouyIV28taMw4SC4+hSnouObQqQkTQNOhU3Zowl0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
golang.org/x/sys v0.0.0-20200828161417-c663848e9a16 h1:54u1berWyLujz9htI1BHtZpcCEYaYNUSDFLXMNDd7To=
|
||||
golang.org/x/sys v0.0.0-20200828161417-c663848e9a16/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
|
193
main.go
193
main.go
@@ -2,7 +2,6 @@ package rtsp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
@@ -12,18 +11,23 @@ import (
|
||||
"time"
|
||||
|
||||
. "github.com/Monibuca/engine/v2"
|
||||
. "github.com/Monibuca/engine/v2/avformat"
|
||||
"github.com/Monibuca/engine/v2/util"
|
||||
. "github.com/Monibuca/plugin-rtp"
|
||||
"github.com/teris-io/shortid"
|
||||
)
|
||||
|
||||
var collection = sync.Map{}
|
||||
var collection sync.Map
|
||||
var config = struct {
|
||||
ListenAddr string
|
||||
AutoPull bool
|
||||
RemoteAddr string
|
||||
Timeout int
|
||||
}{":554", false, "rtsp://localhost/${streamPath}", 0}
|
||||
ListenAddr string
|
||||
AutoPull bool
|
||||
RemoteAddr string
|
||||
Timeout int
|
||||
Reconnect bool
|
||||
AutoPullList []*struct {
|
||||
URL string
|
||||
StreamPath string
|
||||
}
|
||||
}{":554", false, "rtsp://localhost/${streamPath}", 0, false, nil}
|
||||
|
||||
func init() {
|
||||
InstallPlugin(&PluginConfig{
|
||||
@@ -67,6 +71,13 @@ func runPlugin() {
|
||||
w.Write([]byte(fmt.Sprintf(`{"code":1,"msg":"%s"}`, err.Error())))
|
||||
}
|
||||
})
|
||||
if len(config.AutoPullList) > 0 {
|
||||
for _, info := range config.AutoPullList {
|
||||
if err := new(RTSP).PullStream(info.StreamPath, info.URL); err != nil {
|
||||
Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if config.ListenAddr != "" {
|
||||
log.Fatal(ListenRtsp(config.ListenAddr))
|
||||
}
|
||||
@@ -118,7 +129,7 @@ func ListenRtsp(addr string) error {
|
||||
}
|
||||
|
||||
type RTSP struct {
|
||||
Publisher
|
||||
RTP
|
||||
RTSPInfo
|
||||
RTSPClientInfo
|
||||
ID string
|
||||
@@ -131,27 +142,35 @@ type RTSP struct {
|
||||
SDPMap map[string]*SDPInfo
|
||||
nonce string
|
||||
closeOld bool
|
||||
AControl string
|
||||
VControl string
|
||||
ACodec string
|
||||
VCodec string
|
||||
avcsent bool
|
||||
ASdp *SDPInfo
|
||||
VSdp *SDPInfo
|
||||
aacsent bool
|
||||
Timeout int
|
||||
// stats info
|
||||
fuBuffer []byte
|
||||
//tcp channels
|
||||
aRTPChannel int
|
||||
aRTPControlChannel int
|
||||
vRTPChannel int
|
||||
vRTPControlChannel int
|
||||
UDPServer *UDPServer
|
||||
UDPClient *UDPClient
|
||||
SPS []byte
|
||||
PPS []byte
|
||||
AudioSpecificConfig []byte
|
||||
Auth func(string) string
|
||||
aRTPChannel int
|
||||
aRTPControlChannel int
|
||||
vRTPChannel int
|
||||
vRTPControlChannel int
|
||||
UDPServer *UDPServer
|
||||
UDPClient *UDPClient
|
||||
Auth func(string) string
|
||||
}
|
||||
|
||||
func (rtsp *RTSP) setAudioFormat() {
|
||||
switch rtsp.ASdp.Codec {
|
||||
case "aac":
|
||||
rtsp.AudioInfo.SoundFormat = 10
|
||||
case "pcma":
|
||||
rtsp.AudioInfo.SoundFormat = 7
|
||||
rtsp.AudioInfo.SoundRate = rtsp.ASdp.TimeScale
|
||||
rtsp.AudioInfo.SoundSize = 16
|
||||
case "pcmu":
|
||||
rtsp.AudioInfo.SoundFormat = 8
|
||||
rtsp.AudioInfo.SoundRate = rtsp.ASdp.TimeScale
|
||||
rtsp.AudioInfo.SoundSize = 16
|
||||
}
|
||||
}
|
||||
|
||||
type RTSPClientInfo struct {
|
||||
Agent string
|
||||
Session string
|
||||
@@ -159,12 +178,10 @@ type RTSPClientInfo struct {
|
||||
Seq int
|
||||
}
|
||||
type RTSPInfo struct {
|
||||
URL string
|
||||
SyncCount int64
|
||||
SDPRaw string
|
||||
InBytes int
|
||||
OutBytes int
|
||||
|
||||
URL string
|
||||
SDPRaw string
|
||||
InBytes int
|
||||
OutBytes int
|
||||
StreamInfo *StreamInfo
|
||||
}
|
||||
|
||||
@@ -192,115 +209,3 @@ func (conn *RichConn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
return conn.Conn.Write(b)
|
||||
}
|
||||
func (rtsp *RTSP) handleNALU(nalType byte, payload []byte, ts int64) {
|
||||
rtsp.SyncCount++
|
||||
vl := len(payload)
|
||||
switch nalType {
|
||||
// case NALU_SPS:
|
||||
// r := bytes.NewBuffer([]byte{})
|
||||
// r.Write(RTMP_AVC_HEAD)
|
||||
// util.BigEndian.PutUint16(spsHead[1:], uint16(vl))
|
||||
// r.Write(spsHead)
|
||||
// r.Write(payload)
|
||||
// case NALU_PPS:
|
||||
// r := bytes.NewBuffer([]byte{})
|
||||
// util.BigEndian.PutUint16(ppsHead[1:], uint16(vl))
|
||||
// r.Write(ppsHead)
|
||||
// r.Write(payload)
|
||||
// rtsp.PushVideo(0, r.Bytes())
|
||||
// avcsent = true
|
||||
case NALU_IDR_Picture:
|
||||
if !rtsp.avcsent {
|
||||
r := bytes.NewBuffer([]byte{})
|
||||
r.Write(RTMP_AVC_HEAD)
|
||||
spsHead := []byte{0xE1, 0, 0}
|
||||
util.BigEndian.PutUint16(spsHead[1:], uint16(len(rtsp.SPS)))
|
||||
r.Write(spsHead)
|
||||
r.Write(rtsp.SPS)
|
||||
ppsHead := []byte{0x01, 0, 0}
|
||||
util.BigEndian.PutUint16(ppsHead[1:], uint16(len(rtsp.PPS)))
|
||||
r.Write(ppsHead)
|
||||
r.Write(rtsp.PPS)
|
||||
rtsp.PushVideo(0, r.Bytes())
|
||||
rtsp.avcsent = true
|
||||
}
|
||||
r := bytes.NewBuffer([]byte{})
|
||||
iframeHead := []byte{0x17, 0x01, 0, 0, 0}
|
||||
util.BigEndian.PutUint24(iframeHead[2:], 0)
|
||||
r.Write(iframeHead)
|
||||
nalLength := []byte{0, 0, 0, 0}
|
||||
util.BigEndian.PutUint32(nalLength, uint32(vl))
|
||||
r.Write(nalLength)
|
||||
r.Write(payload)
|
||||
rtsp.PushVideo(uint32(ts), r.Bytes())
|
||||
case NALU_Non_IDR_Picture:
|
||||
r := bytes.NewBuffer([]byte{})
|
||||
pframeHead := []byte{0x27, 0x01, 0, 0, 0}
|
||||
util.BigEndian.PutUint24(pframeHead[2:], 0)
|
||||
r.Write(pframeHead)
|
||||
nalLength := []byte{0, 0, 0, 0}
|
||||
util.BigEndian.PutUint32(nalLength, uint32(vl))
|
||||
r.Write(nalLength)
|
||||
r.Write(payload)
|
||||
rtsp.PushVideo(uint32(ts), r.Bytes())
|
||||
}
|
||||
}
|
||||
func (rtsp *RTSP) handleRTP(pack *RTPPack) {
|
||||
data := pack.Buffer
|
||||
switch pack.Type {
|
||||
case RTP_TYPE_AUDIO:
|
||||
if !rtsp.aacsent {
|
||||
rtsp.PushAudio(0, append([]byte{0xAF, 0x00}, rtsp.AudioSpecificConfig...))
|
||||
rtsp.aacsent = true
|
||||
}
|
||||
cc := data[0] & 0xF
|
||||
rtphdr := 12 + cc*4
|
||||
payload := data[rtphdr:]
|
||||
auHeaderLen := (int16(payload[0]) << 8) + int16(payload[1])
|
||||
auHeaderLen = auHeaderLen >> 3
|
||||
auHeaderCount := int(auHeaderLen / 2)
|
||||
var auLenArray []int
|
||||
for iIndex := 0; iIndex < int(auHeaderCount); iIndex++ {
|
||||
auHeaderInfo := (int16(payload[2+2*iIndex]) << 8) + int16(payload[2+2*iIndex+1])
|
||||
auLen := auHeaderInfo >> 3
|
||||
auLenArray = append(auLenArray, int(auLen))
|
||||
}
|
||||
startOffset := 2 + 2*auHeaderCount
|
||||
for _, auLen := range auLenArray {
|
||||
endOffset := startOffset + auLen
|
||||
addHead := []byte{0xAF, 0x01}
|
||||
rtsp.PushAudio(0, append(addHead, payload[startOffset:endOffset]...))
|
||||
startOffset = startOffset + auLen
|
||||
}
|
||||
case RTP_TYPE_VIDEO:
|
||||
cc := data[0] & 0xF
|
||||
//rtp header
|
||||
rtphdr := 12 + cc*4
|
||||
|
||||
//packet time
|
||||
ts := (int64(data[4]) << 24) + (int64(data[5]) << 16) + (int64(data[6]) << 8) + (int64(data[7]))
|
||||
|
||||
//packet number
|
||||
//packno := (int64(data[6]) << 8) + int64(data[7])
|
||||
data = data[rtphdr:]
|
||||
nalType := data[0] & 0x1F
|
||||
|
||||
if nalType >= 1 && nalType <= 23 {
|
||||
rtsp.handleNALU(nalType, data, ts)
|
||||
} else if nalType == 28 {
|
||||
isStart := data[1]&0x80 != 0
|
||||
isEnd := data[1]&0x40 != 0
|
||||
nalType := data[1] & 0x1F
|
||||
//nri := (data[1]&0x60)>>5
|
||||
nal := data[0]&0xE0 | data[1]&0x1F
|
||||
if isStart {
|
||||
rtsp.fuBuffer = []byte{0}
|
||||
}
|
||||
rtsp.fuBuffer = append(rtsp.fuBuffer, data[2:]...)
|
||||
if isEnd {
|
||||
rtsp.fuBuffer[0] = nal
|
||||
rtsp.handleNALU(nalType, rtsp.fuBuffer, ts)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,17 +31,13 @@ func ParseSDP(sdpRaw string) map[string]*SDPInfo {
|
||||
switch typeval[0] {
|
||||
case "m":
|
||||
if len(fields) > 0 {
|
||||
switch fields[0] {
|
||||
case "audio", "video":
|
||||
sdpMap[fields[0]] = &SDPInfo{AVType: fields[0]}
|
||||
info = sdpMap[fields[0]]
|
||||
mfields := strings.Split(fields[1], " ")
|
||||
if len(mfields) >= 3 {
|
||||
info.PayloadType, _ = strconv.Atoi(mfields[2])
|
||||
}
|
||||
info = &SDPInfo{AVType: fields[0]}
|
||||
sdpMap[info.AVType] = info
|
||||
mfields := strings.Split(fields[1], " ")
|
||||
if len(mfields) >= 3 {
|
||||
info.PayloadType, _ = strconv.Atoi(mfields[2])
|
||||
}
|
||||
}
|
||||
|
||||
case "a":
|
||||
if info != nil {
|
||||
for _, field := range fields {
|
||||
@@ -60,6 +56,10 @@ func ParseSDP(sdpRaw string) map[string]*SDPInfo {
|
||||
if len(keyval) >= 2 {
|
||||
key := keyval[0]
|
||||
switch key {
|
||||
case "PCMA":
|
||||
info.Codec = "pcma"
|
||||
case "PCMU":
|
||||
info.Codec = "pcmu"
|
||||
case "MPEG4-GENERIC":
|
||||
info.Codec = "aac"
|
||||
case "H264":
|
||||
|
174
session.go
174
session.go
@@ -13,14 +13,10 @@ import (
|
||||
"time"
|
||||
|
||||
. "github.com/Monibuca/engine/v2"
|
||||
. "github.com/Monibuca/plugin-rtp"
|
||||
"github.com/teris-io/shortid"
|
||||
)
|
||||
|
||||
type RTPPack struct {
|
||||
Type RTPType
|
||||
Buffer []byte
|
||||
}
|
||||
|
||||
type SessionType int
|
||||
|
||||
const (
|
||||
@@ -38,29 +34,6 @@ func (st SessionType) String() string {
|
||||
return "unknow"
|
||||
}
|
||||
|
||||
type RTPType int
|
||||
|
||||
const (
|
||||
RTP_TYPE_AUDIO RTPType = iota
|
||||
RTP_TYPE_VIDEO
|
||||
RTP_TYPE_AUDIOCONTROL
|
||||
RTP_TYPE_VIDEOCONTROL
|
||||
)
|
||||
|
||||
func (rt RTPType) String() string {
|
||||
switch rt {
|
||||
case RTP_TYPE_AUDIO:
|
||||
return "audio"
|
||||
case RTP_TYPE_VIDEO:
|
||||
return "video"
|
||||
case RTP_TYPE_AUDIOCONTROL:
|
||||
return "audio control"
|
||||
case RTP_TYPE_VIDEOCONTROL:
|
||||
return "video control"
|
||||
}
|
||||
return "unknow"
|
||||
}
|
||||
|
||||
type TransType int
|
||||
|
||||
const (
|
||||
@@ -128,53 +101,41 @@ func (session *RTSP) AcceptPush() {
|
||||
}
|
||||
channel := int(buf1)
|
||||
rtpLen := int(binary.BigEndian.Uint16(buf2))
|
||||
pack := new(RTPPack)
|
||||
rtpBytes := make([]byte, rtpLen)
|
||||
if _, err := io.ReadFull(session.connRW, rtpBytes); err != nil {
|
||||
Println(err)
|
||||
return
|
||||
}
|
||||
var pack *RTPPack
|
||||
if err = pack.Unmarshal(rtpBytes); err != nil {
|
||||
Println(err)
|
||||
return
|
||||
}
|
||||
switch channel {
|
||||
case session.aRTPChannel:
|
||||
pack = &RTPPack{
|
||||
Type: RTP_TYPE_AUDIO,
|
||||
Buffer: rtpBytes,
|
||||
}
|
||||
pack.Type = RTP_TYPE_AUDIO
|
||||
elapsed := time.Now().Sub(timer)
|
||||
if elapsed >= 30*time.Second {
|
||||
Println("Recv an audio RTP package")
|
||||
timer = time.Now()
|
||||
}
|
||||
case session.aRTPControlChannel:
|
||||
pack = &RTPPack{
|
||||
Type: RTP_TYPE_AUDIOCONTROL,
|
||||
Buffer: rtpBytes,
|
||||
}
|
||||
pack.Type = RTP_TYPE_AUDIOCONTROL
|
||||
case session.vRTPChannel:
|
||||
pack = &RTPPack{
|
||||
Type: RTP_TYPE_VIDEO,
|
||||
Buffer: rtpBytes,
|
||||
}
|
||||
pack.Type = RTP_TYPE_VIDEO
|
||||
elapsed := time.Now().Sub(timer)
|
||||
if elapsed >= 30*time.Second {
|
||||
Println("Recv an video RTP package")
|
||||
timer = time.Now()
|
||||
}
|
||||
case session.vRTPControlChannel:
|
||||
pack = &RTPPack{
|
||||
Type: RTP_TYPE_VIDEOCONTROL,
|
||||
Buffer: rtpBytes,
|
||||
}
|
||||
pack.Type = RTP_TYPE_VIDEOCONTROL
|
||||
default:
|
||||
Printf("unknow rtp pack type, %v", pack.Type)
|
||||
continue
|
||||
}
|
||||
if pack == nil {
|
||||
Printf("session tcp got nil rtp pack")
|
||||
continue
|
||||
}
|
||||
session.InBytes += rtpLen + 4
|
||||
session.handleRTP(pack)
|
||||
session.PushPack(pack)
|
||||
} else { // rtsp cmd
|
||||
reqBuf := bytes.NewBuffer(nil)
|
||||
reqBuf.WriteByte(buf1)
|
||||
@@ -356,21 +317,22 @@ func (session *RTSP) handleRequest(req *Request) {
|
||||
|
||||
session.SDPRaw = req.Body
|
||||
session.SDPMap = ParseSDP(req.Body)
|
||||
sdp, ok := session.SDPMap["audio"]
|
||||
if ok {
|
||||
session.AControl = sdp.Control
|
||||
session.ACodec = sdp.Codec
|
||||
session.AudioSpecificConfig = sdp.Config
|
||||
Printf("audio codec[%s]\n", session.ACodec)
|
||||
}
|
||||
if sdp, ok = session.SDPMap["video"]; ok {
|
||||
session.VControl = sdp.Control
|
||||
session.VCodec = sdp.Codec
|
||||
session.SPS = sdp.SpropParameterSets[0]
|
||||
session.PPS = sdp.SpropParameterSets[1]
|
||||
Printf("video codec[%s]\n", session.VCodec)
|
||||
}
|
||||
if session.Publisher.Publish(streamPath) {
|
||||
if session.Publish(streamPath) {
|
||||
if session.ASdp, session.HasAudio = session.SDPMap["audio"]; session.HasAudio {
|
||||
if len(session.ASdp.Control) > 0 {
|
||||
session.WriteASC(session.ASdp.Config)
|
||||
} else {
|
||||
session.setAudioFormat()
|
||||
}
|
||||
Printf("audio codec[%s]\n", session.ASdp.Codec)
|
||||
}
|
||||
if session.VSdp, session.HasVideo = session.SDPMap["video"]; session.HasVideo {
|
||||
if len(session.VSdp.SpropParameterSets) > 1 {
|
||||
session.WriteSPS(session.VSdp.SpropParameterSets[0])
|
||||
session.WritePPS(session.VSdp.SpropParameterSets[1])
|
||||
}
|
||||
Printf("video codec[%s]\n", session.VSdp.Codec)
|
||||
}
|
||||
session.Stream.Type = "RTSP"
|
||||
session.RTSPInfo.StreamInfo = &session.Stream.StreamInfo
|
||||
collection.Store(streamPath, session)
|
||||
@@ -417,36 +379,38 @@ func (session *RTSP) handleRequest(req *Request) {
|
||||
// res.Status = "Error Status"
|
||||
// return
|
||||
//}
|
||||
vPath := ""
|
||||
if strings.Index(strings.ToLower(session.VControl), "rtsp://") == 0 {
|
||||
vControlUrl, err := url.Parse(session.VControl)
|
||||
if err != nil {
|
||||
res.StatusCode = 500
|
||||
res.Status = "Invalid VControl"
|
||||
return
|
||||
var vPath, aPath string
|
||||
if session.HasVideo {
|
||||
if strings.Index(strings.ToLower(session.VSdp.Control), "rtsp://") == 0 {
|
||||
vControlUrl, err := url.Parse(session.VSdp.Control)
|
||||
if err != nil {
|
||||
res.StatusCode = 500
|
||||
res.Status = "Invalid VControl"
|
||||
return
|
||||
}
|
||||
if vControlUrl.Port() == "" {
|
||||
vControlUrl.Host = fmt.Sprintf("%s:554", vControlUrl.Host)
|
||||
}
|
||||
vPath = vControlUrl.String()
|
||||
} else {
|
||||
vPath = session.VSdp.Control
|
||||
}
|
||||
if vControlUrl.Port() == "" {
|
||||
vControlUrl.Host = fmt.Sprintf("%s:554", vControlUrl.Host)
|
||||
}
|
||||
vPath = vControlUrl.String()
|
||||
} else {
|
||||
vPath = session.VControl
|
||||
}
|
||||
|
||||
aPath := ""
|
||||
if strings.Index(strings.ToLower(session.AControl), "rtsp://") == 0 {
|
||||
aControlUrl, err := url.Parse(session.AControl)
|
||||
if err != nil {
|
||||
res.StatusCode = 500
|
||||
res.Status = "Invalid AControl"
|
||||
return
|
||||
if session.HasAudio {
|
||||
if strings.Index(strings.ToLower(session.ASdp.Control), "rtsp://") == 0 {
|
||||
aControlUrl, err := url.Parse(session.ASdp.Control)
|
||||
if err != nil {
|
||||
res.StatusCode = 500
|
||||
res.Status = "Invalid AControl"
|
||||
return
|
||||
}
|
||||
if aControlUrl.Port() == "" {
|
||||
aControlUrl.Host = fmt.Sprintf("%s:554", aControlUrl.Host)
|
||||
}
|
||||
aPath = aControlUrl.String()
|
||||
} else {
|
||||
aPath = session.ASdp.Control
|
||||
}
|
||||
if aControlUrl.Port() == "" {
|
||||
aControlUrl.Host = fmt.Sprintf("%s:554", aControlUrl.Host)
|
||||
}
|
||||
aPath = aControlUrl.String()
|
||||
} else {
|
||||
aPath = session.AControl
|
||||
}
|
||||
|
||||
mtcp := regexp.MustCompile("interleaved=(\\d+)(-(\\d+))?")
|
||||
@@ -577,7 +541,7 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
|
||||
return
|
||||
}
|
||||
err = session.UDPClient.SendRTP(pack)
|
||||
session.OutBytes += len(pack.Buffer)
|
||||
session.OutBytes += len(pack.Raw)
|
||||
return
|
||||
}
|
||||
switch pack.Type {
|
||||
@@ -588,12 +552,12 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
|
||||
session.connWLock.Lock()
|
||||
session.connRW.Write(bufChannel)
|
||||
bufLen := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Buffer)))
|
||||
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Raw)))
|
||||
session.connRW.Write(bufLen)
|
||||
session.connRW.Write(pack.Buffer)
|
||||
session.connRW.Write(pack.Raw)
|
||||
session.connRW.Flush()
|
||||
session.connWLock.Unlock()
|
||||
session.OutBytes += len(pack.Buffer) + 4
|
||||
session.OutBytes += len(pack.Raw) + 4
|
||||
case RTP_TYPE_AUDIOCONTROL:
|
||||
bufChannel := make([]byte, 2)
|
||||
bufChannel[0] = 0x24
|
||||
@@ -601,12 +565,12 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
|
||||
session.connWLock.Lock()
|
||||
session.connRW.Write(bufChannel)
|
||||
bufLen := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Buffer)))
|
||||
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Raw)))
|
||||
session.connRW.Write(bufLen)
|
||||
session.connRW.Write(pack.Buffer)
|
||||
session.connRW.Write(pack.Raw)
|
||||
session.connRW.Flush()
|
||||
session.connWLock.Unlock()
|
||||
session.OutBytes += len(pack.Buffer) + 4
|
||||
session.OutBytes += len(pack.Raw) + 4
|
||||
case RTP_TYPE_VIDEO:
|
||||
bufChannel := make([]byte, 2)
|
||||
bufChannel[0] = 0x24
|
||||
@@ -614,12 +578,12 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
|
||||
session.connWLock.Lock()
|
||||
session.connRW.Write(bufChannel)
|
||||
bufLen := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Buffer)))
|
||||
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Raw)))
|
||||
session.connRW.Write(bufLen)
|
||||
session.connRW.Write(pack.Buffer)
|
||||
session.connRW.Write(pack.Raw)
|
||||
session.connRW.Flush()
|
||||
session.connWLock.Unlock()
|
||||
session.OutBytes += len(pack.Buffer) + 4
|
||||
session.OutBytes += len(pack.Raw) + 4
|
||||
case RTP_TYPE_VIDEOCONTROL:
|
||||
bufChannel := make([]byte, 2)
|
||||
bufChannel[0] = 0x24
|
||||
@@ -627,12 +591,12 @@ func (session *RTSP) SendRTP(pack *RTPPack) (err error) {
|
||||
session.connWLock.Lock()
|
||||
session.connRW.Write(bufChannel)
|
||||
bufLen := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Buffer)))
|
||||
binary.BigEndian.PutUint16(bufLen, uint16(len(pack.Raw)))
|
||||
session.connRW.Write(bufLen)
|
||||
session.connRW.Write(pack.Buffer)
|
||||
session.connRW.Write(pack.Raw)
|
||||
session.connRW.Flush()
|
||||
session.connWLock.Unlock()
|
||||
session.OutBytes += len(pack.Buffer) + 4
|
||||
session.OutBytes += len(pack.Raw) + 4
|
||||
default:
|
||||
err = fmt.Errorf("session tcp send rtp got unkown pack type[%v]", pack.Type)
|
||||
}
|
||||
|
@@ -2,9 +2,10 @@ package rtsp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/Monibuca/engine/v2"
|
||||
. "github.com/Monibuca/plugin-rtp"
|
||||
"net"
|
||||
"strings"
|
||||
. "github.com/Monibuca/engine/v2"
|
||||
)
|
||||
|
||||
type UDPClient struct {
|
||||
@@ -151,7 +152,7 @@ func (c *UDPClient) SendRTP(pack *RTPPack) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = conn.Write(pack.Buffer);err != nil {
|
||||
if _, err = conn.Write(pack.Raw); err != nil {
|
||||
err = fmt.Errorf("udp client write bytes error, %v", err)
|
||||
return
|
||||
}
|
||||
|
@@ -9,10 +9,11 @@ import (
|
||||
"time"
|
||||
|
||||
. "github.com/Monibuca/engine/v2"
|
||||
. "github.com/Monibuca/plugin-rtp"
|
||||
)
|
||||
|
||||
type UDPServer struct {
|
||||
Session *RTSP
|
||||
Session *RTSP
|
||||
UDPClient
|
||||
sync.Mutex
|
||||
}
|
||||
@@ -29,7 +30,7 @@ func (s *UDPServer) HandleRTP(pack *RTPPack) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.Session != nil {
|
||||
s.Session.handleRTP(pack)
|
||||
s.Session.PushPack(pack)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,13 +91,11 @@ func (s *UDPServer) SetupAudio() (err error) {
|
||||
Printf("Package recv from AConn.len:%d\n", n)
|
||||
timer = time.Now()
|
||||
}
|
||||
rtpBytes := make([]byte, n)
|
||||
s.AddInputBytes(n)
|
||||
copy(rtpBytes, bufUDP)
|
||||
pack := &RTPPack{
|
||||
Type: RTP_TYPE_AUDIO,
|
||||
Buffer: rtpBytes,
|
||||
Type: RTP_TYPE_AUDIO,
|
||||
}
|
||||
pack.Unmarshal(bufUDP[:n])
|
||||
s.HandleRTP(pack)
|
||||
} else {
|
||||
Println("udp server read audio pack error", err)
|
||||
@@ -131,13 +130,11 @@ func (s *UDPServer) SetupAudio() (err error) {
|
||||
for !s.Stoped {
|
||||
if n, _, err := s.AControlConn.ReadFromUDP(bufUDP); err == nil {
|
||||
//Printf("Package recv from AControlConn.len:%d\n", n)
|
||||
rtpBytes := make([]byte, n)
|
||||
s.AddInputBytes(n)
|
||||
copy(rtpBytes, bufUDP)
|
||||
pack := &RTPPack{
|
||||
Type: RTP_TYPE_AUDIOCONTROL,
|
||||
Buffer: rtpBytes,
|
||||
Type: RTP_TYPE_AUDIOCONTROL,
|
||||
}
|
||||
pack.Unmarshal(bufUDP[:n])
|
||||
s.HandleRTP(pack)
|
||||
} else {
|
||||
Println("udp server read audio control pack error", err)
|
||||
@@ -182,13 +179,11 @@ func (s *UDPServer) SetupVideo() (err error) {
|
||||
Printf("Package recv from VConn.len:%d\n", n)
|
||||
timer = time.Now()
|
||||
}
|
||||
rtpBytes := make([]byte, n)
|
||||
s.AddInputBytes(n)
|
||||
copy(rtpBytes, bufUDP)
|
||||
pack := &RTPPack{
|
||||
Type: RTP_TYPE_VIDEO,
|
||||
Buffer: rtpBytes,
|
||||
Type: RTP_TYPE_VIDEO,
|
||||
}
|
||||
pack.Unmarshal(bufUDP[:n])
|
||||
s.HandleRTP(pack)
|
||||
} else {
|
||||
Println("udp server read video pack error", err)
|
||||
@@ -224,13 +219,11 @@ func (s *UDPServer) SetupVideo() (err error) {
|
||||
for !s.Stoped {
|
||||
if n, _, err := s.VControlConn.ReadFromUDP(bufUDP); err == nil {
|
||||
//Printf("Package recv from VControlConn.len:%d\n", n)
|
||||
rtpBytes := make([]byte, n)
|
||||
s.AddInputBytes(n)
|
||||
copy(rtpBytes, bufUDP)
|
||||
pack := &RTPPack{
|
||||
Type: RTP_TYPE_VIDEOCONTROL,
|
||||
Buffer: rtpBytes,
|
||||
Type: RTP_TYPE_VIDEOCONTROL,
|
||||
}
|
||||
pack.Unmarshal(bufUDP[:n])
|
||||
s.HandleRTP(pack)
|
||||
} else {
|
||||
Println("udp server read video control pack error", err)
|
||||
|
Reference in New Issue
Block a user