mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-11-02 11:54:10 +08:00
cleanup code; remove PAUSE support
This commit is contained in:
@@ -5,14 +5,13 @@
|
|||||||
[](https://goreportcard.com/report/github.com/aler9/rtsp-simple-server)
|
[](https://goreportcard.com/report/github.com/aler9/rtsp-simple-server)
|
||||||
[](https://hub.docker.com/r/aler9/rtsp-simple-server)
|
[](https://hub.docker.com/r/aler9/rtsp-simple-server)
|
||||||
|
|
||||||
_rtsp-simple-server_ is a simple, ready-to-use and zero-dependency RTSP server and RTSP proxy, a software that allows multiple users to publish and read live video and audio streams. RTSP is a standardized protocol that defines how to perform these operations with the help of a server, that is contacted by both readers and publishers in order to negotiate a streaming protocol. The server is then responsible of relaying the publisher streams to the readers.
|
_rtsp-simple-server_ is a simple, ready-to-use and zero-dependency RTSP server and RTSP proxy, a software that allows multiple users to publish and read live video and audio streams over time. RTSP, RTP and RTCP are standardized protocol that describe how to perform these operations with the help of a server, that is contacted by both readers and publishers in order to negotiate a streaming protocol. The server is then responsible of relaying the publisher streams to the readers.
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
* Read and publish streams via UDP and TCP
|
* Read and publish streams via UDP and TCP
|
||||||
* Pull and serve streams from other RTSP servers (RTSP proxy)
|
* Pull and serve streams from other RTSP servers (RTSP proxy)
|
||||||
* Each stream can have multiple video and audio tracks, encoded in any format
|
* Each stream can have multiple video and audio tracks, encoded in any format
|
||||||
* Publish multiple streams at once, each in a separate path, that can be read by multiple users
|
* Publish multiple streams at once, each in a separate path, that can be read by multiple users
|
||||||
* Supports the RTP/RTCP streaming protocol
|
|
||||||
* Supports authentication
|
* Supports authentication
|
||||||
* Supports running a script when a client connects or disconnects
|
* Supports running a script when a client connects or disconnects
|
||||||
* Compatible with Linux, Windows and Mac, does not require any dependency or interpreter, it's a single executable
|
* Compatible with Linux, Windows and Mac, does not require any dependency or interpreter, it's a single executable
|
||||||
|
|||||||
115
main.go
115
main.go
@@ -104,13 +104,6 @@ type programEventClientPlay2 struct {
|
|||||||
|
|
||||||
func (programEventClientPlay2) isProgramEvent() {}
|
func (programEventClientPlay2) isProgramEvent() {}
|
||||||
|
|
||||||
type programEventClientPause struct {
|
|
||||||
res chan error
|
|
||||||
client *serverClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (programEventClientPause) isProgramEvent() {}
|
|
||||||
|
|
||||||
type programEventClientRecord struct {
|
type programEventClientRecord struct {
|
||||||
res chan error
|
res chan error
|
||||||
client *serverClient
|
client *serverClient
|
||||||
@@ -119,8 +112,8 @@ type programEventClientRecord struct {
|
|||||||
func (programEventClientRecord) isProgramEvent() {}
|
func (programEventClientRecord) isProgramEvent() {}
|
||||||
|
|
||||||
type programEventClientFrameUdp struct {
|
type programEventClientFrameUdp struct {
|
||||||
trackFlowType trackFlowType
|
|
||||||
addr *net.UDPAddr
|
addr *net.UDPAddr
|
||||||
|
trackFlowType trackFlowType
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,54 +537,22 @@ outer:
|
|||||||
evt.client.state = _CLIENT_STATE_PLAY
|
evt.client.state = _CLIENT_STATE_PLAY
|
||||||
evt.res <- nil
|
evt.res <- nil
|
||||||
|
|
||||||
case programEventClientPause:
|
|
||||||
p.receiverCount -= 1
|
|
||||||
evt.client.state = _CLIENT_STATE_PRE_PLAY
|
|
||||||
evt.res <- nil
|
|
||||||
|
|
||||||
case programEventClientRecord:
|
case programEventClientRecord:
|
||||||
p.publisherCount += 1
|
p.publisherCount += 1
|
||||||
evt.client.state = _CLIENT_STATE_RECORD
|
evt.client.state = _CLIENT_STATE_RECORD
|
||||||
evt.res <- nil
|
evt.res <- nil
|
||||||
|
|
||||||
case programEventClientFrameUdp:
|
case programEventClientFrameUdp:
|
||||||
// find publisher and track id from ip and port
|
client, trackId := p.findPublisher(evt.addr, evt.trackFlowType)
|
||||||
cl, trackId := func() (*serverClient, int) {
|
if client == nil {
|
||||||
for _, pub := range p.publishers {
|
|
||||||
cl, ok := pub.(*serverClient)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if cl.streamProtocol != _STREAM_PROTOCOL_UDP ||
|
|
||||||
cl.state != _CLIENT_STATE_RECORD ||
|
|
||||||
!cl.ip().Equal(evt.addr.IP) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, t := range cl.streamTracks {
|
|
||||||
if evt.trackFlowType == _TRACK_FLOW_TYPE_RTP {
|
|
||||||
if t.rtpPort == evt.addr.Port {
|
|
||||||
return cl, i
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if t.rtcpPort == evt.addr.Port {
|
|
||||||
return cl, i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, -1
|
|
||||||
}()
|
|
||||||
if cl == nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.udpLastFrameTime = time.Now()
|
client.udpLastFrameTime = time.Now()
|
||||||
p.forwardTrack(cl.path, trackId, evt.trackFlowType, evt.buf)
|
p.forwardFrame(client.path, trackId, evt.trackFlowType, evt.buf)
|
||||||
|
|
||||||
case programEventClientFrameTcp:
|
case programEventClientFrameTcp:
|
||||||
p.forwardTrack(evt.path, evt.trackId, evt.trackFlowType, evt.buf)
|
p.forwardFrame(evt.path, evt.trackId, evt.trackFlowType, evt.buf)
|
||||||
|
|
||||||
case programEventStreamerReady:
|
case programEventStreamerReady:
|
||||||
evt.streamer.ready = true
|
evt.streamer.ready = true
|
||||||
@@ -611,7 +572,7 @@ outer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
case programEventStreamerFrame:
|
case programEventStreamerFrame:
|
||||||
p.forwardTrack(evt.streamer.path, evt.trackId, evt.trackFlowType, evt.buf)
|
p.forwardFrame(evt.streamer.path, evt.trackId, evt.trackFlowType, evt.buf)
|
||||||
|
|
||||||
case programEventTerminate:
|
case programEventTerminate:
|
||||||
break outer
|
break outer
|
||||||
@@ -642,9 +603,6 @@ outer:
|
|||||||
case programEventClientPlay2:
|
case programEventClientPlay2:
|
||||||
evt.res <- fmt.Errorf("terminated")
|
evt.res <- fmt.Errorf("terminated")
|
||||||
|
|
||||||
case programEventClientPause:
|
|
||||||
evt.res <- fmt.Errorf("terminated")
|
|
||||||
|
|
||||||
case programEventClientRecord:
|
case programEventClientRecord:
|
||||||
evt.res <- fmt.Errorf("terminated")
|
evt.res <- fmt.Errorf("terminated")
|
||||||
}
|
}
|
||||||
@@ -672,27 +630,60 @@ func (p *program) close() {
|
|||||||
<-p.done
|
<-p.done
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) forwardTrack(path string, id int, trackFlowType trackFlowType, frame []byte) {
|
func (p *program) findPublisher(addr *net.UDPAddr, trackFlowType trackFlowType) (*serverClient, int) {
|
||||||
|
for _, pub := range p.publishers {
|
||||||
|
cl, ok := pub.(*serverClient)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if cl.streamProtocol != _STREAM_PROTOCOL_UDP ||
|
||||||
|
cl.state != _CLIENT_STATE_RECORD ||
|
||||||
|
!cl.ip().Equal(addr.IP) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range cl.streamTracks {
|
||||||
|
if trackFlowType == _TRACK_FLOW_TYPE_RTP {
|
||||||
|
if t.rtpPort == addr.Port {
|
||||||
|
return cl, i
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if t.rtcpPort == addr.Port {
|
||||||
|
return cl, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *program) forwardFrame(path string, trackId int, trackFlowType trackFlowType, frame []byte) {
|
||||||
for c := range p.clients {
|
for c := range p.clients {
|
||||||
if c.path == path && c.state == _CLIENT_STATE_PLAY {
|
if c.path == path && c.state == _CLIENT_STATE_PLAY {
|
||||||
if c.streamProtocol == _STREAM_PROTOCOL_UDP {
|
if c.streamProtocol == _STREAM_PROTOCOL_UDP {
|
||||||
if trackFlowType == _TRACK_FLOW_TYPE_RTP {
|
if trackFlowType == _TRACK_FLOW_TYPE_RTP {
|
||||||
p.udplRtp.write(&net.UDPAddr{
|
p.udplRtp.write(&udpAddrBufPair{
|
||||||
IP: c.ip(),
|
addr: &net.UDPAddr{
|
||||||
Zone: c.zone(),
|
IP: c.ip(),
|
||||||
Port: c.streamTracks[id].rtpPort,
|
Zone: c.zone(),
|
||||||
}, frame)
|
Port: c.streamTracks[trackId].rtpPort,
|
||||||
|
},
|
||||||
|
buf: frame,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
p.udplRtcp.write(&net.UDPAddr{
|
p.udplRtcp.write(&udpAddrBufPair{
|
||||||
IP: c.ip(),
|
addr: &net.UDPAddr{
|
||||||
Zone: c.zone(),
|
IP: c.ip(),
|
||||||
Port: c.streamTracks[id].rtcpPort,
|
Zone: c.zone(),
|
||||||
}, frame)
|
Port: c.streamTracks[trackId].rtcpPort,
|
||||||
|
},
|
||||||
|
buf: frame,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
c.writeFrame(trackFlowTypeToInterleavedChannel(id, trackFlowType), frame)
|
c.writeFrame(trackFlowTypeToInterleavedChannel(trackId, trackFlowType), frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
522
server-client.go
522
server-client.go
@@ -53,24 +53,23 @@ func (cs serverClientState) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type serverClient struct {
|
type serverClient struct {
|
||||||
p *program
|
p *program
|
||||||
conn *gortsplib.ConnServer
|
conn *gortsplib.ConnServer
|
||||||
state serverClientState
|
state serverClientState
|
||||||
path string
|
path string
|
||||||
authUser string
|
authUser string
|
||||||
authPass string
|
authPass string
|
||||||
authHelper *gortsplib.AuthServer
|
authHelper *gortsplib.AuthServer
|
||||||
authFailures int
|
authFailures int
|
||||||
streamSdpText []byte // filled only if publisher
|
streamSdpText []byte // only if publisher
|
||||||
streamSdpParsed *sdp.Message // filled only if publisher
|
streamSdpParsed *sdp.Message // only if publisher
|
||||||
streamProtocol streamProtocol
|
streamProtocol streamProtocol
|
||||||
streamTracks []*track
|
streamTracks []*track
|
||||||
udpLastFrameTime time.Time
|
udpLastFrameTime time.Time
|
||||||
udpCheckStreamTicker *time.Ticker
|
readBuf *doubleBuffer
|
||||||
readBuf *doubleBuffer
|
writeBuf *doubleBuffer
|
||||||
writeBuf *doubleBuffer
|
|
||||||
|
|
||||||
writeChan chan *gortsplib.InterleavedFrame
|
writeChan chan *gortsplib.InterleavedFrame // only if state = _CLIENT_STATE_PLAY
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,11 +81,9 @@ func newServerClient(p *program, nconn net.Conn) *serverClient {
|
|||||||
ReadTimeout: p.conf.ReadTimeout,
|
ReadTimeout: p.conf.ReadTimeout,
|
||||||
WriteTimeout: p.conf.WriteTimeout,
|
WriteTimeout: p.conf.WriteTimeout,
|
||||||
}),
|
}),
|
||||||
state: _CLIENT_STATE_STARTING,
|
state: _CLIENT_STATE_STARTING,
|
||||||
readBuf: newDoubleBuffer(512 * 1024),
|
readBuf: newDoubleBuffer(512 * 1024),
|
||||||
writeBuf: newDoubleBuffer(2048),
|
done: make(chan struct{}),
|
||||||
writeChan: make(chan *gortsplib.InterleavedFrame),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go c.run()
|
go c.run()
|
||||||
@@ -126,29 +123,30 @@ func (c *serverClient) run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outer:
|
||||||
for {
|
for {
|
||||||
req, err := c.conn.ReadRequest()
|
switch c.state {
|
||||||
if err != nil {
|
case _CLIENT_STATE_PLAY:
|
||||||
if err != io.EOF {
|
ok := c.runPlay()
|
||||||
c.log("ERR: %s", err)
|
if !ok {
|
||||||
|
break outer
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := c.handleRequest(req)
|
case _CLIENT_STATE_RECORD:
|
||||||
if !ok {
|
ok := c.runRecord()
|
||||||
break
|
if !ok {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
ok := c.runNormal()
|
||||||
|
if !ok {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.udpCheckStreamTicker != nil {
|
c.conn.NetConn().Close() // close socket in case it has not been closed yet
|
||||||
c.udpCheckStreamTicker.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for range c.writeChan {
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
if c.p.conf.PostScript != "" {
|
if c.p.conf.PostScript != "" {
|
||||||
@@ -160,16 +158,185 @@ func (c *serverClient) run() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
c.p.events <- programEventClientClose{done, c}
|
|
||||||
<-done
|
|
||||||
|
|
||||||
close(c.writeChan)
|
|
||||||
c.conn.NetConn().Close() // close socket in case it has not been closed yet
|
|
||||||
|
|
||||||
close(c.done) // close() never blocks
|
close(c.done) // close() never blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errClientChangeRunMode = errors.New("change run mode")
|
||||||
|
var errClientTerminate = errors.New("terminate")
|
||||||
|
|
||||||
|
func (c *serverClient) runNormal() bool {
|
||||||
|
var ret bool
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for {
|
||||||
|
req, err := c.conn.ReadRequest()
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
c.log("ERR: %s", err)
|
||||||
|
}
|
||||||
|
ret = false
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.handleRequest(req)
|
||||||
|
switch err {
|
||||||
|
case errClientChangeRunMode:
|
||||||
|
ret = true
|
||||||
|
break outer
|
||||||
|
|
||||||
|
case errClientTerminate:
|
||||||
|
ret = false
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ret {
|
||||||
|
done := make(chan struct{})
|
||||||
|
c.p.events <- programEventClientClose{done, c}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *serverClient) runPlay() bool {
|
||||||
|
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
|
||||||
|
writeDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(writeDone)
|
||||||
|
for frame := range c.writeChan {
|
||||||
|
c.conn.WriteInterleavedFrame(frame)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
for {
|
||||||
|
_, err := c.conn.NetConn().Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
c.log("ERR: %s", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
c.p.events <- programEventClientClose{done, c}
|
||||||
|
<-done
|
||||||
|
|
||||||
|
close(c.writeChan)
|
||||||
|
<-writeDone
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for {
|
||||||
|
req, err := c.conn.ReadRequest()
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
c.log("ERR: %s", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.handleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
c.p.events <- programEventClientClose{done, c}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *serverClient) runRecord() bool {
|
||||||
|
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
|
||||||
|
frame := &gortsplib.InterleavedFrame{}
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for {
|
||||||
|
frame.Content = c.readBuf.swap()
|
||||||
|
frame.Content = frame.Content[:cap(frame.Content)]
|
||||||
|
recv, err := c.conn.ReadInterleavedFrameOrRequest(frame)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
c.log("ERR: %s", err)
|
||||||
|
}
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
|
||||||
|
switch recvt := recv.(type) {
|
||||||
|
case *gortsplib.InterleavedFrame:
|
||||||
|
trackId, trackFlowType := interleavedChannelToTrackFlowType(frame.Channel)
|
||||||
|
|
||||||
|
if trackId >= len(c.streamTracks) {
|
||||||
|
c.log("ERR: invalid track id '%d'", trackId)
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
|
||||||
|
c.p.events <- programEventClientFrameTcp{
|
||||||
|
c.path,
|
||||||
|
trackId,
|
||||||
|
trackFlowType,
|
||||||
|
frame.Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
case *gortsplib.Request:
|
||||||
|
err := c.handleRequest(recvt)
|
||||||
|
if err != nil {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
c.p.events <- programEventClientClose{done, c}
|
||||||
|
<-done
|
||||||
|
|
||||||
|
} else {
|
||||||
|
c.udpLastFrameTime = time.Now()
|
||||||
|
|
||||||
|
udpCheckStreamTicker := time.NewTicker(_UDP_CHECK_STREAM_INTERVAL)
|
||||||
|
udpCheckStreamDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(udpCheckStreamDone)
|
||||||
|
for range udpCheckStreamTicker.C {
|
||||||
|
if time.Since(c.udpLastFrameTime) >= _UDP_STREAM_DEAD_AFTER {
|
||||||
|
c.log("ERR: stream is dead")
|
||||||
|
c.conn.NetConn().Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
req, err := c.conn.ReadRequest()
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
c.log("ERR: %s", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.handleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
c.p.events <- programEventClientClose{done, c}
|
||||||
|
<-done
|
||||||
|
|
||||||
|
udpCheckStreamTicker.Stop()
|
||||||
|
<-udpCheckStreamDone
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *serverClient) close() {
|
func (c *serverClient) close() {
|
||||||
c.conn.NetConn().Close()
|
c.conn.NetConn().Close()
|
||||||
<-c.done
|
<-c.done
|
||||||
@@ -223,23 +390,12 @@ func (c *serverClient) authenticate(ips []interface{}, user string, pass string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ip := c.ip()
|
ip := c.ip()
|
||||||
|
if !ipEqualOrInRange(ip, ips) {
|
||||||
for _, item := range ips {
|
c.log("ERR: ip '%s' not allowed", ip)
|
||||||
switch titem := item.(type) {
|
return errAuthCritical
|
||||||
case net.IP:
|
|
||||||
if titem.Equal(ip) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case *net.IPNet:
|
|
||||||
if titem.Contains(ip) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.log("ERR: ip '%s' not allowed", ip)
|
return nil
|
||||||
return errAuthCritical
|
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -304,13 +460,13 @@ func (c *serverClient) authenticate(ips []interface{}, user string, pass string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
func (c *serverClient) handleRequest(req *gortsplib.Request) error {
|
||||||
c.log(string(req.Method))
|
c.log(string(req.Method))
|
||||||
|
|
||||||
cseq, ok := req.Header["CSeq"]
|
cseq, ok := req.Header["CSeq"]
|
||||||
if !ok || len(cseq) != 1 {
|
if !ok || len(cseq) != 1 {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("cseq missing"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("cseq missing"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
path := func() string {
|
path := func() string {
|
||||||
@@ -343,34 +499,33 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
string(gortsplib.ANNOUNCE),
|
string(gortsplib.ANNOUNCE),
|
||||||
string(gortsplib.SETUP),
|
string(gortsplib.SETUP),
|
||||||
string(gortsplib.PLAY),
|
string(gortsplib.PLAY),
|
||||||
string(gortsplib.PAUSE),
|
|
||||||
string(gortsplib.RECORD),
|
string(gortsplib.RECORD),
|
||||||
string(gortsplib.TEARDOWN),
|
string(gortsplib.TEARDOWN),
|
||||||
}, ", ")},
|
}, ", ")},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return true
|
return nil
|
||||||
|
|
||||||
case gortsplib.DESCRIBE:
|
case gortsplib.DESCRIBE:
|
||||||
if c.state != _CLIENT_STATE_STARTING {
|
if c.state != _CLIENT_STATE_STARTING {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest,
|
c.writeResError(req, gortsplib.StatusBadRequest,
|
||||||
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_STARTING))
|
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_STARTING))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
pconf := c.findConfForPath(path)
|
pconf := c.findConfForPath(path)
|
||||||
if pconf == nil {
|
if pconf == nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest,
|
c.writeResError(req, gortsplib.StatusBadRequest,
|
||||||
fmt.Errorf("unable to find a valid configuration for path '%s'", path))
|
fmt.Errorf("unable to find a valid configuration for path '%s'", path))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req)
|
err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errAuthCritical {
|
if err == errAuthCritical {
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make(chan []byte)
|
res := make(chan []byte)
|
||||||
@@ -378,7 +533,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
sdp := <-res
|
sdp := <-res
|
||||||
if sdp == nil {
|
if sdp == nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("no one is streaming on path '%s'", path))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("no one is streaming on path '%s'", path))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&gortsplib.Response{
|
c.conn.WriteResponse(&gortsplib.Response{
|
||||||
@@ -390,51 +545,51 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
},
|
},
|
||||||
Content: sdp,
|
Content: sdp,
|
||||||
})
|
})
|
||||||
return true
|
return nil
|
||||||
|
|
||||||
case gortsplib.ANNOUNCE:
|
case gortsplib.ANNOUNCE:
|
||||||
if c.state != _CLIENT_STATE_STARTING {
|
if c.state != _CLIENT_STATE_STARTING {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest,
|
c.writeResError(req, gortsplib.StatusBadRequest,
|
||||||
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_STARTING))
|
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_STARTING))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
pconf := c.findConfForPath(path)
|
pconf := c.findConfForPath(path)
|
||||||
if pconf == nil {
|
if pconf == nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest,
|
c.writeResError(req, gortsplib.StatusBadRequest,
|
||||||
fmt.Errorf("unable to find a valid configuration for path '%s'", path))
|
fmt.Errorf("unable to find a valid configuration for path '%s'", path))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.authenticate(pconf.publishIpsParsed, pconf.PublishUser, pconf.PublishPass, req)
|
err := c.authenticate(pconf.publishIpsParsed, pconf.PublishUser, pconf.PublishPass, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errAuthCritical {
|
if err == errAuthCritical {
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ct, ok := req.Header["Content-Type"]
|
ct, ok := req.Header["Content-Type"]
|
||||||
if !ok || len(ct) != 1 {
|
if !ok || len(ct) != 1 {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("Content-Type header missing"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("Content-Type header missing"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if ct[0] != "application/sdp" {
|
if ct[0] != "application/sdp" {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("unsupported Content-Type '%s'", ct))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("unsupported Content-Type '%s'", ct))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
sdpParsed, err := gortsplib.SDPParse(req.Content)
|
sdpParsed, err := gortsplib.SDPParse(req.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("invalid SDP: %s", err))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("invalid SDP: %s", err))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
sdpParsed, req.Content = gortsplib.SDPFilter(sdpParsed, req.Content)
|
sdpParsed, req.Content = gortsplib.SDPFilter(sdpParsed, req.Content)
|
||||||
|
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path can't be empty"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path can't be empty"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make(chan error)
|
res := make(chan error)
|
||||||
@@ -442,7 +597,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
err = <-res
|
err = <-res
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
c.streamSdpText = req.Content
|
c.streamSdpText = req.Content
|
||||||
@@ -454,19 +609,19 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
"CSeq": cseq,
|
"CSeq": cseq,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return true
|
return nil
|
||||||
|
|
||||||
case gortsplib.SETUP:
|
case gortsplib.SETUP:
|
||||||
tsRaw, ok := req.Header["Transport"]
|
tsRaw, ok := req.Header["Transport"]
|
||||||
if !ok || len(tsRaw) != 1 {
|
if !ok || len(tsRaw) != 1 {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header missing"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header missing"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
th := gortsplib.ReadHeaderTransport(tsRaw[0])
|
th := gortsplib.ReadHeaderTransport(tsRaw[0])
|
||||||
if _, ok := th["multicast"]; ok {
|
if _, ok := th["multicast"]; ok {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("multicast is not supported"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("multicast is not supported"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
switch c.state {
|
switch c.state {
|
||||||
@@ -476,15 +631,15 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
if pconf == nil {
|
if pconf == nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest,
|
c.writeResError(req, gortsplib.StatusBadRequest,
|
||||||
fmt.Errorf("unable to find a valid configuration for path '%s'", path))
|
fmt.Errorf("unable to find a valid configuration for path '%s'", path))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req)
|
err := c.authenticate(pconf.readIpsParsed, pconf.ReadUser, pconf.ReadPass, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == errAuthCritical {
|
if err == errAuthCritical {
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// play via UDP
|
// play via UDP
|
||||||
@@ -501,23 +656,23 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
}() {
|
}() {
|
||||||
if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok {
|
if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok {
|
||||||
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("UDP streaming is disabled"))
|
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("UDP streaming is disabled"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
rtpPort, rtcpPort := th.GetPorts("client_port")
|
rtpPort, rtcpPort := th.GetPorts("client_port")
|
||||||
if rtpPort == 0 || rtcpPort == 0 {
|
if rtpPort == 0 || rtcpPort == 0 {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not have valid client ports (%s)", tsRaw[0]))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not have valid client ports (%s)", tsRaw[0]))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.path != "" && path != c.path {
|
if c.path != "" && path != c.path {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP {
|
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't receive tracks with different protocols"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't receive tracks with different protocols"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make(chan error)
|
res := make(chan error)
|
||||||
@@ -525,7 +680,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
err = <-res
|
err = <-res
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&gortsplib.Response{
|
c.conn.WriteResponse(&gortsplib.Response{
|
||||||
@@ -541,23 +696,23 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
"Session": []string{"12345678"},
|
"Session": []string{"12345678"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return true
|
return nil
|
||||||
|
|
||||||
// play via TCP
|
// play via TCP
|
||||||
} else if _, ok := th["RTP/AVP/TCP"]; ok {
|
} else if _, ok := th["RTP/AVP/TCP"]; ok {
|
||||||
if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok {
|
if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok {
|
||||||
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("TCP streaming is disabled"))
|
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("TCP streaming is disabled"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.path != "" && path != c.path {
|
if c.path != "" && path != c.path {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP {
|
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't receive tracks with different protocols"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't receive tracks with different protocols"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make(chan error)
|
res := make(chan error)
|
||||||
@@ -565,7 +720,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
err = <-res
|
err = <-res
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
interleaved := fmt.Sprintf("%d-%d", ((len(c.streamTracks) - 1) * 2), ((len(c.streamTracks)-1)*2)+1)
|
interleaved := fmt.Sprintf("%d-%d", ((len(c.streamTracks) - 1) * 2), ((len(c.streamTracks)-1)*2)+1)
|
||||||
@@ -582,24 +737,24 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
"Session": []string{"12345678"},
|
"Session": []string{"12345678"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return true
|
return nil
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP, RTP/AVP/UDP or RTP/AVP/TCP) (%s)", tsRaw[0]))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP, RTP/AVP/UDP or RTP/AVP/TCP) (%s)", tsRaw[0]))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
// record
|
// record
|
||||||
case _CLIENT_STATE_ANNOUNCE, _CLIENT_STATE_PRE_RECORD:
|
case _CLIENT_STATE_ANNOUNCE, _CLIENT_STATE_PRE_RECORD:
|
||||||
if _, ok := th["mode=record"]; !ok {
|
if _, ok := th["mode=record"]; !ok {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain mode=record"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain mode=record"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
// after ANNOUNCE, c.path is already set
|
// after ANNOUNCE, c.path is already set
|
||||||
if path != c.path {
|
if path != c.path {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
// record via UDP
|
// record via UDP
|
||||||
@@ -616,23 +771,23 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
}() {
|
}() {
|
||||||
if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok {
|
if _, ok := c.p.protocols[_STREAM_PROTOCOL_UDP]; !ok {
|
||||||
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("UDP streaming is disabled"))
|
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("UDP streaming is disabled"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
rtpPort, rtcpPort := th.GetPorts("client_port")
|
rtpPort, rtcpPort := th.GetPorts("client_port")
|
||||||
if rtpPort == 0 || rtcpPort == 0 {
|
if rtpPort == 0 || rtcpPort == 0 {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not have valid client ports (%s)", tsRaw[0]))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not have valid client ports (%s)", tsRaw[0]))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP {
|
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_UDP {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't publish tracks with different protocols"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't publish tracks with different protocols"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) {
|
if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("all the tracks have already been setup"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("all the tracks have already been setup"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make(chan error)
|
res := make(chan error)
|
||||||
@@ -640,7 +795,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
err := <-res
|
err := <-res
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&gortsplib.Response{
|
c.conn.WriteResponse(&gortsplib.Response{
|
||||||
@@ -656,35 +811,35 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
"Session": []string{"12345678"},
|
"Session": []string{"12345678"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return true
|
return nil
|
||||||
|
|
||||||
// record via TCP
|
// record via TCP
|
||||||
} else if _, ok := th["RTP/AVP/TCP"]; ok {
|
} else if _, ok := th["RTP/AVP/TCP"]; ok {
|
||||||
if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok {
|
if _, ok := c.p.protocols[_STREAM_PROTOCOL_TCP]; !ok {
|
||||||
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("TCP streaming is disabled"))
|
c.writeResError(req, gortsplib.StatusUnsupportedTransport, fmt.Errorf("TCP streaming is disabled"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP {
|
if len(c.streamTracks) > 0 && c.streamProtocol != _STREAM_PROTOCOL_TCP {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't publish tracks with different protocols"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("can't publish tracks with different protocols"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
interleaved := th.GetValue("interleaved")
|
interleaved := th.GetValue("interleaved")
|
||||||
if interleaved == "" {
|
if interleaved == "" {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain the interleaved field"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain the interleaved field"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
expInterleaved := fmt.Sprintf("%d-%d", 0+len(c.streamTracks)*2, 1+len(c.streamTracks)*2)
|
expInterleaved := fmt.Sprintf("%d-%d", 0+len(c.streamTracks)*2, 1+len(c.streamTracks)*2)
|
||||||
if interleaved != expInterleaved {
|
if interleaved != expInterleaved {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("wrong interleaved value, expected '%s', got '%s'", expInterleaved, interleaved))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("wrong interleaved value, expected '%s', got '%s'", expInterleaved, interleaved))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) {
|
if len(c.streamTracks) >= len(c.streamSdpParsed.Medias) {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("all the tracks have already been setup"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("all the tracks have already been setup"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make(chan error)
|
res := make(chan error)
|
||||||
@@ -692,7 +847,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
err := <-res
|
err := <-res
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&gortsplib.Response{
|
c.conn.WriteResponse(&gortsplib.Response{
|
||||||
@@ -707,28 +862,28 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
"Session": []string{"12345678"},
|
"Session": []string{"12345678"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return true
|
return nil
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP, RTP/AVP/UDP or RTP/AVP/TCP) (%s)", tsRaw[0]))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("transport header does not contain a valid protocol (RTP/AVP, RTP/AVP/UDP or RTP/AVP/TCP) (%s)", tsRaw[0]))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("client is in state '%s'", c.state))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("client is in state '%s'", c.state))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
case gortsplib.PLAY:
|
case gortsplib.PLAY:
|
||||||
if c.state != _CLIENT_STATE_PRE_PLAY {
|
if c.state != _CLIENT_STATE_PRE_PLAY {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest,
|
c.writeResError(req, gortsplib.StatusBadRequest,
|
||||||
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PRE_PLAY))
|
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PRE_PLAY))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if path != c.path {
|
if path != c.path {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
// check publisher existence
|
// check publisher existence
|
||||||
@@ -737,7 +892,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
err := <-res
|
err := <-res
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
c.writeResError(req, gortsplib.StatusBadRequest, err)
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
// write response before setting state
|
// write response before setting state
|
||||||
@@ -751,6 +906,9 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
c.writeBuf = newDoubleBuffer(2048)
|
||||||
|
c.writeChan = make(chan *gortsplib.InterleavedFrame)
|
||||||
|
|
||||||
// set state
|
// set state
|
||||||
res = make(chan error)
|
res = make(chan error)
|
||||||
c.p.events <- programEventClientPlay2{res, c}
|
c.p.events <- programEventClientPlay2{res, c}
|
||||||
@@ -763,72 +921,23 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
return "tracks"
|
return "tracks"
|
||||||
}(), c.streamProtocol)
|
}(), c.streamProtocol)
|
||||||
|
|
||||||
// when protocol is TCP, the RTSP connection becomes a RTP connection
|
return errClientChangeRunMode
|
||||||
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
|
|
||||||
// write RTP frames sequentially
|
|
||||||
go func() {
|
|
||||||
for frame := range c.writeChan {
|
|
||||||
c.conn.WriteInterleavedFrame(frame)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// receive RTP feedback, do not parse it, wait until connection closes
|
|
||||||
buf := make([]byte, 2048)
|
|
||||||
for {
|
|
||||||
_, err := c.conn.NetConn().Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
c.log("ERR: %s", err)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
case gortsplib.PAUSE:
|
|
||||||
if c.state != _CLIENT_STATE_PLAY {
|
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest,
|
|
||||||
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PLAY))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if path != c.path {
|
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
c.log("paused")
|
|
||||||
|
|
||||||
res := make(chan error)
|
|
||||||
c.p.events <- programEventClientPause{res, c}
|
|
||||||
<-res
|
|
||||||
|
|
||||||
c.conn.WriteResponse(&gortsplib.Response{
|
|
||||||
StatusCode: gortsplib.StatusOK,
|
|
||||||
Header: gortsplib.Header{
|
|
||||||
"CSeq": cseq,
|
|
||||||
"Session": []string{"12345678"},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
|
|
||||||
case gortsplib.RECORD:
|
case gortsplib.RECORD:
|
||||||
if c.state != _CLIENT_STATE_PRE_RECORD {
|
if c.state != _CLIENT_STATE_PRE_RECORD {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest,
|
c.writeResError(req, gortsplib.StatusBadRequest,
|
||||||
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PRE_RECORD))
|
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, _CLIENT_STATE_PRE_RECORD))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if path != c.path {
|
if path != c.path {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("path has changed"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.streamTracks) != len(c.streamSdpParsed.Medias) {
|
if len(c.streamTracks) != len(c.streamSdpParsed.Medias) {
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("not all tracks have been setup"))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("not all tracks have been setup"))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.WriteResponse(&gortsplib.Response{
|
c.conn.WriteResponse(&gortsplib.Response{
|
||||||
@@ -850,79 +959,14 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
|
|||||||
return "tracks"
|
return "tracks"
|
||||||
}(), c.streamProtocol)
|
}(), c.streamProtocol)
|
||||||
|
|
||||||
// when protocol is TCP, the RTSP connection becomes a RTP connection
|
return errClientChangeRunMode
|
||||||
// receive RTP data and parse it
|
|
||||||
if c.streamProtocol == _STREAM_PROTOCOL_TCP {
|
|
||||||
frame := &gortsplib.InterleavedFrame{}
|
|
||||||
for {
|
|
||||||
frame.Content = c.readBuf.swap()
|
|
||||||
frame.Content = frame.Content[:cap(frame.Content)]
|
|
||||||
recv, err := c.conn.ReadInterleavedFrameOrRequest(frame)
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
c.log("ERR: %s", err)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch recvt := recv.(type) {
|
|
||||||
case *gortsplib.InterleavedFrame:
|
|
||||||
trackId, trackFlowType := interleavedChannelToTrackFlowType(frame.Channel)
|
|
||||||
|
|
||||||
if trackId >= len(c.streamTracks) {
|
|
||||||
c.log("ERR: invalid track id '%d'", trackId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
c.p.events <- programEventClientFrameTcp{
|
|
||||||
c.path,
|
|
||||||
trackId,
|
|
||||||
trackFlowType,
|
|
||||||
frame.Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
case *gortsplib.Request:
|
|
||||||
cseq, ok := recvt.Header["CSeq"]
|
|
||||||
if !ok || len(cseq) != 1 {
|
|
||||||
c.writeResError(recvt, gortsplib.StatusBadRequest, fmt.Errorf("cseq missing"))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch recvt.Method {
|
|
||||||
case gortsplib.TEARDOWN:
|
|
||||||
// close connection silently
|
|
||||||
return false
|
|
||||||
|
|
||||||
default:
|
|
||||||
c.writeResError(recvt, gortsplib.StatusBadRequest, fmt.Errorf("unhandled method '%s'", recvt.Method))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.udpLastFrameTime = time.Now()
|
|
||||||
c.udpCheckStreamTicker = time.NewTicker(_UDP_CHECK_STREAM_INTERVAL)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for range c.udpCheckStreamTicker.C {
|
|
||||||
if time.Since(c.udpLastFrameTime) >= _UDP_STREAM_DEAD_AFTER {
|
|
||||||
c.log("ERR: stream is dead")
|
|
||||||
c.conn.NetConn().Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
case gortsplib.TEARDOWN:
|
case gortsplib.TEARDOWN:
|
||||||
// close connection silently
|
// close connection silently
|
||||||
return false
|
return errClientTerminate
|
||||||
|
|
||||||
default:
|
default:
|
||||||
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("unhandled method '%s'", req.Method))
|
c.writeResError(req, gortsplib.StatusBadRequest, fmt.Errorf("unhandled method '%s'", req.Method))
|
||||||
return false
|
return errClientTerminate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type udpAddrFramePair struct {
|
type udpAddrBufPair struct {
|
||||||
addr *net.UDPAddr
|
addr *net.UDPAddr
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ type serverUdpListener struct {
|
|||||||
readBuf *doubleBuffer
|
readBuf *doubleBuffer
|
||||||
writeBuf *doubleBuffer
|
writeBuf *doubleBuffer
|
||||||
|
|
||||||
writeChan chan *udpAddrFramePair
|
writeChan chan *udpAddrBufPair
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ func newServerUdpListener(p *program, port int, trackFlowType trackFlowType) (*s
|
|||||||
trackFlowType: trackFlowType,
|
trackFlowType: trackFlowType,
|
||||||
readBuf: newDoubleBuffer(2048),
|
readBuf: newDoubleBuffer(2048),
|
||||||
writeBuf: newDoubleBuffer(2048),
|
writeBuf: newDoubleBuffer(2048),
|
||||||
writeChan: make(chan *udpAddrFramePair),
|
writeChan: make(chan *udpAddrBufPair),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +69,8 @@ func (l *serverUdpListener) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
l.p.events <- programEventClientFrameUdp{
|
l.p.events <- programEventClientFrameUdp{
|
||||||
l.trackFlowType,
|
|
||||||
addr,
|
addr,
|
||||||
|
l.trackFlowType,
|
||||||
buf[:n],
|
buf[:n],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,13 +85,12 @@ func (l *serverUdpListener) close() {
|
|||||||
<-l.done
|
<-l.done
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *serverUdpListener) write(addr *net.UDPAddr, inbuf []byte) {
|
func (l *serverUdpListener) write(pair *udpAddrBufPair) {
|
||||||
|
// replace input buffer with write buffer
|
||||||
buf := l.writeBuf.swap()
|
buf := l.writeBuf.swap()
|
||||||
buf = buf[:len(inbuf)]
|
buf = buf[:len(pair.buf)]
|
||||||
copy(buf, inbuf)
|
copy(buf, pair.buf)
|
||||||
|
pair.buf = buf
|
||||||
|
|
||||||
l.writeChan <- &udpAddrFramePair{
|
l.writeChan <- pair
|
||||||
addr: addr,
|
|
||||||
buf: buf,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ type streamerUdpListener struct {
|
|||||||
nconn *net.UDPConn
|
nconn *net.UDPConn
|
||||||
running bool
|
running bool
|
||||||
readBuf *doubleBuffer
|
readBuf *doubleBuffer
|
||||||
lastFrameTime time.Time
|
|
||||||
|
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
@@ -37,7 +36,6 @@ func newStreamerUdpListener(p *program, port int, streamer *streamer,
|
|||||||
publisherIp: publisherIp,
|
publisherIp: publisherIp,
|
||||||
nconn: nconn,
|
nconn: nconn,
|
||||||
readBuf: newDoubleBuffer(2048),
|
readBuf: newDoubleBuffer(2048),
|
||||||
lastFrameTime: time.Now(),
|
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +67,7 @@ func (l *streamerUdpListener) run() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
l.lastFrameTime = time.Now()
|
l.streamer.udpLastFrameTime = time.Now()
|
||||||
|
|
||||||
l.p.events <- programEventStreamerFrame{l.streamer, l.trackId, l.trackFlowType, buf[:n]}
|
l.p.events <- programEventStreamerFrame{l.streamer, l.trackId, l.trackFlowType, buf[:n]}
|
||||||
}
|
}
|
||||||
|
|||||||
38
streamer.go
38
streamer.go
@@ -27,16 +27,17 @@ type streamerUdpListenerPair struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type streamer struct {
|
type streamer struct {
|
||||||
p *program
|
p *program
|
||||||
path string
|
path string
|
||||||
ur *url.URL
|
ur *url.URL
|
||||||
proto streamProtocol
|
proto streamProtocol
|
||||||
ready bool
|
ready bool
|
||||||
clientSdpParsed *sdp.Message
|
clientSdpParsed *sdp.Message
|
||||||
serverSdpText []byte
|
serverSdpText []byte
|
||||||
serverSdpParsed *sdp.Message
|
serverSdpParsed *sdp.Message
|
||||||
firstTime bool
|
firstTime bool
|
||||||
readBuf *doubleBuffer
|
udpLastFrameTime time.Time
|
||||||
|
readBuf *doubleBuffer
|
||||||
|
|
||||||
terminate chan struct{}
|
terminate chan struct{}
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
@@ -402,6 +403,7 @@ func (s *streamer) runUdp(conn *gortsplib.ConnClient) bool {
|
|||||||
tickerSendKeepalive := time.NewTicker(_KEEPALIVE_INTERVAL)
|
tickerSendKeepalive := time.NewTicker(_KEEPALIVE_INTERVAL)
|
||||||
defer tickerSendKeepalive.Stop()
|
defer tickerSendKeepalive.Stop()
|
||||||
|
|
||||||
|
s.udpLastFrameTime = time.Now()
|
||||||
tickerCheckStream := time.NewTicker(_CHECK_STREAM_INTERVAL)
|
tickerCheckStream := time.NewTicker(_CHECK_STREAM_INTERVAL)
|
||||||
defer tickerCheckStream.Stop()
|
defer tickerCheckStream.Stop()
|
||||||
|
|
||||||
@@ -431,21 +433,7 @@ func (s *streamer) runUdp(conn *gortsplib.ConnClient) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case <-tickerCheckStream.C:
|
case <-tickerCheckStream.C:
|
||||||
lastFrameTime := time.Time{}
|
if time.Since(s.udpLastFrameTime) >= _STREAM_DEAD_AFTER {
|
||||||
|
|
||||||
for _, pair := range streamerUdpListenerPairs {
|
|
||||||
lft := pair.udplRtp.lastFrameTime
|
|
||||||
if lft.After(lastFrameTime) {
|
|
||||||
lastFrameTime = lft
|
|
||||||
}
|
|
||||||
|
|
||||||
lft = pair.udplRtcp.lastFrameTime
|
|
||||||
if lft.After(lastFrameTime) {
|
|
||||||
lastFrameTime = lft
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Since(lastFrameTime) >= _STREAM_DEAD_AFTER {
|
|
||||||
s.log("ERR: stream is dead")
|
s.log("ERR: stream is dead")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
17
utils.go
17
utils.go
@@ -29,6 +29,23 @@ func parseIpCidrList(in []string) ([]interface{}, error) {
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ipEqualOrInRange(ip net.IP, ips []interface{}) bool {
|
||||||
|
for _, item := range ips {
|
||||||
|
switch titem := item.(type) {
|
||||||
|
case net.IP:
|
||||||
|
if titem.Equal(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case *net.IPNet:
|
||||||
|
if titem.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type trackFlowType int
|
type trackFlowType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
Reference in New Issue
Block a user