mirror of
				https://github.com/AlexxIT/go2rtc.git
				synced 2025-11-01 04:02:37 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			237 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package rtsp
 | |
| 
 | |
| import (
 | |
| 	"github.com/AlexxIT/go2rtc/cmd/app"
 | |
| 	"github.com/AlexxIT/go2rtc/cmd/streams"
 | |
| 	"github.com/AlexxIT/go2rtc/pkg/rtsp"
 | |
| 	"github.com/AlexxIT/go2rtc/pkg/streamer"
 | |
| 	"github.com/AlexxIT/go2rtc/pkg/tcp"
 | |
| 	"github.com/rs/zerolog"
 | |
| 	"net"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| func Init() {
 | |
| 	var conf struct {
 | |
| 		Mod struct {
 | |
| 			Listen string `yaml:"listen"`
 | |
| 		} `yaml:"rtsp"`
 | |
| 	}
 | |
| 
 | |
| 	// default config
 | |
| 	conf.Mod.Listen = ":8554"
 | |
| 
 | |
| 	app.LoadConfig(&conf)
 | |
| 
 | |
| 	log = app.GetLogger("rtsp")
 | |
| 
 | |
| 	// RTSP client support
 | |
| 	streams.HandleFunc("rtsp", rtspHandler)
 | |
| 	streams.HandleFunc("rtsps", rtspHandler)
 | |
| 	streams.HandleFunc("rtspx", rtspHandler)
 | |
| 
 | |
| 	// RTSP server support
 | |
| 	address := conf.Mod.Listen
 | |
| 	if address == "" {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ln, err := net.Listen("tcp", address)
 | |
| 	if err != nil {
 | |
| 		log.Error().Err(err).Msg("[rtsp] listen")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	_, Port, _ = net.SplitHostPort(address)
 | |
| 
 | |
| 	log.Info().Str("addr", address).Msg("[rtsp] listen")
 | |
| 
 | |
| 	go func() {
 | |
| 		for {
 | |
| 			conn, err := ln.Accept()
 | |
| 			if err != nil {
 | |
| 				return
 | |
| 			}
 | |
| 			go tcpHandler(conn)
 | |
| 		}
 | |
| 	}()
 | |
| }
 | |
| 
 | |
| type Handler func(conn *rtsp.Conn) bool
 | |
| 
 | |
| func HandleFunc(handler Handler) {
 | |
| 	handlers = append(handlers, handler)
 | |
| }
 | |
| 
 | |
| var Port string
 | |
| 
 | |
| // internal
 | |
| 
 | |
| var log zerolog.Logger
 | |
| var handlers []Handler
 | |
| 
 | |
| func rtspHandler(url string) (streamer.Producer, error) {
 | |
| 	backchannel := true
 | |
| 
 | |
| 	if i := strings.IndexByte(url, '#'); i > 0 {
 | |
| 		if url[i+1:] == "backchannel=0" {
 | |
| 			backchannel = false
 | |
| 		}
 | |
| 		url = url[:i]
 | |
| 	}
 | |
| 
 | |
| 	conn, err := rtsp.NewClient(url)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if log.Trace().Enabled() {
 | |
| 		conn.Listen(func(msg interface{}) {
 | |
| 			switch msg := msg.(type) {
 | |
| 			case *tcp.Request:
 | |
| 				log.Trace().Msgf("[rtsp] client request:\n%s", msg)
 | |
| 			case *tcp.Response:
 | |
| 				log.Trace().Msgf("[rtsp] client response:\n%s", msg)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	if err = conn.Dial(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	conn.Backchannel = backchannel
 | |
| 	if err = conn.Describe(); err != nil {
 | |
| 		if !backchannel {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		// second try without backchannel, we need to reconnect
 | |
| 		conn.Backchannel = false
 | |
| 		if err = conn.Dial(); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if err = conn.Describe(); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return conn, nil
 | |
| }
 | |
| 
 | |
| func tcpHandler(c net.Conn) {
 | |
| 	var name string
 | |
| 	var closer func()
 | |
| 
 | |
| 	trace := log.Trace().Enabled()
 | |
| 
 | |
| 	conn := rtsp.NewServer(c)
 | |
| 	conn.Listen(func(msg interface{}) {
 | |
| 		if trace {
 | |
| 			switch msg := msg.(type) {
 | |
| 			case *tcp.Request:
 | |
| 				log.Trace().Msgf("[rtsp] server request:\n%s", msg)
 | |
| 			case *tcp.Response:
 | |
| 				log.Trace().Msgf("[rtsp] server response:\n%s", msg)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		switch msg {
 | |
| 		case rtsp.MethodDescribe:
 | |
| 			name = conn.URL.Path[1:]
 | |
| 
 | |
| 			stream := streams.Get(name)
 | |
| 			if stream == nil {
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			log.Debug().Str("stream", name).Msg("[rtsp] new consumer")
 | |
| 
 | |
| 			initMedias(conn)
 | |
| 
 | |
| 			if err := stream.AddConsumer(conn); err != nil {
 | |
| 				log.Warn().Err(err).Str("stream", name).Msg("[rtsp]")
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			closer = func() {
 | |
| 				stream.RemoveConsumer(conn)
 | |
| 			}
 | |
| 
 | |
| 		case rtsp.MethodAnnounce:
 | |
| 			name = conn.URL.Path[1:]
 | |
| 
 | |
| 			stream := streams.Get(name)
 | |
| 			if stream == nil {
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			log.Debug().Str("stream", name).Msg("[rtsp] new producer")
 | |
| 
 | |
| 			stream.AddProducer(conn)
 | |
| 
 | |
| 			closer = func() {
 | |
| 				stream.RemoveProducer(conn)
 | |
| 			}
 | |
| 
 | |
| 		case streamer.StatePlaying:
 | |
| 			log.Debug().Str("stream", name).Msg("[rtsp] start")
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	if err := conn.Accept(); err != nil {
 | |
| 		log.Warn().Err(err).Caller().Send()
 | |
| 		_ = conn.Close()
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	for _, handler := range handlers {
 | |
| 		if handler(conn) {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if closer != nil {
 | |
| 		if err := conn.Handle(); err != nil {
 | |
| 			log.Debug().Err(err).Caller().Send()
 | |
| 		}
 | |
| 
 | |
| 		closer()
 | |
| 
 | |
| 		log.Debug().Str("stream", name).Msg("[rtsp] disconnect")
 | |
| 	}
 | |
| 
 | |
| 	_ = conn.Close()
 | |
| }
 | |
| 
 | |
| func initMedias(conn *rtsp.Conn) {
 | |
| 	// set media candidates from query list
 | |
| 	for key, value := range conn.URL.Query() {
 | |
| 		switch key {
 | |
| 		case streamer.KindVideo, streamer.KindAudio:
 | |
| 			for _, value := range value {
 | |
| 				media := &streamer.Media{
 | |
| 					Kind: key, Direction: streamer.DirectionRecvonly,
 | |
| 				}
 | |
| 
 | |
| 				switch value {
 | |
| 				case "", "copy": // pass empty codecs list
 | |
| 				default:
 | |
| 					codec := streamer.NewCodec(value)
 | |
| 					media.Codecs = append(media.Codecs, codec)
 | |
| 				}
 | |
| 
 | |
| 				conn.Medias = append(conn.Medias, media)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// set default media candidates if query is empty
 | |
| 	if conn.Medias == nil {
 | |
| 		conn.Medias = []*streamer.Media{
 | |
| 			{Kind: streamer.KindVideo, Direction: streamer.DirectionRecvonly},
 | |
| 			{Kind: streamer.KindAudio, Direction: streamer.DirectionRecvonly},
 | |
| 		}
 | |
| 	}
 | |
| }
 | 
