add option sourceOnDemand to pull sources only when there are connected clients (#36)

This commit is contained in:
aler9
2020-07-29 23:30:42 +02:00
parent df10e8898f
commit d7d2ba38f1
10 changed files with 543 additions and 378 deletions

View File

@@ -74,9 +74,10 @@ paths:
# readUser: test # readUser: test
# readPass: tast # readPass: tast
# proxied: proxied:
# source: rtsp://localhost:8554/mystream source: rtsp://192.168.2.198:8554/stream
# sourceProtocol: tcp sourceProtocol: tcp
sourceOnDemand: yes
# original: # original:
# runOnPublish: ffmpeg -i rtsp://localhost:8554/original -b:a 64k -c:v libx264 -preset ultrafast -b:v 500k -max_muxing_queue_size 1024 -f rtsp rtsp://localhost:8554/compressed # runOnPublish: ffmpeg -i rtsp://localhost:8554/original -b:a 64k -c:v libx264 -preset ultrafast -b:v 500k -max_muxing_queue_size 1024 -f rtsp rtsp://localhost:8554/compressed

View File

@@ -71,7 +71,7 @@ docker run --rm -it -v $PWD/rtsp-simple-server.yml:/rtsp-simple-server.yml -p 85
#### Full configuration file #### Full configuration file
To change the configuration, it's enough to edit the `rtsp-simple-server.yml` file, provided with the executable. The default configuration is [available here](rtsp-simple-server.yml). To see or change the configuration, edit the `rtsp-simple-server.yml` file, provided with the executable. The default configuration is [available here](rtsp-simple-server.yml).
#### Usage as RTSP Proxy #### Usage as RTSP Proxy

View File

@@ -23,25 +23,26 @@ const (
clientUdpWriteBufferSize = 128 * 1024 clientUdpWriteBufferSize = 128 * 1024
) )
type serverClientTrack struct { type clientTrack struct {
rtpPort int rtpPort int
rtcpPort int rtcpPort int
} }
type serverClientEvent interface { type clientEvent interface {
isServerClientEvent() isServerClientEvent()
} }
type serverClientEventFrameTcp struct { type clientEventFrameTcp struct {
frame *gortsplib.InterleavedFrame frame *gortsplib.InterleavedFrame
} }
func (serverClientEventFrameTcp) isServerClientEvent() {} func (clientEventFrameTcp) isServerClientEvent() {}
type serverClientState int type clientState int
const ( const (
clientStateStarting serverClientState = iota clientStateInitial clientState = iota
clientStateWaitingDescription
clientStateAnnounce clientStateAnnounce
clientStatePrePlay clientStatePrePlay
clientStatePlay clientStatePlay
@@ -49,10 +50,10 @@ const (
clientStateRecord clientStateRecord
) )
func (cs serverClientState) String() string { func (cs clientState) String() string {
switch cs { switch cs {
case clientStateStarting: case clientStateInitial:
return "STARTING" return "INITIAL"
case clientStateAnnounce: case clientStateAnnounce:
return "ANNOUNCE" return "ANNOUNCE"
@@ -72,36 +73,35 @@ func (cs serverClientState) String() string {
return "UNKNOWN" return "UNKNOWN"
} }
type serverClient struct { type client struct {
p *program p *program
conn *gortsplib.ConnServer conn *gortsplib.ConnServer
state serverClientState state clientState
path string path string
authUser string authUser string
authPass string authPass string
authHelper *gortsplib.AuthServer authHelper *gortsplib.AuthServer
authFailures int authFailures int
streamSdpText []byte // only if publisher streamProtocol gortsplib.StreamProtocol
streamSdpParsed *sdp.SessionDescription // only if publisher streamTracks []*clientTrack
streamProtocol gortsplib.StreamProtocol rtcpReceivers []*gortsplib.RtcpReceiver
streamTracks []*serverClientTrack readBuf *doubleBuffer
rtcpReceivers []*gortsplib.RtcpReceiver writeBuf *doubleBuffer
readBuf *doubleBuffer
writeBuf *doubleBuffer
events chan serverClientEvent // only if state = Play and gortsplib.StreamProtocol = TCP describeRes chan []byte
done chan struct{} events chan clientEvent // only if state = Play and gortsplib.StreamProtocol = TCP
done chan struct{}
} }
func newServerClient(p *program, nconn net.Conn) *serverClient { func newServerClient(p *program, nconn net.Conn) *client {
c := &serverClient{ c := &client{
p: p, p: p,
conn: gortsplib.NewConnServer(gortsplib.ConnServerConf{ conn: gortsplib.NewConnServer(gortsplib.ConnServerConf{
Conn: nconn, Conn: nconn,
ReadTimeout: p.conf.ReadTimeout, ReadTimeout: p.conf.ReadTimeout,
WriteTimeout: p.conf.WriteTimeout, WriteTimeout: p.conf.WriteTimeout,
}), }),
state: clientStateStarting, state: clientStateInitial,
readBuf: newDoubleBuffer(clientTcpReadBufferSize), readBuf: newDoubleBuffer(clientTcpReadBufferSize),
done: make(chan struct{}), done: make(chan struct{}),
} }
@@ -110,31 +110,21 @@ func newServerClient(p *program, nconn net.Conn) *serverClient {
return c return c
} }
func (c *serverClient) log(format string, args ...interface{}) { func (c *client) log(format string, args ...interface{}) {
c.p.log("[client %s] "+format, append([]interface{}{c.conn.NetConn().RemoteAddr().String()}, args...)...) c.p.log("[client %s] "+format, append([]interface{}{c.conn.NetConn().RemoteAddr().String()}, args...)...)
} }
func (c *serverClient) ip() net.IP { func (c *client) isPublisher() {}
func (c *client) ip() net.IP {
return c.conn.NetConn().RemoteAddr().(*net.TCPAddr).IP return c.conn.NetConn().RemoteAddr().(*net.TCPAddr).IP
} }
func (c *serverClient) zone() string { func (c *client) zone() string {
return c.conn.NetConn().RemoteAddr().(*net.TCPAddr).Zone return c.conn.NetConn().RemoteAddr().(*net.TCPAddr).Zone
} }
func (c *serverClient) publisherIsReady() bool { func (c *client) run() {
return c.state == clientStateRecord
}
func (c *serverClient) publisherSdpText() []byte {
return c.streamSdpText
}
func (c *serverClient) publisherSdpParsed() *sdp.SessionDescription {
return c.streamSdpParsed
}
func (c *serverClient) run() {
var runOnConnectCmd *exec.Cmd var runOnConnectCmd *exec.Cmd
if c.p.conf.RunOnConnect != "" { if c.p.conf.RunOnConnect != "" {
runOnConnectCmd = exec.Command("/bin/sh", "-c", c.p.conf.RunOnConnect) runOnConnectCmd = exec.Command("/bin/sh", "-c", c.p.conf.RunOnConnect)
@@ -176,12 +166,7 @@ outer:
close(c.done) // close() never blocks close(c.done) // close() never blocks
} }
func (c *serverClient) close() { func (c *client) writeResError(req *gortsplib.Request, code gortsplib.StatusCode, err error) {
c.conn.NetConn().Close()
<-c.done
}
func (c *serverClient) writeResError(req *gortsplib.Request, code gortsplib.StatusCode, err error) {
c.log("ERR: %s", err) c.log("ERR: %s", err)
header := gortsplib.Header{} header := gortsplib.Header{}
@@ -195,22 +180,10 @@ func (c *serverClient) writeResError(req *gortsplib.Request, code gortsplib.Stat
}) })
} }
func (c *serverClient) findConfForPath(path string) *ConfPath {
if pconf, ok := c.p.conf.Paths[path]; ok {
return pconf
}
if pconf, ok := c.p.conf.Paths["all"]; ok {
return pconf
}
return nil
}
var errAuthCritical = errors.New("auth critical") var errAuthCritical = errors.New("auth critical")
var errAuthNotCritical = errors.New("auth not critical") var errAuthNotCritical = errors.New("auth not critical")
func (c *serverClient) authenticate(ips []interface{}, user string, pass string, req *gortsplib.Request) error { func (c *client) authenticate(ips []interface{}, user string, pass string, req *gortsplib.Request) error {
// validate ip // validate ip
err := func() error { err := func() error {
if ips == nil { if ips == nil {
@@ -288,7 +261,7 @@ func (c *serverClient) authenticate(ips []interface{}, user string, pass string,
return nil return nil
} }
func (c *serverClient) handleRequest(req *gortsplib.Request) bool { func (c *client) handleRequest(req *gortsplib.Request) bool {
c.log(string(req.Method)) c.log(string(req.Method))
cseq, ok := req.Header["CSeq"] cseq, ok := req.Header["CSeq"]
@@ -315,9 +288,6 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
switch req.Method { switch req.Method {
case gortsplib.OPTIONS: case gortsplib.OPTIONS:
// do not check state, since OPTIONS can be requested
// in any state
c.conn.WriteResponse(&gortsplib.Response{ c.conn.WriteResponse(&gortsplib.Response{
StatusCode: gortsplib.StatusOK, StatusCode: gortsplib.StatusOK,
Header: gortsplib.Header{ Header: gortsplib.Header{
@@ -335,13 +305,13 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return true return true
case gortsplib.DESCRIBE: case gortsplib.DESCRIBE:
if c.state != clientStateStarting { if c.state != clientStateInitial {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, clientStateStarting)) fmt.Errorf("client is in state '%s' instead of '%s'", c.state, clientStateInitial))
return false return false
} }
pconf := c.findConfForPath(path) pconf := c.p.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))
@@ -356,9 +326,9 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return true return true
} }
res := make(chan []byte) c.describeRes = make(chan []byte)
c.p.events <- programEventClientDescribe{path, res} c.p.events <- programEventClientDescribe{c, path}
sdp := <-res sdp := <-c.describeRes
if sdp == nil { if sdp == nil {
c.writeResError(req, gortsplib.StatusNotFound, fmt.Errorf("no one is publishing on path '%s'", path)) c.writeResError(req, gortsplib.StatusNotFound, fmt.Errorf("no one is publishing on path '%s'", path))
return false return false
@@ -376,9 +346,9 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return true return true
case gortsplib.ANNOUNCE: case gortsplib.ANNOUNCE:
if c.state != clientStateStarting { if c.state != clientStateInitial {
c.writeResError(req, gortsplib.StatusBadRequest, c.writeResError(req, gortsplib.StatusBadRequest,
fmt.Errorf("client is in state '%s' instead of '%s'", c.state, clientStateStarting)) fmt.Errorf("client is in state '%s' instead of '%s'", c.state, clientStateInitial))
return false return false
} }
@@ -387,7 +357,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return false return false
} }
pconf := c.findConfForPath(path) pconf := c.p.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))
@@ -435,16 +405,13 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
sdpParsed, req.Content = sdpForServer(tracks) sdpParsed, req.Content = sdpForServer(tracks)
res := make(chan error) res := make(chan error)
c.p.events <- programEventClientAnnounce{res, c, path} c.p.events <- programEventClientAnnounce{res, c, path, req.Content, sdpParsed}
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 false
} }
c.streamSdpText = req.Content
c.streamSdpParsed = sdpParsed
c.conn.WriteResponse(&gortsplib.Response{ c.conn.WriteResponse(&gortsplib.Response{
StatusCode: gortsplib.StatusOK, StatusCode: gortsplib.StatusOK,
Header: gortsplib.Header{ Header: gortsplib.Header{
@@ -467,8 +434,8 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
switch c.state { switch c.state {
// play // play
case clientStateStarting, clientStatePrePlay: case clientStateInitial, clientStatePrePlay:
pconf := c.findConfForPath(path) pconf := c.p.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))
@@ -626,7 +593,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return false return false
} }
if len(c.streamTracks) >= len(c.streamSdpParsed.MediaDescriptions) { if len(c.streamTracks) >= len(c.p.paths[c.path].publisherSdpParsed.MediaDescriptions) {
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 false
} }
@@ -678,7 +645,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return false return false
} }
if len(c.streamTracks) >= len(c.streamSdpParsed.MediaDescriptions) { if len(c.streamTracks) >= len(c.p.paths[c.path].publisherSdpParsed.MediaDescriptions) {
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 false
} }
@@ -762,7 +729,7 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
return false return false
} }
if len(c.streamTracks) != len(c.streamSdpParsed.MediaDescriptions) { if len(c.streamTracks) != len(c.p.paths[c.path].publisherSdpParsed.MediaDescriptions) {
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 false
} }
@@ -788,12 +755,12 @@ func (c *serverClient) handleRequest(req *gortsplib.Request) bool {
} }
} }
func (c *serverClient) runPlay(path string) { func (c *client) runPlay(path string) {
pconf := c.findConfForPath(path) pconf := c.p.findConfForPath(path)
if c.streamProtocol == gortsplib.StreamProtocolTcp { if c.streamProtocol == gortsplib.StreamProtocolTcp {
c.writeBuf = newDoubleBuffer(clientTcpWriteBufferSize) c.writeBuf = newDoubleBuffer(clientTcpWriteBufferSize)
c.events = make(chan serverClientEvent) c.events = make(chan clientEvent)
} }
done := make(chan struct{}) done := make(chan struct{})
@@ -863,7 +830,7 @@ func (c *serverClient) runPlay(path string) {
case rawEvt := <-c.events: case rawEvt := <-c.events:
switch evt := rawEvt.(type) { switch evt := rawEvt.(type) {
case serverClientEventFrameTcp: case clientEventFrameTcp:
c.conn.WriteFrame(evt.frame) c.conn.WriteFrame(evt.frame)
} }
} }
@@ -887,8 +854,8 @@ func (c *serverClient) runPlay(path string) {
} }
} }
func (c *serverClient) runRecord(path string) { func (c *client) runRecord(path string) {
pconf := c.findConfForPath(path) pconf := c.p.findConfForPath(path)
c.rtcpReceivers = make([]*gortsplib.RtcpReceiver, len(c.streamTracks)) c.rtcpReceivers = make([]*gortsplib.RtcpReceiver, len(c.streamTracks))
for trackId := range c.streamTracks { for trackId := range c.streamTracks {

View File

@@ -12,11 +12,12 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
type ConfPath struct { type confPath struct {
Source string `yaml:"source"` Source string `yaml:"source"`
sourceUrl *url.URL sourceUrl *url.URL
SourceProtocol string `yaml:"sourceProtocol"` SourceProtocol string `yaml:"sourceProtocol"`
sourceProtocolParsed gortsplib.StreamProtocol sourceProtocolParsed gortsplib.StreamProtocol
SourceOnDemand bool `yaml:"sourceOnDemand"`
PublishUser string `yaml:"publishUser"` PublishUser string `yaml:"publishUser"`
PublishPass string `yaml:"publishPass"` PublishPass string `yaml:"publishPass"`
PublishIps []string `yaml:"publishIps"` PublishIps []string `yaml:"publishIps"`
@@ -41,7 +42,7 @@ type conf struct {
AuthMethods []string `yaml:"authMethods"` AuthMethods []string `yaml:"authMethods"`
authMethodsParsed []gortsplib.AuthMethod authMethodsParsed []gortsplib.AuthMethod
Pprof bool `yaml:"pprof"` Pprof bool `yaml:"pprof"`
Paths map[string]*ConfPath `yaml:"paths"` Paths map[string]*confPath `yaml:"paths"`
} }
func loadConf(fpath string, stdin io.Reader) (*conf, error) { func loadConf(fpath string, stdin io.Reader) (*conf, error) {
@@ -142,14 +143,14 @@ func loadConf(fpath string, stdin io.Reader) (*conf, error) {
} }
if len(conf.Paths) == 0 { if len(conf.Paths) == 0 {
conf.Paths = map[string]*ConfPath{ conf.Paths = map[string]*confPath{
"all": {}, "all": {},
} }
} }
for path, pconf := range conf.Paths { for path, pconf := range conf.Paths {
if pconf == nil { if pconf == nil {
conf.Paths[path] = &ConfPath{} conf.Paths[path] = &confPath{}
pconf = conf.Paths[path] pconf = conf.Paths[path]
} }

405
main.go
View File

@@ -8,6 +8,7 @@ import (
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"time"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib"
"github.com/aler9/sdp/v3" "github.com/aler9/sdp/v3"
@@ -28,29 +29,31 @@ func (programEventClientNew) isProgramEvent() {}
type programEventClientClose struct { type programEventClientClose struct {
done chan struct{} done chan struct{}
client *serverClient client *client
} }
func (programEventClientClose) isProgramEvent() {} func (programEventClientClose) isProgramEvent() {}
type programEventClientDescribe struct { type programEventClientDescribe struct {
path string client *client
res chan []byte path string
} }
func (programEventClientDescribe) isProgramEvent() {} func (programEventClientDescribe) isProgramEvent() {}
type programEventClientAnnounce struct { type programEventClientAnnounce struct {
res chan error res chan error
client *serverClient client *client
path string path string
sdpText []byte
sdpParsed *sdp.SessionDescription
} }
func (programEventClientAnnounce) isProgramEvent() {} func (programEventClientAnnounce) isProgramEvent() {}
type programEventClientSetupPlay struct { type programEventClientSetupPlay struct {
res chan error res chan error
client *serverClient client *client
path string path string
protocol gortsplib.StreamProtocol protocol gortsplib.StreamProtocol
rtpPort int rtpPort int
@@ -61,7 +64,7 @@ func (programEventClientSetupPlay) isProgramEvent() {}
type programEventClientSetupRecord struct { type programEventClientSetupRecord struct {
res chan error res chan error
client *serverClient client *client
protocol gortsplib.StreamProtocol protocol gortsplib.StreamProtocol
rtpPort int rtpPort int
rtcpPort int rtcpPort int
@@ -71,35 +74,35 @@ func (programEventClientSetupRecord) isProgramEvent() {}
type programEventClientPlay1 struct { type programEventClientPlay1 struct {
res chan error res chan error
client *serverClient client *client
} }
func (programEventClientPlay1) isProgramEvent() {} func (programEventClientPlay1) isProgramEvent() {}
type programEventClientPlay2 struct { type programEventClientPlay2 struct {
done chan struct{} done chan struct{}
client *serverClient client *client
} }
func (programEventClientPlay2) isProgramEvent() {} func (programEventClientPlay2) isProgramEvent() {}
type programEventClientPlayStop struct { type programEventClientPlayStop struct {
done chan struct{} done chan struct{}
client *serverClient client *client
} }
func (programEventClientPlayStop) isProgramEvent() {} func (programEventClientPlayStop) isProgramEvent() {}
type programEventClientRecord struct { type programEventClientRecord struct {
done chan struct{} done chan struct{}
client *serverClient client *client
} }
func (programEventClientRecord) isProgramEvent() {} func (programEventClientRecord) isProgramEvent() {}
type programEventClientRecordStop struct { type programEventClientRecordStop struct {
done chan struct{} done chan struct{}
client *serverClient client *client
} }
func (programEventClientRecordStop) isProgramEvent() {} func (programEventClientRecordStop) isProgramEvent() {}
@@ -121,46 +124,45 @@ type programEventClientFrameTcp struct {
func (programEventClientFrameTcp) isProgramEvent() {} func (programEventClientFrameTcp) isProgramEvent() {}
type programEventStreamerReady struct { type programEventSourceReady struct {
source *source source *source
} }
func (programEventStreamerReady) isProgramEvent() {} func (programEventSourceReady) isProgramEvent() {}
type programEventStreamerNotReady struct { type programEventSourceNotReady struct {
source *source source *source
} }
func (programEventStreamerNotReady) isProgramEvent() {} func (programEventSourceNotReady) isProgramEvent() {}
type programEventStreamerFrame struct { type programEventSourceFrame struct {
source *source source *source
trackId int trackId int
streamType gortsplib.StreamType streamType gortsplib.StreamType
buf []byte buf []byte
} }
func (programEventStreamerFrame) isProgramEvent() {} func (programEventSourceFrame) isProgramEvent() {}
type programEventSourceReset struct {
source *source
}
func (programEventSourceReset) isProgramEvent() {}
type programEventTerminate struct{} type programEventTerminate struct{}
func (programEventTerminate) isProgramEvent() {} func (programEventTerminate) isProgramEvent() {}
// a publisher can be either a serverClient or a source
type publisher interface {
publisherIsReady() bool
publisherSdpText() []byte
publisherSdpParsed() *sdp.SessionDescription
}
type program struct { type program struct {
conf *conf conf *conf
rtspl *serverTcpListener rtspl *serverTcp
rtpl *serverUdpListener rtpl *serverUdp
rtcpl *serverUdpListener rtcpl *serverUdp
clients map[*serverClient]struct{}
sources []*source sources []*source
publishers map[string]publisher clients map[*client]struct{}
paths map[string]*path
publisherCount int publisherCount int
readerCount int readerCount int
@@ -188,18 +190,18 @@ func newProgram(args []string, stdin io.Reader) (*program, error) {
} }
p := &program{ p := &program{
conf: conf, conf: conf,
clients: make(map[*serverClient]struct{}), clients: make(map[*client]struct{}),
publishers: make(map[string]publisher), paths: make(map[string]*path),
events: make(chan programEvent), events: make(chan programEvent),
done: make(chan struct{}), done: make(chan struct{}),
} }
for path, pconf := range conf.Paths { for path, pconf := range conf.Paths {
if pconf.Source != "record" { if pconf.Source != "record" {
s := newSource(p, path, pconf.sourceUrl, pconf.sourceProtocolParsed) s := newSource(p, path, pconf)
p.sources = append(p.sources, s) p.sources = append(p.sources, s)
p.publishers[path] = s p.paths[path] = newPath(p, path, s)
} }
} }
@@ -217,17 +219,17 @@ func newProgram(args []string, stdin io.Reader) (*program, error) {
http.DefaultServeMux = http.NewServeMux() http.DefaultServeMux = http.NewServeMux()
} }
p.rtpl, err = newServerUdpListener(p, conf.RtpPort, gortsplib.StreamTypeRtp) p.rtpl, err = newServerUdp(p, conf.RtpPort, gortsplib.StreamTypeRtp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
p.rtcpl, err = newServerUdpListener(p, conf.RtcpPort, gortsplib.StreamTypeRtcp) p.rtcpl, err = newServerUdp(p, conf.RtcpPort, gortsplib.StreamTypeRtcp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
p.rtspl, err = newServerTcpListener(p) p.rtspl, err = newServerTcp(p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -249,163 +251,170 @@ func (p *program) log(format string, args ...interface{}) {
} }
func (p *program) run() { func (p *program) run() {
checkPathsTicker := time.NewTicker(5 * time.Second)
defer checkPathsTicker.Stop()
outer: outer:
for rawEvt := range p.events { for {
switch evt := rawEvt.(type) { select {
case programEventClientNew: case <-checkPathsTicker.C:
c := newServerClient(p, evt.nconn) for _, path := range p.paths {
p.clients[c] = struct{}{} path.check()
c.log("connected") }
case programEventClientClose: case rawEvt := <-p.events:
// already deleted switch evt := rawEvt.(type) {
if _, ok := p.clients[evt.client]; !ok { case programEventClientNew:
c := newServerClient(p, evt.nconn)
p.clients[c] = struct{}{}
c.log("connected")
case programEventClientClose:
delete(p.clients, evt.client)
if evt.client.path != "" {
if path, ok := p.paths[evt.client.path]; ok {
// if this is a publisher
if path.publisher == evt.client {
path.publisherReset()
// delete the path
delete(p.paths, evt.client.path)
}
}
}
evt.client.log("disconnected")
close(evt.done) close(evt.done)
continue
}
delete(p.clients, evt.client) case programEventClientDescribe:
path, ok := p.paths[evt.path]
if evt.client.path != "" { // no path: return 404
if pub, ok := p.publishers[evt.client.path]; ok && pub == evt.client { if !ok {
delete(p.publishers, evt.client.path) evt.client.describeRes <- nil
continue
} }
}
evt.client.log("disconnected") sdpText, wait := path.describe()
close(evt.done)
case programEventClientDescribe: if wait {
pub, ok := p.publishers[evt.path] evt.client.path = evt.path
if !ok || !pub.publisherIsReady() { evt.client.state = clientStateWaitingDescription
continue
}
evt.client.describeRes <- sdpText
case programEventClientAnnounce:
_, ok := p.paths[evt.path]
if ok {
evt.res <- fmt.Errorf("someone is already publishing on path '%s'", evt.path)
continue
}
evt.client.path = evt.path
evt.client.state = clientStateAnnounce
p.paths[evt.path] = newPath(p, evt.path, evt.client)
p.paths[evt.path].publisherSdpText = evt.sdpText
p.paths[evt.path].publisherSdpParsed = evt.sdpParsed
evt.res <- nil evt.res <- nil
continue
}
evt.res <- pub.publisherSdpText() case programEventClientSetupPlay:
path, ok := p.paths[evt.path]
case programEventClientAnnounce: if !ok || !path.publisherReady {
_, ok := p.publishers[evt.path] evt.res <- fmt.Errorf("no one is publishing on path '%s'", evt.path)
if ok { continue
evt.res <- fmt.Errorf("someone is already publishing on path '%s'", evt.path)
continue
}
evt.client.path = evt.path
evt.client.state = clientStateAnnounce
p.publishers[evt.path] = evt.client
evt.res <- nil
case programEventClientSetupPlay:
pub, ok := p.publishers[evt.path]
if !ok || !pub.publisherIsReady() {
evt.res <- fmt.Errorf("no one is streaming on path '%s'", evt.path)
continue
}
sdpParsed := pub.publisherSdpParsed()
if len(evt.client.streamTracks) >= len(sdpParsed.MediaDescriptions) {
evt.res <- fmt.Errorf("all the tracks have already been setup")
continue
}
evt.client.path = evt.path
evt.client.streamProtocol = evt.protocol
evt.client.streamTracks = append(evt.client.streamTracks, &serverClientTrack{
rtpPort: evt.rtpPort,
rtcpPort: evt.rtcpPort,
})
evt.client.state = clientStatePrePlay
evt.res <- nil
case programEventClientSetupRecord:
evt.client.streamProtocol = evt.protocol
evt.client.streamTracks = append(evt.client.streamTracks, &serverClientTrack{
rtpPort: evt.rtpPort,
rtcpPort: evt.rtcpPort,
})
evt.client.state = clientStatePreRecord
evt.res <- nil
case programEventClientPlay1:
pub, ok := p.publishers[evt.client.path]
if !ok || !pub.publisherIsReady() {
evt.res <- fmt.Errorf("no one is streaming on path '%s'", evt.client.path)
continue
}
sdpParsed := pub.publisherSdpParsed()
if len(evt.client.streamTracks) != len(sdpParsed.MediaDescriptions) {
evt.res <- fmt.Errorf("not all tracks have been setup")
continue
}
evt.res <- nil
case programEventClientPlay2:
p.readerCount += 1
evt.client.state = clientStatePlay
close(evt.done)
case programEventClientPlayStop:
p.readerCount -= 1
evt.client.state = clientStatePrePlay
close(evt.done)
case programEventClientRecord:
p.publisherCount += 1
evt.client.state = clientStateRecord
close(evt.done)
case programEventClientRecordStop:
p.publisherCount -= 1
evt.client.state = clientStatePreRecord
// close all other clients that share the same path
for oc := range p.clients {
if oc != evt.client && oc.path == evt.client.path {
go oc.close()
} }
}
close(evt.done) if len(evt.client.streamTracks) >= len(path.publisherSdpParsed.MediaDescriptions) {
evt.res <- fmt.Errorf("all the tracks have already been setup")
case programEventClientFrameUdp: continue
client, trackId := p.findPublisher(evt.addr, evt.streamType)
if client == nil {
continue
}
client.rtcpReceivers[trackId].OnFrame(evt.streamType, evt.buf)
p.forwardFrame(client.path, trackId, evt.streamType, evt.buf)
case programEventClientFrameTcp:
p.forwardFrame(evt.path, evt.trackId, evt.streamType, evt.buf)
case programEventStreamerReady:
evt.source.ready = true
p.publisherCount += 1
evt.source.log("ready")
case programEventStreamerNotReady:
evt.source.ready = false
p.publisherCount -= 1
evt.source.log("not ready")
// close all clients that share the same path
for oc := range p.clients {
if oc.path == evt.source.path {
go oc.close()
} }
evt.client.path = evt.path
evt.client.streamProtocol = evt.protocol
evt.client.streamTracks = append(evt.client.streamTracks, &clientTrack{
rtpPort: evt.rtpPort,
rtcpPort: evt.rtcpPort,
})
evt.client.state = clientStatePrePlay
evt.res <- nil
case programEventClientSetupRecord:
evt.client.streamProtocol = evt.protocol
evt.client.streamTracks = append(evt.client.streamTracks, &clientTrack{
rtpPort: evt.rtpPort,
rtcpPort: evt.rtcpPort,
})
evt.client.state = clientStatePreRecord
evt.res <- nil
case programEventClientPlay1:
path, ok := p.paths[evt.client.path]
if !ok || !path.publisherReady {
evt.res <- fmt.Errorf("no one is publishing on path '%s'", evt.client.path)
continue
}
if len(evt.client.streamTracks) != len(path.publisherSdpParsed.MediaDescriptions) {
evt.res <- fmt.Errorf("not all tracks have been setup")
continue
}
evt.res <- nil
case programEventClientPlay2:
p.readerCount += 1
evt.client.state = clientStatePlay
close(evt.done)
case programEventClientPlayStop:
p.readerCount -= 1
evt.client.state = clientStatePrePlay
close(evt.done)
case programEventClientRecord:
p.publisherCount += 1
evt.client.state = clientStateRecord
p.paths[evt.client.path].publisherSetReady()
close(evt.done)
case programEventClientRecordStop:
p.publisherCount -= 1
evt.client.state = clientStatePreRecord
p.paths[evt.client.path].publisherSetNotReady()
close(evt.done)
case programEventClientFrameUdp:
client, trackId := p.findClientPublisher(evt.addr, evt.streamType)
if client == nil {
continue
}
client.rtcpReceivers[trackId].OnFrame(evt.streamType, evt.buf)
p.forwardFrame(client.path, trackId, evt.streamType, evt.buf)
case programEventClientFrameTcp:
p.forwardFrame(evt.path, evt.trackId, evt.streamType, evt.buf)
case programEventSourceReady:
evt.source.log("ready")
p.paths[evt.source.path].publisherSetReady()
case programEventSourceNotReady:
evt.source.log("not ready")
p.paths[evt.source.path].publisherSetNotReady()
case programEventSourceFrame:
p.forwardFrame(evt.source.path, evt.trackId, evt.streamType, evt.buf)
case programEventSourceReset:
p.paths[evt.source.path].publisherReset()
case programEventTerminate:
break outer
} }
case programEventStreamerFrame:
p.forwardFrame(evt.source.path, evt.trackId, evt.streamType, evt.buf)
case programEventTerminate:
break outer
} }
} }
@@ -416,7 +425,7 @@ outer:
close(evt.done) close(evt.done)
case programEventClientDescribe: case programEventClientDescribe:
evt.res <- nil evt.client.describeRes <- nil
case programEventClientAnnounce: case programEventClientAnnounce:
evt.res <- fmt.Errorf("terminated") evt.res <- fmt.Errorf("terminated")
@@ -446,7 +455,8 @@ outer:
}() }()
for _, s := range p.sources { for _, s := range p.sources {
s.close() s.events <- sourceEventTerminate{}
<-s.done
} }
p.rtspl.close() p.rtspl.close()
@@ -454,7 +464,8 @@ outer:
p.rtpl.close() p.rtpl.close()
for c := range p.clients { for c := range p.clients {
c.close() c.conn.NetConn().Close()
<-c.done
} }
close(p.events) close(p.events)
@@ -466,9 +477,21 @@ func (p *program) close() {
<-p.done <-p.done
} }
func (p *program) findPublisher(addr *net.UDPAddr, streamType gortsplib.StreamType) (*serverClient, int) { func (p *program) findConfForPath(path string) *confPath {
for _, pub := range p.publishers { if pconf, ok := p.conf.Paths[path]; ok {
cl, ok := pub.(*serverClient) return pconf
}
if pconf, ok := p.conf.Paths["all"]; ok {
return pconf
}
return nil
}
func (p *program) findClientPublisher(addr *net.UDPAddr, streamType gortsplib.StreamType) (*client, int) {
for _, path := range p.paths {
cl, ok := path.publisher.(*client)
if !ok { if !ok {
continue continue
} }
@@ -523,7 +546,7 @@ func (p *program) forwardFrame(path string, trackId int, streamType gortsplib.St
buf = buf[:len(frame)] buf = buf[:len(frame)]
copy(buf, frame) copy(buf, frame)
client.events <- serverClientEventFrameTcp{ client.events <- clientEventFrameTcp{
frame: &gortsplib.InterleavedFrame{ frame: &gortsplib.InterleavedFrame{
TrackId: trackId, TrackId: trackId,
StreamType: streamType, StreamType: streamType,

110
path.go Normal file
View File

@@ -0,0 +1,110 @@
package main
import (
"time"
"github.com/aler9/sdp/v3"
)
// a publisher is either a client or a source
type publisher interface {
isPublisher()
}
type path struct {
p *program
id string
publisher publisher
publisherReady bool
publisherSdpText []byte
publisherSdpParsed *sdp.SessionDescription
lastRequested time.Time
}
func newPath(p *program, id string, publisher publisher) *path {
return &path{
p: p,
id: id,
publisher: publisher,
}
}
func (p *path) check() {
hasClients := func() bool {
for c := range p.p.clients {
if c.path == p.id {
return true
}
}
return false
}()
source, publisherIsSource := p.publisher.(*source)
// stop source if needed
if !hasClients &&
publisherIsSource &&
source.state == sourceStateRunning &&
time.Since(p.lastRequested) >= 10*time.Second {
source.log("stopping due to inactivity")
source.state = sourceStateStopped
source.events <- sourceEventApplyState{source.state}
}
}
func (p *path) describe() ([]byte, bool) {
p.lastRequested = time.Now()
// publisher was found but is not ready: wait
if !p.publisherReady {
// start source if needed
if source, ok := p.publisher.(*source); ok && source.state == sourceStateStopped {
source.log("starting on demand")
source.state = sourceStateRunning
source.events <- sourceEventApplyState{source.state}
}
return nil, true
}
// publisher was found and is ready
return p.publisherSdpText, false
}
func (p *path) publisherSetReady() {
p.publisherReady = true
// reply to all clients that are waiting for a description
for c := range p.p.clients {
if c.state == clientStateWaitingDescription &&
c.path == p.id {
c.path = ""
c.state = clientStateInitial
c.describeRes <- p.publisherSdpText
}
}
}
func (p *path) publisherSetNotReady() {
p.publisherReady = false
// close all clients that are reading
for c := range p.p.clients {
if c.state != clientStateWaitingDescription &&
c != p.publisher &&
c.path == p.id {
c.conn.NetConn().Close()
}
}
}
func (p *path) publisherReset() {
// reply to all clients that were waiting for a description
for oc := range p.p.clients {
if oc.state == clientStateWaitingDescription &&
oc.path == p.id {
oc.path = ""
oc.state = clientStateInitial
oc.describeRes <- nil
}
}
}

View File

@@ -15,6 +15,7 @@ readTimeout: 10s
# timeout of write operations # timeout of write operations
writeTimeout: 5s writeTimeout: 5s
# supported authentication methods # supported authentication methods
# WARNING: both methods are insecure, use RTSP inside a VPN to enforce security.
authMethods: [basic, digest] authMethods: [basic, digest]
# enable pprof on port 9999 to monitor performances # enable pprof on port 9999 to monitor performances
pprof: false pprof: false
@@ -29,6 +30,9 @@ paths:
source: record source: record
# if the source is an RTSP url, this is the protocol that will be used to pull the stream # if the source is an RTSP url, this is the protocol that will be used to pull the stream
sourceProtocol: udp sourceProtocol: udp
# if the source is an RTSP url, it will be pulled only when at least one reader
# is connected, saving bandwidth
sourceOnDemand: no
# username required to publish # username required to publish
publishUser: publishUser:

View File

@@ -4,14 +4,14 @@ import (
"net" "net"
) )
type serverTcpListener struct { type serverTcp struct {
p *program p *program
nconn *net.TCPListener nconn *net.TCPListener
done chan struct{} done chan struct{}
} }
func newServerTcpListener(p *program) (*serverTcpListener, error) { func newServerTcp(p *program) (*serverTcp, error) {
nconn, err := net.ListenTCP("tcp", &net.TCPAddr{ nconn, err := net.ListenTCP("tcp", &net.TCPAddr{
Port: p.conf.RtspPort, Port: p.conf.RtspPort,
}) })
@@ -19,7 +19,7 @@ func newServerTcpListener(p *program) (*serverTcpListener, error) {
return nil, err return nil, err
} }
l := &serverTcpListener{ l := &serverTcp{
p: p, p: p,
nconn: nconn, nconn: nconn,
done: make(chan struct{}), done: make(chan struct{}),
@@ -29,11 +29,11 @@ func newServerTcpListener(p *program) (*serverTcpListener, error) {
return l, nil return l, nil
} }
func (l *serverTcpListener) log(format string, args ...interface{}) { func (l *serverTcp) log(format string, args ...interface{}) {
l.p.log("[TCP listener] "+format, args...) l.p.log("[TCP listener] "+format, args...)
} }
func (l *serverTcpListener) run() { func (l *serverTcp) run() {
for { for {
nconn, err := l.nconn.AcceptTCP() nconn, err := l.nconn.AcceptTCP()
if err != nil { if err != nil {
@@ -46,7 +46,7 @@ func (l *serverTcpListener) run() {
close(l.done) close(l.done)
} }
func (l *serverTcpListener) close() { func (l *serverTcp) close() {
l.nconn.Close() l.nconn.Close()
<-l.done <-l.done
} }

View File

@@ -12,7 +12,7 @@ type udpAddrBufPair struct {
buf []byte buf []byte
} }
type serverUdpListener struct { type serverUdp struct {
p *program p *program
nconn *net.UDPConn nconn *net.UDPConn
streamType gortsplib.StreamType streamType gortsplib.StreamType
@@ -23,7 +23,7 @@ type serverUdpListener struct {
done chan struct{} done chan struct{}
} }
func newServerUdpListener(p *program, port int, streamType gortsplib.StreamType) (*serverUdpListener, error) { func newServerUdp(p *program, port int, streamType gortsplib.StreamType) (*serverUdp, error) {
nconn, err := net.ListenUDP("udp", &net.UDPAddr{ nconn, err := net.ListenUDP("udp", &net.UDPAddr{
Port: port, Port: port,
}) })
@@ -31,7 +31,7 @@ func newServerUdpListener(p *program, port int, streamType gortsplib.StreamType)
return nil, err return nil, err
} }
l := &serverUdpListener{ l := &serverUdp{
p: p, p: p,
nconn: nconn, nconn: nconn,
streamType: streamType, streamType: streamType,
@@ -45,7 +45,7 @@ func newServerUdpListener(p *program, port int, streamType gortsplib.StreamType)
return l, nil return l, nil
} }
func (l *serverUdpListener) log(format string, args ...interface{}) { func (l *serverUdp) log(format string, args ...interface{}) {
var label string var label string
if l.streamType == gortsplib.StreamTypeRtp { if l.streamType == gortsplib.StreamTypeRtp {
label = "RTP" label = "RTP"
@@ -55,7 +55,7 @@ func (l *serverUdpListener) log(format string, args ...interface{}) {
l.p.log("[UDP/"+label+" listener] "+format, args...) l.p.log("[UDP/"+label+" listener] "+format, args...)
} }
func (l *serverUdpListener) run() { func (l *serverUdp) run() {
writeDone := make(chan struct{}) writeDone := make(chan struct{})
go func() { go func() {
defer close(writeDone) defer close(writeDone)
@@ -85,12 +85,12 @@ func (l *serverUdpListener) run() {
close(l.done) close(l.done)
} }
func (l *serverUdpListener) close() { func (l *serverUdp) close() {
l.nconn.Close() l.nconn.Close()
<-l.done <-l.done
} }
func (l *serverUdpListener) write(pair *udpAddrBufPair) { func (l *serverUdp) write(pair *udpAddrBufPair) {
// replace input buffer with write buffer // replace input buffer with write buffer
buf := l.writeBuf.swap() buf := l.writeBuf.swap()
buf = buf[:len(pair.buf)] buf = buf[:len(pair.buf)]

205
source.go
View File

@@ -3,13 +3,11 @@ package main
import ( import (
"math/rand" "math/rand"
"net" "net"
"net/url"
"os" "os"
"sync" "sync"
"time" "time"
"github.com/aler9/gortsplib" "github.com/aler9/gortsplib"
"github.com/aler9/sdp/v3"
) )
const ( const (
@@ -18,28 +16,51 @@ const (
sourceTcpReadBufferSize = 128 * 1024 sourceTcpReadBufferSize = 128 * 1024
) )
type source struct { type sourceState int
p *program
path string
u *url.URL
proto gortsplib.StreamProtocol
ready bool
tracks []*gortsplib.Track
serverSdpText []byte
serverSdpParsed *sdp.SessionDescription
terminate chan struct{} const (
done chan struct{} sourceStateStopped sourceState = iota
sourceStateRunning
)
type sourceEvent interface {
isSourceEvent()
} }
func newSource(p *program, path string, u *url.URL, proto gortsplib.StreamProtocol) *source { type sourceEventApplyState struct {
state sourceState
}
func (sourceEventApplyState) isSourceEvent() {}
type sourceEventTerminate struct{}
func (sourceEventTerminate) isSourceEvent() {}
type source struct {
p *program
path string
pconf *confPath
state sourceState
tracks []*gortsplib.Track
events chan sourceEvent
done chan struct{}
}
func newSource(p *program, path string, pconf *confPath) *source {
s := &source{ s := &source{
p: p, p: p,
path: path, path: path,
u: u, pconf: pconf,
proto: proto, events: make(chan sourceEvent),
terminate: make(chan struct{}), done: make(chan struct{}),
done: make(chan struct{}), }
if pconf.SourceOnDemand {
s.state = sourceStateStopped
} else {
s.state = sourceStateRunning
} }
return s return s
@@ -49,45 +70,88 @@ func (s *source) log(format string, args ...interface{}) {
s.p.log("[source "+s.path+"] "+format, args...) s.p.log("[source "+s.path+"] "+format, args...)
} }
func (s *source) publisherIsReady() bool { func (s *source) isPublisher() {}
return s.ready
}
func (s *source) publisherSdpText() []byte {
return s.serverSdpText
}
func (s *source) publisherSdpParsed() *sdp.SessionDescription {
return s.serverSdpParsed
}
func (s *source) run() { func (s *source) run() {
for { running := false
ok := s.do() var doTerminate chan struct{}
if !ok { var doDone chan struct{}
break
}
t := time.NewTimer(sourceRetryInterval) applyState := func(state sourceState) {
select { if state == sourceStateRunning {
case <-s.terminate: if !running {
break s.log("started")
case <-t.C: running = true
doTerminate = make(chan struct{})
doDone = make(chan struct{})
go s.do(doTerminate, doDone)
}
} else {
if running {
close(doTerminate)
<-doDone
running = false
s.log("stopped")
}
} }
} }
applyState(s.state)
outer:
for rawEvt := range s.events {
switch evt := rawEvt.(type) {
case sourceEventApplyState:
applyState(evt.state)
case sourceEventTerminate:
break outer
}
}
if running {
close(doTerminate)
<-doDone
}
close(s.done) close(s.done)
} }
func (s *source) do() bool { func (s *source) do(terminate chan struct{}, done chan struct{}) {
s.log("initializing with protocol %s", s.proto) defer close(done)
for {
ok := s.doInner(terminate)
if !ok {
break
}
s.p.events <- programEventSourceReset{s}
if !func() bool {
t := time.NewTimer(sourceRetryInterval)
defer t.Stop()
select {
case <-terminate:
return false
case <-t.C:
return true
}
}() {
break
}
}
}
func (s *source) doInner(terminate chan struct{}) bool {
s.log("connecting")
var conn *gortsplib.ConnClient var conn *gortsplib.ConnClient
var err error var err error
dialDone := make(chan struct{}) dialDone := make(chan struct{})
go func() { go func() {
conn, err = gortsplib.NewConnClient(gortsplib.ConnClientConf{ conn, err = gortsplib.NewConnClient(gortsplib.ConnClientConf{
Host: s.u.Host, Host: s.pconf.sourceUrl.Host,
ReadTimeout: s.p.conf.ReadTimeout, ReadTimeout: s.p.conf.ReadTimeout,
WriteTimeout: s.p.conf.WriteTimeout, WriteTimeout: s.p.conf.WriteTimeout,
}) })
@@ -95,7 +159,7 @@ func (s *source) do() bool {
}() }()
select { select {
case <-s.terminate: case <-terminate:
return false return false
case <-dialDone: case <-dialDone:
} }
@@ -107,13 +171,13 @@ func (s *source) do() bool {
defer conn.Close() defer conn.Close()
_, err = conn.Options(s.u) _, err = conn.Options(s.pconf.sourceUrl)
if err != nil { if err != nil {
s.log("ERR: %s", err) s.log("ERR: %s", err)
return true return true
} }
tracks, _, err := conn.Describe(s.u) tracks, _, err := conn.Describe(s.pconf.sourceUrl)
if err != nil { if err != nil {
s.log("ERR: %s", err) s.log("ERR: %s", err)
return true return true
@@ -123,17 +187,17 @@ func (s *source) do() bool {
serverSdpParsed, serverSdpText := sdpForServer(tracks) serverSdpParsed, serverSdpText := sdpForServer(tracks)
s.tracks = tracks s.tracks = tracks
s.serverSdpText = serverSdpText s.p.paths[s.path].publisherSdpText = serverSdpText
s.serverSdpParsed = serverSdpParsed s.p.paths[s.path].publisherSdpParsed = serverSdpParsed
if s.proto == gortsplib.StreamProtocolUdp { if s.pconf.sourceProtocolParsed == gortsplib.StreamProtocolUdp {
return s.runUdp(conn) return s.runUdp(terminate, conn)
} else { } else {
return s.runTcp(conn) return s.runTcp(terminate, conn)
} }
} }
func (s *source) runUdp(conn *gortsplib.ConnClient) bool { func (s *source) runUdp(terminate chan struct{}, conn *gortsplib.ConnClient) bool {
type trackListenerPair struct { type trackListenerPair struct {
rtpl *gortsplib.ConnClientUdpListener rtpl *gortsplib.ConnClientUdpListener
rtcpl *gortsplib.ConnClientUdpListener rtcpl *gortsplib.ConnClientUdpListener
@@ -151,7 +215,7 @@ func (s *source) runUdp(conn *gortsplib.ConnClient) bool {
rtpPort := (rand.Intn((65535-10000)/2) * 2) + 10000 rtpPort := (rand.Intn((65535-10000)/2) * 2) + 10000
rtcpPort := rtpPort + 1 rtcpPort := rtpPort + 1
rtpl, rtcpl, _, err = conn.SetupUdp(s.u, track, rtpPort, rtcpPort) rtpl, rtcpl, _, err = conn.SetupUdp(s.pconf.sourceUrl, track, rtpPort, rtcpPort)
if err != nil { if err != nil {
// retry if it's a bind error // retry if it's a bind error
if nerr, ok := err.(*net.OpError); ok { if nerr, ok := err.(*net.OpError); ok {
@@ -175,13 +239,13 @@ func (s *source) runUdp(conn *gortsplib.ConnClient) bool {
}) })
} }
_, err := conn.Play(s.u) _, err := conn.Play(s.pconf.sourceUrl)
if err != nil { if err != nil {
s.log("ERR: %s", err) s.log("ERR: %s", err)
return true return true
} }
s.p.events <- programEventStreamerReady{s} s.p.events <- programEventSourceReady{s}
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -201,7 +265,7 @@ func (s *source) runUdp(conn *gortsplib.ConnClient) bool {
break break
} }
s.p.events <- programEventStreamerFrame{s, trackId, gortsplib.StreamTypeRtp, buf[:n]} s.p.events <- programEventSourceFrame{s, trackId, gortsplib.StreamTypeRtp, buf[:n]}
} }
}(trackId, lp.rtpl) }(trackId, lp.rtpl)
@@ -218,14 +282,14 @@ func (s *source) runUdp(conn *gortsplib.ConnClient) bool {
break break
} }
s.p.events <- programEventStreamerFrame{s, trackId, gortsplib.StreamTypeRtcp, buf[:n]} s.p.events <- programEventSourceFrame{s, trackId, gortsplib.StreamTypeRtcp, buf[:n]}
} }
}(trackId, lp.rtcpl) }(trackId, lp.rtcpl)
} }
tcpConnDone := make(chan error) tcpConnDone := make(chan error)
go func() { go func() {
tcpConnDone <- conn.LoopUDP(s.u) tcpConnDone <- conn.LoopUDP(s.pconf.sourceUrl)
}() }()
var ret bool var ret bool
@@ -233,7 +297,7 @@ func (s *source) runUdp(conn *gortsplib.ConnClient) bool {
outer: outer:
for { for {
select { select {
case <-s.terminate: case <-terminate:
conn.NetConn().Close() conn.NetConn().Close()
<-tcpConnDone <-tcpConnDone
ret = false ret = false
@@ -246,7 +310,7 @@ outer:
} }
} }
s.p.events <- programEventStreamerNotReady{s} s.p.events <- programEventSourceNotReady{s}
for _, lp := range listeners { for _, lp := range listeners {
lp.rtpl.Close() lp.rtpl.Close()
@@ -257,22 +321,22 @@ outer:
return ret return ret
} }
func (s *source) runTcp(conn *gortsplib.ConnClient) bool { func (s *source) runTcp(terminate chan struct{}, conn *gortsplib.ConnClient) bool {
for _, track := range s.tracks { for _, track := range s.tracks {
_, err := conn.SetupTcp(s.u, track) _, err := conn.SetupTcp(s.pconf.sourceUrl, track)
if err != nil { if err != nil {
s.log("ERR: %s", err) s.log("ERR: %s", err)
return true return true
} }
} }
_, err := conn.Play(s.u) _, err := conn.Play(s.pconf.sourceUrl)
if err != nil { if err != nil {
s.log("ERR: %s", err) s.log("ERR: %s", err)
return true return true
} }
s.p.events <- programEventStreamerReady{s} s.p.events <- programEventSourceReady{s}
frame := &gortsplib.InterleavedFrame{} frame := &gortsplib.InterleavedFrame{}
doubleBuf := newDoubleBuffer(sourceTcpReadBufferSize) doubleBuf := newDoubleBuffer(sourceTcpReadBufferSize)
@@ -289,7 +353,7 @@ func (s *source) runTcp(conn *gortsplib.ConnClient) bool {
return return
} }
s.p.events <- programEventStreamerFrame{s, frame.TrackId, frame.StreamType, frame.Content} s.p.events <- programEventSourceFrame{s, frame.TrackId, frame.StreamType, frame.Content}
} }
}() }()
@@ -298,7 +362,7 @@ func (s *source) runTcp(conn *gortsplib.ConnClient) bool {
outer: outer:
for { for {
select { select {
case <-s.terminate: case <-terminate:
conn.NetConn().Close() conn.NetConn().Close()
<-tcpConnDone <-tcpConnDone
ret = false ret = false
@@ -311,12 +375,7 @@ outer:
} }
} }
s.p.events <- programEventStreamerNotReady{s} s.p.events <- programEventSourceNotReady{s}
return ret return ret
} }
func (s *source) close() {
close(s.terminate)
<-s.done
}