mediamtx/main.go

652 lines
14 KiB
Go
Raw Normal View History

2019-12-28 21:07:03 +00:00
package main
import (
"fmt"
"io"
2019-12-28 21:07:03 +00:00
"log"
"net"
2020-06-27 13:42:54 +00:00
"net/http"
_ "net/http/pprof"
2019-12-28 21:07:03 +00:00
"os"
2020-01-03 21:39:55 +00:00
"regexp"
"time"
2019-12-28 21:07:03 +00:00
"gopkg.in/alecthomas/kingpin.v2"
"gopkg.in/yaml.v2"
2020-06-27 11:38:35 +00:00
"gortc.io/sdp"
2019-12-28 21:07:03 +00:00
)
2020-06-26 09:00:10 +00:00
var Version = "v0.0.0"
2019-12-28 21:07:03 +00:00
func parseIpCidrList(in []string) ([]interface{}, error) {
if len(in) == 0 {
return nil, nil
}
var ret []interface{}
for _, t := range in {
_, ipnet, err := net.ParseCIDR(t)
if err == nil {
ret = append(ret, ipnet)
continue
}
ip := net.ParseIP(t)
if ip != nil {
ret = append(ret, ip)
continue
}
return nil, fmt.Errorf("unable to parse ip/network '%s'", t)
}
return ret, nil
}
2020-06-27 11:38:35 +00:00
type trackFlowType int
2019-12-31 12:48:17 +00:00
const (
2020-06-27 11:38:35 +00:00
_TRACK_FLOW_RTP trackFlowType = iota
2019-12-31 12:48:17 +00:00
_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"
}
2020-06-27 11:38:35 +00:00
type programEvent interface {
isProgramEvent()
}
type programEventClientNew struct {
nconn net.Conn
}
func (programEventClientNew) isProgramEvent() {}
type programEventClientClose struct {
done chan struct{}
client *serverClient
}
func (programEventClientClose) isProgramEvent() {}
type programEventClientGetStreamSdp struct {
path string
res chan []byte
}
func (programEventClientGetStreamSdp) isProgramEvent() {}
type programEventClientAnnounce struct {
res chan error
client *serverClient
path string
sdpText []byte
sdpParsed *sdp.Message
}
func (programEventClientAnnounce) isProgramEvent() {}
type programEventClientSetupPlay struct {
res chan error
client *serverClient
path string
protocol streamProtocol
rtpPort int
rtcpPort int
}
func (programEventClientSetupPlay) isProgramEvent() {}
type programEventClientSetupRecord struct {
res chan error
client *serverClient
protocol streamProtocol
rtpPort int
rtcpPort int
}
func (programEventClientSetupRecord) isProgramEvent() {}
type programEventClientPlay1 struct {
res chan error
client *serverClient
}
func (programEventClientPlay1) isProgramEvent() {}
type programEventClientPlay2 struct {
res chan error
client *serverClient
}
func (programEventClientPlay2) isProgramEvent() {}
type programEventClientPause struct {
res chan error
client *serverClient
}
func (programEventClientPause) isProgramEvent() {}
type programEventClientRecord struct {
res chan error
client *serverClient
}
func (programEventClientRecord) isProgramEvent() {}
type programEventFrameUdp struct {
trackFlowType trackFlowType
addr *net.UDPAddr
buf []byte
}
func (programEventFrameUdp) isProgramEvent() {}
type programEventFrameTcp struct {
path string
trackId int
trackFlowType trackFlowType
buf []byte
}
func (programEventFrameTcp) isProgramEvent() {}
type programEventTerminate struct{}
func (programEventTerminate) isProgramEvent() {}
2020-06-27 19:22:50 +00:00
type ConfPath struct {
PublishUser string `yaml:"publishUser"`
PublishPass string `yaml:"publishPass"`
PublishIps []string `yaml:"publishIps"`
publishIps []interface{}
ReadUser string `yaml:"readUser"`
ReadPass string `yaml:"readPass"`
ReadIps []string `yaml:"readIps"`
readIps []interface{}
}
type conf struct {
2020-06-27 19:22:50 +00:00
Protocols []string `yaml:"protocols"`
RtspPort int `yaml:"rtspPort"`
RtpPort int `yaml:"rtpPort"`
RtcpPort int `yaml:"rtcpPort"`
ReadTimeout time.Duration `yaml:"readTimeout"`
WriteTimeout time.Duration `yaml:"writeTimeout"`
PreScript string `yaml:"preScript"`
PostScript string `yaml:"postScript"`
Pprof bool `yaml:"pprof"`
Paths map[string]*ConfPath `yaml:"paths"`
}
2020-06-27 19:22:50 +00:00
func loadConf(fpath string, stdin io.Reader) (*conf, error) {
if fpath == "stdin" {
var ret conf
err := yaml.NewDecoder(stdin).Decode(&ret)
if err != nil {
return nil, err
}
return &ret, nil
} else {
// conf.yml is optional
2020-06-27 19:22:50 +00:00
if fpath == "conf.yml" {
if _, err := os.Stat(fpath); err != nil {
return &conf{}, nil
}
}
2020-06-27 19:22:50 +00:00
f, err := os.Open(fpath)
if err != nil {
return nil, err
}
defer f.Close()
var ret conf
err = yaml.NewDecoder(f).Decode(&ret)
if err != nil {
return nil, err
}
return &ret, nil
}
2019-12-28 21:07:03 +00:00
}
2020-05-10 13:33:42 +00:00
type program struct {
conf *conf
2020-06-27 12:18:16 +00:00
protocols map[streamProtocol]struct{}
tcpl *serverTcpListener
udplRtp *serverUdpListener
udplRtcp *serverUdpListener
clients map[*serverClient]struct{}
publishers map[string]*serverClient
publisherCount int
receiverCount int
2020-06-27 11:38:35 +00:00
events chan programEvent
done chan struct{}
2020-05-10 13:33:42 +00:00
}
func newProgram(sargs []string, stdin io.Reader) (*program, error) {
2020-06-27 19:22:50 +00:00
k := kingpin.New("rtsp-simple-server",
"rtsp-simple-server "+Version+"\n\nRTSP server.")
2020-05-11 07:31:56 +00:00
2020-06-27 19:22:50 +00:00
argVersion := k.Flag("version", "print version").Bool()
argConfPath := k.Arg("confpath", "path to a config file. The default is conf.yml. Use 'stdin' to read config from stdin").Default("conf.yml").String()
2020-05-11 07:31:56 +00:00
2020-06-27 19:22:50 +00:00
kingpin.MustParse(k.Parse(sargs))
2020-05-11 07:31:56 +00:00
if *argVersion == true {
2020-05-11 07:39:49 +00:00
fmt.Println(Version)
2020-05-10 19:32:40 +00:00
os.Exit(0)
2020-01-26 11:58:56 +00:00
}
conf, err := loadConf(*argConfPath, stdin)
if err != nil {
return nil, err
}
if conf.ReadTimeout == 0 {
conf.ReadTimeout = 5 * time.Second
}
if conf.WriteTimeout == 0 {
conf.WriteTimeout = 5 * time.Second
}
if len(conf.Protocols) == 0 {
conf.Protocols = []string{"udp", "tcp"}
}
protocols := make(map[streamProtocol]struct{})
for _, proto := range conf.Protocols {
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")
}
if conf.RtspPort == 0 {
conf.RtspPort = 8554
}
if conf.RtpPort == 0 {
conf.RtpPort = 8000
}
if (conf.RtpPort % 2) != 0 {
2020-05-11 07:31:56 +00:00
return nil, fmt.Errorf("rtp port must be even")
}
if conf.RtcpPort == 0 {
conf.RtcpPort = 8001
}
if conf.RtcpPort != (conf.RtpPort + 1) {
2020-05-11 07:31:56 +00:00
return nil, fmt.Errorf("rtcp and rtp ports must be consecutive")
}
2020-06-27 19:22:50 +00:00
if len(conf.Paths) == 0 {
conf.Paths = map[string]*ConfPath{
"all": {},
}
}
2020-06-27 19:22:50 +00:00
for _, pconf := range conf.Paths {
if pconf.PublishUser != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.PublishUser) {
return nil, fmt.Errorf("publish username must be alphanumeric")
}
}
if pconf.PublishPass != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.PublishPass) {
return nil, fmt.Errorf("publish password must be alphanumeric")
}
}
pconf.publishIps, err = parseIpCidrList(pconf.PublishIps)
if err != nil {
return nil, err
2020-01-03 21:39:55 +00:00
}
2020-06-27 19:22:50 +00:00
if pconf.ReadUser != "" && pconf.ReadPass == "" || pconf.ReadUser == "" && pconf.ReadPass != "" {
return nil, fmt.Errorf("read username and password must be both filled")
2020-05-11 07:31:56 +00:00
}
2020-06-27 19:22:50 +00:00
if pconf.ReadUser != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.ReadUser) {
return nil, fmt.Errorf("read username must be alphanumeric")
}
}
if pconf.ReadPass != "" {
if !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(pconf.ReadPass) {
return nil, fmt.Errorf("read password must be alphanumeric")
}
}
if pconf.ReadUser != "" && pconf.ReadPass == "" || pconf.ReadUser == "" && pconf.ReadPass != "" {
return nil, fmt.Errorf("read username and password must be both filled")
}
pconf.readIps, err = parseIpCidrList(pconf.ReadIps)
if err != nil {
return nil, err
2020-05-11 07:31:56 +00:00
}
}
2019-12-28 21:07:03 +00:00
p := &program{
conf: conf,
protocols: protocols,
2020-06-27 11:38:35 +00:00
clients: make(map[*serverClient]struct{}),
publishers: make(map[string]*serverClient),
events: make(chan programEvent),
done: make(chan struct{}),
2019-12-28 21:07:03 +00:00
}
2020-06-27 12:18:16 +00:00
p.log("rtsp-simple-server %s", Version)
if conf.Pprof {
2020-06-27 13:42:54 +00:00
go func(mux *http.ServeMux) {
server := &http.Server{
Addr: ":9999",
Handler: mux,
}
p.log("pprof is available on :9999")
panic(server.ListenAndServe())
}(http.DefaultServeMux)
http.DefaultServeMux = http.NewServeMux()
}
p.udplRtp, err = newServerUdpListener(p, conf.RtpPort, _TRACK_FLOW_RTP)
2019-12-28 21:07:03 +00:00
if err != nil {
return nil, err
}
p.udplRtcp, err = newServerUdpListener(p, conf.RtcpPort, _TRACK_FLOW_RTCP)
2019-12-28 21:07:03 +00:00
if err != nil {
return nil, err
}
2020-05-10 14:08:41 +00:00
p.tcpl, err = newServerTcpListener(p)
2019-12-28 21:07:03 +00:00
if err != nil {
return nil, err
}
2020-05-10 14:08:41 +00:00
go p.udplRtp.run()
go p.udplRtcp.run()
go p.tcpl.run()
2020-06-27 11:38:35 +00:00
go p.run()
2019-12-28 21:07:03 +00:00
2020-05-10 13:33:42 +00:00
return p, nil
2019-12-28 21:07:03 +00:00
}
2020-06-27 12:18:16 +00:00
func (p *program) log(format string, args ...interface{}) {
log.Printf("[%d/%d/%d] "+format, append([]interface{}{len(p.clients),
p.publisherCount, p.receiverCount}, args...)...)
}
2020-06-27 11:38:35 +00:00
func (p *program) run() {
outer:
for rawEvt := range p.events {
switch evt := rawEvt.(type) {
case programEventClientNew:
c := newServerClient(p, evt.nconn)
p.clients[c] = struct{}{}
2020-06-27 12:18:16 +00:00
c.log("connected")
2020-06-27 11:38:35 +00:00
case programEventClientClose:
// already deleted
if _, ok := p.clients[evt.client]; !ok {
close(evt.done)
continue
}
delete(p.clients, evt.client)
if evt.client.path != "" {
if pub, ok := p.publishers[evt.client.path]; ok && pub == evt.client {
delete(p.publishers, evt.client.path)
// if the publisher has disconnected
// close all other connections that share the same path
for oc := range p.clients {
if oc.path == evt.client.path {
go oc.close()
}
}
}
}
2020-06-27 12:18:16 +00:00
switch evt.client.state {
case _CLIENT_STATE_PLAY:
p.receiverCount -= 1
case _CLIENT_STATE_RECORD:
p.publisherCount -= 1
}
evt.client.log("disconnected")
2020-06-27 11:38:35 +00:00
close(evt.done)
case programEventClientGetStreamSdp:
pub, ok := p.publishers[evt.path]
if !ok {
evt.res <- nil
continue
}
evt.res <- pub.streamSdpText
case programEventClientAnnounce:
_, ok := p.publishers[evt.path]
if ok {
evt.res <- fmt.Errorf("another client is already publishing on path '%s'", evt.path)
continue
}
evt.client.path = evt.path
evt.client.streamSdpText = evt.sdpText
evt.client.streamSdpParsed = evt.sdpParsed
evt.client.state = _CLIENT_STATE_ANNOUNCE
p.publishers[evt.path] = evt.client
evt.res <- nil
case programEventClientSetupPlay:
pub, ok := p.publishers[evt.path]
if !ok {
evt.res <- fmt.Errorf("no one is streaming on path '%s'", evt.path)
continue
}
if len(evt.client.streamTracks) >= len(pub.streamSdpParsed.Medias) {
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, &track{
rtpPort: evt.rtpPort,
rtcpPort: evt.rtcpPort,
})
evt.client.state = _CLIENT_STATE_PRE_PLAY
evt.res <- nil
case programEventClientSetupRecord:
evt.client.streamProtocol = evt.protocol
evt.client.streamTracks = append(evt.client.streamTracks, &track{
rtpPort: evt.rtpPort,
rtcpPort: evt.rtcpPort,
})
evt.client.state = _CLIENT_STATE_PRE_RECORD
evt.res <- nil
case programEventClientPlay1:
pub, ok := p.publishers[evt.client.path]
if !ok {
evt.res <- fmt.Errorf("no one is streaming on path '%s'", evt.client.path)
continue
}
if len(evt.client.streamTracks) != len(pub.streamSdpParsed.Medias) {
evt.res <- fmt.Errorf("not all tracks have been setup")
continue
}
evt.res <- nil
case programEventClientPlay2:
2020-06-27 12:18:16 +00:00
p.receiverCount += 1
2020-06-27 11:38:35 +00:00
evt.client.state = _CLIENT_STATE_PLAY
evt.res <- nil
case programEventClientPause:
2020-06-27 12:18:16 +00:00
p.receiverCount -= 1
2020-06-27 11:38:35 +00:00
evt.client.state = _CLIENT_STATE_PRE_PLAY
evt.res <- nil
case programEventClientRecord:
2020-06-27 12:18:16 +00:00
p.publisherCount += 1
2020-06-27 11:38:35 +00:00
evt.client.state = _CLIENT_STATE_RECORD
evt.res <- nil
case programEventFrameUdp:
// find publisher and track id from ip and port
pub, trackId := func() (*serverClient, int) {
for _, pub := range p.publishers {
if pub.streamProtocol != _STREAM_PROTOCOL_UDP ||
pub.state != _CLIENT_STATE_RECORD ||
!pub.ip().Equal(evt.addr.IP) {
continue
}
for i, t := range pub.streamTracks {
if evt.trackFlowType == _TRACK_FLOW_RTP {
if t.rtpPort == evt.addr.Port {
return pub, i
}
} else {
if t.rtcpPort == evt.addr.Port {
return pub, i
}
}
}
}
return nil, -1
}()
if pub == nil {
continue
}
pub.udpLastFrameTime = time.Now()
p.forwardTrack(pub.path, trackId, evt.trackFlowType, evt.buf)
case programEventFrameTcp:
p.forwardTrack(evt.path, evt.trackId, evt.trackFlowType, evt.buf)
case programEventTerminate:
break outer
}
}
go func() {
for rawEvt := range p.events {
switch evt := rawEvt.(type) {
case programEventClientClose:
close(evt.done)
case programEventClientGetStreamSdp:
evt.res <- nil
case programEventClientAnnounce:
evt.res <- fmt.Errorf("terminated")
case programEventClientSetupPlay:
evt.res <- fmt.Errorf("terminated")
case programEventClientSetupRecord:
evt.res <- fmt.Errorf("terminated")
case programEventClientPlay1:
evt.res <- fmt.Errorf("terminated")
case programEventClientPlay2:
evt.res <- fmt.Errorf("terminated")
case programEventClientPause:
evt.res <- fmt.Errorf("terminated")
case programEventClientRecord:
evt.res <- fmt.Errorf("terminated")
}
}
}()
2020-05-10 14:33:20 +00:00
p.tcpl.close()
p.udplRtcp.close()
p.udplRtp.close()
2020-06-27 11:38:35 +00:00
for c := range p.clients {
c.close()
}
close(p.events)
close(p.done)
}
func (p *program) close() {
p.events <- programEventTerminate{}
<-p.done
}
func (p *program) forwardTrack(path string, id int, trackFlowType trackFlowType, frame []byte) {
for c := range p.clients {
if c.path == path && c.state == _CLIENT_STATE_PLAY {
if c.streamProtocol == _STREAM_PROTOCOL_UDP {
if trackFlowType == _TRACK_FLOW_RTP {
p.udplRtp.write(&net.UDPAddr{
IP: c.ip(),
Zone: c.zone(),
Port: c.streamTracks[id].rtpPort,
}, frame)
2020-06-27 11:38:35 +00:00
} else {
p.udplRtcp.write(&net.UDPAddr{
IP: c.ip(),
Zone: c.zone(),
Port: c.streamTracks[id].rtcpPort,
}, frame)
2020-06-27 11:38:35 +00:00
}
} else {
c.writeFrame(trackToInterleavedChannel(id, trackFlowType), frame)
2020-06-27 11:38:35 +00:00
}
}
}
2020-05-10 14:33:20 +00:00
}
2019-12-28 21:07:03 +00:00
func main() {
_, err := newProgram(os.Args[1:], os.Stdin)
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
}
2020-06-24 20:42:39 +00:00
select {}
2019-12-28 21:07:03 +00:00
}