2019-12-28 21:07:03 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"os"
|
2020-01-03 21:39:55 +00:00
|
|
|
"regexp"
|
2020-01-15 21:48:51 +00:00
|
|
|
"strings"
|
2019-12-28 21:07:03 +00:00
|
|
|
"sync"
|
2020-01-21 08:29:02 +00:00
|
|
|
"time"
|
2019-12-28 21:07:03 +00:00
|
|
|
|
2020-01-26 11:41:26 +00:00
|
|
|
"github.com/aler9/gortsplib"
|
2019-12-28 21:07:03 +00:00
|
|
|
"gopkg.in/alecthomas/kingpin.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
var Version string = "v0.0.0"
|
|
|
|
|
2020-01-21 08:29:02 +00:00
|
|
|
const (
|
|
|
|
_READ_TIMEOUT = 5 * time.Second
|
|
|
|
_WRITE_TIMEOUT = 5 * time.Second
|
|
|
|
)
|
|
|
|
|
2019-12-31 12:48:17 +00:00
|
|
|
type trackFlow int
|
|
|
|
|
|
|
|
const (
|
|
|
|
_TRACK_FLOW_RTP trackFlow = iota
|
|
|
|
_TRACK_FLOW_RTCP
|
|
|
|
)
|
|
|
|
|
|
|
|
type track struct {
|
|
|
|
rtpPort int
|
|
|
|
rtcpPort int
|
|
|
|
}
|
|
|
|
|
|
|
|
type streamProtocol int
|
|
|
|
|
|
|
|
const (
|
2020-01-20 16:20:53 +00:00
|
|
|
_STREAM_PROTOCOL_UDP streamProtocol = iota
|
2019-12-31 12:48:17 +00:00
|
|
|
_STREAM_PROTOCOL_TCP
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s streamProtocol) String() string {
|
|
|
|
if s == _STREAM_PROTOCOL_UDP {
|
|
|
|
return "udp"
|
|
|
|
}
|
|
|
|
return "tcp"
|
|
|
|
}
|
|
|
|
|
2019-12-28 21:07:03 +00:00
|
|
|
type program struct {
|
2020-02-16 15:05:08 +00:00
|
|
|
protocols map[streamProtocol]struct{}
|
|
|
|
rtspPort int
|
|
|
|
rtpPort int
|
|
|
|
rtcpPort int
|
|
|
|
publishUser string
|
|
|
|
publishPass string
|
|
|
|
preScript string
|
|
|
|
postScript string
|
|
|
|
mutex sync.RWMutex
|
|
|
|
rtspl *serverTcpListener
|
|
|
|
rtpl *serverUdpListener
|
|
|
|
rtcpl *serverUdpListener
|
|
|
|
clients map[*client]struct{}
|
|
|
|
publishers map[string]*client
|
2019-12-28 21:07:03 +00:00
|
|
|
}
|
|
|
|
|
2020-02-16 15:05:08 +00:00
|
|
|
func newProgram() (*program, error) {
|
|
|
|
kingpin.CommandLine.Help = "rtsp-simple-server " + Version + "\n\n" +
|
|
|
|
"RTSP server."
|
|
|
|
|
|
|
|
argVersion := kingpin.Flag("version", "print rtsp-simple-server version").Bool()
|
|
|
|
argProtocolsStr := kingpin.Flag("protocols", "supported protocols").Default("udp,tcp").String()
|
|
|
|
argRtspPort := kingpin.Flag("rtsp-port", "port of the RTSP TCP listener").Default("8554").Int()
|
|
|
|
argRtpPort := kingpin.Flag("rtp-port", "port of the RTP UDP listener").Default("8000").Int()
|
|
|
|
argRtcpPort := kingpin.Flag("rtcp-port", "port of the RTCP UDP listener").Default("8001").Int()
|
|
|
|
argPublishUser := kingpin.Flag("publish-user", "optional username required to publish").Default("").String()
|
|
|
|
argPublishPass := kingpin.Flag("publish-pass", "optional password required to publish").Default("").String()
|
|
|
|
argPreScript := kingpin.Flag("pre-script", "optional script to run on client connect").Default("").String()
|
|
|
|
argPostScript := kingpin.Flag("post-script", "optional script to run on client disconnect").Default("").String()
|
|
|
|
|
|
|
|
kingpin.Parse()
|
|
|
|
|
|
|
|
version := *argVersion
|
|
|
|
protocolsStr := *argProtocolsStr
|
|
|
|
rtspPort := *argRtspPort
|
|
|
|
rtpPort := *argRtpPort
|
|
|
|
rtcpPort := *argRtcpPort
|
|
|
|
publishUser := *argPublishUser
|
|
|
|
publishPass := *argPublishPass
|
|
|
|
preScript := *argPreScript
|
|
|
|
postScript := *argPostScript
|
|
|
|
|
|
|
|
if version == true {
|
|
|
|
fmt.Println("rtsp-simple-server " + Version)
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
2020-01-26 11:58:56 +00:00
|
|
|
|
|
|
|
if rtspPort == 0 {
|
|
|
|
return nil, fmt.Errorf("rtsp port not provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
if rtpPort == 0 {
|
|
|
|
return nil, fmt.Errorf("rtp port not provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
if rtcpPort == 0 {
|
|
|
|
return nil, fmt.Errorf("rtcp port not provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rtpPort % 2) != 0 {
|
|
|
|
return nil, fmt.Errorf("rtp port must be even")
|
|
|
|
}
|
|
|
|
|
|
|
|
if rtcpPort != (rtpPort + 1) {
|
|
|
|
return nil, fmt.Errorf("rtcp port must be rtp port plus 1")
|
|
|
|
}
|
|
|
|
|
2020-01-15 21:48:51 +00:00
|
|
|
protocols := make(map[streamProtocol]struct{})
|
|
|
|
for _, proto := range strings.Split(protocolsStr, ",") {
|
|
|
|
switch proto {
|
|
|
|
case "udp":
|
|
|
|
protocols[_STREAM_PROTOCOL_UDP] = struct{}{}
|
|
|
|
|
|
|
|
case "tcp":
|
|
|
|
protocols[_STREAM_PROTOCOL_TCP] = struct{}{}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unsupported protocol: %s", proto)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(protocols) == 0 {
|
2020-01-26 11:58:56 +00:00
|
|
|
return nil, fmt.Errorf("no protocols provided")
|
2020-01-15 21:48:51 +00:00
|
|
|
}
|
|
|
|
|
2020-02-16 15:05:08 +00:00
|
|
|
if publishUser != "" {
|
|
|
|
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(publishUser) {
|
|
|
|
return nil, fmt.Errorf("publish username must be alphanumeric")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if publishPass != "" {
|
|
|
|
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(publishPass) {
|
|
|
|
return nil, fmt.Errorf("publish password must be alphanumeric")
|
2020-01-03 21:39:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-16 15:05:08 +00:00
|
|
|
if publishUser != "" && publishPass == "" || publishUser == "" && publishPass != "" {
|
|
|
|
return nil, fmt.Errorf("publish username and password must be both filled")
|
|
|
|
}
|
|
|
|
|
2020-01-03 21:39:55 +00:00
|
|
|
log.Printf("rtsp-simple-server %s", Version)
|
|
|
|
|
2019-12-28 21:07:03 +00:00
|
|
|
p := &program{
|
2020-02-16 15:05:08 +00:00
|
|
|
protocols: protocols,
|
|
|
|
rtspPort: rtspPort,
|
|
|
|
rtpPort: rtpPort,
|
|
|
|
rtcpPort: rtcpPort,
|
|
|
|
publishUser: publishUser,
|
|
|
|
publishPass: publishPass,
|
|
|
|
preScript: preScript,
|
|
|
|
postScript: postScript,
|
|
|
|
clients: make(map[*client]struct{}),
|
|
|
|
publishers: make(map[string]*client),
|
2019-12-28 21:07:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
2020-01-20 14:22:01 +00:00
|
|
|
p.rtpl, err = newServerUdpListener(p, rtpPort, _TRACK_FLOW_RTP)
|
2019-12-28 21:07:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 14:22:01 +00:00
|
|
|
p.rtcpl, err = newServerUdpListener(p, rtcpPort, _TRACK_FLOW_RTCP)
|
2019-12-28 21:07:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-20 14:22:01 +00:00
|
|
|
p.rtspl, err = newServerTcpListener(p)
|
2019-12-28 21:07:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *program) run() {
|
2019-12-29 00:52:52 +00:00
|
|
|
go p.rtpl.run()
|
|
|
|
go p.rtcpl.run()
|
|
|
|
go p.rtspl.run()
|
2019-12-28 21:07:03 +00:00
|
|
|
|
2019-12-29 00:52:52 +00:00
|
|
|
infty := make(chan struct{})
|
|
|
|
<-infty
|
2019-12-28 21:07:03 +00:00
|
|
|
}
|
|
|
|
|
2019-12-31 13:55:46 +00:00
|
|
|
func (p *program) forwardTrack(path string, id int, flow trackFlow, frame []byte) {
|
2019-12-29 00:05:25 +00:00
|
|
|
for c := range p.clients {
|
2020-01-20 13:41:04 +00:00
|
|
|
if c.path == path && c.state == _CLIENT_STATE_PLAY {
|
2019-12-31 12:48:17 +00:00
|
|
|
if c.streamProtocol == _STREAM_PROTOCOL_UDP {
|
|
|
|
if flow == _TRACK_FLOW_RTP {
|
2020-01-26 17:08:15 +00:00
|
|
|
p.rtpl.chanWrite <- &udpWrite{
|
|
|
|
addr: &net.UDPAddr{
|
|
|
|
IP: c.ip,
|
|
|
|
Port: c.streamTracks[id].rtpPort,
|
|
|
|
},
|
|
|
|
buf: frame,
|
|
|
|
}
|
2019-12-31 12:48:17 +00:00
|
|
|
} else {
|
2020-01-26 17:08:15 +00:00
|
|
|
p.rtcpl.chanWrite <- &udpWrite{
|
|
|
|
addr: &net.UDPAddr{
|
|
|
|
IP: c.ip,
|
|
|
|
Port: c.streamTracks[id].rtcpPort,
|
|
|
|
},
|
|
|
|
buf: frame,
|
|
|
|
}
|
2019-12-31 12:48:17 +00:00
|
|
|
}
|
2019-12-29 00:05:25 +00:00
|
|
|
|
|
|
|
} else {
|
2020-01-26 17:08:15 +00:00
|
|
|
c.chanWrite <- &gortsplib.InterleavedFrame{
|
2020-01-26 11:41:26 +00:00
|
|
|
Channel: trackToInterleavedChannel(id, flow),
|
|
|
|
Content: frame,
|
2020-01-26 17:08:15 +00:00
|
|
|
}
|
2019-12-29 00:05:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-28 21:07:03 +00:00
|
|
|
func main() {
|
2020-02-16 15:05:08 +00:00
|
|
|
p, err := newProgram()
|
2019-12-28 21:07:03 +00:00
|
|
|
if err != nil {
|
2019-12-29 11:32:54 +00:00
|
|
|
log.Fatal("ERR: ", err)
|
2019-12-28 21:07:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
p.run()
|
|
|
|
}
|