mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-11-02 20:04:01 +08:00
add option sourceOnDemand to pull sources only when there are connected clients (#36)
This commit is contained in:
7
Makefile
7
Makefile
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
9
conf.go
9
conf.go
@@ -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
405
main.go
@@ -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
110
path.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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
205
source.go
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user