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) {
218230func (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
312362func (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