Code refactoring for #1758
This commit is contained in:
+96
-229
@@ -2,7 +2,6 @@ package rtsp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -10,6 +9,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/tcp/websocket"
|
"github.com/AlexxIT/go2rtc/pkg/tcp/websocket"
|
||||||
@@ -26,13 +26,7 @@ func NewClient(uri string) *Conn {
|
|||||||
ID: core.NewID(),
|
ID: core.NewID(),
|
||||||
FormatName: "rtsp",
|
FormatName: "rtsp",
|
||||||
},
|
},
|
||||||
uri: uri,
|
uri: uri,
|
||||||
udpRtpConns: make(map[byte]*UDPConnection),
|
|
||||||
udpRtcpConns: make(map[byte]*UDPConnection),
|
|
||||||
udpRtpListeners: make(map[byte]*UDPConnection),
|
|
||||||
udpRtcpListeners: make(map[byte]*UDPConnection),
|
|
||||||
portToChannel: make(map[int]byte),
|
|
||||||
channelCounter: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,10 +37,13 @@ func (c *Conn) Dial() (err error) {
|
|||||||
|
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
|
|
||||||
if c.Transport == "" || c.Transport == "tcp" || c.Transport == "udp" {
|
switch c.Transport {
|
||||||
timeout := core.ConnDialTimeout
|
case "", "tcp", "udp":
|
||||||
|
var timeout time.Duration
|
||||||
if c.Timeout != 0 {
|
if c.Timeout != 0 {
|
||||||
timeout = time.Second * time.Duration(c.Timeout)
|
timeout = time.Second * time.Duration(c.Timeout)
|
||||||
|
} else {
|
||||||
|
timeout = core.ConnDialTimeout
|
||||||
}
|
}
|
||||||
conn, err = tcp.Dial(c.URL, timeout)
|
conn, err = tcp.Dial(c.URL, timeout)
|
||||||
|
|
||||||
@@ -55,7 +52,7 @@ func (c *Conn) Dial() (err error) {
|
|||||||
} else {
|
} else {
|
||||||
c.Protocol = "rtsp+udp"
|
c.Protocol = "rtsp+udp"
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
conn, err = websocket.Dial(c.Transport)
|
conn, err = websocket.Dial(c.Transport)
|
||||||
c.Protocol = "ws"
|
c.Protocol = "ws"
|
||||||
}
|
}
|
||||||
@@ -73,6 +70,9 @@ func (c *Conn) Dial() (err error) {
|
|||||||
c.sequence = 0
|
c.sequence = 0
|
||||||
c.state = StateConn
|
c.state = StateConn
|
||||||
|
|
||||||
|
c.udpConn = nil
|
||||||
|
c.udpAddr = nil
|
||||||
|
|
||||||
c.Connection.RemoteAddr = conn.RemoteAddr().String()
|
c.Connection.RemoteAddr = conn.RemoteAddr().String()
|
||||||
c.Connection.Transport = conn
|
c.Connection.Transport = conn
|
||||||
c.Connection.URL = c.uri
|
c.Connection.URL = c.uri
|
||||||
@@ -229,63 +229,35 @@ func (c *Conn) Record() (err error) {
|
|||||||
|
|
||||||
func (c *Conn) SetupMedia(media *core.Media) (byte, error) {
|
func (c *Conn) SetupMedia(media *core.Media) (byte, error) {
|
||||||
var transport string
|
var transport string
|
||||||
var mediaIndex int = -1
|
|
||||||
|
|
||||||
// try to use media position as channel number
|
|
||||||
for i, m := range c.Medias {
|
|
||||||
if m.Equal(media) {
|
|
||||||
mediaIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mediaIndex == -1 {
|
|
||||||
return 0, fmt.Errorf("wrong media: %v", media)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Transport == "udp" {
|
if c.Transport == "udp" {
|
||||||
transport, err := c.setupUDPTransport()
|
conn1, conn2, err := ListenUDPPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.sendSetupRequest(media, transport)
|
c.udpConn = append(c.udpConn, conn1, conn2)
|
||||||
|
|
||||||
|
port := conn1.LocalAddr().(*net.UDPAddr).Port
|
||||||
|
transport = fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", port, port+1)
|
||||||
|
} else {
|
||||||
|
// try to use media position as channel number
|
||||||
|
for i, m := range c.Medias {
|
||||||
|
if m.Equal(media) {
|
||||||
|
transport = fmt.Sprintf(
|
||||||
|
// i - RTP (data channel)
|
||||||
|
// i+1 - RTCP (control channel)
|
||||||
|
"RTP/AVP/TCP;unicast;interleaved=%d-%d", i*2, i*2+1,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transport = c.setupTCPTransport(mediaIndex)
|
if transport == "" {
|
||||||
return c.sendSetupRequest(media, transport)
|
return 0, fmt.Errorf("wrong media: %v", media)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) setupTCPTransport(mediaIndex int) string {
|
|
||||||
channel := byte(mediaIndex * 2)
|
|
||||||
transport := fmt.Sprintf("RTP/AVP/TCP;unicast;interleaved=%d-%d", channel, channel+1)
|
|
||||||
return transport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) setupUDPTransport() (string, error) {
|
|
||||||
portPair, err := GetUDPPorts(nil, 10)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rtpChannel := c.getChannelForPort(portPair.RTPPort)
|
|
||||||
rtcpChannel := c.getChannelForPort(portPair.RTCPPort)
|
|
||||||
|
|
||||||
c.udpRtpListeners[rtpChannel] = &UDPConnection{
|
|
||||||
Conn: *portPair.RTPListener,
|
|
||||||
Channel: rtpChannel,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.udpRtcpListeners[rtcpChannel] = &UDPConnection{
|
|
||||||
Conn: *portPair.RTCPListener,
|
|
||||||
Channel: rtcpChannel,
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", portPair.RTPPort, portPair.RTCPPort)
|
|
||||||
return transport, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) sendSetupRequest(media *core.Media, transport string) (byte, error) {
|
|
||||||
rawURL := media.ID // control
|
rawURL := media.ID // control
|
||||||
if !strings.Contains(rawURL, "://") {
|
if !strings.Contains(rawURL, "://") {
|
||||||
rawURL = c.URL.String()
|
rawURL = c.URL.String()
|
||||||
@@ -339,109 +311,48 @@ func (c *Conn) sendSetupRequest(media *core.Media, transport string) (byte, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse server response
|
// Parse server response
|
||||||
responseTransport := res.Header.Get("Transport")
|
transport = res.Header.Get("Transport")
|
||||||
|
|
||||||
if c.Transport == "udp" {
|
if c.Transport == "udp" {
|
||||||
// Parse UDP response: client_ports=1234-1235;server_port=1234-1235
|
channel := byte(len(c.udpConn) - 2)
|
||||||
var clientPorts []int
|
|
||||||
var serverPorts []int
|
|
||||||
|
|
||||||
if strings.Contains(transport, "client_port=") {
|
// Dahua: RTP/AVP/UDP;unicast;client_port=49292-49293;server_port=43670-43671;ssrc=7CB694B4
|
||||||
parts := strings.Split(responseTransport, "client_port=")
|
// OpenIPC: RTP/AVP/UDP;unicast;client_port=59612-59613
|
||||||
if len(parts) > 1 {
|
if s := core.Between(transport, "server_port=", ";"); s != "" {
|
||||||
portPart := strings.Split(strings.Split(parts[1], ";")[0], "-")
|
s1, s2, _ := strings.Cut(s, "-")
|
||||||
for _, p := range portPart {
|
port1 := core.Atoi(s1)
|
||||||
if port, err := strconv.Atoi(p); err == nil {
|
port2 := core.Atoi(s2)
|
||||||
clientPorts = append(clientPorts, port)
|
// TODO: more smart handling empty server ports
|
||||||
}
|
if port1 > 0 && port2 > 0 {
|
||||||
}
|
remoteIP := c.conn.RemoteAddr().(*net.TCPAddr).IP
|
||||||
|
c.udpAddr = append(c.udpAddr,
|
||||||
|
&net.UDPAddr{IP: remoteIP, Port: port1},
|
||||||
|
&net.UDPAddr{IP: remoteIP, Port: port2},
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// Try to open a hole in the NAT router (to allow incoming UDP packets)
|
||||||
|
// by send a UDP packet for RTP and RTCP to the remote RTSP server.
|
||||||
|
// https://github.com/FFmpeg/FFmpeg/blob/aa91ae25b88e195e6af4248e0ab30605735ca1cd/libavformat/rtpdec.c#L416-L438
|
||||||
|
_, _ = c.WriteToUDP([]byte{0x80, 0x00, 0x00, 0x00}, channel)
|
||||||
|
_, _ = c.WriteToUDP([]byte{0x80, 0xC8, 0x00, 0x01}, channel+1)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(responseTransport, "server_port=") {
|
return channel, nil
|
||||||
parts := strings.Split(responseTransport, "server_port=")
|
|
||||||
if len(parts) > 1 {
|
|
||||||
portPart := strings.Split(strings.Split(parts[1], ";")[0], "-")
|
|
||||||
for _, p := range portPart {
|
|
||||||
if port, err := strconv.Atoi(p); err == nil {
|
|
||||||
serverPorts = append(serverPorts, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create UDP connections for RTP and RTCP if we have both server ports
|
|
||||||
if len(serverPorts) >= 2 {
|
|
||||||
if host, _, err := net.SplitHostPort(c.Connection.RemoteAddr); err == nil {
|
|
||||||
rtpServerPort := serverPorts[0]
|
|
||||||
rtcpServerPort := serverPorts[1]
|
|
||||||
|
|
||||||
cleanHost := host
|
|
||||||
if strings.Contains(cleanHost, ":") {
|
|
||||||
cleanHost = fmt.Sprintf("[%s]", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteRtpAddr := fmt.Sprintf("%s:%d", cleanHost, rtpServerPort)
|
|
||||||
remoteRtcpAddr := fmt.Sprintf("%s:%d", cleanHost, rtcpServerPort)
|
|
||||||
|
|
||||||
if rtpAddr, err := net.ResolveUDPAddr("udp", remoteRtpAddr); err == nil {
|
|
||||||
if rtpConn, err := net.DialUDP("udp", nil, rtpAddr); err == nil {
|
|
||||||
channel := c.getChannelForPort(rtpServerPort)
|
|
||||||
c.udpRtpConns[channel] = &UDPConnection{
|
|
||||||
Conn: *rtpConn,
|
|
||||||
Channel: channel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rtcpAddr, err := net.ResolveUDPAddr("udp", remoteRtcpAddr); err == nil {
|
|
||||||
if rtcpConn, err := net.DialUDP("udp", nil, rtcpAddr); err == nil {
|
|
||||||
channel := c.getChannelForPort(rtcpServerPort)
|
|
||||||
c.udpRtcpConns[channel] = &UDPConnection{
|
|
||||||
Conn: *rtcpConn,
|
|
||||||
Channel: channel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to open a hole in the NAT router (to allow incoming UDP packets)
|
|
||||||
// by send a UDP packet for RTP and RTCP to the remote RTSP server.
|
|
||||||
go c.tryHolePunching(clientPorts, serverPorts)
|
|
||||||
|
|
||||||
var rtpPort string
|
|
||||||
if media.Direction == core.DirectionRecvonly {
|
|
||||||
rtpPort = core.Between(transport, "client_port=", "-")
|
|
||||||
} else {
|
|
||||||
rtpPort = core.Between(responseTransport, "server_port=", "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
i, err := strconv.Atoi(rtpPort)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.getChannelForPort(i), nil
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// we send our `interleaved`, but camera can answer with another
|
// we send our `interleaved`, but camera can answer with another
|
||||||
|
|
||||||
// Transport: RTP/AVP/TCP;unicast;interleaved=10-11;ssrc=10117CB7
|
// Transport: RTP/AVP/TCP;unicast;interleaved=10-11;ssrc=10117CB7
|
||||||
// Transport: RTP/AVP/TCP;unicast;destination=192.168.1.111;source=192.168.1.222;interleaved=0
|
// Transport: RTP/AVP/TCP;unicast;destination=192.168.1.111;source=192.168.1.222;interleaved=0
|
||||||
// Transport: RTP/AVP/TCP;ssrc=22345682;interleaved=0-1
|
// Transport: RTP/AVP/TCP;ssrc=22345682;interleaved=0-1
|
||||||
if !strings.HasPrefix(responseTransport, "RTP/AVP/TCP;") {
|
// Escam Q6 has a bug:
|
||||||
// Escam Q6 has a bug:
|
// Transport: RTP/AVP;unicast;destination=192.168.1.111;source=192.168.1.222;interleaved=0-1
|
||||||
// Transport: RTP/AVP;unicast;destination=192.168.1.111;source=192.168.1.222;interleaved=0-1
|
s := core.Between(transport, "interleaved=", "-")
|
||||||
if !strings.Contains(responseTransport, ";interleaved=") {
|
i, err := strconv.Atoi(s)
|
||||||
return 0, fmt.Errorf("wrong transport: %s", responseTransport)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
channel := core.Between(responseTransport, "interleaved=", "-")
|
|
||||||
i, err := strconv.Atoi(channel)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, fmt.Errorf("wrong transport: %s", transport)
|
||||||
}
|
}
|
||||||
|
|
||||||
return byte(i), nil
|
return byte(i), nil
|
||||||
@@ -460,106 +371,62 @@ func (c *Conn) Teardown() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
c.closeUDP()
|
|
||||||
|
|
||||||
if c.mode == core.ModeActiveProducer {
|
if c.mode == core.ModeActiveProducer {
|
||||||
_ = c.Teardown()
|
_ = c.Teardown()
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.OnClose != nil {
|
if c.OnClose != nil {
|
||||||
_ = c.OnClose()
|
_ = c.OnClose()
|
||||||
}
|
}
|
||||||
|
for _, conn := range c.udpConn {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) closeUDP() {
|
func (c *Conn) WriteToUDP(b []byte, channel byte) (int, error) {
|
||||||
for _, listener := range c.udpRtpListeners {
|
return c.udpConn[channel].WriteToUDP(b, c.udpAddr[channel])
|
||||||
_ = listener.Conn.Close()
|
|
||||||
}
|
|
||||||
for _, listener := range c.udpRtcpListeners {
|
|
||||||
_ = listener.Conn.Close()
|
|
||||||
}
|
|
||||||
for _, conn := range c.udpRtpConns {
|
|
||||||
_ = conn.Conn.Close()
|
|
||||||
}
|
|
||||||
for _, conn := range c.udpRtcpConns {
|
|
||||||
_ = conn.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.udpRtpListeners = make(map[byte]*UDPConnection)
|
|
||||||
c.udpRtcpListeners = make(map[byte]*UDPConnection)
|
|
||||||
c.udpRtpConns = make(map[byte]*UDPConnection)
|
|
||||||
c.udpRtcpConns = make(map[byte]*UDPConnection)
|
|
||||||
c.portToChannel = make(map[int]byte)
|
|
||||||
c.channelCounter = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) sendUDPRtpPacket(data []byte) error {
|
const listenUDPAttemps = 10
|
||||||
for len(data) >= 4 && data[0] == '$' {
|
|
||||||
channel := data[1]
|
|
||||||
size := binary.BigEndian.Uint16(data[2:4])
|
|
||||||
|
|
||||||
if len(data) < 4+int(size) {
|
var listenUDPMu sync.Mutex
|
||||||
return fmt.Errorf("incomplete RTP packet: %d < %d", len(data), 4+size)
|
|
||||||
|
func ListenUDPPair() (*net.UDPConn, *net.UDPConn, error) {
|
||||||
|
listenUDPMu.Lock()
|
||||||
|
defer listenUDPMu.Unlock()
|
||||||
|
|
||||||
|
for i := 0; i < listenUDPAttemps; i++ {
|
||||||
|
// Get a random even port from the OS
|
||||||
|
ln1, err := net.ListenUDP("udp", &net.UDPAddr{IP: nil, Port: 0})
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send RTP data without interleaved header
|
var port1 = ln1.LocalAddr().(*net.UDPAddr).Port
|
||||||
rtpData := data[4 : 4+size]
|
var port2 int
|
||||||
|
|
||||||
if conn, ok := c.udpRtpConns[channel]; ok {
|
// 11. RTP over Network and Transport Protocols (https://www.ietf.org/rfc/rfc3550.txt)
|
||||||
if err := conn.Conn.SetWriteDeadline(time.Now().Add(Timeout)); err != nil {
|
// For UDP and similar protocols,
|
||||||
return nil
|
// RTP SHOULD use an even destination port number and the corresponding
|
||||||
}
|
// RTCP stream SHOULD use the next higher (odd) destination port number
|
||||||
|
if port1&1 > 0 {
|
||||||
if _, err := conn.Conn.Write(rtpData); err != nil {
|
port2 = port1 - 1
|
||||||
return err
|
} else {
|
||||||
}
|
port2 = port1 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
data = data[4+size:] // Move to next packet
|
ln2, err := net.ListenUDP("udp", &net.UDPAddr{IP: nil, Port: port2})
|
||||||
}
|
if err != nil {
|
||||||
|
_ = ln1.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
if port1 < port2 {
|
||||||
}
|
return ln1, ln2, nil
|
||||||
|
} else {
|
||||||
func (c *Conn) tryHolePunching(clientPorts, serverPorts []int) {
|
return ln2, ln1, nil
|
||||||
if len(clientPorts) < 2 || len(serverPorts) < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
host, _, _ := net.SplitHostPort(c.Connection.RemoteAddr)
|
|
||||||
if strings.Contains(host, ":") {
|
|
||||||
host = fmt.Sprintf("[%s]", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RTP hole punch
|
|
||||||
if rtpListener, ok := c.udpRtpListeners[c.getChannelForPort(clientPorts[0])]; ok {
|
|
||||||
if addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", host, serverPorts[0])); err == nil {
|
|
||||||
rtpListener.Conn.WriteToUDP([]byte{0x80, 0x00, 0x00, 0x00}, addr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RTCP hole punch
|
return nil, nil, fmt.Errorf("can't open two UDP ports")
|
||||||
if rtcpListener, ok := c.udpRtcpListeners[c.getChannelForPort(clientPorts[1])]; ok {
|
|
||||||
if addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", host, serverPorts[1])); err == nil {
|
|
||||||
rtcpListener.Conn.WriteToUDP([]byte{0x80, 0xC8, 0x00, 0x01}, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) getChannelForPort(port int) byte {
|
|
||||||
if channel, exists := c.portToChannel[port]; exists {
|
|
||||||
return channel
|
|
||||||
}
|
|
||||||
|
|
||||||
c.channelCounter++
|
|
||||||
if c.channelCounter == 0 {
|
|
||||||
c.channelCounter = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
channel := c.channelCounter
|
|
||||||
c.portToChannel[port] = channel
|
|
||||||
|
|
||||||
return channel
|
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-160
@@ -2,6 +2,7 @@ package rtsp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -13,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/tcp"
|
"github.com/AlexxIT/go2rtc/pkg/tcp"
|
||||||
"github.com/pion/rtcp"
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,27 +49,10 @@ type Conn struct {
|
|||||||
state State
|
state State
|
||||||
stateMu sync.Mutex
|
stateMu sync.Mutex
|
||||||
|
|
||||||
// UDP
|
udpConn []*net.UDPConn
|
||||||
|
udpAddr []*net.UDPAddr
|
||||||
udpRtpConns map[byte]*UDPConnection
|
|
||||||
udpRtcpConns map[byte]*UDPConnection
|
|
||||||
udpRtpListeners map[byte]*UDPConnection
|
|
||||||
udpRtcpListeners map[byte]*UDPConnection
|
|
||||||
portToChannel map[int]byte
|
|
||||||
channelCounter byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UDPConnection struct {
|
|
||||||
Conn net.UDPConn
|
|
||||||
Channel byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type TransportMode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReceiveMTU = 1500
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProtoRTSP = "RTSP/1.0"
|
ProtoRTSP = "RTSP/1.0"
|
||||||
MethodOptions = "OPTIONS"
|
MethodOptions = "OPTIONS"
|
||||||
@@ -108,23 +91,25 @@ const (
|
|||||||
func (c *Conn) Handle() (err error) {
|
func (c *Conn) Handle() (err error) {
|
||||||
var timeout time.Duration
|
var timeout time.Duration
|
||||||
|
|
||||||
var keepaliveDT time.Duration
|
|
||||||
var keepaliveTS time.Time
|
|
||||||
|
|
||||||
switch c.mode {
|
switch c.mode {
|
||||||
case core.ModeActiveProducer:
|
case core.ModeActiveProducer:
|
||||||
|
var keepaliveDT time.Duration
|
||||||
|
|
||||||
if c.keepalive > 5 {
|
if c.keepalive > 5 {
|
||||||
keepaliveDT = time.Duration(c.keepalive-5) * time.Second
|
keepaliveDT = time.Duration(c.keepalive-5) * time.Second
|
||||||
} else {
|
} else {
|
||||||
keepaliveDT = 25 * time.Second
|
keepaliveDT = 25 * time.Second
|
||||||
}
|
}
|
||||||
keepaliveTS = time.Now().Add(keepaliveDT)
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go c.handleKeepalive(ctx, keepaliveDT)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
if c.Timeout == 0 {
|
if c.Timeout == 0 {
|
||||||
// polling frames from remote RTSP Server (ex Camera)
|
// polling frames from remote RTSP Server (ex Camera)
|
||||||
timeout = time.Second * 5
|
timeout = time.Second * 5
|
||||||
|
|
||||||
if len(c.Receivers) == 0 {
|
if len(c.Receivers) == 0 || c.Transport == "udp" {
|
||||||
// if we only send audio to camera
|
// if we only send audio to camera
|
||||||
// https://github.com/AlexxIT/go2rtc/issues/659
|
// https://github.com/AlexxIT/go2rtc/issues/659
|
||||||
timeout += keepaliveDT
|
timeout += keepaliveDT
|
||||||
@@ -149,150 +134,58 @@ func (c *Conn) Handle() (err error) {
|
|||||||
return fmt.Errorf("wrong RTSP conn mode: %d", c.mode)
|
return fmt.Errorf("wrong RTSP conn mode: %d", c.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(c.udpConn); i++ {
|
||||||
|
go c.handleUDPData(byte(i))
|
||||||
|
}
|
||||||
|
|
||||||
for c.state != StateNone {
|
for c.state != StateNone {
|
||||||
ts := time.Now()
|
ts := time.Now()
|
||||||
time := ts.Add(timeout)
|
|
||||||
|
|
||||||
if err = c.conn.SetReadDeadline(time); err != nil {
|
_ = c.conn.SetReadDeadline(ts.Add(timeout))
|
||||||
|
|
||||||
|
if err = c.handleTCPData(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Transport == "udp" {
|
|
||||||
if err = c.handleUDPClientData(time); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err = c.handleTCPClientData(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if keepaliveDT != 0 && ts.After(keepaliveTS) {
|
|
||||||
req := &tcp.Request{Method: MethodOptions, URL: c.URL}
|
|
||||||
if err = c.WriteRequest(req); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keepaliveTS = ts.Add(keepaliveDT)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) handleUDPClientData(time time.Time) error {
|
func (c *Conn) handleKeepalive(ctx context.Context, d time.Duration) {
|
||||||
if c.playErr != nil {
|
ticker := time.NewTicker(d)
|
||||||
return c.playErr
|
for {
|
||||||
}
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
if c.state == StatePlay && c.playOK {
|
req := &tcp.Request{Method: MethodOptions, URL: c.URL}
|
||||||
return nil
|
if err := c.WriteRequest(req); err != nil {
|
||||||
}
|
return
|
||||||
|
|
||||||
var buf4 []byte
|
|
||||||
|
|
||||||
buf4, err := c.reader.Peek(4)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch string(buf4) {
|
|
||||||
case "RTSP":
|
|
||||||
var res *tcp.Response
|
|
||||||
if res, err = c.ReadResponse(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Fire(res)
|
|
||||||
c.playOK = true
|
|
||||||
|
|
||||||
for _, listener := range c.udpRtpListeners {
|
|
||||||
go func(listener *UDPConnection) {
|
|
||||||
defer listener.Conn.Close()
|
|
||||||
|
|
||||||
for c.state != StateNone {
|
|
||||||
if err := listener.Conn.SetReadDeadline(time); err != nil {
|
|
||||||
c.playErr = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer := make([]byte, ReceiveMTU)
|
|
||||||
n, _, err := listener.Conn.ReadFromUDP(buffer)
|
|
||||||
if err != nil {
|
|
||||||
c.playErr = err
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
packet := &rtp.Packet{}
|
|
||||||
if err := packet.Unmarshal(buffer[:n]); err != nil {
|
|
||||||
c.playErr = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, receiver := range c.Receivers {
|
|
||||||
if receiver.ID == listener.Channel {
|
|
||||||
receiver.WriteRTP(packet)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Recv += len(buffer[:n])
|
|
||||||
}
|
|
||||||
}(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, listener := range c.udpRtcpListeners {
|
|
||||||
go func(listener *UDPConnection) {
|
|
||||||
defer listener.Conn.Close()
|
|
||||||
|
|
||||||
for c.state != StateNone {
|
|
||||||
if err := listener.Conn.SetReadDeadline(time); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer := make([]byte, ReceiveMTU)
|
|
||||||
n, _, err := listener.Conn.ReadFromUDP(buffer)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := &RTCP{Channel: listener.Channel}
|
|
||||||
|
|
||||||
if err := msg.Header.Unmarshal(buffer[:n]); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Packets, err = rtcp.Unmarshal(buffer[:n])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Fire(msg)
|
|
||||||
}
|
|
||||||
}(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "OPTI", "TEAR", "DESC", "SETU", "PLAY", "PAUS", "RECO", "ANNO", "GET_", "SET_":
|
|
||||||
var req *tcp.Request
|
|
||||||
if req, err = c.ReadRequest(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.Fire(req)
|
|
||||||
if req.Method == MethodOptions {
|
|
||||||
res := &tcp.Response{Request: req}
|
|
||||||
if err = c.WriteResponse(res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("RTSP wrong input")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) handleTCPClientData() error {
|
func (c *Conn) handleUDPData(channel byte) {
|
||||||
|
// TODO: handle timeouts and drop TCP connection after any error
|
||||||
|
conn := c.udpConn[channel]
|
||||||
|
|
||||||
|
for {
|
||||||
|
// TP-Link Tapo camera has crazy 10000 bytes packet size
|
||||||
|
buf := make([]byte, 10240)
|
||||||
|
|
||||||
|
n, _, err := conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.handleRawPacket(channel, buf[:n]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) handleTCPData() error {
|
||||||
// we can read:
|
// we can read:
|
||||||
// 1. RTP interleaved: `$` + 1B channel number + 2B size
|
// 1. RTP interleaved: `$` + 1B channel number + 2B size
|
||||||
// 2. RTSP response: RTSP/1.0 200 OK
|
// 2. RTSP response: RTSP/1.0 200 OK
|
||||||
@@ -390,9 +283,13 @@ func (c *Conn) handleTCPClientData() error {
|
|||||||
|
|
||||||
c.Recv += int(size)
|
c.Recv += int(size)
|
||||||
|
|
||||||
|
return c.handleRawPacket(channel, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) handleRawPacket(channel byte, buf []byte) error {
|
||||||
if channel&1 == 0 {
|
if channel&1 == 0 {
|
||||||
packet := &rtp.Packet{}
|
packet := &rtp.Packet{}
|
||||||
if err = packet.Unmarshal(buf); err != nil {
|
if err := packet.Unmarshal(buf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,14 +302,15 @@ func (c *Conn) handleTCPClientData() error {
|
|||||||
} else {
|
} else {
|
||||||
msg := &RTCP{Channel: channel}
|
msg := &RTCP{Channel: channel}
|
||||||
|
|
||||||
if err = msg.Header.Unmarshal(buf); err != nil {
|
if err := msg.Header.Unmarshal(buf); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Packets, err = rtcp.Unmarshal(buf)
|
//var err error
|
||||||
if err != nil {
|
//msg.Packets, err = rtcp.Unmarshal(buf)
|
||||||
return nil
|
//if err != nil {
|
||||||
}
|
// return nil
|
||||||
|
//}
|
||||||
|
|
||||||
c.Fire(msg)
|
c.Fire(msg)
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-14
@@ -86,21 +86,9 @@ func (c *Conn) packetWriter(codec *core.Codec, channel, payloadType uint8) core.
|
|||||||
|
|
||||||
flushBuf := func() {
|
flushBuf := func() {
|
||||||
//log.Printf("[rtsp] channel:%2d write_size:%6d buffer_size:%6d", channel, n, len(buf))
|
//log.Printf("[rtsp] channel:%2d write_size:%6d buffer_size:%6d", channel, n, len(buf))
|
||||||
|
if err := c.writeInterleavedData(buf[:n]); err != nil {
|
||||||
if c.Transport == "udp" {
|
c.Send += n
|
||||||
if err := c.sendUDPRtpPacket(buf[:n]); err == nil {
|
|
||||||
c.Send += n
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := c.conn.SetWriteDeadline(time.Now().Add(Timeout)); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := c.conn.Write(buf[:n]); err == nil {
|
|
||||||
c.Send += n
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,3 +174,25 @@ func (c *Conn) packetWriter(codec *core.Codec, channel, payloadType uint8) core.
|
|||||||
|
|
||||||
return handlerFunc
|
return handlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Conn) writeInterleavedData(data []byte) error {
|
||||||
|
if c.Transport != "udp" {
|
||||||
|
_ = c.conn.SetWriteDeadline(time.Now().Add(Timeout))
|
||||||
|
_, err := c.conn.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(data) >= 4 && data[0] == '$' {
|
||||||
|
channel := data[1]
|
||||||
|
size := uint16(data[2])<<8 | uint16(data[3])
|
||||||
|
rtpData := data[4 : 4+size]
|
||||||
|
|
||||||
|
if _, err := c.WriteToUDP(rtpData, channel); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data = data[4+size:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package rtsp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var mu sync.Mutex
|
|
||||||
|
|
||||||
type UDPPortPair struct {
|
|
||||||
RTPListener *net.UDPConn
|
|
||||||
RTCPListener *net.UDPConn
|
|
||||||
RTPPort int
|
|
||||||
RTCPPort int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UDPPortPair) Close() {
|
|
||||||
if p.RTPListener != nil {
|
|
||||||
_ = p.RTPListener.Close()
|
|
||||||
}
|
|
||||||
if p.RTCPListener != nil {
|
|
||||||
_ = p.RTCPListener.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUDPPorts(ip net.IP, maxAttempts int) (*UDPPortPair, error) {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
if ip == nil {
|
|
||||||
ip = net.IPv4(0, 0, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < maxAttempts; i++ {
|
|
||||||
// Get a random even port from the OS
|
|
||||||
tempListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: ip, Port: 0})
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := tempListener.LocalAddr().(*net.UDPAddr)
|
|
||||||
basePort := addr.Port
|
|
||||||
tempListener.Close()
|
|
||||||
|
|
||||||
// 11. RTP over Network and Transport Protocols (https://www.ietf.org/rfc/rfc3550.txt)
|
|
||||||
// For UDP and similar protocols,
|
|
||||||
// RTP SHOULD use an even destination port number and the corresponding
|
|
||||||
// RTCP stream SHOULD use the next higher (odd) destination port number
|
|
||||||
if basePort%2 == 1 {
|
|
||||||
basePort--
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to bind both ports
|
|
||||||
rtpListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: ip, Port: basePort})
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rtcpListener, err := net.ListenUDP("udp", &net.UDPAddr{IP: ip, Port: basePort + 1})
|
|
||||||
if err != nil {
|
|
||||||
rtpListener.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return &UDPPortPair{
|
|
||||||
RTPListener: rtpListener,
|
|
||||||
RTCPListener: rtcpListener,
|
|
||||||
RTPPort: basePort,
|
|
||||||
RTCPPort: basePort + 1,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("failed to allocate consecutive UDP ports after %d attempts", maxAttempts)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user