Skip to content

Commit c87885b

Browse files
authored
Merge pull request #1758 from seydx/rtsp-udp
Add RTSP UDP transport support
2 parents cca216a + 98f88d0 commit c87885b

3 files changed

Lines changed: 312 additions & 145 deletions

File tree

pkg/rtsp/client.go

Lines changed: 128 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/url"
1010
"strconv"
1111
"strings"
12+
"sync"
1213
"time"
1314

1415
"github.com/AlexxIT/go2rtc/pkg/tcp/websocket"
@@ -36,14 +37,22 @@ func (c *Conn) Dial() (err error) {
3637

3738
var conn net.Conn
3839

39-
if c.Transport == "" {
40-
timeout := core.ConnDialTimeout
40+
switch c.Transport {
41+
case "", "tcp", "udp":
42+
var timeout time.Duration
4143
if c.Timeout != 0 {
4244
timeout = time.Second * time.Duration(c.Timeout)
45+
} else {
46+
timeout = core.ConnDialTimeout
4347
}
4448
conn, err = tcp.Dial(c.URL, timeout)
45-
c.Protocol = "rtsp+tcp"
46-
} else {
49+
50+
if c.Transport != "udp" {
51+
c.Protocol = "rtsp+tcp"
52+
} else {
53+
c.Protocol = "rtsp+udp"
54+
}
55+
default:
4756
conn, err = websocket.Dial(c.Transport)
4857
c.Protocol = "ws"
4958
}
@@ -61,6 +70,9 @@ func (c *Conn) Dial() (err error) {
6170
c.sequence = 0
6271
c.state = StateConn
6372

73+
c.udpConn = nil
74+
c.udpAddr = nil
75+
6476
c.Connection.RemoteAddr = conn.RemoteAddr().String()
6577
c.Connection.Transport = conn
6678
c.Connection.URL = c.uri
@@ -218,15 +230,27 @@ func (c *Conn) Record() (err error) {
218230
func (c *Conn) SetupMedia(media *core.Media) (byte, error) {
219231
var transport string
220232

221-
// try to use media position as channel number
222-
for i, m := range c.Medias {
223-
if m.Equal(media) {
224-
transport = fmt.Sprintf(
225-
// i - RTP (data channel)
226-
// i+1 - RTCP (control channel)
227-
"RTP/AVP/TCP;unicast;interleaved=%d-%d", i*2, i*2+1,
228-
)
229-
break
233+
if c.Transport == "udp" {
234+
conn1, conn2, err := ListenUDPPair()
235+
if err != nil {
236+
return 0, err
237+
}
238+
239+
c.udpConn = append(c.udpConn, conn1, conn2)
240+
241+
port := conn1.LocalAddr().(*net.UDPAddr).Port
242+
transport = fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", port, port+1)
243+
} else {
244+
// try to use media position as channel number
245+
for i, m := range c.Medias {
246+
if m.Equal(media) {
247+
transport = fmt.Sprintf(
248+
// i - RTP (data channel)
249+
// i+1 - RTCP (control channel)
250+
"RTP/AVP/TCP;unicast;interleaved=%d-%d", i*2, i*2+1,
251+
)
252+
break
253+
}
230254
}
231255
}
232256

@@ -286,27 +310,53 @@ func (c *Conn) SetupMedia(media *core.Media) (byte, error) {
286310
}
287311
}
288312

289-
// we send our `interleaved`, but camera can answer with another
290-
291-
// Transport: RTP/AVP/TCP;unicast;interleaved=10-11;ssrc=10117CB7
292-
// Transport: RTP/AVP/TCP;unicast;destination=192.168.1.111;source=192.168.1.222;interleaved=0
293-
// Transport: RTP/AVP/TCP;ssrc=22345682;interleaved=0-1
313+
// Parse server response
294314
transport = res.Header.Get("Transport")
295-
if !strings.HasPrefix(transport, "RTP/AVP/TCP;") {
315+
316+
if c.Transport == "udp" {
317+
channel := byte(len(c.udpConn) - 2)
318+
319+
// Dahua: RTP/AVP/UDP;unicast;client_port=49292-49293;server_port=43670-43671;ssrc=7CB694B4
320+
// OpenIPC: RTP/AVP/UDP;unicast;client_port=59612-59613
321+
if s := core.Between(transport, "server_port=", ";"); s != "" {
322+
s1, s2, _ := strings.Cut(s, "-")
323+
port1 := core.Atoi(s1)
324+
port2 := core.Atoi(s2)
325+
// TODO: more smart handling empty server ports
326+
if port1 > 0 && port2 > 0 {
327+
remoteIP := c.conn.RemoteAddr().(*net.TCPAddr).IP
328+
c.udpAddr = append(c.udpAddr,
329+
&net.UDPAddr{IP: remoteIP, Port: port1},
330+
&net.UDPAddr{IP: remoteIP, Port: port2},
331+
)
332+
333+
go func() {
334+
// Try to open a hole in the NAT router (to allow incoming UDP packets)
335+
// by send a UDP packet for RTP and RTCP to the remote RTSP server.
336+
// https://github.com/FFmpeg/FFmpeg/blob/aa91ae25b88e195e6af4248e0ab30605735ca1cd/libavformat/rtpdec.c#L416-L438
337+
_, _ = c.WriteToUDP([]byte{0x80, 0x00, 0x00, 0x00}, channel)
338+
_, _ = c.WriteToUDP([]byte{0x80, 0xC8, 0x00, 0x01}, channel+1)
339+
}()
340+
}
341+
}
342+
343+
return channel, nil
344+
} else {
345+
// we send our `interleaved`, but camera can answer with another
346+
347+
// Transport: RTP/AVP/TCP;unicast;interleaved=10-11;ssrc=10117CB7
348+
// Transport: RTP/AVP/TCP;unicast;destination=192.168.1.111;source=192.168.1.222;interleaved=0
349+
// Transport: RTP/AVP/TCP;ssrc=22345682;interleaved=0-1
296350
// Escam Q6 has a bug:
297351
// Transport: RTP/AVP;unicast;destination=192.168.1.111;source=192.168.1.222;interleaved=0-1
298-
if !strings.Contains(transport, ";interleaved=") {
352+
s := core.Between(transport, "interleaved=", "-")
353+
i, err := strconv.Atoi(s)
354+
if err != nil {
299355
return 0, fmt.Errorf("wrong transport: %s", transport)
300356
}
301-
}
302357

303-
channel := core.Between(transport, "interleaved=", "-")
304-
i, err := strconv.Atoi(channel)
305-
if err != nil {
306-
return 0, err
358+
return byte(i), nil
307359
}
308-
309-
return byte(i), nil
310360
}
311361

312362
func (c *Conn) Play() (err error) {
@@ -327,5 +377,56 @@ func (c *Conn) Close() error {
327377
if c.OnClose != nil {
328378
_ = c.OnClose()
329379
}
380+
for _, conn := range c.udpConn {
381+
_ = conn.Close()
382+
}
330383
return c.conn.Close()
331384
}
385+
386+
func (c *Conn) WriteToUDP(b []byte, channel byte) (int, error) {
387+
return c.udpConn[channel].WriteToUDP(b, c.udpAddr[channel])
388+
}
389+
390+
const listenUDPAttemps = 10
391+
392+
var listenUDPMu sync.Mutex
393+
394+
func ListenUDPPair() (*net.UDPConn, *net.UDPConn, error) {
395+
listenUDPMu.Lock()
396+
defer listenUDPMu.Unlock()
397+
398+
for i := 0; i < listenUDPAttemps; i++ {
399+
// Get a random even port from the OS
400+
ln1, err := net.ListenUDP("udp", &net.UDPAddr{IP: nil, Port: 0})
401+
if err != nil {
402+
continue
403+
}
404+
405+
var port1 = ln1.LocalAddr().(*net.UDPAddr).Port
406+
var port2 int
407+
408+
// 11. RTP over Network and Transport Protocols (https://www.ietf.org/rfc/rfc3550.txt)
409+
// For UDP and similar protocols,
410+
// RTP SHOULD use an even destination port number and the corresponding
411+
// RTCP stream SHOULD use the next higher (odd) destination port number
412+
if port1&1 > 0 {
413+
port2 = port1 - 1
414+
} else {
415+
port2 = port1 + 1
416+
}
417+
418+
ln2, err := net.ListenUDP("udp", &net.UDPAddr{IP: nil, Port: port2})
419+
if err != nil {
420+
_ = ln1.Close()
421+
continue
422+
}
423+
424+
if port1 < port2 {
425+
return ln1, ln2, nil
426+
} else {
427+
return ln2, ln1, nil
428+
}
429+
}
430+
431+
return nil, nil, fmt.Errorf("can't open two UDP ports")
432+
}

0 commit comments

Comments
 (0)