golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
5.8 KiB
321 lines
5.8 KiB
package main |
|
|
|
import ( |
|
"sync" |
|
"sync/atomic" |
|
"time" |
|
|
|
"github.com/aler9/gortsplib" |
|
) |
|
|
|
const ( |
|
sourceRtspRetryInterval = 5 * time.Second |
|
) |
|
|
|
type sourceRtspState int |
|
|
|
const ( |
|
sourceRtspStateStopped sourceRtspState = iota |
|
sourceRtspStateRunning |
|
) |
|
|
|
type sourceRtsp struct { |
|
p *program |
|
path *path |
|
state sourceRtspState |
|
tracks []*gortsplib.Track |
|
innerRunning bool |
|
|
|
innerTerminate chan struct{} |
|
innerDone chan struct{} |
|
setState chan sourceRtspState |
|
terminate chan struct{} |
|
done chan struct{} |
|
} |
|
|
|
func newSourceRtsp(p *program, path *path) *sourceRtsp { |
|
s := &sourceRtsp{ |
|
p: p, |
|
path: path, |
|
setState: make(chan sourceRtspState), |
|
terminate: make(chan struct{}), |
|
done: make(chan struct{}), |
|
} |
|
|
|
atomic.AddInt64(p.countSourcesRtsp, +1) |
|
|
|
if path.conf.SourceOnDemand { |
|
s.state = sourceRtspStateStopped |
|
} else { |
|
s.state = sourceRtspStateRunning |
|
atomic.AddInt64(p.countSourcesRtspRunning, +1) |
|
} |
|
|
|
return s |
|
} |
|
|
|
func (s *sourceRtsp) isSource() {} |
|
|
|
func (s *sourceRtsp) run(initialState sourceRtspState) { |
|
s.applyState(initialState) |
|
|
|
outer: |
|
for { |
|
select { |
|
case state := <-s.setState: |
|
s.applyState(state) |
|
|
|
case <-s.terminate: |
|
break outer |
|
} |
|
} |
|
|
|
if s.innerRunning { |
|
close(s.innerTerminate) |
|
<-s.innerDone |
|
} |
|
|
|
close(s.setState) |
|
close(s.done) |
|
} |
|
|
|
func (s *sourceRtsp) applyState(state sourceRtspState) { |
|
if state == sourceRtspStateRunning { |
|
if !s.innerRunning { |
|
s.path.log("rtsp source started") |
|
s.innerRunning = true |
|
s.innerTerminate = make(chan struct{}) |
|
s.innerDone = make(chan struct{}) |
|
go s.runInner() |
|
} |
|
} else { |
|
if s.innerRunning { |
|
close(s.innerTerminate) |
|
<-s.innerDone |
|
s.innerRunning = false |
|
s.path.log("rtsp source stopped") |
|
} |
|
} |
|
} |
|
|
|
func (s *sourceRtsp) runInner() { |
|
defer close(s.innerDone) |
|
|
|
outer: |
|
for { |
|
ok := s.runInnerInner() |
|
if !ok { |
|
break outer |
|
} |
|
|
|
t := time.NewTimer(sourceRtspRetryInterval) |
|
defer t.Stop() |
|
|
|
select { |
|
case <-s.innerTerminate: |
|
break outer |
|
case <-t.C: |
|
} |
|
} |
|
} |
|
|
|
func (s *sourceRtsp) runInnerInner() bool { |
|
s.path.log("connecting to rtsp source") |
|
|
|
var conn *gortsplib.ConnClient |
|
var err error |
|
dialDone := make(chan struct{}, 1) |
|
go func() { |
|
defer close(dialDone) |
|
conn, err = gortsplib.NewConnClient(gortsplib.ConnClientConf{ |
|
Host: s.path.conf.SourceUrl.Host, |
|
ReadTimeout: s.p.conf.ReadTimeout, |
|
WriteTimeout: s.p.conf.WriteTimeout, |
|
ReadBufferCount: 2, |
|
}) |
|
}() |
|
|
|
select { |
|
case <-s.innerTerminate: |
|
return false |
|
case <-dialDone: |
|
} |
|
|
|
if err != nil { |
|
s.path.log("rtsp source ERR: %s", err) |
|
return true |
|
} |
|
|
|
_, err = conn.Options(s.path.conf.SourceUrl) |
|
if err != nil { |
|
conn.Close() |
|
s.path.log("rtsp source ERR: %s", err) |
|
return true |
|
} |
|
|
|
tracks, _, err := conn.Describe(s.path.conf.SourceUrl) |
|
if err != nil { |
|
conn.Close() |
|
s.path.log("rtsp source ERR: %s", err) |
|
return true |
|
} |
|
|
|
// create a filtered SDP that is used by the server (not by the client) |
|
s.path.sourceSdp = tracks.Write() |
|
s.path.sourceTrackCount = len(tracks) |
|
s.tracks = tracks |
|
|
|
if s.path.conf.SourceProtocolParsed == gortsplib.StreamProtocolUDP { |
|
return s.runUDP(conn) |
|
} else { |
|
return s.runTCP(conn) |
|
} |
|
} |
|
|
|
func (s *sourceRtsp) runUDP(conn *gortsplib.ConnClient) bool { |
|
for _, track := range s.tracks { |
|
_, err := conn.SetupUDP(s.path.conf.SourceUrl, gortsplib.TransportModePlay, track, 0, 0) |
|
if err != nil { |
|
conn.Close() |
|
s.path.log("rtsp source ERR: %s", err) |
|
return true |
|
} |
|
} |
|
|
|
_, err := conn.Play(s.path.conf.SourceUrl) |
|
if err != nil { |
|
conn.Close() |
|
s.path.log("rtsp source ERR: %s", err) |
|
return true |
|
} |
|
|
|
s.p.sourceRtspReady <- s |
|
s.path.log("rtsp source ready") |
|
|
|
var wg sync.WaitGroup |
|
|
|
// receive RTP packets |
|
for trackId := range s.tracks { |
|
wg.Add(1) |
|
go func(trackId int) { |
|
defer wg.Done() |
|
|
|
for { |
|
buf, err := conn.ReadFrameUDP(trackId, gortsplib.StreamTypeRtp) |
|
if err != nil { |
|
break |
|
} |
|
|
|
s.p.readersMap.forwardFrame(s.path, trackId, |
|
gortsplib.StreamTypeRtp, buf) |
|
} |
|
}(trackId) |
|
} |
|
|
|
// receive RTCP packets |
|
for trackId := range s.tracks { |
|
wg.Add(1) |
|
go func(trackId int) { |
|
defer wg.Done() |
|
|
|
for { |
|
buf, err := conn.ReadFrameUDP(trackId, gortsplib.StreamTypeRtcp) |
|
if err != nil { |
|
break |
|
} |
|
|
|
s.p.readersMap.forwardFrame(s.path, trackId, |
|
gortsplib.StreamTypeRtcp, buf) |
|
} |
|
}(trackId) |
|
} |
|
|
|
tcpConnDone := make(chan error) |
|
go func() { |
|
tcpConnDone <- conn.LoopUDP() |
|
}() |
|
|
|
var ret bool |
|
|
|
outer: |
|
for { |
|
select { |
|
case <-s.innerTerminate: |
|
conn.Close() |
|
<-tcpConnDone |
|
ret = false |
|
break outer |
|
|
|
case err := <-tcpConnDone: |
|
conn.Close() |
|
s.path.log("rtsp source ERR: %s", err) |
|
ret = true |
|
break outer |
|
} |
|
} |
|
|
|
wg.Wait() |
|
|
|
s.p.sourceRtspNotReady <- s |
|
s.path.log("rtsp source not ready") |
|
|
|
return ret |
|
} |
|
|
|
func (s *sourceRtsp) runTCP(conn *gortsplib.ConnClient) bool { |
|
for _, track := range s.tracks { |
|
_, err := conn.SetupTCP(s.path.conf.SourceUrl, gortsplib.TransportModePlay, track) |
|
if err != nil { |
|
conn.Close() |
|
s.path.log("rtsp source ERR: %s", err) |
|
return true |
|
} |
|
} |
|
|
|
_, err := conn.Play(s.path.conf.SourceUrl) |
|
if err != nil { |
|
conn.Close() |
|
s.path.log("rtsp source ERR: %s", err) |
|
return true |
|
} |
|
|
|
s.p.sourceRtspReady <- s |
|
s.path.log("rtsp source ready") |
|
|
|
tcpConnDone := make(chan error) |
|
go func() { |
|
for { |
|
trackId, streamType, content, err := conn.ReadFrameTCP() |
|
if err != nil { |
|
tcpConnDone <- err |
|
return |
|
} |
|
|
|
s.p.readersMap.forwardFrame(s.path, trackId, streamType, content) |
|
} |
|
}() |
|
|
|
var ret bool |
|
|
|
outer: |
|
for { |
|
select { |
|
case <-s.innerTerminate: |
|
conn.Close() |
|
<-tcpConnDone |
|
ret = false |
|
break outer |
|
|
|
case err := <-tcpConnDone: |
|
conn.Close() |
|
s.path.log("rtsp source ERR: %s", err) |
|
ret = true |
|
break outer |
|
} |
|
} |
|
|
|
s.p.sourceRtspNotReady <- s |
|
s.path.log("rtsp source not ready") |
|
|
|
return ret |
|
}
|
|
|