2020-06-30 13:12:39 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"net"
|
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/aler9/gortsplib"
|
2020-07-12 15:24:12 +00:00
|
|
|
"github.com/pion/sdp"
|
2020-06-30 13:12:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2020-07-12 10:34:35 +00:00
|
|
|
_STREAMER_RETRY_INTERVAL = 5 * time.Second
|
|
|
|
_STREAMER_CHECK_STREAM_INTERVAL = 5 * time.Second
|
|
|
|
_STREAMER_KEEPALIVE_INTERVAL = 60 * time.Second
|
|
|
|
_STREAMER_RECEIVER_REPORT_INTERVAL = 10 * time.Second
|
2020-06-30 13:12:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type streamerUdpListenerPair struct {
|
2020-07-12 10:34:35 +00:00
|
|
|
rtpl *streamerUdpListener
|
|
|
|
rtcpl *streamerUdpListener
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type streamer struct {
|
2020-07-12 10:34:35 +00:00
|
|
|
p *program
|
|
|
|
path string
|
2020-07-12 20:53:22 +00:00
|
|
|
u *url.URL
|
2020-07-12 10:34:35 +00:00
|
|
|
proto streamProtocol
|
|
|
|
ready bool
|
2020-07-12 15:24:12 +00:00
|
|
|
clientSdpParsed *sdp.SessionDescription
|
2020-07-12 10:34:35 +00:00
|
|
|
serverSdpText []byte
|
2020-07-12 15:24:12 +00:00
|
|
|
serverSdpParsed *sdp.SessionDescription
|
2020-07-12 10:34:35 +00:00
|
|
|
rtcpReceivers []*rtcpReceiver
|
|
|
|
readBuf *doubleBuffer
|
2020-06-30 13:12:39 +00:00
|
|
|
|
|
|
|
terminate chan struct{}
|
|
|
|
done chan struct{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newStreamer(p *program, path string, source string, sourceProtocol string) (*streamer, error) {
|
2020-07-12 20:53:22 +00:00
|
|
|
u, err := url.Parse(source)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("'%s' is not a valid source not an RTSP url", source)
|
|
|
|
}
|
2020-07-12 20:53:22 +00:00
|
|
|
if u.Scheme != "rtsp" {
|
2020-06-30 13:12:39 +00:00
|
|
|
return nil, fmt.Errorf("'%s' is not a valid RTSP url", source)
|
|
|
|
}
|
2020-07-12 20:53:22 +00:00
|
|
|
if u.Port() == "" {
|
|
|
|
u.Host += ":554"
|
2020-07-02 09:58:50 +00:00
|
|
|
}
|
2020-07-12 20:53:22 +00:00
|
|
|
if u.User != nil {
|
|
|
|
pass, _ := u.User.Password()
|
|
|
|
user := u.User.Username()
|
2020-06-30 13:12:39 +00:00
|
|
|
if user != "" && pass == "" ||
|
|
|
|
user == "" && pass != "" {
|
|
|
|
fmt.Errorf("username and password must be both provided")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
proto, err := func() (streamProtocol, error) {
|
|
|
|
switch sourceProtocol {
|
|
|
|
case "udp":
|
|
|
|
return _STREAM_PROTOCOL_UDP, nil
|
|
|
|
|
|
|
|
case "tcp":
|
|
|
|
return _STREAM_PROTOCOL_TCP, nil
|
|
|
|
}
|
|
|
|
return streamProtocol(0), fmt.Errorf("unsupported protocol '%s'", sourceProtocol)
|
|
|
|
}()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s := &streamer{
|
|
|
|
p: p,
|
|
|
|
path: path,
|
2020-07-12 20:53:22 +00:00
|
|
|
u: u,
|
2020-06-30 13:12:39 +00:00
|
|
|
proto: proto,
|
2020-07-11 08:48:18 +00:00
|
|
|
readBuf: newDoubleBuffer(512 * 1024),
|
2020-06-30 13:12:39 +00:00
|
|
|
terminate: make(chan struct{}),
|
|
|
|
done: make(chan struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *streamer) log(format string, args ...interface{}) {
|
|
|
|
s.p.log("[streamer "+s.path+"] "+format, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *streamer) publisherIsReady() bool {
|
|
|
|
return s.ready
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *streamer) publisherSdpText() []byte {
|
|
|
|
return s.serverSdpText
|
|
|
|
}
|
|
|
|
|
2020-07-12 15:24:12 +00:00
|
|
|
func (s *streamer) publisherSdpParsed() *sdp.SessionDescription {
|
2020-06-30 13:12:39 +00:00
|
|
|
return s.serverSdpParsed
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *streamer) run() {
|
|
|
|
for {
|
|
|
|
ok := s.do()
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
t := time.NewTimer(_STREAMER_RETRY_INTERVAL)
|
2020-06-30 13:12:39 +00:00
|
|
|
select {
|
|
|
|
case <-s.terminate:
|
2020-07-12 10:34:35 +00:00
|
|
|
break
|
2020-06-30 13:12:39 +00:00
|
|
|
case <-t.C:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
close(s.done)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *streamer) do() bool {
|
2020-06-30 13:12:39 +00:00
|
|
|
s.log("initializing with protocol %s", s.proto)
|
|
|
|
|
|
|
|
var nconn net.Conn
|
|
|
|
var err error
|
|
|
|
dialDone := make(chan struct{})
|
|
|
|
go func() {
|
2020-07-12 20:53:22 +00:00
|
|
|
nconn, err = net.DialTimeout("tcp", s.u.Host, s.p.conf.ReadTimeout)
|
2020-06-30 13:12:39 +00:00
|
|
|
close(dialDone)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-s.terminate:
|
|
|
|
return false
|
|
|
|
case <-dialDone:
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
defer nconn.Close()
|
|
|
|
|
|
|
|
conn, err := gortsplib.NewConnClient(gortsplib.ConnClientConf{
|
2020-07-12 20:53:22 +00:00
|
|
|
Conn: nconn,
|
2020-06-30 13:12:39 +00:00
|
|
|
ReadTimeout: s.p.conf.ReadTimeout,
|
|
|
|
WriteTimeout: s.p.conf.WriteTimeout,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
_, err = conn.Options(s.u)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
clientSdpParsed, _, err := conn.Describe(s.u)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// create a filtered SDP that is used by the server (not by the client)
|
2020-07-12 20:53:22 +00:00
|
|
|
serverSdpParsed, serverSdpText := sdpForServer(clientSdpParsed)
|
2020-06-30 13:12:39 +00:00
|
|
|
|
|
|
|
s.clientSdpParsed = clientSdpParsed
|
|
|
|
s.serverSdpText = serverSdpText
|
|
|
|
s.serverSdpParsed = serverSdpParsed
|
|
|
|
|
|
|
|
if s.proto == _STREAM_PROTOCOL_UDP {
|
|
|
|
return s.runUdp(conn)
|
|
|
|
} else {
|
|
|
|
return s.runTcp(conn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *streamer) runUdp(conn *gortsplib.ConnClient) bool {
|
|
|
|
publisherIp := conn.NetConn().RemoteAddr().(*net.TCPAddr).IP
|
|
|
|
|
|
|
|
var streamerUdpListenerPairs []streamerUdpListenerPair
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
for _, pair := range streamerUdpListenerPairs {
|
2020-07-12 10:34:35 +00:00
|
|
|
pair.rtpl.close()
|
|
|
|
pair.rtcpl.close()
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-07-12 15:24:12 +00:00
|
|
|
for i, media := range s.clientSdpParsed.MediaDescriptions {
|
2020-06-30 13:12:39 +00:00
|
|
|
var rtpPort int
|
|
|
|
var rtcpPort int
|
2020-07-12 10:34:35 +00:00
|
|
|
var rtpl *streamerUdpListener
|
|
|
|
var rtcpl *streamerUdpListener
|
2020-06-30 13:12:39 +00:00
|
|
|
func() {
|
|
|
|
for {
|
|
|
|
// choose two consecutive ports in range 65536-10000
|
|
|
|
// rtp must be pair and rtcp odd
|
|
|
|
rtpPort = (rand.Intn((65535-10000)/2) * 2) + 10000
|
|
|
|
rtcpPort = rtpPort + 1
|
|
|
|
|
|
|
|
var err error
|
2020-07-12 10:34:35 +00:00
|
|
|
rtpl, err = newStreamerUdpListener(s.p, rtpPort, s, i,
|
2020-07-12 20:53:22 +00:00
|
|
|
gortsplib.StreamTypeRtp, publisherIp)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
rtcpl, err = newStreamerUdpListener(s.p, rtcpPort, s, i,
|
2020-07-12 20:53:22 +00:00
|
|
|
gortsplib.StreamTypeRtcp, publisherIp)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
2020-07-12 10:34:35 +00:00
|
|
|
rtpl.close()
|
2020-06-30 13:12:39 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
rtpServerPort, rtcpServerPort, _, err := conn.SetupUdp(s.u, media, rtpPort, rtcpPort)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
2020-07-12 10:34:35 +00:00
|
|
|
rtpl.close()
|
|
|
|
rtcpl.close()
|
2020-06-30 13:12:39 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
rtpl.publisherPort = rtpServerPort
|
|
|
|
rtcpl.publisherPort = rtcpServerPort
|
2020-06-30 13:12:39 +00:00
|
|
|
|
|
|
|
streamerUdpListenerPairs = append(streamerUdpListenerPairs, streamerUdpListenerPair{
|
2020-07-12 10:34:35 +00:00
|
|
|
rtpl: rtpl,
|
|
|
|
rtcpl: rtcpl,
|
2020-06-30 13:12:39 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
_, err := conn.Play(s.u)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-07-12 15:24:12 +00:00
|
|
|
s.rtcpReceivers = make([]*rtcpReceiver, len(s.clientSdpParsed.MediaDescriptions))
|
|
|
|
for trackId := range s.clientSdpParsed.MediaDescriptions {
|
2020-07-12 10:34:35 +00:00
|
|
|
s.rtcpReceivers[trackId] = newRtcpReceiver()
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
for _, pair := range streamerUdpListenerPairs {
|
|
|
|
pair.rtpl.start()
|
|
|
|
pair.rtcpl.start()
|
|
|
|
}
|
2020-06-30 13:12:39 +00:00
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
sendKeepaliveTicker := time.NewTicker(_STREAMER_KEEPALIVE_INTERVAL)
|
|
|
|
checkStreamTicker := time.NewTicker(_STREAMER_CHECK_STREAM_INTERVAL)
|
|
|
|
receiverReportTicker := time.NewTicker(_STREAMER_RECEIVER_REPORT_INTERVAL)
|
2020-06-30 13:12:39 +00:00
|
|
|
|
|
|
|
s.p.events <- programEventStreamerReady{s}
|
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
var ret bool
|
2020-06-30 13:12:39 +00:00
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
outer:
|
2020-06-30 13:12:39 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-s.terminate:
|
2020-07-12 10:34:35 +00:00
|
|
|
ret = false
|
|
|
|
break outer
|
2020-06-30 13:12:39 +00:00
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
case <-sendKeepaliveTicker.C:
|
2020-07-12 20:53:22 +00:00
|
|
|
_, err = conn.Do(&gortsplib.Request{
|
2020-06-30 13:12:39 +00:00
|
|
|
Method: gortsplib.OPTIONS,
|
|
|
|
Url: &url.URL{
|
|
|
|
Scheme: "rtsp",
|
2020-07-12 20:53:22 +00:00
|
|
|
Host: s.u.Host,
|
2020-06-30 13:12:39 +00:00
|
|
|
Path: "/",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
2020-07-12 10:34:35 +00:00
|
|
|
ret = true
|
|
|
|
break outer
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
case <-checkStreamTicker.C:
|
2020-07-12 15:24:12 +00:00
|
|
|
for trackId := range s.clientSdpParsed.MediaDescriptions {
|
2020-07-12 11:21:26 +00:00
|
|
|
if time.Since(s.rtcpReceivers[trackId].lastFrameTime()) >= s.p.conf.StreamDeadAfter {
|
2020-07-12 10:34:35 +00:00
|
|
|
s.log("ERR: stream is dead")
|
|
|
|
ret = true
|
|
|
|
break outer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case <-receiverReportTicker.C:
|
2020-07-12 15:24:12 +00:00
|
|
|
for trackId := range s.clientSdpParsed.MediaDescriptions {
|
2020-07-12 10:34:35 +00:00
|
|
|
frame := s.rtcpReceivers[trackId].report()
|
|
|
|
streamerUdpListenerPairs[trackId].rtcpl.writeChan <- &udpAddrBufPair{
|
|
|
|
addr: &net.UDPAddr{
|
|
|
|
IP: conn.NetConn().RemoteAddr().(*net.TCPAddr).IP,
|
|
|
|
Zone: conn.NetConn().RemoteAddr().(*net.TCPAddr).Zone,
|
|
|
|
Port: streamerUdpListenerPairs[trackId].rtcpl.publisherPort,
|
|
|
|
},
|
|
|
|
buf: frame,
|
|
|
|
}
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-12 10:34:35 +00:00
|
|
|
|
|
|
|
sendKeepaliveTicker.Stop()
|
|
|
|
checkStreamTicker.Stop()
|
|
|
|
receiverReportTicker.Stop()
|
|
|
|
|
|
|
|
s.p.events <- programEventStreamerNotReady{s}
|
|
|
|
|
|
|
|
for _, pair := range streamerUdpListenerPairs {
|
|
|
|
pair.rtpl.stop()
|
|
|
|
pair.rtcpl.stop()
|
|
|
|
}
|
|
|
|
|
2020-07-12 15:24:12 +00:00
|
|
|
for trackId := range s.clientSdpParsed.MediaDescriptions {
|
2020-07-12 10:34:35 +00:00
|
|
|
s.rtcpReceivers[trackId].close()
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *streamer) runTcp(conn *gortsplib.ConnClient) bool {
|
2020-07-12 15:24:12 +00:00
|
|
|
for i, media := range s.clientSdpParsed.MediaDescriptions {
|
2020-07-12 20:53:22 +00:00
|
|
|
_, err := conn.SetupTcp(s.u, media, i)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
_, err := conn.Play(s.u)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-07-12 15:24:12 +00:00
|
|
|
s.rtcpReceivers = make([]*rtcpReceiver, len(s.clientSdpParsed.MediaDescriptions))
|
|
|
|
for trackId := range s.clientSdpParsed.MediaDescriptions {
|
2020-07-12 10:34:35 +00:00
|
|
|
s.rtcpReceivers[trackId] = newRtcpReceiver()
|
|
|
|
}
|
2020-06-30 13:12:39 +00:00
|
|
|
|
2020-07-12 10:34:35 +00:00
|
|
|
s.p.events <- programEventStreamerReady{s}
|
2020-06-30 13:12:39 +00:00
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
frame := &gortsplib.InterleavedFrame{}
|
|
|
|
|
2020-06-30 13:12:39 +00:00
|
|
|
chanConnError := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
for {
|
2020-07-11 08:48:18 +00:00
|
|
|
frame.Content = s.readBuf.swap()
|
2020-07-04 07:14:24 +00:00
|
|
|
frame.Content = frame.Content[:cap(frame.Content)]
|
2020-07-12 20:53:22 +00:00
|
|
|
|
|
|
|
err := conn.ReadFrame(frame)
|
2020-06-30 13:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
s.log("ERR: %s", err)
|
|
|
|
close(chanConnError)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
trackId, streamType := gortsplib.ConvChannelToTrackIdAndStreamType(frame.Channel)
|
2020-06-30 13:12:39 +00:00
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
s.rtcpReceivers[trackId].onFrame(streamType, frame.Content)
|
|
|
|
s.p.events <- programEventStreamerFrame{s, trackId, streamType, frame.Content}
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
// a ticker to check the stream is not needed since there's already a deadline
|
|
|
|
// on the RTSP reads
|
2020-07-12 10:34:35 +00:00
|
|
|
receiverReportTicker := time.NewTicker(_STREAMER_RECEIVER_REPORT_INTERVAL)
|
|
|
|
|
|
|
|
var ret bool
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
outer:
|
2020-07-12 10:34:35 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-s.terminate:
|
|
|
|
ret = false
|
2020-07-12 20:53:22 +00:00
|
|
|
break outer
|
2020-07-12 10:34:35 +00:00
|
|
|
|
|
|
|
case <-chanConnError:
|
|
|
|
ret = true
|
2020-07-12 20:53:22 +00:00
|
|
|
break outer
|
2020-07-12 10:34:35 +00:00
|
|
|
|
|
|
|
case <-receiverReportTicker.C:
|
2020-07-12 15:24:12 +00:00
|
|
|
for trackId := range s.clientSdpParsed.MediaDescriptions {
|
2020-07-12 10:34:35 +00:00
|
|
|
frame := s.rtcpReceivers[trackId].report()
|
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
channel := gortsplib.ConvTrackIdAndStreamTypeToChannel(trackId, gortsplib.StreamTypeRtcp)
|
2020-07-12 10:34:35 +00:00
|
|
|
|
2020-07-12 20:53:22 +00:00
|
|
|
conn.WriteFrame(&gortsplib.InterleavedFrame{
|
2020-07-12 10:34:35 +00:00
|
|
|
Channel: channel,
|
|
|
|
Content: frame,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
2020-07-12 10:34:35 +00:00
|
|
|
|
|
|
|
receiverReportTicker.Stop()
|
|
|
|
|
|
|
|
s.p.events <- programEventStreamerNotReady{s}
|
|
|
|
|
2020-07-12 15:24:12 +00:00
|
|
|
for trackId := range s.clientSdpParsed.MediaDescriptions {
|
2020-07-12 10:34:35 +00:00
|
|
|
s.rtcpReceivers[trackId].close()
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
2020-06-30 13:12:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *streamer) close() {
|
|
|
|
close(s.terminate)
|
|
|
|
<-s.done
|
|
|
|
}
|